Skip to main content

If you don't have an IBM ID and password, register here.

By clicking Submit, you agree to the developerWorks terms of use.

The first time you sign into developerWorks, a profile is created for you. This profile includes the first name, last name, and display name you identified when you registered with developerWorks. Select information in your developerWorks profile is displayed to the public, but you may edit the information at any time. Your first name, last name (unless you choose to hide them), and display name will accompany the content that you post.

All information submitted is secure.

The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerworks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

By clicking Submit, you agree to the developerWorks terms of use.

All information submitted is secure.

The Python web services developer: SOAP over SMTP

Sending and receiving SOAP requests over SMTP

Mike Olson, Principal Consultant, Fourthought, Inc.
Photo of Mike Olson
Mike Olson is a consultant and co-founder of Fourthought Inc., a software vendor and consultancy specializing in XML solutions for enterprise knowledge management applications. Fourthought develops 4Suite, and 4Suite Server, open source platforms for XML middleware. You can contact Mr. Olson at mike.olson@fourthought.com.
Uche Ogbuji (uche@ogbuji.net), Principal Consultant, Fourthought, Inc.
Photo of Uche Ogbuji
Uche Ogbuji is a consultant and co-founder of Fourthought Inc., a software vendor and consultancy specializing in XML solutions for enterprise knowledge management applications. Fourthought develops 4Suite, and 4Suite Server, open source platforms for XML middleware. Mr. Ogbuji is a Computer Engineer and writer born in Nigeria, living and working in Boulder, Colorado, USA. You can contact Mr. Ogbuji at uche@ogbuji.net.

Summary:  When most people think of SOAP, they think of sending XML requests, and getting XML responses over the HTTP protocol. This does not always have to be the case. In fact, the SOAP protocol defines HTTP as one possible transport for SOAP messages. In this article Mike Olson and Uche Ogbuji explore sending and receiving SOAP requests over the Simple Mail Transfer Protocol (SMTP).

Date:  04 Mar 2003
Level:  Introductory

Comments:  

Introduction

Most people only think of SOAP over HTTP for a couple of reasons. First, it is the most common transport of the SOAP protocol, by far having the most services available on the web. Second, because of how the HTTP protocol works, it fits very well into a SOAP request/response structure. In HTTP, you send a message to the server. The format of an HTTP request is flexible enough that you can embed a SOAP request within the body. The HTTP protocol then specifies a response (to all HTTP requests) that is in turn flexible enough to embed a SOAP response. This allows for a very straightforward implementation of a SOAP service.

This is not the case with the SMTP protocol. When using SMTP, the request format is flexible enough that you can attach a SOAP request. However, the response structure is not very flexible. An SMTP response is as simple as "O.K." The SMTP Service Extensions (ESMTP) specification does add a bit more information to a response such as "Unknown User" but there is still not enough flexibility in the response format to put in an entire SOAP response. The only way to send a response with SMTP is with another e-mail message.

Because of this, developers of SMTP SOAP services need to add additional logic to be able to track incoming SOAP requests, then send the SOAP response over a separate SMTP message to the recipient. This adds a fair amount of complexity (as we will see shortly) to SOAP services that use SMTP.

So, why would you want to use SOAP over SMTP? The most common answer is "I cannot use HTTP." One of the more common reasons for this is firewalls. If you are behind a firewall, odds are that you have no control over where HTTP requests are processed. However, there is a very good chance that you receive e-mail. Another reason would be that the request/response messaging model may not be the desired model for your application. SMTP makes perfect sense for publish/subscribe or a one-way messaging model. As a final reason, the service you are writing is not real-time. If your service has to perform complex queries, or complex calculations that can take over 300 seconds (common HTTP request time out) then you need an asynchronous approach, such as SMTP.


Processing SOAP messages

