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