4.2. Python XML HTTP Post to send a SOAP message to a JWSDP or to a .NET web service.

In the following sections you will find code template to send a SOAP message "by hand" to a Java WSDP web service (document/literal). I will also use this technique to consume the Infobel web service.

4.2.1. XML HTTP Post to my Java WSDP web service.

It is relatively easy to send a file to a web server with Python. In the case of a JWSDP web service, do not forget to add the Content-type in the header of your message, so the call is accepted by the server. Again, this is a command-line example, and the SOAP response is printed without any parsing. I will show later how to use PyXml to parse the SOAP message in the case of the Infobel web service, because the data returned are much more complex.

If you want to test this code with my web service, check here if it is on-line. As usually, you can change the SOAP message, and consume another web service, by building a client with your favorite and well-known tool, and catch the SOAP message echanged between your client and the web service.

4.2.2. Build a SOAP message using DOM with PyXml, and post it to a web service.

What about to build dynamically the soap message you have to post? Find below how to create a SOAP message with DOM using PyXml and Python.

# post an xml file, use DOM to build SOAP message
import sys, httplib

from xml.dom     import implementation
from xml.dom.ext import PrettyPrint

import StringIO

# define namespaces used
ec = "http://schemas.xmlsoap.org/soap/encoding/"
soapEnv = "http://schemas.xmlsoap.org/soap/envelope/"
myns = "http://phonedirlux.homeip.net/types"

# DOM document
domdoc = implementation.createDocument(None, '', None)

# SOAP envelope namespace
seObj = domdoc.createElementNS(soapEnv, "SOAP-ENV:Envelope")
seObj.setAttributeNS(soapEnv, "SOAP-ENV:encodingStyle", ec)

# add it to the root
domdoc.appendChild(seObj)

header = domdoc.createElement("SOAP-ENV:Header")
seObj.appendChild(header)

body = domdoc.createElement("SOAP-ENV:Body")

readls = domdoc.createElementNS(myns, "ns1:readLS")

string_1 = domdoc.createElement("String_1")
string_1.appendChild(domdoc.createTextNode("Message created with PyXml, your e-mail"))

readls.appendChild(string_1)

body.appendChild(readls)

seObj.appendChild(body)

soapStr = StringIO.StringIO()
PrettyPrint(domdoc, soapStr)

# view the soap message

print soapStr.getvalue()

# construct the header and post

webservice = httplib.HTTP("www.pascalbotte.be")
webservice.putrequest("POST", "/rcx-ws/rcx")
webservice.putheader("Host", "www.pascalbotte.be")
webservice.putheader("User-Agent", "My post")
webservice.putheader("Content-type", "text/xml; charset=\"UTF-8\"")
webservice.putheader("Content-length", "%d" % len(soapStr.getvalue()))
webservice.putheader("SOAPAction", "\"\"")
webservice.endheaders()
webservice.send(soapStr.getvalue())

# get the response

statuscode, statusmessage, header = webservice.getreply()
print "Response: ", statuscode, statusmessage
print "headers: ", header
res = webservice.getfile().read()
print res

4.2.3. Post an XML SOAP message to a .NET web service.

Let's see how to post a more advanced SOAP message to the Infobel web service, and also see how to parse the response using Python and PyXml (See the Section called Tip: a way to solve interoperability problem with a NuSoap client (for document/literal web service). in Chapter 2 for a description of the Infobel web service we will consume here). We are going to use Sax and so we need a handler. Before to create a handler, we need to know exactly the structure of the SOAP message returned by the web service. Let's take a look at the data hilighted in bold. in this simple example we will parse the records found and ignore the other fields (elements of the query, status, etc...).

