embeddable Python web server (was 'derived from CGIHTTPServer.py')

Frank Sergeant frank.sergeant at redneck.net
Fri Feb 18 11:55:28 EST 2000


< Michael Ströder <michael.stroeder at inka.de> asks about an embeddable
  Python web server>

Well, here is what I've been using very successfully.  It lets you
embed a web server into your Python application.  The web server
will serve up ordinary files, but when the magic directory name
'cgi-bin' is used it results in a call to a method in your application.
(I think I should have used a different magic directory name, such
as 'py-bin'.)  I was driven to this because I wanted to run a Python
web server on Windows and the one that came with the Python distribution
involved forking, etc. which were not supported under Windows.  Also,
I wanted the application to stay resident rather than starting up
repeatedly as it would under standard CGI.

This is not polished yet, but I hope it will give
you something to work with.  Any suggestions and improvements are
always welcome and I look forward to making this available on the
web eventually.


  -- Frank
  frank at canyon-medical.com


                     -------- cgilite.py ---------
#! /usr/local/bin/python

"""cgi lite

Excerpts from the full cgi.py module.  See the original module for
extensive comments.  Frank Sergeant  frank at canyon-medical.com.
(My modifications freely available for all purposes.)

"""

# Imports
# =======

import string
import urllib


# Parsing functions
# =================

# Maximum input we will accept when REQUEST_METHOD is POST
# 0 ==> unlimited input
maxlen = 0

def parse_qs(qs, keep_blank_values=0, strict_parsing=0):
    """Parse a query given as a string argument.

        Arguments:

        qs: URL-encoded query string to be parsed

        keep_blank_values: flag indicating whether blank values in
            URL encoded queries should be treated as blank strings.  
            A true value inicates that blanks should be retained as 
            blank strings.  The default false value indicates that
            blank values are to be ignored and treated as if they were
            not included.

        strict_parsing: flag indicating what to do with parsing errors.
            If false (the default), errors are silently ignored.
            If true, errors raise a ValueError exception.
    """
    name_value_pairs = string.splitfields(qs, '&')
    dict = {}
    for name_value in name_value_pairs:
        nv = string.splitfields(name_value, '=')
        if len(nv) != 2:
            if strict_parsing:
                raise ValueError, "bad query field: %s" % `name_value`
            continue
        name = urllib.unquote(string.replace(nv[0], '+', ' '))
        value = urllib.unquote(string.replace(nv[1], '+', ' '))
        if len(value) or keep_blank_values:
            ## fcs ## warning, this puts all values into list, even
            ##  when the value is a single item.  Consider changing this.
            #if dict.has_key (name):
            #    dict[name].append(value)
            #else:
            #    dict[name] = [value]
            if dict.has_key (name):
                # if we have more than a single value, then create a list
                if type(dict[name]) == type([]):
                   dict[name].append(value)
                else:
                   dict[name] = list (dict[name], value)
            else:
                # with a single value, do not put it into a list
                dict[name] = value
    return dict


# Utilities
# =========

