Thursday, August 11, 2016

Node.js: My first SOAP service

I created a simple HelloWorld SOAP service running on Node.js. Why did I do that? I wanted to try if Node.js was a viable solution to use as middleware layer in an application landscape. Not all clients can call JSON services. SOAP is still very common. If Node.js is to be considered for such a role, it should be possible to host SOAP services on it. My preliminary conclusion is that it is possible to host SOAP services on Node.js but you should carefully consider how you want to do this.

I tried to create the SOAP service in two distinct ways.
  • xml2js. This Node.js module allows transforming XML to JSON and back. The JSON which is created can be used to easily access content with JavaScript. This module is fast and lightweight, but does not provide specific SOAP functionality.
  • soap. This Node.js module provides some abstractions and features which make working with SOAP easier. The module is specifically useful when calling SOAP services (when Node.js is the client). When hosting SOAP services, the means to control the specific response to a call are limited (or undocumented)
Using both modules, I encountered some challenges which I will describe and how (and if) I solved them. You can find my sample code here.


xml2js

xml2js is relatively low level and specifically meant to convert XML to JSON and the other way around. It is SAX based which helps keeping the service calls non-blocking. Hosting a service is something you can control with express routers. You can see this here. The rest of the service code can be found here.

body-parser

The first challenge was getting the SOAP request in a usable form to my service. You have to make sure you have a correct body-parser configured to respond to the correct content type in the request. If you set the body-parser to text for type */* it will work nicely. I did this in my service JavaScript file since different services might require different body-parsers.

router.use(bodyParser.text({ type: '*/*' }));

Serving the WSDL

In order for a SOAP service to provide functionality, it helps greatly if it can serve its own WSDL. I respond to a GET request at ?wsdl with the WSDL file. You have to mind that ?wsdl is a GET query string and you have to check for it accordingly.

async

There were several asynchronous function calls in the processing of the request and creating the response of the service call. In order to make the code better readable, I used the async module waterfall function to provide something what looks like a synchronous chain and is more easy to read than several levels of nested callbacks.

Namespace prefix

Namespace prefixes are not static.

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:tns="http://www.examples.com/wsdl/HelloService.wsdl">
   <soap:Body>
      <tns:sayHelloResponse>
         <tns:greeting>Hello Maarten</tns:greeting>
      </tns:sayHelloResponse>
   </soap:Body>
</soap:Envelope>

Is the same as

<blabla:Envelope xmlns:blabla="http://schemas.xmlsoap.org/soap/envelope/" xmlns:blablabla="http://www.examples.com/wsdl/HelloService.wsdl">
   <blabla:Body>
      <blablabla:sayHelloResponse>
         <blablabla:greeting>Hello Maarten</blablabla:greeting>
      </blablabla:sayHelloResponse>
   </blabla:Body>
</blabla:Envelope>

Thus it is dangerous to query for a fixed namespace prefix in the request. To work around this, I stripped the namespace prefixes. This is of course not a solid solution if a certain node contains two elements with the same name but a different namespace, this will fail. Such a case however is rare and to be avoided.

parseString(req.body, { tagNameProcessors: [stripPrefix] }, cb);

Finding elements

When a JSON object is returned by xml2js, elements are put in an array of objects. It is dangerous to just pick the first item in the array, because that item might not always be the first (depending on optional elements for example and I'm not sure xml2js puts an object always at the same spot in the array). I created a little search function to get around that.

Creating the response

xml2js can create XML if you provide it with the correct JSON input. What I did to obtain this correct JSON message was to first use SOAP-UI to create a sample XML response message (by creating a mock service). I used this XML as input for xml2js parseString function. This gave me the correct JSON output which I could alter to give me an output message which contained processed input.

soap

Using the soap module, I managed to create a simple helloworld service with very little coding. You can find the code here. I was quite happy with it until I noticed the created response had some errors. I wanted:


<soapenv:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="urn:examples:helloservice">
   <soapenv:Header/>
   <soapenv:Body>
      <urn:sayHelloResponse soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
         <greeting xsi:type="xsd:string">Hello Maarten</greeting>
      </urn:sayHelloResponse>
   </soapenv:Body>
</soapenv:Envelope>

I got:

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:tns="http://www.examples.com/wsdl/HelloService.wsdl">
   <soap:Body>
      <tns:sayHelloResponse>
         <tns:greeting>Hello Maarten</tns:greeting>
      </tns:sayHelloResponse>
   </soap:Body>
</soap:Envelope>

Although it was pretty close, I did not manage to get the namespace of the sayHelloResponse and greeting element fixed. It is one of the dangers of abstractions. I noticed that the possible options for the client part were far more elaborate than for the server part. I did not find a suitable alternative for this module though.

Some things

The soap module provides an easy way to create simple SOAP services and clients. However, I found it lacking in flexibility (or documentation with samples). Some handy features though are support for basic authentication and WS-Security headers. Currently I would go for the xml2js option instead of trying to invest serious time in understanding how to get the soap module to do exactly what I want it to do. I would consider soap for SOAP clients though. WS-Security can also be implemented without the soap module (wssecurity module). Also the setup with the express routers and body parsers works quite nicely. I did not find an easy way to validate a message based on a WSDL.

No comments:

Post a Comment