SOAP (originally Simple Object Access Protocol) is a messaging protocol specification for exchanging structured information in the implementation of web services in computer networks. Its purpose is to provide extensibility, neutrality and independence. It uses XML Information Set for its message format, and relies on application layer protocols, most often Hypertext Transfer Protocol (HTTP) or Simple Mail Transfer Protocol (SMTP), for message negotiation and transmission.

SOAP allows processes running on disparate operating systems (such as Windows and Linux) to communicate using Extensible Markup Language (XML). Since Web protocols like HTTP are installed and running on all operating systems, SOAP allows clients to invoke web services and receive responses independent of language and platforms.

1 Configuring call

You can easily create a SOAP client to perform SOAP calls by using SOAPClient and a consumer configurator:

Copy
<script>
    var soap = new Ax.net.SOAPClient("http://www.acme.com/soap/servlet/rpcrouter", options => {
        options.setUser("user");
        options.setPassword("password");
        // Enable request/response copy. After call we can see both messages by asking:
        // soap.getRequestCopy()
        // soap.getReponseCopy()
        options.setRequestCopy();
        options.setResponseCopy();

        // Force connection using digest AUTH. If server is using BASIC auth, connection will fail.
        // Default is false. In that case client tries BASIC and if fails switches to DIGEST.
        // options.setDigest(true);
    });
</script>

The object returned is cached across multiple calls for same options. This optimizes memory on multiple request and allows to perform statistics collection on this object.

Options configuration allow to configure connection parameters:

SOAP configuration methods
Return Options Description
Protocol
SOAPClientConfig setGzip(boolean gzipenabled) Enable compression (default is false).
When using compression, debug resquest copy and response copy will be compressed. So during debug, dissable compression if you want to inspect SOAP message body.
SOAPClientConfig setTimeout(int seconds) The HTTP timeout (0 if not defined)
SOAPClientConfig setEncodingStyle(String encodingStyle) Sets the encoding style for this SOAPElement object to one specified.
Authentication
SOAPClientConfig setUser(String user) Configures user to authenticate thru digest authentication.
SOAPClientConfig setPassword(String pass) Defines password to authenticate thru digest authentication.
SOAPClientConfig setDigest(boolean onlydigest) If true, indicates to use only Digest authentication when performing the SOAP call. If False, first system tries a Basic authentication and if fails, it tries to perform a Digest authentication.
SOAPClientConfig setSSLProtocol(String protocol) Setup the SSLContext protocol (default TSL).
SOAPClientConfig setSSLTrustAllFactory(boolean trustall) Trust any certificate provided by server connected even it's not known or invalid.
SOAPClientConfig setSSLClientKeyStore(InputStream keystoreStream, String keystorePassword, String keystoreAlias) Defines client authentication using certificates. If keystoreAlias is null, soap will use first certificate in keystore.
Proxy
SOAPClientConfig setProxyHost(String host) The proxy host (if need)
SOAPClientConfig setProxyPort(int port) The proxy port
SOAPClientConfig setProxyUser(String user) The proxy user (if need)
SOAPClientConfig setProxyPassword(String password) The proxy password
Debug
SOAPClientConfig setRequestCopy() Indicate SOAP to keep a copy of request XML message that can be retrieved after call by using soap.getRequestCopy()
SOAPClientConfig setResponseCopy() Indicate SOAP to keep a copy of response XML message that can be retrieved after call by using soap.getResponseCopy()

2 Call a remote endpoint

Once you have a SOAP object configured you can perfom the SOAP call. The SOAP call method will return a SOAP Response Object.

SOAP object methods
Return Options Description
JSSOAPResponse call(Consumer<SOAPCallConfig> configurator) .
JSSOAPResponse call(String service, String method, Object...args) .
N/A addMapping(String serviceName, Class<?> bean) .
N/A stats() .
MemoryResultSet getClientStatistics() A memory ResultSet with client statistics
MemoryResultSet getServerStatistics() A memory ResultSet with server statistics
String getRequestCopy() The request SOAP XML message if setRequestCopy() has been called during configuration or null.
String getResponseCopy() The response SOAP XML message if setRequestCopy() has been called during configuration or null.

