SOAP on the Google App Engine platform

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.

8 thoughts on “SOAP on the Google App Engine platform

  1. Albert Drouart

    This was a great tutorial! Just a note to other readers if you try and use zope-interface-3.5.1 (the current version) you'll have problems because of dependencies on setuptools (which is problematic in google appengine's environment)…. I recommend you stick with 3.5.0 to work through this example.

    Thanks again!

  2. kien vu

    Hi there!
    Thank you for your sample. It was really useful for me. But I have a problem with array type. can you please help me? how to return an array data. I try this:

    @soapmethod(GetValueRequest.typecode, GetValueResponse.typecode, operation='GetValue', soapaction='GetValue')
    def soap_GetValue(self, request, response, **kw):
    msg = request
    result = GetValueResponse()

    v2=self.get_Value(msg._sourceid,msg._key,msg._tag,msg._fromtime,msg._totime,msg._email,msg._from,msg._limit)
    i = 0
    for v1 in v2:
    result[i]._sourceid = v1.sid
    result[i]._value = v1.rvalue
    result[i]._geocode = v1.geocode
    result[i]._publish = v1.publish
    result[i]._tag = v1.rtag
    result[i]._dtime = v1.rtime
    return request,result

    with GetValueResponse is an array. And i got this error:
    ArrayOfGetValueResult_Holder instance has no attribute '_sourceid'

    What should i do? please help me…

  3. carson Post author

    kien vu,

    It doesn't look like you are incrementing your index when you iterate over v2. My guess would be that the result isn't built like you think it should be.

  4. Memorysaver

    Thanks for you great soap tutorial. I follow your tutorial and everything works great. Howerver, my project need to build soap client in window mobile device and connect to the cloud. According to MSDN , it indicates that "The .NET Compact Framework does not support all the code generated by the Web Services Description Language Tool (Wsdl.exe). However, applications that use Web services can use the generated proxy when you add a Web reference to a Smart Device project in Visual Studio." As a result, I need to locate the wsdl file that I upload in my web service, right ? Please help me about how to locate my wsdl by trying http://memory-saver.appspot.com/echo?wsdl ("echo" is my operation), but it comes out some weird wsdl stuff. Thank you for reading my comment. I am really appreciate your help.

  5. Jeremy

    I'm able to get as far as the "No resource for get" step but run into trouble after that. Am I supposed to actually run the wsdl2py for a second time and overwrite the previously generated files? I've created the example client to test the service but still get the "no resource for get" message. Do I have to create a new mapping for the example client? Thanks for any help

  6. BradleyAllen

    Got an error in the echo.py file, it was missing a " on line 9,
    portType="")

    worked, but as copied from the website it was like this
    portType=") <– error

    and the wsdl2py had to be renamed with a .py extension and then it worked, at least in how i used it with dos, may not have put the python at the front after thinking about it…

    and what it says is nothing to see hear? where does this come from? how to add a button and text box to echo what i send it? or etc?
    thanks

  7. BradleyAllen

    Ok just noticed there was one more set of code to copy and try, it reads:

    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:

    but what should that code be named? if I run the same wsdl file and same wsdl2py again the code below this never gets used. It appears to be a .py file, i named it client.py and ran client.py bradz.wsdl file and it got 4 errors:

    C:\Users\myfiles\bradley\bradley>client.py bradz.wsdl <–in DOS cmd shell
    Traceback (most recent call last):
    File "C:\Users\myfiles\bradley\bradley\client.py", line 9, in
    rsp = port.Echo(msg)
    File "C:\Users\myfiles\bradley\bradley\EchoServer_client.py", line 40, in Echo
    response = self.binding.Receive(EchoResponse.typecode)
    File "C:\Users\myfiles\bradley\bradley\ZSI\client.py", line 531, in Receive
    self.ReceiveSOAP(**kw)
    File "C:\Users\myfiles\bradley\bradley\ZSI\client.py", line 416, in ReceiveSOAP
    'Response is "%s", not "text/xml"' % self.reply_headers.type)
    TypeError: Response is "text/plain", not "text/xml"

    Could you please clarify a bit on this step?

Leave a Reply

Your email address will not be published. Required fields are marked *