=========================== 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. .. Note we cannot use the sectnum directive. It generates duplicate section numbers. .. contents:: :depth: 4 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: a. Copy the default configuration script ``config.py`` from the Quixote distribution to a location of your choice. b. Edit your copy of the configuration file. The default configuration script contains comments to help with this task. c. 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: - We create a subclass ``MyPublisher`` of class ``Publisher`` and put our initialization and clean-up code there. - Method ``start_request`` is called by Quixote at the start of handling each request, before it calls our request handling code. This enables us to add our connection and proxy objects to the request object for use in our request handling code. - And, when we get a keyboard interrupt (a Ctrl-C), we trap that exception and call the ``cleanup`` method in our publisher subclass. This enables us, during testing, for example, to stop our Medusa server with Ctrl-C and then restart it, performing clean-up each time. 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. .. _`ReusingConnections`: http://www.quixote.ca/qx/ReusingConnections