Skip to content

Commit

Permalink
merged in commits from kaven276
Browse files Browse the repository at this point in the history
  • Loading branch information
vpulim committed Apr 30, 2012
2 parents 024b343 + b533ac8 commit 65c1688
Show file tree
Hide file tree
Showing 15 changed files with 1,276 additions and 350 deletions.
66 changes: 66 additions & 0 deletions Readme.md
Expand Up @@ -5,6 +5,72 @@ Current limitations:
* Only a few XSD Schema types are supported
* Only WS-Security is supported using UsernameToken and PasswordText encoding

## Update of kaven276's node-soap from milewise's node-soap

### can parse multiRef request, use=encoding support

Soap request can use multiRef to mapping language's object model to xml, now node-soap can parse the request and mapping it to a javascript object that use object reference as multiRef.

### Fiber integration, support async javascript service

For soap server, it has javascript service defined, when node-soap received and parse the soap request, it will route to call the javascript service function, and it will take the return value to convert to response soap xml. But the javascript service function may need to call some other file/network async functions to get the final return value, but node-soap will not wait for it, it just synchronously call and get return value. But this version integrate Fiber to support async service.

var SoapServices = {
'ESyncNotifySPServiceService': {
'ESyncNotifySP': {
'eOrderRelationUpdateNotify': function(args) {
var fiber = Fiber.current;
var oraReqNV = args["eOrderRelationUpdateNotifyRequest"];
Request({
uri: cfg.psp_url + '.e',
method: 'post',
headers: {
'Content-Type': "application/x-www-form-urlencoded"
},
body: QueryString.stringify(oraReqNV)
},
function(error, response, body) {
if (!error && response.statusCode === 200) {
fiber.run({
'eOrderRelationUpdateNotifyResponse': {
'RecordSequenceID': oraReqNV.RecordSequenceID,
'ResultCode': body
}
});
} else {
fiber.run({
'eOrderRelationUpdateNotifyResponse': {
'RecordSequenceID': oraReqNV.RecordSequenceID,
'ResultCode': 1
}
});
}
});
return yield();
}
}
}
}

### correct method routing

How to determine the javascript service method? Firstly, use wsdl:service->wsdl:port->wsdlsoap:address.location to determine what binding/portType to use, Secondly, if style=RPC, then treat top element's name under soap body as the portType's method name,
if style=RPC, then use http header SOAPAction to match the operation's soapAction, if there is no http header SOAPAction or no soapAction defined in wsdl, then check every method's input message's element name to find the match.

### support both RPC/Document style request/response

### xml namespace support

For WSDL parsing, different schema definitions will go into their respective schema parsed javascript objects, it allow the same named types/complexTypes/elements/... in different schemas. Formerly, different schema object with same name will just override and keep the last one, it's totally namespace ignorant and has potential serious problems.

For soap xml generation, the soap envelope has all the xmlns definition(exception for wsdl/soap itself related) defined in the wsdl that not only bring from the wsdl definition element, but from all of the wsdl and its import/include subs. But notice that if a xmlns defined in wsdl's element is conflict with definition element's xmlns map, it may have problems, but it's very rare case.

The soap request by client or response by server will give top element in soap:body a xml namespace prefix that is defined in envelope. And your javascript object for xml can have object attribute with xml namespace prefix, so the result xml payload will have the same namespace prefix. This way, you can use your javascript to make the right namespaced soap xml.

### generate minimum of parsed javascript definition object

For the standard parsed format, remove any child/children when the child or child's useful information is attached to the parent's properties, remove fixed attribute of WSDL xml node, remove parent properties so there is no circular reference. The result is a clean and more clear parsed definition object.

## Install

