web developer & system programmer

coder . cl

ramblings and thoughts on programming...


pyxser and zsi webservices

published: 18-10-2009 / updated: 18-10-2009
posted in: programming, projects, python, pyxser, tips
by Daniel Molina Wegener

pyxser, my Python-Object to XML serialization/deserialization extension — completely written in C — that can be used at least in a lot of kinds of object communications and object storage tasks. Since it is not a binary serializer, you can handle the serialized objects with a lot of available APIs. In this post I will explain how to work with pyxser and the ZSI Python framework to work with WebServices.

the server side

ZSI is a Python framework to work with WebServices. It can be used under mod_python. The first step to work with ZSI is to define a proper WSDL for our WebService. In this case we just only communicate xsd:string types to carry our pyxser messages:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<wsdl:definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
          xmlns:tns="http://python.con/SimpleOperation/"
          xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
          xmlns:xsd="http://www.w3.org/2001/XMLSchema"
          xmlns:pyxs="http://projects.coder.cl/pyxser/model/"
          targetNamespace="http://python.con/SimpleOperation/"
          name="SimpleOperation">
  <wsdl:types>
    <xsd:schema targetNamespace="http://python.con/SimpleOperation/"
        xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
        xmlns:tns="http://python.con/SimpleOperation/"
        xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema"
        xmlns:pyxs="http://projects.coder.cl/pyxser/model/">
      <xsd:complexType name="message">
    <xsd:sequence>
      <xsd:element name="content" type="xsd:string" />
    </xsd:sequence>
      </xsd:complexType>
      <xsd:element name="request" type="tns:message" />
      <xsd:element name="response" type="tns:message" />
    </xsd:schema>
  </wsdl:types>
  <wsdl:message name="SimpleOperationRequest">
    <wsdl:part element="tns:request" name="request" />
  </wsdl:message>
  <wsdl:message name="SimpleOperationResponse">
    <wsdl:part element="tns:response" name="response" />
  </wsdl:message>
  <wsdl:portType name="SimpleOperation">
    <wsdl:operation name="SimpleOperation">
      <wsdl:input message="tns:SimpleOperationRequest" />
      <wsdl:output message="tns:SimpleOperationResponse" />
    </wsdl:operation>
  </wsdl:portType>
  <wsdl:binding name="SimpleOperationSOAP" type="tns:SimpleOperation">
    <soap:binding style="document"
          transport="http://schemas.xmlsoap.org/soap/http" />
    <wsdl:operation name="SimpleOperation">
      <soap:operation soapAction="http://python.con/SimpleOperation/SimpleOperation" />
      <wsdl:input>
    <soap:body use="literal" />
      </wsdl:input>
      <wsdl:output>
    <soap:body use="literal" />
      </wsdl:output>
    </wsdl:operation>
  </wsdl:binding>
  <wsdl:service name="SimpleOperation">
    <wsdl:port binding="tns:SimpleOperationSOAP" name="SimpleOperation">
      <soap:address location="http://python.con/" />
    </wsdl:port>
  </wsdl:service>
</wsdl:definitions>

The tns:message complex type holds a single element containing the serialized object defined as xsd:string. The type is used as tns:request and tns:response, We can send and receive any serialized object through our WebService. One time defined the WebService, we generate the proper type and service handlers with ZSI command line utilities:

[dmw@www ~]$ wsdl2py --complexType --file SimpleOperation.wsdl
[dmw@www ~]$ wsdl2dispatch --file SimpleOperation.wsdl
[dmw@www ~]$
[dmw@www ~]$ ls -1 *py
SimpleOperation_services_types.py
SimpleOperation_services.py
SimpleOperation_services_server.py

So, we have generated our types and handlers, and now we must write the mod_python handler. Since ZSI does not allow to customize the use of the ZSI.dispatch.AsHandler() function parameters, we use directly the _Dispatch() function with some modifications over our mod_python handler.


### we import the apache module from mod_python
from mod_python import apache
### we import the Zolera SOAP Infrastructure
import ZSI as z
import ZSI.dispatch as zd
import ZSI.TC as tc

### we import our WebService definition
### created with wsdl2py and wsdl2dispatch
import simple.SimpleOperation as so ### our WS request handler
import simple.SimpleOperation_services_types as sot
import simple.SimpleOperation_services as sos
import simple.SimpleOperation_services_server as sor

