Etiqueta: SOAP

  • From Zero to SOAP

    From Zero to SOAP

    SOAP (Simple Object Access Protocol) is often chosen over REST or GraphQL for scenarios requiring high security, reliability, and formal contracts, particularly in enterprise environments. Its built-in support for WS-Security, strong typing, and detailed error handling makes it ideal for industries like finance or healthcare that demand strict compliance and complex, stateful transactions. SOAP’s WSDL provides a clear, formal contract between client and server, ensuring interoperability across different platforms and legacy systems. However, REST and GraphQL are generally preferred for modern web applications due to their simplicity, flexibility, and lower overhead, making them more suitable for mobile or web-based services where performance and ease of use are prioritized. The choice ultimately depends on the specific requirements of the project, with SOAP excelling in structured, secure, and complex use cases.

    In previous post we have explores API REST, GraphQL and Websocket network aplicatuib interfaces, now is turn of SOAP. In this post we are going to develop a simple dockerized NodeJS SOAP server that offers an add callculation service.

    Addition SOAP Server

    First step is to build a SOAP server that will implement arithmethic add operation. The server technology used by its simpicity is a Dockerized NodeJS server. Let’s create a nodeJS server from scratch

    npm init -y

    Next install server dependencies for soap and express

    npm install soap express

    This is SOAP server itself:

    const express = require('express');
    const soap = require('soap');
    const http = require('http');
    
    const app = express();
    
    const service = {
      MyService: {
        MyPort: {
          AddNumbers: function (args) {
            const aValue = parseInt(args.a , 10);
            const bValue = parseInt(args.b , 10);
            console.log(' args.a + args.b',  aValue + bValue);
            return { result: aValue + bValue };
          },
        },
      },
    };
    
    const xml = `
    <definitions name="MyService"
      targetNamespace="http://example.com/soap"
      xmlns="http://schemas.xmlsoap.org/wsdl/"
      xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
      xmlns:tns="http://example.com/soap"
      xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    
      <message name="AddNumbersRequest">
        <part name="a" type="xsd:int"/>
        <part name="b" type="xsd:int"/>
      </message>
      
      <message name="AddNumbersResponse">
        <part name="result" type="xsd:int"/>
      </message>
    
      <portType name="MyPort">
        <operation name="AddNumbers">
          <input message="tns:AddNumbersRequest"/>
          <output message="tns:AddNumbersResponse"/>
        </operation>
      </portType>
    
      <binding name="MyBinding" type="tns:MyPort">
        <soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>
        <operation name="AddNumbers">
          <soap:operation soapAction="AddNumbers"/>
          <input>
            <soap:body use="encoded" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
          </input>
          <output>
            <soap:body use="encoded" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
          </output>
        </operation>
      </binding>
    
      <service name="MyService">
        <port name="MyPort" binding="tns:MyBinding">
          <soap:address location="http://localhost:8000/wsdl"/>
        </port>
      </service>
    </definitions>
    `;
    
    const server = http.createServer(app);
    server.listen(8000, () => {
      console.log('SOAP server running on http://localhost:8000/wsdl');
    });
    
    soap.listen(server, '/wsdl', service, xml);
    

    This Node.js script creates a SOAP web service using the express and soap libraries. The service, named MyService, exposes a single operation called AddNumbers, which takes two integer arguments (a and b), adds them together, and returns the result. The WSDL (Web Services Description Language) XML definition specifies how clients can interact with this service, including the request and response message structures, binding details, and the service endpoint (http://localhost:8000/wsdl). The server listens on port 8000, and when a SOAP request is received, it executes the AddNumbers operation and logs the sum to the console.

    The soap.listen() function attaches the SOAP service to the HTTP server, making it accessible to clients that send SOAP requests. The AddNumbers function extracts and parses the input arguments from the request, computes their sum, and returns the result in a SOAP response. This setup enables interoperability with SOAP-based clients that conform to the specified WSDL schema.

    Lets dockerize server:

    # Official image for Node.js
    FROM node:18
    
    # Fix working directory
    WORKDIR /app
    
    # Copy necessary files
    COPY package.json package-lock.json ./
    RUN npm install
    
    # Copy rest of files
    COPY . .
    
    # Expose server port
    EXPOSE 8000
    
    # Command for executing server
    CMD ["node", "server.js"]

    This Dockerfile sets up a containerized environment for running a Node.js application. It starts with the official Node.js 18 image as the base. The working directory inside the container is set to /app. It then copies the package.json and package-lock.json files into the container and runs npm install to install dependencies. After that, it copies the rest of the application files into the container. The file exposes port 8000, which is likely used by the Node.js server. Finally, it specifies the command to run the server using node server.js when the container starts.

    Build the image from command line:

    docker build -t soap-server .

    … and execute the image:

    docker run -p 8000:8000 soap-server

    Server ready, now is turn of Swift iOS App client.

    iOS SOAP Client

    iOS app client UI interface is just two textfield for fill in the input operator for the addition, a button for executing the operation and finally a text labed that presents the results.

    import SwiftUI
    
    struct ContentView: View {
        @State private var result: String = ""
        @State private var aStr: String = ""
        @State private var bStr: String = ""
    
        var body: some View {
            VStack {
                Group {
                    TextField("a", text: $aStr)
                    TextField("b", text: $bStr)
                }
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .padding()
    
                Button("Do remote addition") {
                    guard let aInt = Int(aStr), let bInt = Int(bStr) else {
                        return
                    }
                    callSoapService(a: aInt, b: bInt) { response in
                        DispatchQueue.main.async {
                            self.result = response
                        }
                    }
                }            .padding()
                    .background(Color.blue)
                    .foregroundColor(.white)
                    .cornerRadius(15)
                
                Text("a+b= \(result)")
                    .font(.title)
                    .padding()
            }
        }

    When button is tapped then callSoaService  function is being called:

    import SwiftUI
    
    struct ContentView: View {
        @State private var result: String = ""
        @State private var aStr: String = ""
        @State private var bStr: String = ""
    
        var body: some View {
            VStack {
                Group {
                    TextField("a", text: $aStr)
                    TextField("b", text: $bStr)
                }
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .padding()
    
                Button("Do remote addition") {
                    guard let aInt = Int(aStr), let bInt = Int(bStr) else {
                        return
                    }
                    callSoapService(a: aInt, b: bInt) { response in
                        DispatchQueue.main.async {
                            self.result = response
                        }
                    }
                }            .padding()
                    .background(Color.blue)
                    .foregroundColor(.white)
                    .cornerRadius(15)
                
                Text("a+b= \(result)")
                    .font(.title)
                    .padding()
            }
        }

    This Swift function callSoapService(a:b:completion:) sends a SOAP request to a web service at http://localhost:8000/wsdl, passing two integers (a and b) to the AddNumbers method. It constructs an XML SOAP message, sends it via an HTTP POST request, and processes the response asynchronously using URLSession. The response is parsed to extract the result from the <tns:result> tag using a regular expression in the extractResult(from:) function, returning it via a completion handler.

    If the web service is running correctly, it should return the sum of a and b. However, the function may fail if the server is unavailable, the response format changes, or if the SOAP service requires additional headers. Also, the regular expression parsing method may not be robust against namespace variations.

    Build and run iOS app:

     

    Conclusions

    SOAP does not provide the flexibility that provides a regular API REST or GraphQL, but for those scenarios wher do we need a very strict API contracts is quite suitable technology.

    You can find source code used for writing this post in following repository