Putting it all together
After importing the library we set up the proxy object, remote
. This object translates method invocations
into remote SOAP messages. Its initializer takes the key parameters that
govern remote requests: the URI of the server ( known as the "endpoint"),
the XML namespace of the request element (this is where SOAP-as-RPC gives
lip service to its XML underpinnings), and the SOAPAction header value.
Next we determine the method argument, which in the case of this Web
service is simply the language for Haddock's rantings, Swedish ("se") or
English (strangely enough, "us" rather than "en").
Finally, we invoke the method of the right name, Curse
on the proxy object to make the SOAP call, then
we print the results. The following session illustrates the use of the
program:
$ python curse.py
What captain Haddock had to say: "Ectoplasmic Byproduct!"
|
Our own SOAP server
Implementing a SOAP server in SOAP.py is quite easy. As an example, we
shall emulate the field and also implement a trivial service: a program
which, given a year and month, prints back a calendar as a string. The
server program for this is Listing 2 (calendar-ws.py).
Listing 2: SOAP.py program to implement calendar server
#!/usr/bin/env python
import sys, calendar
#Import the SOAP.py machinery
from WebServices import SOAP
CAL_NS = "http://uche.ogbuji.net/eg/ws/simple-cal"
class Calendar:
def getMonth(self, year, month):
return calendar.month(year, month)
def getYear(self, year):
return calendar.calendar(year)
server = SOAP.SOAPServer(("localhost", 8888))
cal = Calendar()
server.registerObject(cal, CAL_NS)
print "Starting server..."
server.serve_forever()
|
After the requisite imports, we define the namespace (CAL_NS
) expected for SOAP request elements to our
server. Next we define the class that implements all the methods which are
to be exposed as SOAP methods. One can register individual functions as
SOAP methods as well, but using the class approach is most flexible,
especially if you wish to manage state between invocations. This Calendar
class defines one method, getMonth
, which uses Python's built-in calendar
module to return a monthly calendar in text form, and another method which
returns an entire year's calendar.
Then an instance of the SOAP server framework is created, with
instructions to listen on port 8888. We must also create an instance of
the Calendar
class, which is registered to
handle SOAP messages in the next line, with the relevant namespace
indicated. Finally, we call the serve_forever
methods, which doesn't return until the process is terminated.
In order to run the server, open up another command shell and execute
python calendar-ws.py
. Use ctrl-C to kill the
process when you are done.
We could test the server with a client also written with SOAP.py, but
that would be too obvious. Let us instead write a client in low-level
Python to construct the SOAP response as an XML string and send an HTTP
message. This program (testcal.py) is in Listing 3.
Listing 3: A client written using the Python core libraries to access the calendar service
import sys, httplib
SERVER_ADDR = "127.0.0.1"
SERVER_PORT = 8888
CAL_NS = "http://uche.ogbuji.net/ws/eg/simple-cal"
BODY_TEMPLATE = """<SOAP-ENV:Envelope
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:s="http://uche.ogbuji.net/eg/ws/simple-cal"
xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/1999/XMLSchema"
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>
<s:getMonth>
<year xsi:type="xsd:integer">%s</year>
<month xsi:type="xsd:integer">%s</month>
</s:getMonth>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>"""
def GetMonth():
year = 2001
month = 12
body = BODY_TEMPLATE%(year, month)
blen = len(body)
requestor = httplib.HTTP(SERVER_ADDR, SERVER_PORT)
requestor.putrequest("POST", "cal-server")
requestor.putheader("Host", SERVER_ADDR)
requestor.putheader("Content-Type", "text/plain; charset="utf-8"")
requestor.putheader("Content-Length", str(blen))
requestor.putheader("SOAPAction", "http://uche.ogbuji.net/eg/ws/simple-car")
requestor.endheaders()
requestor.send(body)
(status_code, message, reply_headers) = requestor.getreply()
reply_body = requestor.getfile().read()
print "status code:", status_code
print "status message:", message
print "HTTP reply body:\n", reply_body
if __name__ == "__main__":
GetMonth()
|
The following session illustrates the running of this test.
$ python testcal.py
status code: 200
status message: OK
HTTP reply body:
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:xsd="http://www.w3.org/1999/XMLSchema" xmlns:SOAP-
ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"
xmlns:SO
AP-ENC="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>
<getMonthResponse SOAP-ENC:root="1">
<Result xsi:type="xsd:string"> December 2001
Mo Tu We Th Fr Sa Su
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
</Result>
</getMonthResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
|
Bytes under scrutiny
One thing useful to note is that you can get details of the actual SOAP
messages being exchanged and other key data for debugging and tracing, if
you look for the line self.debug = 0
and change
the "0" to "1" (this is line 210 in SOAP.py version 0.9.7.) As an
example, here is a session with the earlier curses.py program with
debugging information turned on:
$ python curse.py
*** Outgoing HTTP headers **********************************************
POST /scripts/Haddock.exe/soap/IHaddock HTTP/1.0
Host: www.tankebolaget.se
User-agent: SOAP.py 0.9.7 (actzero.com)
Content-type: text/xml; charset="UTF-8"
Content-length: 523
SOAPAction: "urn:HaddockIntf-IHaddock#Curse"
************************************************************************
*** Outgoing SOAP ******************************************************
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:xsd="http://www.w3.org/1999/XMLSchema" xmlns:SOAP-
ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"
xmlns:SO
AP-ENC="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>
<ns1:Curse xmlns:ns1="urn:HaddockIntf-IHaddock" SOAP-ENC:root="1">
<LangCode xsi:type="xsd:string">us</LangCode>
</ns1:Curse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
************************************************************************
*** Incoming HTTP headers **********************************************
HTTP/1.? 200 OK
Server: Microsoft-IIS/5.0
Date: Tue, 11 Sep 2001 16:40:19 GMT
Content-Type: text/xml
Content-Length: 528
Content:
************************************************************************
*** Incoming SOAP ******************************************************
<?xml version="1.0" encoding="UTF-8"?><SOAP-ENV:Envelope xmlns:SOAP-
ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/1999/XMLSchema"
xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xml
soap.org/soap/encoding/"><SOAP-ENV:Body><NS1:CurseResponse xmlns:NS1="urn:HaddockIntf-
IHaddock" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><NS1:return
xsi:type="xsd:string">Anacoluthons!</NS1:return></NS1:CurseRespon
se></SOAP-ENV:Body></SOAP-ENV:Envelope>
************************************************************************
What captain Haddock had to say: "Anacoluthons!"
|
To compare, you would get this same information in a traditional Python
script or program with the following code:
import calendar
return calendar.month(2001, 10)
|
SOAP.py concentrate
As we've noted, there are some hiccups with interoperability of SOAP.py,
but hopefully the debugging data available is of help -- and among the
developers who have signed up to keep this project moving along is Mike
Olson, co-author of this column. In the next installment of this column
we shall look at one of the other Python SOAP implementations.