There are three possible ways to process SOAP requests and responses over SMTP. The first approach is dependent on the abilities of your SMTP Server. With this approach, you write your SOAP processing application, then connect it to an e-mail address. Most e-mail servers will allow you to pipe messages that are received on a specific address to an application. Your application would then read in the SMTP message (most likely from standard process input), process the request, and send off the response.

The second approach assumes that you have access to the mailbox file. With this approach, you would regularly scan the mail file for SOAP requests, remove these messages from the mail file, process the requests, and send out the responses.

The last approach, and the one we will use here as the other two are heavily dependent on local configuration options, is to write an SMTP Server. With this approach, you listen for SMTP requests on a port, process the requests as they are received, and send out the responses when processing is done.


Linking requests and responses

No matter how you decide to handle SOAP over SMTP, you need a mechanism to link SOAP requests and SOAP responses. The SOAP experimental e-mail binding (see Resources) recommends using the "Message-ID" and "In-Reply-To" SMTP headers. The client, puts a unique identifier into the "Message-ID" header when the message is sent, then the server uses that same identifier in the "In-Reply-To" header for the response message. This allows the client to match a received response with the appropriate request.


Example dependencies

The example for this column is written using Python 2.2.1 and uses some features, mainly the new server architecture. We will also be using ZSI 1.2 to process our SOAP messages. Installation of ZSI is fairly painless as it uses standard python distutils for its distribution. See the Resources section at the end of the column for download information.


A SOAP SMTP server

Using Python's builtin smtpd library, writing an SMTP server is relatively painless. As with all of Python's server architectures, you inherit from a base class, smtpd.SMTPServer in this case (see Listing 1), and override certain methods based on the functionality that you desire. In this case, we override the process_message method which is called whenever a new message arrived. To start the server we use Python 2.2's new asyncore to start handling requests.

		
class OurServer(smtpd.SMTPServer):

    #A place to store the current ID of the message that we are processing.
    currentID = None

    #This is the callback from SMTPServer that all SMPTServers
    #must implement to handle messages
    def process_message(self, peer, mailfrom, rcpttos, data):
       #
       # Snip out code for process_message
       # It will be discussed below
       #

#This is the 2.2 way of running asynchronous servers
server = OurServer(("localhost", 8023),
                   (None, 0))
try:
    asyncore.loop()
except KeyboardInterrupt:
    pass

Once a message is received, we can do some simple tests to see if it is indeed a SOAP request. If so, then we can pass it off to ZSI to dispatch the message. As the ZSI _Dispatch method does not allow for additional parameters, we need to store the current message ID (so we can set it in the results) on the instance of the server. Of course, in a high traffic server this solution will never work as you can expect more than one incoming request at a time. To get around this we would either need to somehow pass this information through ZSI, or create a separate instance of the server for every request.

Once we have the results, whether they are a valid response, or a fault, we send the results off in a e-mail message. You'll notice that when we are sending an e-mail message, we use the common method common.SendMessage (see Listing 2). This helper function sends a message to a specified server using a different thread. This is needed so that the sending process is not blocked by waiting for a server connection, network delays, etc. In a production system, this function would have to be more complex and handle issues of network failure, a server's going down, etc. with some sort of queuing mechanism.

		
    def process_message(self, peer, mailfrom, rcpttos, data):
        #Parse the message into an email.Message instance
        p = Parser.Parser()
        m = p.parsestr(data)
        print "Received Message"

        #See if it is a SOAP request
        if m.has_key('To') and m['To'] == 'calendar@localhost':
            self.process_soap(m)
        else :
            #In normal circumstances, this would probably
            #forward the email message to another SMTP Server
            print "Unknown Email message"
            print m

    def process_soap(self,message):

        #Parse the SOAP Message
        ps = parse.ParsedSoap(message.get_payload(decode=1))

        #Store the current ID
        self.currentID = message['Message-Id']
        print "Processing Message: " + self.currentID

        #Use ZSI's dispatcher to call the correct function based on the message.
        dispatch._Dispatch(ps,
                           [self],
                           self.send_xml,
                           self.send_fault)

    #ZSI Callback to send an SOAP(non-Fault) response.
    def send_xml(self,xml):
        self.return_soap(xml)

    #ZSI callback to send a fault.
    def send_fault(self,fault):
        sys.stderr.write("FAULT While processing request:\n");
        s = cStringIO.StringIO()
        fault.serialize(s)
        st = s.getvalue()
        print st
        #Serialize the fault and send it to the client
        self.return_soap(st)

    #Called by our code to send result XML.
    def return_soap(self,st):
        msg = MIMEText.MIMEText(st)

        msg['Subject'] = "Test Message"
        msg['To'] = 'calendar@localhost'
        msg['From'] = 'Mike.Olson@Fourthought.com'
        msg['Message-Id'] = "2"
        msg['In-Reply-To'] = self.currentID or 0

        print "Sending Reply"
        common.SendMessage("127.0.0.1",8024,"me@fourthought.com",
        ["Mike.Olson@Fourthought.com"],msg)
                         
    #Implementation of our SOAP Service.
    def getMonth(self,year,month):
        print "Request for %d,%d" % (year,month)
        return calendar.month(year, month)


