I generally don't recomend using SOAP instead of REST but I have been required to use SOAP so much now that I think it is inevitably going to be a requirement for a long time for certain projects. So when I noticed a question on stack overflow about using SOAP on the Google App Engine I thought it might be a nice exercise to see how easy it is to get fringe toolsets to work in the GAE.
I'm going to assume basic knowledge of the GAE system. If you haven't already tried it out you should read the getting started guide. You shouldn't need to know any more than what is in that guide to work through this example. I also assume knowledge of creating WSDL files by hand. You will need to be able to do this to generate the SOAP server and client. You can find a WSDL tutorial here that may help. I also recommend creating an empty directory that will house your project for this example.
The first step involves getting a library from the Python Web Services project. You will want to download the ZSI 2.1a1 package (grab the the tgz file: ZSI-2.1-a1.tar.gz). This is the current alpha release of ZSI but the current stable release won't work. The stable release has a problem with the expat library but the problem is fixed in 2.1. It is also worth stating that ZSI 2.1 seems to be better put together. Extract the files into a temp directory and then copy only the directory named "ZSI" into your project directory.
Next you will need to get zope.interface. Go to the zope.interface download page and find the file named: zope.interface-3.5.0.zip. Unzip this file in a temp directory and then copy the directory named "zope" into your project.
These two external libraries are all you need to create a Python SOAP service. The next step in creating the service involves using the GAE webapp framework so it would help to read up on that. This framework works with WSGI and it may also help to learn about WSGI as well. I referenced the django example and example of using routes to get an idea of what needed to be done to connect the GAE WSGI up to the ZSI SOAP service. They both use the WSGIApplication webapp call to tie in their output with the WSGI framework and that is common to all applications that use the webapp framework.
Now that all the libraries are in place and the WSGI and GAE webapp interface make some sense it is time to move on to creating a WSDL file. Here is a simple one that I used for the test:
<?xml version="1.0" encoding="UTF-8"?> <definitions xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:http="http://schemas.xmlsoap.org/wsdl/http/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:tns="urn:ZSI" targetNamespace="urn:ZSI" > <types> <xsd:schema elementFormDefault="qualified" targetNamespace="urn:ZSI"> <xsd:element name="Echo"> <xsd:complexType> <xsd:sequence> <xsd:element name="value" type="xsd:anyType"/> </xsd:sequence> </xsd:complexType> </xsd:element> </xsd:schema> </types> <message name="EchoRequest"> <part name="parameters" element="tns:Echo" /> </message> <message name="EchoResponse"> <part name="parameters" element="tns:Echo"/> </message> <portType name="EchoServer"> <operation name="Echo"> <input message="tns:EchoRequest"/> <output message="tns:EchoResponse"/> </operation> </portType> <binding name="EchoServer" type="tns:EchoServer"> <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/> <operation name="Echo"> <soap:operation soapAction="Echo"/> <input> <soap:body use="literal"/> </input> <output> <soap:body use="literal"/> </output> </operation> </binding> <service name="EchoServer"> <port name="EchoServer" binding="tns:EchoServer"> <soap:address location="http://gae.ioncannon.net/echo"/> </port> </service> </definitions>
Notice at the bottom I set the address to the location of my test service along with the application domain name. This is where the application will end up in production. If you want to test first you can stick your localhost information in there instead.
Next you will need to run the wsdl2py application that comes with ZSI on your WSDL file to generate the classes needed for the server. To do this I simply copied the wsdl2py application into the project and ran the following command:
python wsdl2py SimpleEcho.wsdl
After you do this you will find a couple new files in your project directory. I then removed the wsdl2py application so it didn't get uploaded later.
Next create the test endpoint application itself. For this example I named the file echo.py and added the hooks that connect the service to the WSGI interface:
import sys from google.appengine.ext import webapp from google.appengine.ext.webapp.util import run_wsgi_app from EchoServer_server import * from ZSI.twisted.wsgi import SOAPApplication, soapmethod, SOAPHandlerChainFactory, WSGIApplication class EchoService(SOAPApplication): factory = SOAPHandlerChainFactory wsdl_content = dict(name='Echo', targetNamespace='urn:echo', imports=(), portType='') @soapmethod(EchoRequest.typecode, EchoResponse.typecode, operation='Echo', soapaction='Echo') def soap_Echo(self, request, response, **kw): response = request return request,response application = WSGIApplication() application['echo'] = EchoService() def main(): run_wsgi_app(application) if __name__ == "__main__": main()
Next create the application description file app.yaml that is needed to upload and install the application in the GAE. For simplicity I just map everything to the main program:
application: iechotest version: 1 runtime: python api_version: 1 handlers: - url: /.* script: echo.py
At this point my project directory looked like this:
soaptest/ ZSI/<zsi files> zope/<zope interface files> app.yaml EchoServer_client.py EchoServer_types.py EchoServer_server.py echo.py SimpleEcho.wsdl
The application is ready to be uploaded to GAE at this point. After uploading you can stick the endpoint URL into a browser and you should see a message saying "NO RESOURCE FOR GET". If you get some type of error use the logs in your application console to look at the application stack trace. If there are no errors you are ready to build a client to test the SOAP service.
To create the client you use the same WSDL file with wsdl2py again. This actually generates the same class files and you need the same libraries in place to run it. The following example client will test the service:
from EchoServer_client import * import sys, time TRACE=None loc = EchoServerLocator() port = loc.getEchoServer() msg = EchoRequest() msg._value = 1 rsp = port.Echo(msg) print "INTEGER: ", rsp._value msg._value = "HI" rsp = port.Echo(msg) print "STRING: ", rsp._value msg._value = 1.10000 rsp = port.Echo(msg) print "FLOAT: ", rsp._value msg._value = dict(milk=dict(cost=3.15, unit="gal"))
As long as the service is in the same place as the WSDL location setting you don't need to specify an endpoint for the getServer call.
And that is all there is to it. It is fairly involved but if you already understand some of the concepts here it seems fairly strait forward. Along the way I found a SOAP tutorial for Python that may be helpful as well.