Soap call configuration (SOAPCallConfig):

Return Options Description
N/A setServiceName(String service) .
N/A setAction(String action) Set SOAP Action URI
N/A setBody(String arg) Allows to set full SOAP Body message from a String.
N/A addParameter(String name, Object arg) Allows to set SOAP Body parameters. Parameter name is constructed as xml tag and arg is the content of this tag.
Envelope getEnvelope() Returns Envelope component of SOAP Message.

Soap call return object (JSSOAPResponse):

Return Options Description
Object getValue() Retrive the result.
int getStatusCode() Retrive the http status code.
String getFaultCode() Retrive the error code in the SOAP call.
String getFaultString() Retrive the message error in the SOAP call.
Vector getFaultEntries() Retrieve a vector with application-specific error messages. The detail element can contain child elements called detail entries.
Vector getDetailEntries() Retrieve a vector with the detail application-specific error messages.

3 Examples

3.1 Axional time server

The following example shows a call to an Axional Server time service.

Copy
var soap = new Ax.net.SOAPClient("http://www.myserver.com/soap/servlet/rpcrouter", options => {
   options.setUser("username");
   options.setPassword("password");
   options.setRequestCopy();
   options.setResponseCopy();
});

console.log(soap.call("SOAPAPPServer", "getServerTime"));
console.log("");
console.log("*** REQUEST *** ");
console.log("");
console.log(soap.getRequestCopy());
console.log("");
console.log("*** RESPONSE*** ");
console.log("");
console.log(soap.getResponseCopy());
SOAPResponse: value=1573765951085

*** REQUEST ***
POST /soap/servlet/rpcrouter HTTP/1.0
Host: www.mydeister.com
Content-Type: text/xml;charset=utf-8
Content-Length: 409
SOAPAction: ""
Authorization: Basic XXXXXXXXXX=

<?xml version='1.0' encoding='UTF-8'?>
<SOAP-ENV:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Body>
<ns1:getServerTime xmlns:ns1="urn:SOAPAPPServer" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
</ns1:getServerTime>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
POST /soap/servlet/rpcrouter HTTP/1.0
Host: www.mydeister.com
Content-Type: text/xml;charset=utf-8
Content-Length: 409
SOAPAction: ""
Authorization: Basic XXXXXXXXXX=

*** RESPONSE ***
<?xml version='1.0' encoding='UTF-8'?>
<SOAP-ENV:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Body>
<ns1:getServerTime xmlns:ns1="urn:SOAPAPPServer" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
</ns1:getServerTime>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

<?xml version='1.0' encoding='UTF-8'?>
<SOAP-ENV:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Body>
<ns1:getServerTimeResponse xmlns:ns1="urn:SOAPAPPServer" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<return xsi:type="xsd:long">1573765951085</return>
</ns1:getServerTimeResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

3.2 SiiFactFEV1SOAP

SiiFactFEV1SOAP - SII – Suministro Inmediato de Información

