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 ;)