def escape(s, quote=None):
    """Replace special characters '&', '<' and '>' by SGML entities."""
    s = string.replace(s, "&", "&") # Must be done first!
    s = string.replace(s, "<", "<")
    s = string.replace(s, ">", ">",)
    if quote:
        s = string.replace(s, '"', """)
    return s

                     -------- cgilite.py ---------
"""httpd.py   February 18, 2000   Frank Sergeant  frank at canyon-medical.com

This is a simple HTTP server, based on CGIHTTPServer.py and working
much like Tcl's tclhttpd, for use as an embedded web server in a
Python application.

Certain URLs are interpreted as calls to Python functions.  This allows
persistent state as opposed to the cgi approach which would start up
Python for each request.  Another problem with CGIHTTPServer.py is that
is forked a new process to handle each cgi request and fork does not
work on Windows.

This module builds on SimpleHTTPServer by implementing GET and POST
requests to run Python methods.  It collects the query information
and stuffs it into a dictionary which is passed to the Python function.
The Python function then works much like a cgi program except that it
reads the passed parameters from the dictionary argument rather than
from environment variables.

If the URL is http://somehost/cgi-bin/patlist then the method to be
executed will be py_patlist().  There does not need to be a .../cgi-bin
directory.  Probably, I should have used 'py-bin' instead of 'cgi-bin'
as the magic directory name.

(My modifications freely available for all purposes.)

"""


__version__ = "0.1"


import os
import sys
import time
import socket
import string
import urllib
import BaseHTTPServer
import SimpleHTTPServer

import cgilite

# Dummy response page for testing
DEFAULT_TESTING_MESSAGE = """\
<head>
<title>This is just a silly test</title>
</head>
<body>
<h1>Don't worry, be happy</h1>
<p>This is a first test of calling a
Python function via a URL.
</body>
"""

class PyHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):

    """Incomplete HTTP server.  It handles GET and POST "cgi" commands but
    only for the Python application it is embedded in.  The application will
    be a subclass of PyHTTPRequestHandler.  The "cgi" URL is interpreted as a
    request to run a method of the application beginning with a prefix
    of 'py_'.  For example, a URL of http://someserver/cgi-bin/trash would
    be taken as a request to run self.py_trash().  GET (where directory is
    not 'cgi-bin') and HEAD requests are handled normally, from
    the document directory rooted at the working directory this program is
    started with.

    """

    def do_POST(self):
        """Serve a POST request.
        This is only implemented for CGI scripts.

        """
        print "Entered do_POST"
        if self.is_cgi():
            self.run_py()
        else:
            self.send_error(501, "Can only POST to CGI scripts")

    def send_head(self):
        """Version of send_head that support CGI scripts"""
        if self.is_cgi():
            return self.run_py()
        else:
            return SimpleHTTPServer.SimpleHTTPRequestHandler.send_head(self)

    def is_cgi(self):
        """test whether PATH corresponds to a CGI script.

        Set cgi_info to a tuple (dir, rest) if PATH requires running a
        CGI script.

        Note that rest begins with a slash if it is not empty.

        The default implementation tests whether the path
        begins with one of the strings in the list
        self.cgi_directories (and the next character is a '/'
        or the end of the string).

        """

        path = self.path

        print "entering is_cgi with path = %s" % path
        for x in self.cgi_directories:
            i = len(x)
            if path[:i] == x and (not path[i:] or path[i] == '/'):
                #if we find the directory
                self.cgi_info = path[:i], path[i+1:]
                print "is_cgi returns true with (%s, %s)" % self.cgi_info
                return 1
        print "is_cgi returns false"
        return 0

    #cgi_directories = ['/cgi-bin', '/htbin']
    cgi_directories = ['/cgi-bin']




    def run_py(self):
        """Execute the requested method."""

        self.send_response(200, "Script output follows")

        self.send_header ("Content-type", "text/html")
        self.end_headers()

        try:
            self.parseRequestLine()
            if self.command == 'POST':
               form = string.strip (self.rfile.readline())
               if self.query:
                  form = form + "&" + string.strip (self.query)
            else:
               # must be GET
               form = string.strip (self.query)

            print "form = %s" % form
            dict = cgilite.parse_qs (form, 1)  # keep blank values
            print "dict = %s" % dict

            print "(scriptfile, script) = (%s, %s)"  \
                     % (self.scriptfile, self.script)

            # Call the requested method
            methodName = "py_" + self.script
            print "method name is %s" % methodName
            method = getattr (self, methodName)
            generatedHTML = method (dict)
            self.wfile.write( generatedHTML )
        except:
            print "Hit exception under run_py"
            self.server.handle_error(self.request, self.client_address)

    def parseRequestLine (self):
        # extract any parameters from the end of the request line
        #  and store them in query
        dir, rest = self.cgi_info
        i = string.rfind(rest, '?')
        if i >= 0:
            rest, self.query = rest[:i], rest[i+1:]
        else:
            self.query = ''
        i = string.find(rest, '/')
        if i >= 0:
            script, rest = rest[:i], rest[i:]
        else:
            script, rest = rest, ''
        self.scriptname = dir + '/' + script
        self.script = script
        self.scriptfile = self.translate_path(self.scriptname)
        self.rest = rest

    def show_headers (self):
        print "status = %s"  % self.headers.status
        print "headers ----------------"
        for i in self.headers.headers:
            print "        %s"  % i
        print "end headers ------------"

    def set_root (dir):
        # make the request document root directory the current directory
        os.chdir(dir)


def test(HandlerClass = PyHTTPRequestHandler,
         ServerClass = BaseHTTPServer.HTTPServer):
    SimpleHTTPServer.test(HandlerClass, ServerClass)


if __name__ == '__main__':
    # Well, you'd need to define your class named Application and
    #  have it supply the 'py_...' methods that the special URLs
    #  would invoke.
    class MyApp (PyHTTPRequestHandler, Application):
       pass

    test (MyApp)

    -------------------------------------------------




More information about the Python-list mailing list