<?xml version="1.0" encoding="utf-8"?>
  <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <soap:Body>
      <SearchResponse xmlns="http://www.infobel.com/WebService/">
      	<SearchResult>
          <Status>...</Status>
	  <Qcountry>aeCountryBE</Qcountry>
	  <Qservice>aeSrvStandard</Qservice>
	  <QName>...</QName>
	  <QCity>bruxelles</QCity>
	  <QZip />
	  <QXCoord>0</QXCoord>
	  <QYCoord>0</QYCoord>
	  <QRange>0</QRange>
	  <QPageStep>5</QPageStep>
	  <QLanguage>aeLangFrench</QLanguage>
	  <Result>
	    <NumRecs>11</NumRecs>
	    <collRecord>
	      <CRecord>
	        <Name>...</Name>
		<Address>...</Address>
		<City>Bruxelles / Brussel</City>
		<Zip>1000</Zip>
		<Phone>...</Phone>
		<Fax> </Fax>
		<XCoord>...</XCoord>
		<YCoord>...</YCoord>
	      </CRecord>
	      <CRecord>
	      ...
	      </CRecord>
	    </collRecord>
	  </Result>
        </SearchResult>
      </SearchResponse>
    </soap:Body>
  </soap:Envelope>

The listing above shows that all the fields we need are located under the tags "CRecord". We must parse all the document and start caching the data in a structure (containing name, address, etc...) once the CRecord tag start. And output these data cached when the CRecord ends.

Now you can create the file named ListInfobelRecord.py, and copy the code below:

Example 4-4. A Python handler for the .NET Infobel web service.

Be sure to install the last version of the PyXml, see PyXml download page.

# infobel sax parsing handler

from xml.sax import saxutils

# see in bold all the steps necessary to
# catch and print a value

class ListInfobelRecord(saxutils.DefaultHandler):
  def __init__(self, value):
    self.search_value = value
    self.flag_value = 0
    self.flag_record = 0
    # set a flag for this value to 0
    self.flag_name = 0
    self.flag_address = 0
    self.flag_city = 0
    self.flag_zip = 0
    self.flag_phone = 0
    self.flag_fax = 0
    self.flag_xcoord = 0
    self.flag_ycoord = 0
    self.value_found = ""
    # initialize a buffer
    self.record_name = ""
    self.record_address = ""
    self.record_city = ""
    self.record_zip = ""
    self.record_phone = ""
    self.record_fax = ""
    self.record_xcoord = ""
    self.record_ycoord = ""
    print "init ok"

  def startElement(self, name, attrs):
    if name == self.search_value:
      self.flag_value = 1
      self.value_found = ""

    if self.flag_record:
      # if tag start: flag
      if name == "Name":
	self.flag_name = 1
      elif name == "Address":
	self.flag_address = 1
      elif name == "City":
	self.flag_city = 1
      elif name == "Zip":
	self.flag_zip = 1
      elif name == "Phone":
	self.flag_phone = 1
      elif name == "Fax":
	self.flag_fax = 1
      elif name == "XCoord":
	self.flag_xcoord = 1
      elif name == "YCoord":
	self.flag_ycoord = 1

    if name == "CRecord":
      # if CRecord start
      # reset all the buffer
      self.flag_record = 1
      self.record_name = ""
      self.record_address = ""
      self.record_city = ""
      self.record_zip = ""
      self.record_phone = ""
      self.record_fax = ""
      self.record_xcoord = ""
      self.record_ycoord = ""
    #print "start: " + name

  def characters(self, ch):
    if self.flag_value:
      self.value_found =self.value_found + ch
    # if flag for this value on
    # append all the characters
    # in the buffer
    elif self.flag_name:
      self.record_name = self.record_name + ch
    elif self.flag_address:
      self.record_address = self.record_address + ch
    elif self.flag_city:
      self.record_city = self.record_city + ch
    elif self.flag_zip:
      self.record_zip = self.record_zip + ch
    elif self.flag_phone:
      self.record_phone = self.record_phone + ch
    elif self.flag_fax:
      self.record_fax = self.record_fax + ch
    elif self.flag_xcoord:
      self.record_xcoord = self.record_xcoord + ch
    elif self.flag_ycoord:
      self.record_ycoord = self.record_ycoord + ch

  def endElement(self, name):
    if name == self.search_value:
      self.flag_value = 0
      print "Record found: " + self.value_found
    # if end of CRecord
    # print
    if name == "CRecord":
      self.flag_record = 0
      print "record: "
      print "name: " , self.record_name
      print "address: " , self.record_address
      print "city: " , self.record_city
      print "phone: " , self.record_phone
      print "fax: " , self.record_fax
      print "xcoord: " , self.record_xcoord
      print "ycoord: " , self.record_ycoord
      print

    # reset the flag for this value
    if name == "Name":
      self.flag_name = 0
    elif name == "Address":
      self.flag_address = 0
    elif name == "City":
      self.flag_city = 0
    elif name == "Zip":
      self.flag_zip = 0
    elif name == "Phone":
      self.flag_phone = 0
    elif name == "Fax":
      self.flag_fax = 0
    elif name == "XCoord":
      self.flag_xcoord = 0
    elif name == "YCoord":
      self.flag_ycoord = 0
    #print "end: " + name