Install with [npm](http://github.com/isaacs/npm):
Expand Down
35 changes: 21 additions & 14 deletions lib/client.js
Expand Up @@ -2,13 +2,18 @@
* Copyright (c) 2011 Vinay Pulim <vinay@milewise.com>
* MIT Licensed
*/

function findKey(obj, val) {
for (var n in obj) if (obj[n] === val) return n;
}

var http = require('./http'),
assert = require('assert');
assert = require('assert'),
url = require('url');

var Client = function(wsdl) {
var Client = function(wsdl, endpoint) {
this.wsdl = wsdl;
this._initializeServices(null);
this._initializeServices(endpoint);
}

Client.prototype.setEndpoint = function(endpoint) {
Expand Down Expand Up @@ -81,47 +86,49 @@ Client.prototype._invoke = function(method, arguments, location, callback) {
headers = {
SOAPAction: ((ns.lastIndexOf("/") != ns.length - 1) ? ns + "/" : ns) + name,
'Content-Type': "text/xml; charset=utf-8"
},
options = {};

};
options = {},
alias = findKey(defs.xmlns, ns);

// Allow the security object to add headers
if (self.security && self.security.addHeaders)
self.security.addHeaders(headers);
if (self.security && self.security.addOptions)
self.security.addOptions(options);

if (input.parts) {
assert.ok(!style || style == 'rpc', 'invalid message definition for document style binding');
message = self.wsdl.objectToRpcXML(name, arguments);
encoding = 'soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" ';
message = self.wsdl.objectToRpcXML(name, arguments, alias, ns);
(method.inputSoap === 'encoded') && (encoding = 'soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" ');
}
else {
assert.ok(!style || style == 'document', 'invalid message definition for rpc style binding');
message = self.wsdl.objectToDocumentXML(input.$name, arguments);
message = self.wsdl.objectToDocumentXML(input.$name, arguments, input.targetNSAlias, input.targetNamespace);
}
xml = "<soap:Envelope " +
"xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" " +
encoding +
"xmlns:ns0=\""+ns+"\">" +
this.wsdl.xmlnsInEnvelope + '>' +
"<soap:Header>" +
(self.security ? self.security.toXML() : "") +
"</soap:Header>" +
"<soap:Body>" +
message +
"</soap:Body>" +
"</soap:Envelope>";

self.logger_req && self.logger_req(xml);

http.request(location, xml, function(err, response, body) {
if (err) {
callback(err);
}
else {
self.logger_res && self.logger_res(body);
try {
var obj = self.wsdl.xmlToObject(body);
}
catch (error) {
callback(error, null, body);
return;
return callback(error, null, body);
}
callback(null, obj[output.$name], body);
}
Expand Down
93 changes: 63 additions & 30 deletions lib/server.js
Expand Up @@ -3,6 +3,11 @@
* MIT Licensed
*/

function findKey(obj, val) {
for (var n in obj) if (obj[n] === val) return n;
}

require('fibers');
var url = require('url'),
compress = null;

Expand Down Expand Up @@ -44,6 +49,9 @@ Server.prototype._requestListener = function(req, res) {
res.end();
}
else if (req.method === 'POST') {
res.setHeader('Content-Type', req.headers['content-type']);
//res.setHeader("Content-Type","application/soap+xml");
//res.setHeader("Encoding","utf-8");
var chunks = [], gunzip;
if (compress && req.headers["content-encoding"] == "gzip") {
gunzip = new compress.Gunzip;
Expand All @@ -55,69 +63,94 @@ Server.prototype._requestListener = function(req, res) {
})
req.on('end', function() {
var xml = chunks.join(''), result;
self.logger_req && self.logger_req(xml,res,req);
if (gunzip) {
gunzip.end();
gunzip = null
}
Fiber(function() {
try {
result = self._process(xml);
result = self._process(xml, req.url);
self.logger_res && self.logger_res(result,res,req);
}
catch (err) {
result = err.stack;
}
res.write(result);
res.end();
}).run();
});
}
else {
res.end();
}
}

Server.prototype._process = function(input) {
Server.prototype._process = function(input,URL) {
var pathname = url.parse(URL).pathname.replace(/\/$/,'');
var self = this,
obj = this.wsdl.xmlToObject(input),
messageName = Object.keys(obj)[0],
args = obj[messageName],
portTypes = this.wsdl.definitions.portTypes;
bindings = this.wsdl.definitions.bindings, binding,
methods, method, methodName;
var serviceName, portName;

// use port.location and current url to find the right binding
binding = (function(self){
var services = self.wsdl.definitions.services;
for(serviceName in services ) {
var service = services[serviceName];
var ports = service.ports;
for(portName in ports) {
var port = ports[portName];
var portPathname = url.parse(port.location).pathname.replace(/\/$/,'');
if(portPathname===pathname)
return port.binding;
}
}
})(this);

for (var portName in portTypes) {
var portType = portTypes[portName];
var methods = portType.methods;
for (var methodName in methods) {
var method = methods[methodName];
if (method.input.$name == messageName) {
return self._executeMethod(methodName, method.output.$name, args);
}
}
}
methods = binding.methods;
if(binding.style === 'rpc') {
methodName = Object.keys(obj)[0];
return self._executeMethod(serviceName, portName, methodName, methodName+'Response', obj[methodName], 'rpc');
} else {
var messageElemName = Object.keys(obj)[0];
var pair = binding.topElements[messageElemName];
return self._executeMethod(serviceName, portName, pair.methodName, pair.outputName, obj[messageElemName], 'document');
}
return '';
}

Server.prototype._executeMethod = function(methodName, outputName, args) {
var services = this.services;
for (var serviceName in services) {
var service = services[serviceName];
for (var portName in service) {
var port = service[portName];
var method = port[methodName];
if (method) {
var body = this.wsdl.objectToDocumentXML(outputName, method(args))
return this._envelope(body);
}
}
Server.prototype._executeMethod = function(serviceName, portName, methodName, outputName, args, style) {
var method, body;
try {
method = this.services[serviceName][portName][methodName];
} catch(e) {
return this._envelope(body);
}
return this._envelope();
if(style==='rpc') {
body = this.wsdl.objectToRpcXML(outputName, method(args), '', this.wsdl.definitions.$targetNamespace);
} else {
var element = this.wsdl.definitions.services[serviceName].ports[portName].binding.methods[methodName].output;
body = this.wsdl.objectToDocumentXML(outputName, method(args), element.targetNSAlias, element.targetNamespace);
}
return this._envelope(body);
}

Server.prototype._envelope = function(body) {
var defs = this.wsdl.definitions,
ns = defs.$targetNamespace,
encoding = '',
alias = findKey(defs.xmlns, ns);
var xml = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
"<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">" +
"<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" " +
encoding +
this.wsdl.xmlnsInEnvelope + '>' +
"<soap:Body>" +
body +
"</soap:Body>" +
"</soap:Envelope>";

return xml;
}

Expand Down
5 changes: 3 additions & 2 deletions lib/soap.js
Expand Up @@ -9,6 +9,7 @@ var Client = require('./client').Client,
crypto = require('crypto'),
WSDL = require('./wsdl').WSDL;

var WSDL = require('./wsdl').WSDL;
var _wsdlCache = {};

function _requestWSDL(url, callback) {
Expand All @@ -24,9 +25,9 @@ function _requestWSDL(url, callback) {
}
}

function createClient(url, callback) {
function createClient(url, callback, endpoint) {
_requestWSDL(url, function(err, wsdl) {
callback(err, wsdl && new Client(wsdl));
callback(err, wsdl && new Client(wsdl, endpoint));
})
}

Expand Down

0 comments on commit 65c1688

Please sign in to comment.