Tuesday, November 20, 2007

XML RPC server inside apache mod_python

Writing XML RPC server and client in python is extremely easy. There are many examples. xmlrpclib is part of python, so writing client doesn't need anything extra. There are many examples of XMLRPC server as well. I used Julien Oster's.

The problem comes when you want to use XML RPC server in a production environment alongside your apache web server. If you are not a big shop then you very likely want to host both of them on same machine. Then two different servers can't listen for HTTP on same port. You might use an alternate port (second in popularity to port 80), but the users behind corporate firewall will suffer. If you understand the protocol stack, you would know it shouldn't be difficult to run the XMLRPC server inside an apache server. I realized that last weekend, and in couple of hours I hosted Julien Oster's XML RPC server inside my apache mod_python framework.

Download the XML RPC server. You will find a single file xmlrpcserver.py.

You will find a class XmlRpcServer in it. That's all you need. Add following method to it:
    def handle(self,req):
length = int(req.headers_in["Content-length"])

request_string = req.read(length)
request = StringIO(request_string)

request.seek(0)
response = StringIO()

try:
self.execute(request, response, None)
except Exception, e:

return apache.HTTP_INTERNAL_SERVER_ERROR
finally:
response.seek(0)

rstr = response.read()
req.headers_out['Content-type'] = 'text/xml'

req.headers_out['Content-length'] = "%d"%len(rstr)

req.write(rstr)
return apache.OK

Now host the following code in your index.py file (or any python file you have configured as PythonHandler in your apache settings)

from mod_python import apache

from xmlrpcserver import XmlRpcServer

def handler(req):

try:
xmlserver = XmlRpcServer()
app = Application()

xmlserver.register_class('app',app)

result = xmlserver.handle(req)

return result
except Exception, e:
return apache.HTTP_INTERNAL_SERVER_ERROR

# The following class is where you can put your application logic
class Application:
def __init__(self):
pass

def getName(self):
return 'example'

Once you save the above index.py to your webserver, you can use a python client to invoke XMLRPC calls to your apache server.

Assuming you saved above file to $(DOCROOT)/xmlrpc/index.py, your client code would look like this:
import xmlrpclib

remote = xmlrpclib.Server('http://yourserver.com/xmlrpc/')
name = remote.app.getName()


And you are all set!

syntax highlighted by Code2HTML

7 comments:

Joss82 said...

I have found a way to achieve the same functionality using only the xmlrpclib library, included in python 2.5
So you don't need to install any additional library. And it is self-documenting too if you access it from a web browser. Enjoy :

#!env python2.5
# -*- coding:utf-8 -*-

from DocXMLRPCServer import ServerHTMLDoc
import xmlrpclib
from mod_python import apache

class ExposedClass:
    def __init__(self, foo = 42):
        self.foo = foo
    def exposed_func1(self):
        """Returns "foo" """
        return self.foo
    
def handler(req):
    exposed_instance = ExposedClass()
    all_methods = list_public_methods(exposed_instance)
    all_methods.append("system.listMethods")
    if req.method == "GET":
        req.content_type = "text/html; charset=UTF-8"
        req.send_http_header()
        srv = ServerHTMLDoc()
        req.write(srv.docserver("XMLRPC server in Apache", "", dict([(m,getattr(exposed_instance,m,None)) for m in all_methods]) ))
        return_val = apache.OK
    elif req.method == "POST":
        data = req.read()
        if data == "":
            req.content_type = "text/html"
            req.send_http_header()
            return_val = apache.HTTP_EXPECTATION_FAILED
        else:
            req.content_type = "text/xml"
            req.send_http_header()
            params, method = xmlrpclib.loads(data)
            
            if method == "system.listMethods":
                req.write(xmlrpclib.dumps((all_methods,), methodresponse = True))
            else:
                if method in all_methods:
                    req.write(xmlrpclib.dumps((getattr(exposed_instance,method)(*params),), methodresponse = True, allow_none = True))
                else:
                    req.write(xmlrpclib.dumps(((-2, "Unknown function : %s" % str(method), "",{}),), methodresponse = True, allow_none = True))
        return_val = apache.OK
    return return_val

def list_public_methods(obj):
    """Returns a list of attribute strings, found in the specified
    object, which represent callable attributes"""

    return [member for member in dir(obj)
                if not member.startswith('_') and
                    callable(getattr(obj, member))]

Felipe said...

thanks Jayesh Salvi and joss82, both examples worked like a charm, I will use joss82 implementation for production, but I will disable the list_methods option to avoid curious people

Jayesh said...

You guys might also like this other post I wrote about how to implement XML RPC server on top of Google App Engine.

The actual recipe I wrote is in App engine docs' cookbook.

It is more attractive option because Google App engine is free to start and you can pay for more resources if traffic to your site exceeds. (I think the paying part is yet to be made public)

Anonymous said...

I managed to do the bit that Joss82 posted, but couldn't do the original poster's method at all. Any help possible? thom_41 at (spamfree) yahoo.com

Joss82 said...

My bit should be enough to run a fully operational web service. You just need to change ExposedClass so it does what you want. What else do you need exactly?

Anonymous said...

@joss82 I don't need anything else really! Just curious about the differences in performance between both approaches. But your approach is working nicely for me at the moment. Thanks!

p.s I might add support for system.methodHelp and system.methodSignature at a later stage and will post back here then.

Anonymous said...

Hey... newbie here trying to implement a simple xmlrpc server on apache with mod_python. I tried the first method did not work. The one offered by josh there's a webpage that displays a title, func1() returns foo and listallmethods. Was wondering what to do from here to say have a client call methods from the server and display in a page ?