Introduction of SOAP project in Python and its application in web development

  • 2020-05-09 18:47:53
  • OfStack

SOAP.py client and server

SOAP.py contains some basic things. There is no Web service description language (Web Services Description Language, WSDL) or anything else, only transparent support for SOAP clients and servers implemented with Python. Even one nice feature in this package is only infrastructure-related: SOAP.py supports secure sockets layer (SSL) for encrypted SOAP transmissions. To use this feature, you must install M2Crypto, M2Crypto is a library that contains a variety of encryption tools and formats, from RSA and DSA to HTTPs, S/MIME, and so on. In this part 1, we are not going to discuss SOAP.py's SSL support.
Summary of SOAP operations

So far, the SOAP utility seems to be a popular open source activity using Python. Here's a rundown of the projects and their current status. First, participants:

      4Suite SOAP, managed by Fourthought       SOAPy, managed by Adam Elman       SOAP.py, Python project one Web service project       soaplib, managed by Secret Labs       Orchard, managed by Ken MacLeod       PySOAP, managed by Dave Warner

4Suite SOAP is our own implementation, which we used in the first three parts of this column (see resources for a link to it). It is still under development.

SOAPy was announced in April 2001 and is currently in the preparatory stage of alpha, but seems to have stopped development.

SOAP.py development is frozen. The project is sponsored by actzero, which is no longer in the business. Organizations that volunteer to develop/maintain SOAP.py are being invited.

The development of soaplib also seems to have been delayed, which may be understandable given the amount of work Secret Labs has been doing. The Swedish company is run by Fredrik Lundh, known in Python circles as a "workaholic" and a member of the board of Python Association. Secret Labs also develops PythonWare (a core and important additional module of Python); PythonWorks (1 leading Python IDE); Python Imaging Library and many other good things (the daily Python-URL Web log is one of them).

Orchard is a data management framework, basically a way of managing different data formats with a common interface. It implements an SOAP client as the basic method (known as a node) for sending Orchard data items to the SOAP server in a remote procedure call.

The PySOAP project is primarily intended to be part of the Church management suite of Dave Warner, but it has never released any documentation, and seems like a dead project.

The installation

Start by downloading the distribution (SOAPpy 0.9.7 is the latest distribution at the time of this writing), unpack the file, go to the results directory, and copy the file SOAP.py to its preferred location. Of course, this "tendency" is where the skill comes in. Because many of these SOAP lib use different case combinations of "soap.py" as module names, be careful. Of course, UNIX users only need to care about the exact matching of case, but for Windows users, even conflicts between "SOAP.py" and "soap.py" can cause problems. Orchard's SOAP.py also has a conflict-prone name, but it probably avoids all problems because its modules are cleverly placed in the Orchard package.

In short, it is recommended that you ensure that all Python SOAP modules are installed with a distinct package name. In our case, we found a suitable directory in PYTHONPATH and created an WebServices package with SOAP.py in it. Therefore, in Linux:


$ mkdir ~/lib/python/WebServices
$ touch ~/lib/python/WebServices/__init__.py
$ cp SOAPpy097/SOAP.py ~/lib/python/WebServices


Please note important clause 2, which will generate 1 s 161en__.py file, this file marks WebServices directory as Python package. If you need to package this code into an Windows version, you may want to enter 1 comment into an empty file, because 1 Windows tool does not create an empty file.

You've gone into the subject

For the publicly available SOAP server, there are already several active registries. The most popular is probably XMethods. Of course, it's also a pretty interesting guide to what SOAP is really like, without listening to the hype. Most of the public Web services here are still just a few minor things, hardly worth the hassle of our brave new model, but that's another story. In fact, we will select a public service to demonstrate and test how SOAP.py can be used as an SOAP client.

Or, we could try it. The first service the authors tried, the health care provider locator, shows the trap in the current state of SOAP interoperability when it encounters the following error message:


WebServices.SOAP.faultType: <Fault soap:Client: 
Server did not recognize the value of
HTTP Header SOAPAction: "".>