A SOAP SMTP client

An SMTP SOAP client needs to listen for incoming mail messages that represent replies, so it looks a lot like a server implementation. We again override smtpd.SMTPServer to create a listener (on a different port) to listen for replies (see Listing 3). One of the big differences between the server and the client listener is that we start the listener off in a separate thread. This allows us to have one thread that is handling user input, and a second, our listener, that is listening and processing responses.

Another difference is that the listener contains a dictionary of "responses" that map "In-Reply-To" IDs to call-back methods. Using this approach, the main input thread can shoot off as many requests as it would like as long as it registers each with the listener. The main thread can then continue on its merry way then, whenever a responses comes in, the listener invokes the call-back to handle the response.

		
class ClientServer(smtpd.SMTPServer):
    #A simple server to receive our SOAP responses.
    #The responses dictionary is a mapping from
    #Message ID to a callback to handle the response.
    responses = {}

    #this is the method we must override in to handle SMTP messages
    def process_message(self, peer, mailfrom, rcpttos, data):
        #Parse the message into a email.Message instance
        p = Parser.Parser()
        m = p.parsestr(data)
        #See if this is a reply that we were waiting for.
        if m.has_key('In-Reply-To') and self.responses.has_key(m['In-Reply-To']):
            mID = m['In-Reply-To']
            #Invoke the response callback with the parsed SOAP.
            self.responses[mID](mID,parse.ParsedSoap(m.get_payload(decode=1)))
            del self.responses[mID]
        else:
            #In a product server, this would probably forward the message to another
            #SMTP Server.
            print "Unknown Email message"
            print m
            
    #method used to register that we are expecting a response from the server.
    def expectResponse(self,mId,callback):
        self.responses[str(mId)] = callback

User input is gathered in the HandleInput method (see Listing 4). This asks the user to enter in a month and a year. Then it creates a SOAP request and sends it to the server. It also registers this request with the listener so that the listener knows to expect a response. In this example, all of the requests are registered with the DisplayResults call-back. This method simply displays the results to the screen.

		
def DisplayResults(ID,ps):
    #This method is the generic callback used by all requests.
    #It uses the parsed SOAP to print out the results.
    print "\nResults for ID: " + ID
    tc = TC.String()
    data = _child_elements(ps.body_root)
    if len(data) == 0: print None
    print tc.parse(data[0], ps)

def HandleInput(server):
    #This method is used to query the user for a year and a month.
    #When one is received, then a new message is sent, and the server
    #is told to expect the results
    done = 0
    lastID = 1
    while not done:
        year = raw_input("Year of request(Return to exit): ")
        if not year: done = 1
        else:
            year = int(year)
            month = int(raw_input("Month of request: "))

            lastID += 1
            mID = lastID
            
            msg = MIMEText.MIMEText(BODY_TEMPLATE%(year,month))

            msg['Subject'] = "Test Message"
            msg['To'] = 'calendar@localhost'
            msg['From'] = 'Mike.Olson@Fourthought.com'
            msg['Message-Id'] = str(mID)

            server.expectResponse(mID,DisplayResults)
            print "Sending out message ID: " + str(mID)
            common.SendMessage("127.0.0.1",8023,"me@fourthought.com",
             ["Mike.Olson@Fourthought.com"],msg)