Copy
<script>

		var xmlcmd = `
<ns2:ConsultaLRFacturasEmitidas>
<ns1:Cabecera>
<ns1:IDVersionSii>1.1</ns1:IDVersionSii>
<ns1:Titular>
<ns1:NombreRazon>ACME</ns1:NombreRazon>
<ns1:NIF>73209131Q</ns1:NIF>
</ns1:Titular>
</ns1:Cabecera>
<ns2:FiltroConsulta>
<ns1:PeriodoImpositivo>
<ns1:Ejercicio>2019</ns1:Ejercicio>
<ns1:Periodo>01</ns1:Periodo>
</ns1:PeriodoImpositivo>
</ns2:FiltroConsulta>
</ns2:ConsultaLRFacturasEmitidas>
`;

    var ksc = new Ax.io.File('/tmp/test_sii_cert.pfx');
    var soap = new Ax.net.SOAPClient("https://www7.aeat.es/wlpl/SSII-FACT/ws/fe/SiiFactFEV1SOAP", options => {
        options.setSSLTrustAllFactory(true);
        options.setSSLClientKeyStore(ksc.toInputStream(), '0000', null);
    });
    
    var response = soap.call(call => {
        call.setMethodName(null);
        call.setAction("SuministroLRFacturasEmitidas");
        call.getEnvelope().declareNamespace("ns1", "https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/ssii/fact/ws/SuministroInformacion.xsd");
        call.getEnvelope().declareNamespace("ns2", "https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/ssii/fact/ws/ConsultaLR.xsd");

        call.setBody(xmlcmd);
    });
    console.log(response);
    console.log("HTTP Status Code: " + response.getStatusCode());

    if (response.getFaultCode() == null) {
        var xml = new Ax.xml.XMLElement(response.getValue());
        var json = xml.toJSON();
        
        if (json.jas("ns1:SuministroLRFacturasEmitidas")) {
            console.log(json.get("ns1:SuministroLRFacturasEmitidas"));
        }
    } else {
        console.log("SOAP Response Error Code: " + response.getFaultCode());
        console.log("SOAP Response Error Msg.: " + response.getFaultString());
        console.log("SOAP Response Fault Entries.:");
        console.log(response.getFaultEntries());
        console.log("SOAP Response Fault Details.:");
        console.log(response.getDetailEntries());
    }
</script>

4 Client statistics

To inspect SOAP client statistics simply call:

Copy
console.log(soap.getClientStatistics());
+-----+----------+-----+-------------------------------------------------+--------+------+-------+------+-------+-------+--------+-----+--------+---+------+------+-----------+
 SOAP client queue