Oh. SOAPAction is an HTTP header that should be used to mark the services being accessed. It is a required header in the SOAP request, but the above error still exists even after the required header is set (just a pair of empty double quotes). The authors found this problem with most MS SOAP implementations. After testing the services, we concluded that the Delphi implementation seemed to work best with SOAP.py, but when testing the services-even with Delphi implementations-it returned complex types, such as lists, which SOAP.py could not use, and an instance of WebServices.SOAP.typedArrayType without data.

In the end, the author chose a rather suitable Web service that returns to the abusive language commonly used by captain Haddock from the comic book adventures of tintin (yes, most Web services do). Listing 1 (curse.py) is the program.

Listing 1: the SOAP.py program that accesses the Curse generator SOAP service


 #!/usr/bin/env python
#http://xmethods.net/detail.html?id=175
import sys
#Import the SOAP.py machinery
from WebServices import SOAP
remote = SOAP.SOAPProxy(
"http://www.tankebolaget.se/scripts/Haddock.exe/soap/IHaddock",
 namespace="urn:HaddockIntf-IHaddock",
 soapaction="urn:HaddockIntf-IHaddock#Curse"
)
try:
 lang = sys.argv[1]
except IndexError:
 lang = "us"
result = remote.Curse(LangCode=lang)
print "What captain Haddock had to say: "%s""%result


I'm going to combine the 1 to 1

After importing the library, we will set up the proxy object remote. This object converts the method call to a remote SOAP message. Its initializer USES the key parameters that manage the remote request: the URI of the server (known as the "endpoint"), the XML namespace of the request element (through which SOAP-as-RPC turns the verbal commitment into the XML base), and the SOAPAction header value.

Next, we will determine the method parameters, which for this Web service are only in Haddock's swearing language, Swedish (" se ") or English (oddly, "us" instead of "en").

Finally, we call the method with the correct name, Curse of the proxy object makes the SOAP call, and then print out the result. The following session demonstrates the use of this program:


$ python curse.py
What captain Haddock had to say: "Ectoplasmic Byproduct!"


Our own SOAP server

Implementing SOAP servers with SOAP.py is fairly easy. As an example, we'll copy the fields and implement a very common service: a program that gives the year and month and prints out the calendar as a string. Its application server is shown in listing 2 (calendar-ws.py).

Listing 2: the SOAP.py program that implements the 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 necessary imports, we define the namespace (CAL_NS) that the SOAP request element expects for our server. Next we define the class that implements all the methods that will be exposed as SOAP methods. You can also register a single function as an SOAP method, but class methods are the most flexible, especially if you want to manage state between calls. The Calendar class defines a method, getMonth, that returns a monthly calendar in a text form using Python's built-in calendar module, as well as another method that returns a full year calendar.

Then create an instance of the SOAP server framework with instructions to listen for port 8888. We must also create an instance of the Calendar class, which is registered in the next line to process the SOAP message, indicating the associated namespace for it. Finally, we call the serve_forever method, which does not return until the process terminates.

To run the server, open another command shell and execute python calendar-ws.py. Kill the process at the end of execution using ctrl-C.

We could have tested the server with the client also written with SOAP.py, but that was too obvious. Let's write a low-level Python client that builds the SOAP response as an XML string and sends an HTTP message. The program (testcal.py) is in listing 3.

Listing 3: a client that accesses the calendar service written with the Python core library


 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  reply_body = requestor.getfi-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()
le().read()
  print "status code:", status_code
  print "status message:", message
  print "HTTP reply body:\n", reply_body
if __name__ == "__main__":
  GetMonth()


The following session demonstrates this test in action.


$ 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>


Carefully examined bytes

If you look up the line self.debug = 0 and change the "0" to "1" (this is line 210 in version 0.9.7 of SOAP.py), one thing to note is that you can get the details of the actual SOAP message being exchanged and other critical data for debugging and tracing, which is useful to you. As an example, a session of the previous curses.py program with the debug information display switch turned on is provided below:


$ 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!"


For comparison, you can get the same information in the old Python script or program with the following code:


import calendar
return calendar.month(2001, 10)


SOAP py summary

We have noted that there are still some interoperability issues with SOAP.py, but the available debugging tools are expected to help.


Related articles: