Node.js: how to consume SOAP XML web service

Xmlnode.jsSoap

Xml Problem Overview


I wonder what is the best way to consume SOAP XML web service with node.js

Thanks!

Xml Solutions


Solution 1 - Xml

You don't have that many options.

You'll probably want to use one of:

Solution 2 - Xml

I think that an alternative would be to:

Yes, this is a rather dirty and low level approach but it should work without problems

Solution 3 - Xml

If node-soap doesn't work for you, just use node request module and then convert the xml to json if needed.

My request wasn't working with node-soap and there is no support for that module beyond the paid support, which was beyond my resources. So i did the following:

  1. downloaded SoapUI on my Linux machine.
  2. copied the WSDL xml to a local file
    curl http://192.168.0.28:10005/MainService/WindowsService?wsdl > wsdl_file.xml
  3. In SoapUI I went to File > New Soap project and uploaded my wsdl_file.xml.
  4. In the navigator i expanded one of the services and right clicked the request and clicked on Show Request Editor.

From there I could send a request and make sure it worked and I could also use the Raw or HTML data to help me build an external request.

Raw from SoapUI for my request

POST http://192.168.0.28:10005/MainService/WindowsService HTTP/1.1
Accept-Encoding: gzip,deflate
Content-Type: text/xml;charset=UTF-8
SOAPAction: "http://Main.Service/AUserService/GetUsers"
Content-Length: 303
Host: 192.168.0.28:10005
Connection: Keep-Alive
User-Agent: Apache-HttpClient/4.1.1 (java 1.5)

XML from SoapUI

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:qtre="http://Main.Service">
   <soapenv:Header/>
   <soapenv:Body>
      <qtre:GetUsers>
         <qtre:sSearchText></qtre:sSearchText>
      </qtre:GetUsers>
   </soapenv:Body>
</soapenv:Envelope> 

I used the above to build the following node request:

var request = require('request');
let xml =
`<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:qtre="http://Main.Service">
   <soapenv:Header/>
   <soapenv:Body>
      <qtre:GetUsers>
         <qtre:sSearchText></qtre:sSearchText>
      </qtre:GetUsers>
   </soapenv:Body>
</soapenv:Envelope>`

var options = {
  url: 'http://192.168.0.28:10005/MainService/WindowsService?wsdl',
  method: 'POST',
  body: xml,
  headers: {
    'Content-Type':'text/xml;charset=utf-8',
    'Accept-Encoding': 'gzip,deflate',
    'Content-Length':xml.length,
    'SOAPAction':"http://Main.Service/AUserService/GetUsers"
  }
};

let callback = (error, response, body) => {
  if (!error && response.statusCode == 200) {
    console.log('Raw result', body);
    var xml2js = require('xml2js');
    var parser = new xml2js.Parser({explicitArray: false, trim: true});
    parser.parseString(body, (err, result) => {
      console.log('JSON result', result);
    });
  };
  console.log('E', response.statusCode, response.statusMessage);  
};
request(options, callback);

Solution 4 - Xml

I managed to use soap,wsdl and Node.js You need to install soap with npm install soap

Create a node server called server.js that will define soap service to be consumed by a remote client. This soap service computes Body Mass Index based on weight(kg) and height(m).

const soap = require('soap');
const express = require('express');
const app = express();
/**
 * this is remote service defined in this file, that can be accessed by clients, who will supply args
 * response is returned to the calling client
 * our service calculates bmi by dividing weight in kilograms by square of height in metres
 */
const service = {
  BMI_Service: {
    BMI_Port: {
      calculateBMI(args) {
        //console.log(Date().getFullYear())
        const year = new Date().getFullYear();
        const n = args.weight / (args.height * args.height);
        console.log(n);
        return { bmi: n };
      }
    }
  }
};
// xml data is extracted from wsdl file created
const xml = require('fs').readFileSync('./bmicalculator.wsdl', 'utf8');
//create an express server and pass it to a soap server
const server = app.listen(3030, function() {
  const host = '127.0.0.1';
  const port = server.address().port;
});
soap.listen(server, '/bmicalculator', service, xml);

