node.jssoapsoap-clienttranslatenode-soap

How do I translate this PHP SoapClient code into node.js using node-soap?


I am completely new to SOAP Api's and have an API that only accepts SOAP requests. The documentation is very bad to non-existent, but they do have some example implementations showing everything of in PHP, Java and .Net. I am able to understand the PHP code slightly and My goal is and I have tried to translate it to JS with "soap", but I just get the error: connect ECONNREFUSED ::1:80. The API requires the use of certificates and needs TLS 1.2 handshaking.

Here is the code snippit in PHP, that I want to translate into Node.js with node-soap:

function createSoapClient()
{
    $sslOptions = array(
        'local_cert' => $_POST['certifikat'],
        'verify_peer' => true,
        'cafile' => $_POST['ca'],
        'CN_match' => 'kt-ext-portwise.statenspersonadressregister.se');

    $streamcontext = stream_context_create(
        array('ssl' => $sslOptions));

    $options = array(
        'location' => $_POST['url'],
        'stream_context' => $streamcontext);

    // For the client to be able to read the file locally it requires a "file:// in front of the path"
    $wsdl = 'file://' . dirname(__FILE__) . DIRECTORY_SEPARATOR . 'resurser/personsok-2021.1.wsdl';

    return new SoapClient($wsdl, $options);
}

I seem to be able to create a Soap Client but when I call the Soap function "PersonSok" it throws me that error: connect ECONNREFUSED ::1:80. I use express to start the node.js project. Here is my code so far: (the two functions and only a part of the whole code)

// Send request
export function sendRequest() {
    return new Promise((resolve, reject) => {
        var args = { "Identifieringsinformation": createIdentifieringsInformation()};
        args["PersonsokningFraga"] = {"IdNummer": 195704133106};

        console.log("Passing arguments to PersonSok: " + args);
        
        // Create soap client
        createSoapClient().then((client) => {
            client.PersonSok(args, function (err, result) {
                if(err) {
                    reject(err);
                } else {
                    resolve(result);
                }
            });
        });
    });
}

// Create SOAP Client
export function createSoapClient() {
    return new Promise(async (resolve, reject) => {
        var url = './assets/personsok.wsdl';

        // The commented code below is just some methods I have tried, but still same error.
        /*
        const secureContext = tls.createSecureContext({
            cert: fs.readFileSync('assets/Kommun_A.pem'),
            ca: fs.readFileSync('assets/DigiCert.pem'),
        });
        */

        //var client = await soap.createClientAsync(url, { secureContext, secureOptions: tls.SSL_OP_NO_TLSv1_2, rejectUnauthorized: false });

        var client = await soap.createClientAsync(url, { rejectUnauthorized: false, strictSSl: false, secureOptions: tls.SSL_OP_NO_TLSv1_2 });
        var wssec = new soap.ClientSSLSecurity('assets/private-key.pem', 'assets/csr.pem', 'assets/public-cert.pem');
        client.setSecurity(wssec);

        if(!client) {
            reject("Error: could not create SOAP client.");
        }

        console.log("Created SOAP Client");
        resolve(client);
    });
}

I appreciate all the help I can get! :)))


Solution

  • I finally found a working translation. In this case I am trying to post a request to the Swedish SPAR API (Statens personadressregister) so there could be differences between this and the API your are trying to reach.

    But on the road I got confronted with three errors after each other, here is how I fixed them:

    Error 1: "ECONNECTREFUSED ::1:80" Solution to error 1: In my previous test code I didn't set an endpoint to where the request should go as there is in the PHP option "location" corresponding to client.setEndpoint(url). I can't really explain this, but this thread set the cert and key option in both the client.ClientSSLSecurity() and in a new and overriden HttpAgent inside of the wsdl_options of the created SOAP client.

    Error 2: "ECONNECT socket hangup" Solution to error 2: I saw, that my previous SOAP request had a default header of "connection": "close". I suppose, that the connection for some reason close before any response could be finished. The solution was therefore to add the code: client.addHttpHeader("connection", "keep-alive"). I also saw this solution for others on stack overflow having a similar error.

    In this stage I was able to establish a connection and a successful auth between my client and SPAR's API.

    Error 3: Validation error Solution to error 3: So this is more of a specific error relating to the API I was trying to reach, but might also become a problem of your's. I realised, that my arguments has to be in a correct order of the object, otherwise it will throw (in my case) a validation error. Check your wsdl for more information on this! For example this didn't work for me:

    const args = {
       arg2: blabla,
       arg1: blabaa,
       ...
    }
    

    Last but not least, here is the entire code. Fill in the information, that you need to make your request. This one worked ME and I am just around 40 intensive hours experienced with SOAP so far, so don't take my word for everything. I just tried explain with the best of my knowledge :)

    const soap = require('soap');
    const tls = require('tls');
    const fs = require('fs');
    const https = require('https');
    const express = require('express');
    const app = express();
    const port = 8080;
    
    const wsdlUrl = "url";
    const endpoint = "url";
    const cert = "pathToCert";
    const key = "pathToKey";
    
    const args = { 
        {yourArgs}
    };
    
    // Create Client
    async function createClient() {
        var sec = new soap.ClientSSLSecurity(
            key, // key
            cert, // cert
        );
    
        var client = await soap.createClientAsync(wsdlUrl, { 
            wsdl_options: {
                httpsAgent: new https.Agent({
                    key: fs.readFileSync(key),
                    cert: fs.readFileSync(cert),
                }),
            },
        });
        client.addHttpHeader('connection', 'keep-alive');
        client.setEndpoint(endpoint);
        client.setSecurity(sec);
        
    
        return client;
    }
    
    // Send soap request with custom arguments
    async function sendSOAPRequest(args) {
        var client = await createClient();
        var result = await client.MyFunction(args);
    
        return result;
    }
    
    
    
    
    app.get('/', (req, res) => {
        res.send("Node-SOAP test.");
    });
    app.get('/yourNodeJSEndpoint', async (req, res) => {
        var result = await sendSOAPRequest(args);
        res.send(result);
    });
      
    app.listen(port, () => {
        console.log(`Example app listening on port ${port}`);
    });

    Ask me if you have any questions, suggestions or better explanations on this matter.