Js: 如何使用 SOAP XML Web 服务

我想知道使用 node.js 消费 SOAP XML Web 服务的最佳方式是什么

谢谢!

236357 次浏览

You don't have that many options.

You'll probably want to use one of:

I think that an alternative would be to:

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

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.

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)...

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

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.

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

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);

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(...){

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.

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.

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

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.

this works like a charm for me

easy-soap-request

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

simple and straightforward

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)
                

})