We are ready to post the SOAP message and parse the response using Sax and the handler we just create: ListInfobelRecord. Follow the example below:

Example 4-5. Use the Python Sax handler to parse the SOAP response

Again, the SOAP message could comes from a TcpTunnelGui application running between a client created with your prefered tool and the web service you want to consume with Python.

# infobel client using Python

import sys, httplib

from xml.sax import make_parser, SAXException
from xml.sax.handler import feature_namespaces
from ListInfobelRecord import ListInfobelRecord

if __name__ == '__main__':

	 SM_TEMPLATE = """<SOAP-ENV:Envelope
	 SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
	 xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" 
	 xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"
	 xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" 
	 xmlns:xsd="http://www.w3.org/1999/XMLSchema">
	 <SOAP-ENV:Body>
	 <Search xmlns="http://www.infobel.com/WebService/"><inputQuery>
	 <login>%s</login>
	 <password>%s</password>
	 <country>aeCountryBE</country>
	 <service>aeSrvStandard</service>
	 <Name>durand</Name>
	 <City>bruxelles</City>
	 <Zip></Zip>
	 <XCoord>0</XCoord>
	 <YCoord>0</YCoord>
	 <Range>0</Range>
	 <PageStep>5</PageStep>
	 <Language>aeLangFrench</Language>
	 <CoordType>aeCTWGS</CoordType>
	 </inputQuery></Search>
	 </SOAP-ENV:Body>
	 </SOAP-ENV:Envelope>"""

	 SoapMessage = SM_TEMPLATE % ("infobel", "test")

	 #print SoapMessage

	 webservice = httplib.HTTP("hal.kapitol.com")
	 webservice.putrequest("POST", "/infobelservices/service1.asmx")
	 webservice.putheader("Host", "hal.kapitol.com")
	 webservice.putheader("User-Agent", "Python Post")
	 webservice.putheader("Content-type", "text/xml; charset=\"UTF-8\"")
	 webservice.putheader("Content-length", "%d" % len(SoapMessage))
	 webservice.putheader("SOAPAction", "\"http://www.infobel.com/WebService/Search\"")
	 webservice.endheaders()
	 webservice.send(SoapMessage)

	 # get the response

	 statuscode, statusmessage, header = webservice.getreply()
	 print "Response: ", statuscode, statusmessage
	 print "headers: ", header

         # parse the response
	 
	 parser = make_parser()
	 parser.setFeature(feature_namespaces, 0)

	 myHandler = ListInfobelRecord('NumRecs')

	 parser.setContentHandler(myHandler)

	 parser.parse(webservice.getfile())

In the next section I will use the DOM to create, at runtime, the SOAP message.