+-----+----------+-----+-------------------------------------------------+--------+------+-------+------+-------+-------+--------+-----+--------+---+------+------+-----------+
|rowid|type      |age  |url                                              |username|locked|succeed|errors|mintime|maxtime|meantime|type |is_valid|qop|snonce|cnonce|nonce_count|
|rowid|Type      |Age  |URL                                              |Username|Locked|Total s|Total |Time mi|Time ma|Time mea|Auth |Valid   |QOP|Nonce |Nonce |Nonce count|
|     |          |     |                                                 |        |      |ucceed |errors|n      |x      |n       |     |        |   |server|client|           |
+-----+----------+-----+-------------------------------------------------+--------+------+-------+------+-------+-------+--------+-----+--------+---+------+------+-----------+
|303  |JavaScript|995ms|[http://www.mydeister.com/soap/servlet/rpcrouter]|android |[ ]   |    5.0|   0.0|   19ms|  556ms|   179ms|Basic|true    |   |      |      |           |
+-----+----------+-----+-------------------------------------------------+--------+------+-------+------+-------+-------+--------+-----+--------+---+------+------+-----------+

5 Axional SOAP services

Axional Studio provides a wide range of web services grouped in various URN

  • SOAPSQLServer, Database interface
  • SOAPOBJServer, Application Objects
  • SOAPAPPServer, Application Services
  • SOAPSQCServer, Catalogued SQL services
  • SOAPSQCServerV2, Catalogued SQL services (V2)
  • SOAPXMLServer, XML services

5.1 SOAPSQLServer

You can use SOAPSQLServer endpoint to access a remote database over internet and perform SQL DML and DDL operations.

In the following examples we will use a remote database called test_data and an table named table_soap. To run the examples, create the table in the remote database.

Copy
CREATE TABLE IF NOT EXISTS table_soap
(
    c0 SERIAL NOT NULL, 
    c1 CHAR(10), 
    c2 INT NOT NULL, 
    c3 INT8 NOT NULL, 
    c4 FLOAT NOT NULL, 
    c5 DECIMAL(12,4) NOT NULL, 
    c6 DATE NOT NULL, 
    c7 DATETIME HOUR TO SECOND NOT NULL, 
    c8 DATETIME YEAR TO SECOND NOT NULL, 
    c9 BLOB NOT NULL
);

5.1.1 Query

To perform a query call the executeQuery on desired database and send the query string. The result will be a ResultSet.

Copy
<script>
    var soap = new Ax.net.SOAPClient("http://www.acme.com/soap/servlet/rpcrouter", options => {
        options.setUser("user");
        options.setPassword("password");
    });
    console.log(soap.call("SOAPSQLServer", "executeQuery", "test_data", "SELECT FIRST 5 tabid, tabname, nrows FROM systables"));
</script>
+-----+----------+-------+
|tabid|tabname   |nrows  |
+-----+----------+-------+
|    1|systables |113.000|
|    2|syscolumns|786.000|
|    3|sysindices|150.000|
|    4|systabauth|111.000|
|    5|syscolauth| 54.000|
+-----+----------+-------+

5.1.2 Insert

You can insert into a table using a JSON object. The returned information is a SQLCA object containing:

  • count: number of rows processed in remote database server
  • serial: the last autoincrement (serial) value generated (if any)
  • time: SQL operation time in milliseconds
  • type: remote SQL operation type
  • sql: remote SQL statement

Copy
<script>
    
    var file = new Ax.io.File("/tmp/sample.txt");
    file.write("This is a text file to serve as BLOB);

    var row1 = { 
    	"c1": "abc", 
    	"c2" : 123, 
    	"c3" : 1234567890000123, 
    	"c4" : 156.90, 
    	"c5" : 156.9123, 
    	"c6" : new Date(), 
    	"c7" : new Date(), 
    	"c8" : new Date(), 
    	// You can upload binary data into BLOB data type by using a File reference
    	"c9" : file
    };
    var sqlca = soap.call("SOAPSQLServer", "executeInsert", "test_data", "table_soap", row1);
    console.log(sqlca);
</script>
{serial=1, count=1, time=3, type=INSERT, sql=INSERT INTO table_soap (c1,c2,c3,c4,c5,c6,c7,c8,c9) VALUES (?,?,?,?,?,?,?,?,?)}

Reading data

Let's see now how to read data we have just inserted back.

Copy
<script>
    var row2 = soap.call("SOAPSQLServer", "executeQuery", "test_data", "SELECT * from table_soap WHERE c0 = " + sqlca.getSerial()).toOne();
    console.log(row2);
    
    // Write blob (bytes) to file
    var file2 = new Ax.io.File("/tmp/sample2.txt");
    file2.write(row2.c9);
</script>
{c3=1234567890000123, c4=156.9000, c5=156.9123, c6=2019-02-22, c7=11:13:11, c8=2019-02-22 11:13:11.0, c9=[B@26b95b0b, c0=1, c1=abc, c2=123}

5.1.3 Update

Let's now update previous inserted row. As we know serial returned is 1 we can us it as primary key.

Copy
<script>
    var row = { 
        "c1": "ABC", 
        "c2": "456", 
    };
    var key = { 
        "c0": 1, 
    };
    console.log(soap.call("SOAPSQLServer", "executeUpdate", "test_data", "table_soap", row, key));
</script>
{serial=0, count=0, time=0, type=UPDATE, sql=UPDATE table_soap SET c1 = ?,c2 = ? WHERE c0 = ?}

Notice update operation returns an SQLCA with number of rows updated in count variable.

5.1.4 Delete

The delete operation requires a JSON object with keys for delete condition.

Copy
<script>
    console.log(soap.call("SOAPSQLServer", "executeDelete", "test_data", "table_soap", key));
</script>
{serial=0, count=0, time=1, type=DELETE, sql=DELETE FROM table_soap WHERE c0 = ?}

5.1.5 submitSQL

submitSQL can be used to perform asynchronous (non blocking) database operations. Operation returns the task ID (int) in the queue.

Copy
<script>
    console.log(soap.call("SOAPSQLServer", "submitSQL", "test_data", "INSERT INTO table VALUES(1, 2)", "task name"));
</script>
567