Sherman
Sherman

Reputation: 897

node-soap adding namespace to envelope

I am trying to consume this soap service: http://testws.truckstop.com:8080/v13/Posting/LoadPosting.svc?singleWsdl with node-soap, but the client is mangling the namespaces and I have been unable to find a working solution.

I believe the answer is to either add a namespace to the soap envelope, or overwrite the soap envelope.

Using Soap UI, the request should look like:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
xmlns:v11="http://webservices.truckstop.com/v11" 
xmlns:web="http://schemas.datacontract.org/2004/07/WebServices">
   <soapenv:Header/>
   <soapenv:Body>
      <v11:GetLoads>
         <v11:listRequest>
            <web:IntegrationId>integrationId</web:IntegrationId>
            <web:Password>password</web:Password>
            <web:UserName>username</web:UserName>
         </v11:listRequest>
      </v11:GetLoads>
   </soapenv:Body>
</soapenv:Envelope>

However, when I do:

client = soap.createClient(url);
let query = {
        listRequest: {
            Password: password,
            UserName: username,
            IntegrationId: integrationId
        }
    };
let results = client.GetLoads(query);

The client generates this xml:

<?xml version="1.0" encoding="utf-8"?>
    <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" 
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
        xmlns:msc="http://schemas.microsoft.com/ws/2005/12/wsdl/contract" 
        xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" 
        xmlns:tns="http://webservices.truckstop.com/v11" 
        xmlns:q1="http://schemas.datacontract.org/2004/07/WebServices.Posting" 
        xmlns:q2="http://schemas.datacontract.org/2004/07/WebServices.Objects" 
        xmlns:q3="http://schemas.datacontract.org/2004/07/WebServices.Posting" 
        xmlns:q4="http://schemas.datacontract.org/2004/07/WebServices.Objects" 
        xmlns:q5="http://schemas.datacontract.org/2004/07/WebServices.Posting" 
        xmlns:q6="http://schemas.datacontract.org/2004/07/WebServices.Objects" 
        xmlns:q7="http://schemas.datacontract.org/2004/07/WebServices.Posting" 
        xmlns:q8="http://schemas.datacontract.org/2004/07/WebServices.Objects" 
        xmlns:q9="http://schemas.datacontract.org/2004/07/WebServices.Posting" 
        xmlns:q10="http://schemas.datacontract.org/2004/07/WebServices.Objects" 
        xmlns:q11="http://schemas.datacontract.org/2004/07/WebServices.Posting" 
        xmlns:q12="http://schemas.datacontract.org/2004/07/WebServices.Objects">
        <soap:Body>
            <GetLoads xmlns="http://webservices.truckstop.com/v11">
                <listRequest>
                    <ns1:IntegrationId>integrationId</ns1:IntegrationId>
                    <ns1:Password>password</ns1:Password>
                    <ns1:UserName>usernam</ns1:UserName>
                </listRequest>
            </GetLoads>
        </soap:Body>
    </soap:Envelope>

This fails because IntegrationId, Password and UserName need http://schemas.datacontract.org/2004/07/WebServices, but the namespace isn't referenced in the envelope.

I've tried updating the client to add the namespace as suggested here:

client.wsdl.definitions.xmlns.ns1 = "http://schemas.datacontract.org/2004/07/WebServices";
client.wsdl.xmlnInEnvelope = client.wsdl._xmlnsMap();

I can see the namespace in client.wsdl.xmlnInEnvelope, but it doesn't seem to change the actual generated xml.

Is there another step required to refresh the client to use the updated envelope?

I also tried overriding the root element as shown here:

        var wsdlOptions = {
            //namespaceArrayElements: "xmlns:ns1=http://schemas.datacontract.org/2004/07/WebServices"

            "overrideRootElement": {
                "namespace": "xmlns:tns",
                "xmlnsAttributes": [{
                    "name": "xmlns:tns",
                    "value": "http://webservices.truckstop.com/v11"
                }, {
                    "name": "xmlns:ns1",
                    "value": "http://schemas.datacontract.org/2004/07/WebServices"
                }]
            }
        };
        this.loadPostClient = soap.createClient(this.tsConfig.loadPostUrl, wsdlOptions);