4.2.4. Consume a .NET web service with Python, a more complete example.

You can find below a more complete example showing how to consume a .NET web service: using DOM to create your SOAP message and using Sax to parse the response.

Example 4-6. Consume a .NET web service: create your SOAP message with DOM using PyXml

In the code below we still use our class ListInfobelRecord to parse the SOAP response. You will find in bold the code related to the DOM creation of the SOAP query. I wrote a little function handling the creation of the tags contained in the query (13 fields). I'm sure the Python programmers will handle in a more elegant way the job... If you are interested to share your solution with others (I have 100 visitors per day for the time being), you can mail me your code. I will be glad to improve my doc!

# infobel client using Python

import sys, httplib

from xml.sax import make_parser, SAXException
from xml.sax.handler import feature_namespaces
from ListInfobelRecord import ListInfobelRecord
from xml.dom     import implementation
from xml.dom.ext import PrettyPrint

import StringIO

def makeTag(tagname, value, domobj, parent):
  "Create a tag + value and append to the parent object"
  elem = domobj.createElement(tagname)
  if(len(value) > 0):
    buf = domobj.createTextNode(value)
    elem.appendChild(buf)
  parent.appendChild(elem)

ec = "http://schemas.xmlsoap.org/soap/encoding/"
soapEnv = "http://schemas.xmlsoap.org/soap/envelope/"
infobelNS = "http://www.infobel.com/WebService/"

domdoc = implementation.createDocument(None, '', None)

seObj = domdoc.createElementNS(soapEnv, "SOAP-ENV:Envelope")
seObj.setAttributeNS(soapEnv, "SOAP-ENV:encodingStyle", ec)

domdoc.appendChild(seObj)

body = domdoc.createElement("SOAP-ENV:Body")

search = domdoc.createElementNS(infobelNS, "Search")

inputquery = domdoc.createElement("inputQuery")

makeTag("login", "infobel", domdoc, inputquery)
makeTag("password", "test", domdoc, inputquery)
makeTag("country", "aeCountryBE", domdoc, inputquery)
makeTag("service", "aeSrvStandard", domdoc, inputquery)
makeTag("Name", "durand", domdoc, inputquery)
makeTag("City", "bruxelles", domdoc, inputquery)
makeTag("Zip", "", domdoc, inputquery)
makeTag("XCoord", "0", domdoc, inputquery)
makeTag("YCoord", "0", domdoc, inputquery)
makeTag("Range", "0", domdoc, inputquery)
makeTag("PageStep", "5", domdoc, inputquery)
makeTag("Language", "aeLangFrench", domdoc, inputquery)
makeTag("CoordType", "aeCTWGS", domdoc, inputquery)

search.appendChild(inputquery)

body.appendChild(search)

seObj.appendChild(body)

soapStr = StringIO.StringIO()
PrettyPrint(domdoc, soapStr)

print soapStr.getvalue()

if __name__ == '__main__':

	 webservice = httplib.HTTP("hal.kapitol.com")
	 webservice.putrequest("POST", "/infobelservices/service1.asmx")
	 webservice.putheader("Host", "hal.kapitol.com")
	 webservice.putheader("User-Agent", "Python Post")
	 webservice.putheader("Content-type", "text/xml; charset=\"UTF-8\"")
	 webservice.putheader("Content-length", "%d" % len(soapStr.getvalue()))
	 webservice.putheader("SOAPAction", "\"http://www.infobel.com/WebService/Search\"")
	 webservice.endheaders()
	 webservice.send(soapStr.getvalue())

	 # get the response

	 statuscode, statusmessage, header = webservice.getreply()
	 print "Response: ", statuscode, statusmessage
	 print "headers: ", header

	 parser = make_parser()
	 parser.setFeature(feature_namespaces, 0)

	 dh = ListInfobelRecord('NumRecs')

	 parser.setContentHandler(dh)

	 parser.parse(webservice.getfile())