Using the Medusa Web Server

Author: Dave Kuhlman
Address:
dkuhlman@rexx.com
http://www.rexx.com/~dkuhlman
Revision: 1.1d
Date: Jan. 21, 2004
Copyright: Copyright (c) 2004 Dave Kuhlman. This documentation is covered by The MIT License: http://www.opensource.org/licenses/mit-license.

Abstract

Documentation on how to set up Quixote with Medusa.

Contents

Medusa is a 'server platform' -- it provides a framework for implementing asynchronous socket-based servers (TCP/IP and on Unix, Unix domain, sockets).

For our purposes, Medusa provides an HTTP 1.1 Web server.

This document explains how to use Medusa in support of Quixote.

Install Medusa

Down-load Medusa from http://www.amk.ca/python/code/medusa.html.

Unroll it with something like:

tar xvzf medusa-0.5.4.tar.gz

Install Medusa with the following (You may have to become root to do this.):

cd medusa-0.5.4
python setup.py install

Create a driver script

Here is a sample, taken from qxdemo, the Quixote demo application:

#!/usr/bin/env python

try:
    from medusa import http_server, xmlrpc_handler
except ImportError:
    print '\n' + '='*50
    print 'medusa/ package not found; fetch it from '
    print '  http://www.amk.ca/files/python/'
    print 'and install it.'
    print '='*50
    raise
    
import asyncore
from quixote.publish import Publisher
from quixote.server import medusa_http

from quixote import enable_ptl
enable_ptl()

# Server that exposes the links demo program on port 8080
PORT = 8080    # [1]

print 'Now serving the qxdemo.links demo on port %i' % PORT
server = http_server.http_server('', PORT)
publisher = Publisher('qxdemo.ui.links')    # [2]
publisher.config.debug_log = "/tmp/debug"    # [3]
publisher.config.error_log = "/tmp/error"
publisher.setup_logs()
dh = medusa_http.QuixoteHandler(publisher, 'qxdemo/links server', server)    # [4]
server.install_handler(dh)
try:
    asyncore.loop()
except KeyboardInterrupt:    # [5]
    pass

Notes and customization (see [n] comments in the code):

  1. Change the port that you want to receive requests on.

  2. Change the Python path to your application. If the root of your application is in a __init__.py file, specify the path to the directory (package) that contains it, for example:

    publisher = Publisher('qxdemo.ui')
    

    If the root of your application is in a plain Python source file such as qxdemo/ui/links.py, specify the path to that module, for example:

    publisher = Publisher('qxdemo.ui.links')
    
  3. You can also specify log files (and a lot more) in a Quixote configuration file. In order to do so:

    1. Copy the default configuration script config.py from the Quixote distribution to a location of your choice.

    2. Edit your copy of the configuration file. The default configuration script contains comments to help with this task.

    3. Replace the lines containing publisher.config. ... with the following line containing the path to your configuration script:

      publisher.read_config('myconfig.py')
      
  4. Change the server name string. In our sample script above, the value of the SERVER_SOFTWARE environment variable will be "qxdemo/links server".

  5. The try:except: block enables us to catch a break (Ctrl-C) and perform any needed clean-up. For example, had we subclassed the Publisher class and created any connections, proxies, etc, this would allow us to close connections etc.

Start Your Server

Note: You will need to add the directory containing your application to your PYTHONPATH before running the following code.

In order to start your server, run your driver script. For example, if the name of your driver script is server-medusa.py, the following will start your server:

python server-medusa.py

Creating and Reusing Connections, Proxies, Etc

In some applications it is useful to create and reuse things like database connections, proxies for XML-RPC and SOAP client access, etc. Here is a more extensive Medusa driver script that shows how to do this:

#!/usr/bin/env python

#
# Medusa server that exposes the skeleton program.
#

from optparse import OptionParser

try:
    from medusa import http_server, xmlrpc_handler
except ImportError:
    print '\n' + '='*50
    print 'medusa/ package not found; fetch it from '
    print '  http://www.amk.ca/files/python/'
    print 'and install it.'
    print '='*50
    raise

import asyncore
from quixote.publish import Publisher
from quixote.server import medusa_http
from pyPgSQL import PgSQL
import xmlrpclib

from quixote import enable_ptl
enable_ptl()

# Warning: Must import SOAPpy *after* enable_ptl.  I don't know why.
from SOAPpy import SOAPProxy

# Default port to listen on.
PORT = 8080

CONNECT_ARGS_FILE = 'connection_args.txt'

#
# A container for persistent connections etc.
#
class PersistentContainer:
    pass

class MyPublisher(Publisher):
    def __init__(self, root_namespace, config=None):
        Publisher.__init__(self, root_namespace, config)
        self.container = PersistentContainer()
        connectionFile = file(CONNECT_ARGS_FILE, 'r')
        connectionArgs = connectionFile.read().strip()
        connectionFile.close()
        self.container.dbConnection = PgSQL.connect(connectionArgs)
        self.container.xmlrpcProxy = xmlrpclib.ServerProxy('http://localhost:8082')
        self.container.soapProxy = SOAPProxy(str("http://localhost:8083/"))
        meerkatURI = "http://www.oreillynet.com/meerkat/xml-rpc/server.php"
        self.container.meerkatServer = xmlrpclib.Server(meerkatURI)

    def cleanup(self):
        self.container.dbConnection.close()

    def start_request(self, request):
        Publisher.start_request(self, request)
        request.container = self.container


def start_server(port):
    print 'Now serving the skeleton services on port %i' % port
    server = http_server.http_server('', port)
    publisher = MyPublisher('skeleton.ui')
    publisher.read_config('config.py')
    publisher.setup_logs()
    dh = medusa_http.QuixoteHandler(publisher, 'skeleton/services on medusa server', server)
    server.install_handler(dh)
    try:
        asyncore.loop()
    except KeyboardInterrupt:
        publisher.cleanup()


USAGE_TEXT = """
    python %prog [options]
example:
    python %prog -p 8081"""

def usage(parser):
    parser.print_help()
    sys.exit(-1)

def main():
    global Verbose
    parser = OptionParser(USAGE_TEXT)
    parser.add_option("-p", "--port", type="int",
        dest="port", help="port to listen on")
    (options, args) = parser.parse_args()
    port = PORT
    if options.port:
        port = options.port
    if len(args) == 0:
        start_server(port)
    else:
        usage(parser)
        
if __name__ == "__main__":
    main()

Explanation:

See Also

ReusingConnections: The Quixote Wiki section ReusingConnections describes start-up scripts, how to reuse resources, and how to clean-up resources and connections when the server shuts down.