node-soap客户端相关源码解读
node-soap结构——lib目录
源码学习初级阶段,着重在client端,并且先不考虑security设置。重点看以下几个方面:
wsdl解析完成后,如何关联services到client中
client中services调用时候,如何出发xml文档生成
http请求怎么处理
.
|____client.js // webservice client我们关心的
|____http.js //我们关心的
|____nscontext.js //wsdl namespace
|____security // security
| |____BasicAuthSecurity.js
| |____BearerSecurity.js
| |____ClientSSLSecurity.js
| |____ClientSSLSecurityPFX.js
| |____index.js
| |____templates
| | |____wsse-security-header.ejs
| | |____wsse-security-token.ejs
| |____WSSecurity.js
| |____WSSecurityCert.js
|____server.js // webservice server
|____soap.js // api interface 我们关心的
|____utils.js
|____wsdl.js // wsdl xml2obj, obj2xml
createClient
createClient的使用方法
var soap = require('soap');
soap.createClient(url,function(err, client) {
// do something with client
console.log(client.describe())
});
createClient的具体实现
_requestWSDL: 基于url获取wsdl内容,并构建wsdl对象。缓存wsdl对象
new Client(wsdl, endpoint, options),通过wsdl,服务endpoint,配置options构建client对象
"use strict";
var Client = require('./client').Client,
Server = require('./server').Server,
HttpClient = require('./http'),
security = require('./security'),
passwordDigest = require('./utils').passwordDigest,
open_wsdl = require('./wsdl').open_wsdl,
WSDL = require('./wsdl').WSDL;
var _wsdlCache = {};
function _requestWSDL(url, options, callback) {
...
// 通过wsdl URL从request或者本地cache中获取wsdl对象,然后调用callback,传回
else {
open_wsdl(url, options, function(err, wsdl) {
if (err) {
return callback(err);
} else {
_wsdlCache[url] = wsdl;
}
callback(null, wsdl);
});
}
}
function createClient(url, options, callback, endpoint) {
if (typeof options === 'function') {
endpoint = callback;
callback = options;
options = {};
}
endpoint = options.endpoint || endpoint;
// 获取到WSDL对象,然后把wsdl和Client对象&&处理,通过callback传回,也就是createClient
回调中的client参数
_requestWSDL(url, options, function(err, wsdl) {
callback(err, wsdl && new Client(wsdl, endpoint, options));
});
}
function listen(server, pathOrOptions, services, xml) {
// server 相关,先不关心
...
}
Client的构建
client最重要的三件事:
初始化配置
初始化服务(API)
服务对象格式化XML文档
发送XML文档,处理响应body
var Client = function(wsdl, endpoint, options) {
events.EventEmitter.call(this);
options = options || {};
this.wsdl = wsdl;
this._initializeOptions(options);
// 重点看这个
this._initializeServices(endpoint);
this.httpClient = options.httpClient || new HttpClient(options);
};
初始化Services _initializeServices
Client.prototype._initializeServices = function(endpoint) {
var definitions = this.wsdl.definitions,
services = definitions.services;
for (var name in services) {
this[name] = this._defineService(services[name], endpoint);
}
};
// Port关联到service树结构中
Client.prototype._defineService = function(service, endpoint) {
var ports = service.ports,
def = {};
for (var name in ports) {
def[name] = this._definePort(ports[name], endpoint ? endpoint : ports[name].location);
}
return def;
};
// method关联到service树结构中
Client.prototype._definePort = function(port, endpoint) {
var location = endpoint,
binding = port.binding,
methods = binding.methods,
def = {};
for (var name in methods) {
def[name] = this._defineMethod(methods[name], location);
this[name] = def[name];
}
return def;
};
// 最终function(args, callback, options, extraHeaders)作为叶子关联到services的树中,
// 安放到对应的port和method下.
// client.runMethod(args, function(){})实际会调用这个叶子function
// 而这个函数,根本上,调用了_invoke
Client.prototype._defineMethod = function(method, location) {
var self = this;
var temp;
return function(args, callback, options, extraHeaders) {
...
self._invoke(method, args, location, function(error, result, raw, soapHeader) {
callback(error, result, raw, soapHeader);
}, options, extraHeaders);
};
};
小结
以上,完成了client的_initializeServices:
从wsdl解析definitions
definitions中解析services
services中解析ports
ports中解析methods
为每一个methods定义一个函数,这个函数实际上就是_invoke函数。
最后,用户通过client.xxxxMethod(args, callback, options, extraHeaders)会调用这个invoke
剧透一下,invoke就负责把method名字,args参数,extraHeaders,options组装成xml内容,发送出去。响应结果解析后,通过callback的result参数传回。
_invoke
Client.prototype._invoke = function(method, args, location, callback, options, extraHeaders) {
var self = this,
name = method.$name,
input = method.input,
output = method.output,
style = method.style,
defs = this.wsdl.definitions,
envelopeKey = this.wsdl.options.envelopeKey,
ns = defs.$targetNamespace,
encoding = '',
message = '',
xml = null,
req = null,
soapAction,
alias = findPrefix(defs.xmlns, ns),
// 设置headers,依据wsdl
headers = {
"Content-Type": "text/xml; charset=utf-8"
},
xmlnsSoap = "xmlns:" + envelopeKey + "=\"http://schemas.xmlsoap.org/soap/envelope/\"";
if (this.wsdl.options.forceSoap12Headers) {
headers["Content-Type"] = "application/soap+xml; charset=utf-8";
xmlnsSoap = "xmlns:" + envelopeKey + "=\"http://www.w3.org/2003/05/soap-envelope\"";
}
...
options = options || {};
//Add extra headers
for (var header in this.httpHeaders ) { headers[header] = this.httpHeaders[header]; }
for (var attr in extraHeaders) { headers[attr] = extraHeaders[attr]; }
// 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);
...
// message构建,主要是函数、参数;函数namespace设置等
// stype == 'rpc' objectToRpcXML
// style == 'document' objectToDocumentXML
if ((style === 'rpc')&& ( ( input.parts || input.name==="element" ) || args === null) ) {
assert.ok(!style || style === 'rpc', 'invalid message definition for document style binding');
message = self.wsdl.objectToRpcXML(name, args, alias, ns,(input.name!=="element" ));
(method.inputSoap === 'encoded') && (encoding = 'soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" ');
} else if (typeof (args) === 'string') {
message = args;
} else {
assert.ok(!style || style === 'document', 'invalid message definition for rpc style binding');
// pass `input.$lookupType` if `input.$type` could not be found
message = self.wsdl.objectToDocumentXML(input.$name, args, input.targetNSAlias, input.targetNamespace, (input.$type || input.$lookupType));
}
//拼装xml
xml = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
"<" + envelopeKey + ":Envelope " +
...
message +
"</" + envelopeKey + ":Body>" +
"</" + envelopeKey + ":Envelope>";
if(self.security && self.security.postProcess){
xml = self.security.postProcess(xml);
}
self.lastMessage = message;
self.lastRequest = xml;
self.lastEndpoint = location;
self.emit('message', message);
self.emit('request', xml);
var tryJSONparse = function(body) {
try {
return JSON.parse(body);
}
catch(err) {
return undefined;
}
};
// 1. 调用httpClient将xml打包,发送到webservice服务端,并将回包简单过滤处理
// 2. 调用wsdl将xml转为obj
req = self.httpClient.request(location, xml, function(err, response, body) {
var result;
var obj;
self.lastResponse = body;
self.lastResponseHeaders = response && response.headers;
self.lastElapsedTime = response && response.elapsedTime;
self.emit('response', body, response);
if (err) {
callback(err);
} else {
try {
obj = self.wsdl.xmlToObject(body);
} catch (error) {
// When the output element cannot be looked up in the wsdl and the body is JSON
// instead of sending the error, we pass the body in the response.
if(!output || !output.$lookupTypes) {
debug('Response element is not present. Unable to convert response xml to json.');
// If the response is JSON then return it as-is.
var json = _.isObject(body) ? body : tryJSONparse(body);
if (json) {
return callback(null, response, json);
}
}
error.response = response;
error.body = body;
self.emit('soapError', error);
return callback(error, response, body);
}
if (!output){
// one-way, no output expected
return callback(null, null, body, obj.Header);
}
if( typeof obj.Body !== 'object' ) {
var error = new Error('Cannot parse response');
error.response = response;
error.body = body;
return callback(error, obj, body);
}
result = obj.Body[output.$name];
// RPC/literal response body may contain elements with added suffixes I.E.
// 'Response', or 'Output', or 'Out'
// This doesn't necessarily equal the ouput message name. See WSDL 1.1 Section 2.4.5
if(!result){
result = obj.Body[output.$name.replace(/(?:Out(?:put)?|Response)$/, '')];
}
if (!result) {
['Response', 'Out', 'Output'].forEach(function (term) {
if (obj.Body.hasOwnProperty(name + term)) {
return result = obj.Body[name + term];
}
});
}
callback(null, result, body, obj.Header);
}
}, headers, options, self);
// Added mostly for testability, but possibly useful for debugging
if(req && req.headers) //fixes an issue when req or req.headers is indefined
self.lastRequestHeaders = req.headers;
};
总结
本文简单介绍node-soap的client实现。
client实现重依赖wsdl作对象和文档的转化。所有namespace,xml,soap的格式解析都在wsdl中处理。后面,找机会再看一下wsdl
client简单,清晰。并且提供事件监听
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。