This changes the root body element:

<soap:Body>
    <xmlns:tns:GetLoads 
        xmlns:tns="http://webservices.truckstop.com/v11" 
        xmlns:ns1="http://schemas.datacontract.org/2004/07/WebServices">
        <listRequest>
            <ns1:IntegrationId>integrationId</ns1:IntegrationId>
            <ns1:Password>password</ns1:Password>
            <ns1:UserName>username</ns1:UserName>
        </listRequest>
    </xmlns:tns:GetLoads>
</soap:Body>

But the remote server doesn't understand.

Thank you for reading!

Upvotes: 4

Views: 7171

Answers (3)

josephdpurcell
josephdpurcell

Reputation: 1482

I was able to do this in node soap v1.0.0 like so:

if (!soapClient['wsdl'].xmlnsInEnvelope.includes('xmlns:ns1=')) {
  soapClient['wsdl'].xmlnsInEnvelope += 'xmlns:ns1="http://schemas.datacontract.org/2004/07/WebServices"';
}

Why this approach? The accepted answer gives this error: "Property '_xmlnsMap' is private and only accessible within class 'WSDL'.ts(2341)", as already noted.

I don't know the pros/cons of this approach vs the other answer which involves writing your own WSDL class. I do know this was quick to implement and seems to work for my use case.

Upvotes: 0

Matt Jones
Matt Jones

Reputation: 74

It's been a few years, but I ran into a similar need of adding custom attributes to the soap envelope and wanted to give an alternative.

As of this writing, that _xmlnsMap() is a private method on the WSDL class so you can use it at your own risk. I always take private methods as subject to change from the developer without any notice to the library consumers so I wanted to find another way and turns out its possible.

TL;DR - Create your own WSDL class instance and pass it to your own Client class instance.

  • Use the open_wsdl method to bring in your WSDL
  • Use the callback to build your own custom attributes in a concatenated string.
  • Assign the attributes to the public xmlnsInEnvelope property.
  • return the updated WSDL instance (I used a promise).
const fetchWSDL = new Promise<WSDL>((resolve, reject) => {
      // method that returns a WSDL instance from a url/file path
      open_wsdl(this.wsdl, (err: any, wsdl?: WSDL) => {
        // Build custom attributes
        if (wsdl && wsdl.definitions.xmlns) {
          const xmlns: { [key: string]: string } = {
           [your namespaces]: 'values',
          };

          // turn your custom attributes map into a single concatenated string 
          let str = '';
          for (const alias in xmlns) {
            const ns = xmlns[alias];
            str += ' xmlns:' + alias + '="' + ns + '"';
          }

          // Leverage public attribute on WSDL instance to apply our custom attributes
          wsdl.xmlnsInEnvelope = str;

          resolve(wsdl);
        }
        reject(err);
      });
    });

Use the updated WSDL instance to create your own client.
NOTE: the createClient method is just a convenience wrapper for creating a WSDL instance and returning a new Client instance.

const ModifiedWSDL = await fetchWSDL;

// Create client with our modified WSDL instance
this.client = new Client(ModifiedWSDL)

// adjust your Client instance as needed

A bit more code that the OP, but hopefully more in line with node-soap types and safer to use if you plan to upgrade.

Upvotes: 1

Sherman
Sherman

Reputation: 897

This answer was correct all along

It wasn't working for me due to autocomplete and similar fields

client.wsdl.xmlnInEnvelope = client.wsdl._xmlnsMap();

Should have been:

client.wsdl.xmlnsInEnvelope = client.wsdl._xmlnsMap();

I left out an s and was setting xmlnInEnvelope instead of xmlnsInEvelope

Upvotes: 4

Related Questions