Next, create a client.js file that will consume soap service defined by server.js. This file will provide arguments for the soap service and call the url with SOAP's service ports and endpoints.

const express = require('express');
const soap = require('soap');
const url = 'http://localhost:3030/bmicalculator?wsdl';
const args = { weight: 65.7, height: 1.63 };
soap.createClient(url, function(err, client) {
  if (err) console.error(err);
  else {
    client.calculateBMI(args, function(err, response) {
      if (err) console.error(err);
      else {
        console.log(response);
        res.send(response);
      }
    });
  }
});

Your wsdl file is an xml based protocol for data exchange that defines how to access a remote web service. Call your wsdl file bmicalculator.wsdl

<definitions name="HelloService" targetNamespace="http://www.examples.com/wsdl/HelloService.wsdl" 
  xmlns="http://schemas.xmlsoap.org/wsdl/" 
  xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" 
  xmlns:tns="http://www.examples.com/wsdl/HelloService.wsdl" 
  xmlns:xsd="http://www.w3.org/2001/XMLSchema">

  <message name="getBMIRequest">
    <part name="weight" type="xsd:float"/>
    <part name="height" type="xsd:float"/>
  </message>

  <message name="getBMIResponse">
    <part name="bmi" type="xsd:float"/>
  </message>

  <portType name="Hello_PortType">
    <operation name="calculateBMI">
      <input message="tns:getBMIRequest"/>
      <output message="tns:getBMIResponse"/>
    </operation>
  </portType>

  <binding name="Hello_Binding" type="tns:Hello_PortType">
    <soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>
    <operation name="calculateBMI">
      <soap:operation soapAction="calculateBMI"/>
      <input>
        <soap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" namespace="urn:examples:helloservice" use="encoded"/>
      </input>
      <output>
        <soap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" namespace="urn:examples:helloservice" use="encoded"/>
      </output>
    </operation>
  </binding>

  <service name="BMI_Service">
    <documentation>WSDL File for HelloService</documentation>
    <port binding="tns:Hello_Binding" name="BMI_Port">
      <soap:address location="http://localhost:3030/bmicalculator/" />
    </port>
  </service>
</definitions>

Hope it helps

Solution 5 - Xml

The simplest way I found to just send raw XML to a SOAP service using Node.js is to use the Node.js http implementation. It looks like this.

var http = require('http');
var http_options = {
  hostname: 'localhost',
  port: 80,
  path: '/LocationOfSOAPServer/',
  method: 'POST',
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded',
    'Content-Length': xml.length
  }
}

var req = http.request(http_options, (res) => {
  console.log(`STATUS: ${res.statusCode}`);
  console.log(`HEADERS: ${JSON.stringify(res.headers)}`);
  res.setEncoding('utf8');
  res.on('data', (chunk) => {
    console.log(`BODY: ${chunk}`);
  });

  res.on('end', () => {
    console.log('No more data in response.')
  })
});

req.on('error', (e) => {
  console.log(`problem with request: ${e.message}`);
});

// write data to request body
req.write(xml); // xml would have been set somewhere to a complete xml document in the form of a string
req.end();

You would have defined the xml variable as the raw xml in the form of a string.

But if you just want to interact with a SOAP service via Node.js and make regular SOAP calls, as opposed to sending raw xml, use one of the Node.js libraries. I like node-soap.

Solution 6 - Xml

Depending on the number of endpoints you need it may be easier to do it manually.

I have tried 10 libraries "soap nodejs" I finally do it manually.

Solution 7 - Xml