### we import the logging module
import logging as log

### we setup the service logging
LOG_FILENAME = '/var/www/wsSample/logs/request-log.txt'
### we setup a debugging logging
log.basicConfig(filename=LOG_FILENAME,level=log.DEBUG,)

### we register our WebService types inside the
### ZSI framework for handling message types
tc.RegisterType(sos.SimpleOperationRequest)
tc.RegisterType(sos.SimpleOperationResponse)

### we setup the encoding for our WebServices messages
mod = __import__('encodings.utf_8', globals(), locals(), '*')
mod = __import__('encodings.utf_16_be', globals(), locals(), '*')

### we start a typesmod as None
typesmod=None

### we define a custom handler for message types, We
### can add the proper type to handle the incoming message
def hdlrTypecode(mod, e):
    # we log the module name
    log.debug("mod: " + mod.__name__ + "; e: " + repr(e))
    # if the module regards our implementation...
    if mod.__name__ == 'simple.SimpleOperation_services_types':
        if e.localName == u'request':
            # setup the module according to the
            # request message
            typesmod=sot
            return sos.SimpleOperationRequest.typecode
        if e.localName == u'response':
            # setup the module according to the
            # response message
            typesmod=sot
            return sos.SimpleOperationResponse.typecode

### we define our WebService handler, it is currently
### using a modification over the predefined ZSI.dispatch module
### since we can not define a /type code/ handler and
### /type module/ for the ZSI framework.
def handler(req):
    log.debug("handler(): req    = " + repr(req))
    ### we setup our WebService handling modules
    mods = (so,)
    log.debug("handler(): mods   = " + repr(mods))
    ### we parse the request through ZSI
    sreq = z.ParsedSoap(req)
    kw = {}
    kw['request'] = req
    ### we dispatch the request to the /pseudo-private/
    ### _Dispatch function, to allow our WebService to
    ### be identified according to the request message
    ### by using the gettypecode callback and the typesmodule
    ### definition
    zd._Dispatch(sreq, mods,
                 zd._ModPythonSendXML, zd._ModPythonSendFault,
                 gettypecode=hdlrTypecode, typesmodule=sot, **kw)
    return apache.OK

We define the simple.SimpleOperation as so module import, where we must handle the requested SOAP operation, it must define the request function as was defined in the WSDL above, and use a single parameter as request and return a SimpleOperationResponse as result, where it was defined to return a response object, which is defined in the generated classes as SimpleOperationResponse, and the received argument is a SimpleOperationRequest object, both defined in the SimpleOperation_services python module. The code for the WebService handling module, passed to the _Dispatch() function with named argument mods which is a tuple containing a reference to the simple.SimpleOperation module, and it will handle the request only if it has a function defined as follows.

from simple.SimpleOperation_services_server import *
from simple.SimpleOperation_services_types import *
from simple.SimpleOperation_services import *
import logging as log
import pyxser as ser

LOG_FILENAME = '/var/www/wsSample/logs/request-log.txt'
log.basicConfig(filename=LOG_FILENAME,level=log.DEBUG,)

def request(ureq):
    try:
        log.debug("SimpleOperation(): ureq     = " + str(ureq))
        ### the request object (created previously as ZSI.ParsedSoap
        ### object contains our serialized object)
        msg = ureq._content
        log.debug("SimpleOperation(): msg      = " + str(msg))
        ### we deserialize the given object, so we can use it...
        oreq = ser.unserialize(obj=msg.encode("utf-8"), enc="utf-8")
        ### and operate over its members, including calling methods
        ### from the unserialized object
        oreq.first_element += 100
        oreq.second_element -= 100
        oreq.some_method_call()
        ### so, in this case we send the object back to the WebService
        ### client, by serializing it and instantiating a
        ### SimpleOperationResponse object defined in the generated
        ### modules with the wsdl2py utility
        msg = ser.serialize(obj=oreq, enc="utf-8", depth=0)
        res = SimpleOperationResponse()
        log.debug("SimpleOperation(): res      = " + str(res))
        ### we set the message content
        res._content = msg
        log.debug("SimpleOperation(): _content = " + str(msg))
        ### and we return the original but recomputed object
        ### to the client.
        return res
    except Exception, e:
        log.error("SimpleOperation(): error    = " + str(e))
        res = SimpleOperationResponse()
        log.debug("SimpleOperation(): res      = " + str(res))
        res._content = "Bad Request: " + str(e)
        log.debug("SimpleOperation(): _content = " + str(msg))
        return res

