SOAP examples

Create a SOAP channel to turn on automatic (de-)serialization of incoming/returned SOAP messages. You deal with the SOAP body only, headers are dealt with by Zato itself.

As with XML, the examples below use lxml because it’s a very good choice for efficient XML processing but you’re not constrained to that one library only.

lxml offers every feature you need for working XML, such as namespaces, XPath, XSLT, XInclude, XML Schema, RelaxNG and many more but if you prefer other tools you can use anything and Zato won’t constrain you.

Accessing SOAP request

The SOAP body converted to an ObjectifiedElement is available as self.request.payload. As with any other channels, the raw request is available as self.request.raw_request should you need to access SOAP headers manually.

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
  xmlns:ns="http://example.com/ns">
   <soapenv:Header/>
   <soapenv:Body>
     <ns:request>
      <ns:customer>
       <ns:id>123</ns:id>
       <ns:name type="NCHZ">John Brown</ns:name>
      </ns:customer>
     </ns:request>
   </soapenv:Body>
</soapenv:Envelope>
1
2
3
4
5
6
7
8
9
from zato.server.service import Service

class MyService(Service):
    def handle(self):
        root = self.request.payload
        self.logger.info(type(root))
        self.logger.info(root.customer.id.text)
        self.logger.info(root.customer.name.text)
        self.logger.info(root.customer.name.get('type'))
INFO - <type 'lxml.objectify.ObjectifiedElement'>
INFO - 123
INFO - John Brown
INFO - NCHZ

Creating responses

Assign a string representation of the SOAP body to return to self.response.payload. It doesn't matter how the payload is produced as long as it results in a string. The payload is wrapped in a SOAP header by Zato.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# lxml
from lxml.builder import E as e
from lxml.etree import tostring

# Zato
from zato.server.service import Service

class MyService(Service):
    def handle(self):
        root = e.response(e.customer(e.preferred_currency('SGD')))
        self.response.payload = tostring(root)
$ curl localhost:11223/example -H 'SOAPAction:example' -d @soap1.xml
<?xml version='1.0' encoding='UTF-8'?>
 <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
  xmlns="https://zato.io/ns/20130518">
   <soap:Body>
    <response>
     <customer>
      <preferred_currency>SGD</preferred_currency>
     </customer>
    </response>
   </soap:Body>
  </soap:Envelope>
$

Invoking a SOAP service (from WSDL)

If you created an outgoing SOAP connection using the serialization format of Suds and linked the connection to a WSDL, the remote end can be invoked directly using pure-Python business objects automatically read from the WSDL's XSD schema.

Note that the client object is based on Suds and offers everything Suds does.

Assumming the WSDL is as here the code to invoke it will be as follows - observe that no manual serialization is needed to invoke WSDL-based resources.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
from zato.server.service import Service

class MyService(Service):
    """ Obtains BLZ bank details for input bank code.
    More about BLZ at Wikipedia - https://en.wikipedia.org/wiki/Bankleitzahl.
    """
    def handle(self):

        with self.outgoing.soap.get('BLZ').conn.client() as client:

            # Prepare input data
            bank_code = '12070000'

            # Only pure-Python objects are used to invoke a remote service
            output = client.service.getBank(bank_code)

            # Log response received
            self.logger.info('BIC `%s`', output.bic)
            self.logger.info('Name `%s`', output.bezeichnung)

In server.log:

INFO - BIC `DEUTDEBB160`
INFO - Name `Deutsche Bank Ld Brandenburg`

Invoking a SOAP service (no WSDL)

After creating an outgoing SOAP connection with serialization format of String and without providing a link to a WSDL, you need to invoke it with a string on input. The string will be wrapped in a SOAP body by Zato. Likewise, Zato will add all the SOAP headers required.

Request and response as expected/returned by a sample CurrencyConverter online:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
 xmlns:web="http://www.webserviceX.NET/">
   <soapenv:Header/>
   <soapenv:Body>
      <web:ConversionRate>
         <web:FromCurrency>NOK</web:FromCurrency>
         <web:ToCurrency>HRK</web:ToCurrency>
      </web:ConversionRate>
   </soapenv:Body>
</soapenv:Envelope>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/CurrencyConvertersoap/envelope/"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:xsd="http://www.w3.org/2001/XMLSchema">
   <soap:Body>
      <ConversionRateResponse xmlns="http://www.webserviceX.NET/">
         <ConversionRateResult>0.9384</ConversionRateResult>
      </ConversionRateResponse>
   </soap:Body>
</soap:Envelope>

It doesn't matter how a request is produced as long as an outgoing connection is invoked with its string representation.

Sample code to invoke the service and get the conversion rate:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# lxml
from lxml.etree import Element, fromstring, QName, SubElement, tostring

# Zato
from zato.server.service import Service

class MyService(Service):
    def handle(self):

        # Remote service's namespace
        ns = 'http://www.webserviceX.NET/'

        # Create a request
        root = Element(QName(ns, 'ConversionRate'), nsmap={None:ns})
        FromCurrency = SubElement(root, 'FromCurrency')
        ToCurrency = SubElement(root, 'ToCurrency')

        FromCurrency.text = 'NOK'
        ToCurrency.text = 'HRK'

        # Convert the XML object to string that will be wrapped in the SOAP body
        req = tostring(root)

        # Invoke a service and fetch its response
        response = self.outgoing.soap.get('CurrencyConverter').conn.send(self.cid, req)

        # Convert the response to XML and fetch the rate using XPath
        xml = fromstring(response.text.encode('utf-8'))
        rate = xml.xpath('//ws:ConversionRateResult/text()', namespaces={'ws':ns})

        # XPath above returned a one-element list of results hence index notation here
        self.logger.info(rate[0])
INFO - 0.9384