I successfully used "soap" package (https://www.npmjs.com/package/soap) on more than 10 tracking WebApis (Tradetracker, Bbelboon, Affilinet, Webgains, ...).

Problems usually come from the fact that programmers does not investigate to much about what remote API needs in order to connect or authenticate.

For instance PHP resends cookies from HTTP headers automatically, but when using 'node' package, it have to be explicitly set (for instance by 'soap-cookie' package)...

Solution 8 - Xml

Solution 9 - Xml

I used the node net module to open a socket to the webservice.

/* on Login request */
socket.on('login', function(credentials /* {username} {password} */){	
	if( !_this.netConnected ){
		_this.net.connect(8081, '127.0.0.1', function() {
			logger.gps('('+socket.id + ') '+credentials.username+' connected to: 127.0.0.1:8081');
			_this.netConnected = true;
			_this.username = credentials.username;
			_this.password = credentials.password;
			_this.m_RequestId = 1;
			/* make SOAP Login request */
			soapGps('', _this, 'login', credentials.username);				
		});			
	} else {
		/* make SOAP Login request */
		_this.m_RequestId = _this.m_RequestId +1;
		soapGps('', _this, 'login', credentials.username);			
	}
});

Send soap requests

/* SOAP request func */
module.exports = function soapGps(xmlResponse, client, header, data) {
	/* send Login request */
	if(header == 'login'){
		var SOAP_Headers = 	"POST /soap/gps/login HTTP/1.1\r\nHost: soap.example.com\r\nUser-Agent: SOAP-client/SecurityCenter3.0\r\n" +
							"Content-Type: application/soap+xml; charset=\"utf-8\"";		
		var SOAP_Envelope=  "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
							"<env:Envelope xmlns:env=\"http://www.w3.org/2003/05/soap-envelope\" xmlns:SOAP-ENC=\"http://www.w3.org/2003/05/soap-encoding\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:n=\"http://www.example.com\"><env:Header><n:Request>" +
							"Login" +
							"</n:Request></env:Header><env:Body>" +
							"<n:RequestLogin xmlns:n=\"http://www.example.com.com/gps/soap\">" +
							"<n:Name>"+data+"</n:Name>" +
							"<n:OrgID>0</n:OrgID>" +										
							"<n:LoginEntityType>admin</n:LoginEntityType>" +
							"<n:AuthType>simple</n:AuthType>" +
							"</n:RequestLogin></env:Body></env:Envelope>";
					
		client.net.write(SOAP_Headers + "\r\nContent-Length:" + SOAP_Envelope.length.toString() + "\r\n\r\n");
		client.net.write(SOAP_Envelope);
		return;
	}

Parse soap response, i used module - xml2js

var parser = new xml2js.Parser({
	normalize: true,
	trim: true,
	explicitArray: false
});
//client.net.setEncoding('utf8');

client.net.on('data', function(response) {
	parser.parseString(response);
});

parser.addListener('end', function( xmlResponse ) {
	var response = xmlResponse['env:Envelope']['env:Header']['n:Response']._;
	/* handle Login response */
	if (response == 'Login'){
		/* make SOAP LoginContinue request */
		soapGps(xmlResponse, client, '');
	}
	/* handle LoginContinue response */
	if (response == 'LoginContinue') {
		if(xmlResponse['env:Envelope']['env:Body']['n:ResponseLoginContinue']['n:ErrCode'] == "ok") {			
			var nTimeMsecServer = xmlResponse['env:Envelope']['env:Body']['n:ResponseLoginContinue']['n:CurrentTime'];
			var nTimeMsecOur = new Date().getTime();
		} else {
			/* Unsuccessful login */
			io.to(client.id).emit('Error', "invalid login");
			client.net.destroy();
		}
	}
});

Hope it helps someone

Solution 10 - Xml

Adding to Kim .J's solution: you can add preserveWhitespace=true in order to avoid a Whitespace error. Like this:

soap.CreateClient(url,preserveWhitespace=true,function(...){

Solution 11 - Xml

You can use wsdlrdr also. EasySoap is basically rewrite of wsdlrdr with some extra methods. Be careful that easysoap doesn't have the getNamespace method which is available at wsdlrdr.

Solution 12 - Xml

For those who are new to SOAP and want a quick explanation and guide, I strongly recommend this awesome article.

You can also use node-soap package, with this simple tutorial.

Solution 13 - Xml

If you just need a one-time conversion, https://www.apimatic.io/dashboard?modal=transform lets you do this by making a free account (no affiliation, it just worked for me).

If you transform into Swagger 2.0, you can make a js lib with

$ wget https://repo1.maven.org/maven2/io/swagger/codegen/v3/swagger-codegen-cli/3.0.20/swagger-codegen-cli-3.0.20.jar \
  -O swagger-codegen-cli.jar
$ java -jar swagger-codegen-cli.jar generate \
  -l javascript -i orig.wsdl-Swagger20.json -o ./fromswagger

Solution 14 - Xml

I had a webservice to consume with prefix - namespace and never was able to make it work with node-soap.

So i tried axios post method.

Go to your browser and paste the url information from axios: https://somewebservice.company.com.br/WCF/Soap/calc.svc?wsdl

Scroll down untill you see Soap operation name you are interested in.

soap operation name

Then copy the operation soapAction = "http://xyzxyzxyz/xyz/xyz/ObtenerSaldoDeParcelaDeEmprestimo"

in the axiosCall header.

const axiosCall = require('axios')
const xml2js = require('xml2js')

let xml = `<soapenv:Envelope 
                xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
                xmlns:tem="http://pempuri.org/" 
                xmlns:ser="http://schemas.example.org/2004/07/MyServices.Model">
            
            <soapenv:Header/>
                <soapenv:Body>
                
                <tem:DocumentState>
                <tem:DocumentData>
                     <ser:ID>0658</ser:ID>
                     <ser:Info>0000000001</ser:Info>
               </tem:DocumentData>
               </tem:DocumentState>
                
                </soapenv:Body>
            </soapenv:Envelope>         

let url = 'https://somewebservice.company.com.br/WCF/Soap/calc.svc?wsdl'

axiosCall.post( url,
            xml,
			{
				headers: {
					'Content-Type': 'text/xml',
					SOAPAction: 'http://xyzxyzxyz/xyz/xyz/ObtenerSaldoDeParcelaDeEmprestimo'
				}
			})
			.then((response)=>{
                 // xml2js to parse the xml response from the server 
                 // to a json object and then be able to iterate over it.

                 xml2js.parseString(response.data, (err, result) => {
					
                    if(err) {
						throw err;
					}
                    console.log(result)
				}
										
			})

				
			})
			.catch((error)=>{
				
				console.log(error)
				
			})

Solution 15 - Xml

In my opinion, avoid querying SOAP APIs with nodejs.

Two alternatives :

  1. If you're the owner of the SOAP API, make it handle both xml and json requests because javascript handles well json.

  2. Implement an API gateway in php (because php handles well SOAP). The gateway will receive your input as json, then query the SOAP API in xml and transforms the xml response into json.

Solution 16 - Xml

this works like a charm for me

easy-soap-request

https://www.npmjs.com/package/easy-soap-request

simple and straightforward

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionWHITECOLORView Question on Stackoverflow
Solution 1 - XmlJuicy ScripterView Answer on Stackoverflow
Solution 2 - XmltmanolatosView Answer on Stackoverflow
Solution 3 - XmljtlindseyView Answer on Stackoverflow
Solution 4 - XmlKim .JView Answer on Stackoverflow
Solution 5 - XmlHalfstopView Answer on Stackoverflow
Solution 6 - Xmldam1View Answer on Stackoverflow
Solution 7 - XmlsmentekView Answer on Stackoverflow
Solution 8 - XmleuroblazeView Answer on Stackoverflow
Solution 9 - XmlVince LoweView Answer on Stackoverflow
Solution 10 - XmlJ.AliagaView Answer on Stackoverflow
Solution 11 - XmlKankan-0View Answer on Stackoverflow
Solution 12 - XmlMajidJafariView Answer on Stackoverflow
Solution 13 - XmlunhammerView Answer on Stackoverflow
Solution 14 - XmlDomingoView Answer on Stackoverflow
Solution 15 - XmlmbessonView Answer on Stackoverflow
Solution 16 - XmlMr.PView Answer on Stackoverflow