Introduction
Node.js is great platform for writing scalable server applications. Some of these applications would need to communicate with existing web services. As long as these services are Rest based this will not be a problem - Rest services are first class citizens in node. If we need to consume a soap web service, we could do a short googling to find node-soap or alternatively we can decide to handcraft the soap envelope ourselves. The real challenge is when node needs to consume soap services that utilize WS-* standards (WS-Security, MTOM etc). When I faced this situation a few months ago I could not find any module to help. This is why I decided to build Ws.js.
Using the Code
1. First you must install the Ws.js module:
npm install ws.js
2. Now write your code:
var ws = require('ws.js')
, Http = ws.Http
, Security = ws.Security
, UsernameToken = ws.UsernameToken
var request = '<Envelope xmlns="http://schemas.xmlsoap.org/soap/envelope/">' +
'<Header />' +
'<Body>' +
'<EchoString xmlns="http://tempuri.org/">' +
'<s>123</s>' +
'</EchoString>' +
'</Body>' +
'</Envelope>'
var ctx = { request: request
, url: "http://service/security"
, action: "http://tempuri.org/EchoString"
, contentType: "text/xml"
}
var handlers = [ new Security({}, [new UsernameToken({username: "yaron", password: "1234"})])
, new Http()
]
ws.send(handlers, ctx, function(ctx) {
console.log("response: " + ctx.response);
})
Let's analyze this sample. This following code imports the relevant modules:
var ws = require('ws.js')
, Http = ws.Http
, Security = ws.Security
, UsernameToken = ws.UsernameToken
The next lines define the soap envelope and some required information such as the url. Note that we need to build the soap out of band - Ws.js is not a soap engine. However this is usually easy as we typically have a working soap sample.
var request = '<Envelope xmlns="http://schemas.xmlsoap.org/soap/envelope/">' +
'<Header />' +
'<Body>' +
'<EchoString xmlns="http://tempuri.org/">' +
'<s>123</s>' +
'</EchoString>' +
'</Body>' +
'</Envelope>'
var ctx = { request: request
, url: "http://service/security"
, action: "http://tempuri.org/EchoString"
, contentType: "text/xml"
}
The next lines are the heart of the ws-* usage. We define the protocols we want to use in the request. This specific request uses the ws-security standard and configures it to send a username token.
var handlers = [ new Security({}, [new UsernameToken({username: "yaron", password: "1234"})])
, new Http()
]
Finally, this piece of code sends the request (using the specified protocols) and handles the response.
ws.send(handlers, ctx, function(ctx) {
console.log("response: " + ctx.response);
})
The resulting soap looks like this:
<Envelope xmlns="http://schemas.xmlsoap.org/soap/envelope/" xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<Header>
<o:Security>
<u:Timestamp>
<u:Created>2012-02-26T11:03:40Z</u:Created>
<u:Expires>2012-02-26T11:08:40Z</u:Expires>
</u:Timestamp>
<o:UsernameToken>
<o:Username>yaron</o:Username>
<o:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">1234</o:Password>
</o:UsernameToken>
</o:Security>
</Header>
<Body>
<EchoString xmlns="http://tempuri.org/">
<s>123</s>
</EchoString>
</Body>
MTOM Sample
Sending MTOM attachments is pretty similar. We just need to specify the file we want to send and the path to the soap element it corresponds to:
ws.addAttachment(ctx, "request", "//*[local-name(.)='File1']",
"me.jpg", "image/jpeg")
var handlers = [ new Mtom()
, new Http()
];
The full sample is here.
Supported Protocols
Currently Ws.js supports the following protocols:
- MTOM
- WS-Security (username token only)
- WS-Addressing (all versions)
- HTTP(S)
Behind the Scenes
Ws.js uses the chain of responsibility design pattern to call the different protocols. This is an extensible patterns so anyone can add new protocol implementations. While this is a known pattern for soap stacks, implementing it in javascript can be a little tricky. The key is for each handler to have a send() and a receive() methods. Sending actually passes the control to the next handler. We give that handler a callback method. That callback will call our receive() passing it the context and the original callback we got (which the downstream handler has no idea about). It is best to see the code:
SecurityHandler.prototype.send = function(ctx, callback) {
var self = this
this.next.send(ctx, function(ctx) {
self.receive(ctx, callback)
})
}
SecurityHandler.prototype.receive = function(ctx, callback) {
callback(ctx)
}
var s = new SecurityHandler()
s.next = new HttpHandler()
s.send(ctx, function(ctx) {...})
Like many node app, Ws.js also uses some external modules. It especially relies on strong xml processing libraries. As I note here, it was not trivial to find dom based node.js xml parser which works on windows. I finally found xmldom and xpath.js.
Other notable libraries used by Ws.js are node-formidable and node-bufferjs which are helpful in the context of mime parsing.
The future of Ws.js
Ws.js is a growing framework. For future versions I plan to add more advanced security standards like X.509 digital signature and encryption. If you have any special request send me an email from my blog. If you want to help feel free to fork Ws.js on github - that's the fastest way to grow Ws.js.
More Information
Check out the project github page
Check out my blog
Check out my twitter
Drop me an email from my blog