pyxser automatically imports the required modules to instantiate the proper object type, so we need to let pyxser know how to deserialize the objects by allowing it to know the object class. For example the sent object was a member of the testpkg.sample module, which is guaranteed to be loaded since it is on the PYTHONPATH. On the opposed side, pyxser will return None since it is not knowing the serialized object type. The same happens at the client size, pyxser must know the serialized object types.

the client side

We know the server side, but the client side? how complex is it?. Not too complex, we proceed in the same way. We need a copy or dummy object — a class without the server side methods, but with the same properties and namespace — or copy of the server side objects. So we generate the same objects with the ZSI commands wsdl2py and wsdl2dispatch, we put them in a safe place, with the dummy objects too. So we import them in our client:

#!/usr/bin/env python
#

# we import the pyxser module
import pyxser as ser
import sys, httplib, time
# we import the dummy package
from testpkg.sample import *
# we import the service definition created by ZSI
from simple.SimpleOperation_services_types import *
from simple.SimpleOperation_services import *

# we instantiate the object with
reqo = TestAnotherObject()
# we fill it with data and operate with it
reqo.first_element = 100
reqo.second_element = 100
reqo.we_call_some_method()
# we serialize the object
hstr = ser.serialize(obj = reqo, enc = "utf-8", depth = 0)

kwloc = { 'tracefile' : sys.stdout }
# we instantiate the service locator
loc = SimpleOperationLocator();
# we redefine the service endpoint
so = loc.getSimpleOperation(url = "http://python.con/wsHandler.py", **kwloc)
# we create a request message defined in
# simple.SimpleOperation_services
req = SimpleOperationRequest()
# we put the serialized object in the
# request message
req._content = hstr
# we finally call the SimpleOperation defined
# in the WSDL file
ret = so.SimpleOperation(req)
# so we can get our recomputed object back

print repr(ret)

Then we can generate a working API for the pyxser XML schema with other APIs like JAXB for Java, which generates the proper interfaces to work with the pyxser XML schema. Another example, to work with Java, is to generate the client from the Axis API. We should find an implementation to send and receive the message from and to the pyxser enabled WebService as follows:

import junit.framework.TestCase;
import con.python.SimpleOperation.*;

public class TestAsPyxserClient extends TestCase {
    public void testClient() {
        try {
            String msg = getMessage(500, 300);
            SimpleOperation_ServiceLocator loc = new SimpleOperation_ServiceLocator();
            loc.setEndpointAddress("SimpleOperation",
                                   "http://python.con/wsHandler.py");
            SimpleOperation s = loc.getSimpleOperation();
            Message m = new Message();
            m.setContent(msg);
            Message r = s.simpleOperation(m);
            System.out.println(r.getContent());
        } catch (Exception e) {
            System.err.println("Error: " + e);
            e.printStackTrace();
        } catch (Throwable e) {
            System.err.println("Error: " + e);
            e.printStackTrace();
        }
    }

    /* here we have a pyxser serialized object
       hard-coded — I know it's ugly) but we
       can work with the proper API like JAXB to
       operate over the pyxser types, just apply any
       XSD to Java (or whatever another language)
       class generator */
    private String getMessage(int a, int b) {
        String msg =
            "<pyxs:obj xmlns:pyxs='http://projects.coder.cl/pyxser/model/' version='1.0' "
                + "type='TestAnotherObject' module='testpkg.sample' objid='id-139657772'>n"
                + "<pyxs:prop type='int' name='first_element'>" + a + "</pyxs:prop>n"
                + "<pyxs:prop type='int' name='second_element'>" + b + "</pyxs:prop>n"
                + "</pyxs:obj>n";
        return msg;
    }
}


conclusions

The main idea is to reduce response times while we are working with Python enabled WebService. Parsing tasks are hard to do from python itself. Since pyxser uses a XML Schema based serialization and finds itself how to instantiate the proper objects in both sides — client and server — we can literally export python objects from it’s environment and also import them. As FOSS work, I will create the proper API to serialize Java and Mono objects into the pyxser schema, so we can share them between platforms. I hope this would help your integration tasks ;)


No coments yet.

post a comment

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>