def StartServer():
    #Start up our response server in another thread.
    server = ClientServer(("localhost", 8024),
                          (None, 0))
    def run():
        try:
            asyncore.loop()
        except KeyboardInterrupt:
            pass
    print "Starting Client Server"
    t = threading.Thread(None,run)
    t.start()
    return server

if __name__ == '__main__':
    server = StartServer()
    HandleInput(server)


Running the example

The client application queries the user for a month and a date. It will let you make as many queries as you'd like. Once a valid reply is returned, it will be printed to the screen. You'll notice that you might not get all the replies back in the same order that they were sent. The output is shown in Listing 5.

		
[molson@penny src]$ python client.py
Starting Client Server
Year of request(Return to exit): 2003
Month of request: 1
Sending out message ID: 2
Year of request(Return to exit): 2004
Month of request: 1
Sending out message ID: 3
Year of request(Return to exit): 2005
Month of request: 1
Sending out message ID: 4
Year of request(Return to exit):
Results for ID: 2
January 2003
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

Results for ID: 3
January 2004
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

Results for ID: 4
January 2005
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


Next column

In the next installment of this column, we will look into what is required to interact with Google's SOAP API.



Download

NameSizeDownload method
ws-pyth12example.zipHTTP

Information about download methods


Resources

About the authors

Photo of Mike Olson

Mike Olson is a consultant and co-founder of Fourthought Inc., a software vendor and consultancy specializing in XML solutions for enterprise knowledge management applications. Fourthought develops 4Suite, and 4Suite Server, open source platforms for XML middleware. You can contact Mr. Olson at mike.olson@fourthought.com.

Photo of Uche Ogbuji

Uche Ogbuji is a consultant and co-founder of Fourthought Inc., a software vendor and consultancy specializing in XML solutions for enterprise knowledge management applications. Fourthought develops 4Suite, and 4Suite Server, open source platforms for XML middleware. Mr. Ogbuji is a Computer Engineer and writer born in Nigeria, living and working in Boulder, Colorado, USA. You can contact Mr. Ogbuji at uche@ogbuji.net.

Report abuse help

Report abuse

Thank you. This entry has been flagged for moderator attention.


Report abuse help

Report abuse

Report abuse submission failed. Please try again later.


developerWorks: Sign in

If you don't have an IBM ID and password, register here.


Forgot your IBM ID?


Forgot your password?
Change your password


By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. This profile includes the first name, last name, and display name you identified when you registered with developerWorks. Select information in your developerWorks profile is displayed to the public, but you may edit the information at any time. Your first name, last name (unless you choose to hide them), and display name will accompany the content that you post.

Choose your display name

The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

(Must be between 3 – 31 characters.)


By clicking Submit, you agree to the developerWorks terms of use.

 


Rate this article

Comments

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=SOA and web services
ArticleID=11761
ArticleTitle=The Python web services developer: SOAP over SMTP
publish-date=03042003
author1-email=mike.olson@fourthought.com
author1-email-cc=
author2-email=uche@ogbuji.net
author2-email-cc=

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

For articles in technology zones (such as Java technology, Linux, Open source, XML), Popular tags shows the top tags for all technology zones. For articles in product zones (such as Info Mgmt, Rational, WebSphere), Popular tags shows the top tags for just that product zone.

For articles in technology zones (such as Java technology, Linux, Open source, XML), My tags shows your tags for all technology zones. For articles in product zones (such as Info Mgmt, Rational, WebSphere), My tags shows your tags for just that product zone.

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).