Python web app server in 48 lines
Saturday, November 3rd, 2007
I needed to write a little application which would be accessible from a remote client, and which wouldn’t need any custom software running on the client. I needed a web application. But I didn’t feel like setting up a whole Apache web server with mod_python and whatever.
Of course, there’s CherryPy, but it feels a bit heavy for the very simple application I required. So I wrote TinyWAPP: Tiny Web App. A tiny web application server in Python in only 48 lines.
Code
Here’s the code:
#!/usr/bin/python
import BaseHTTPServer
from BaseHTTPServer import BaseHTTPRequestHandler
class TinyWAPPHandler(BaseHTTPRequestHandler):
def do_GET(self):
self.call(*self.parse(self.path))
def do_POST(self):
length = int(self.headers.getheader('content-length'))
if length:
params = self.rfile.read(length).replace('\n', '&')
else:
params = ''
self.call(*self.parse(self.path+'?'+params))
def parse(self, reqinfo):
if '?' in reqinfo:
path, params = reqinfo.split('?', 1)
params = dict([p.split('=', 1) for p in params.split('&') if '=' in p])
return((path.strip('/'),params))
else:
return((self.path.strip('/'), {}))
def call(self, path, params):
try:
if hasattr(self.app_class, path) and callable(getattr(self.app_class, path)):
out = getattr(self.app_class, path)(**params)
elif path == '' and hasattr(self.app_class, 'index'):
out = self.app_class.index(**params)
elif hasattr(self.app_class, 'default'):
out = self.app_class.default(**params)
else:
out = 'Not Found'
except Exception, e:
self.wfile.write(e)
raise e
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
self.wfile.write(out)
class TinyWAPP:
def __init__(self, app_class, listen_addr = '', listen_port = 80):
TinyWAPPHandler.app_class = app_class
httpd = BaseHTTPServer.HTTPServer((listen_addr, listen_port), TinyWAPPHandler)
httpd.serve_forever()
It’s not very feature-rich and it only supports the most basic GET and POST requests. It’s also not conforming completely too standards, but hey, neither do 100% of all of the Web browsers, so I guess it’s okay ;-)
How it works
The way it works is it sits and listens on the port you specify (default is 80). When a GET or POST request comes in, it parses it, and then calls a method on an object that corresponds to the path you specified with the parameters specified. For example
http://example.com/List?listname=people
Would call a method YourWebAppObj.List(listname='people')
Two special methods, index() and default() can be specified. index() is called when no path is specified. default() will be called when a non-existing path is specified.
Example application
Here’s a small example program which uses TinyWAPP:
#!/usr/bin/python
import tinywapp
html_template = """
%s
"""
pages = {
'index' : html_template % (
"""
Hello %(name)s!
This page has been called %(count)i times.
Test GET (params)
Test POST
"""),
'form' : html_template % (
"""
"""),
'formout' : html_template % (
"""
Hello, %(name)s! You entered:
%(text)s"""), } class App: def __init__(self): self.count = 0 def index(self, name='John Doe'): self.count += 1 return(pages['index'] % ({'name':name, 'count':self.count})) def form(self): return(pages['form']) def formout(self, user, text): return(pages['formout'] % ({'name':user, 'text':text})) a = App() t = tinywapp.TinyWAPP(a, listen_port=8000)
Running behind Apache
If you want to run this web application behind Apache, you can do so quite easily using URL rewriting. Here’s an example virtualhost configuration:
ServerAdmin webmaster@test.dev.local ServerName test.dev.local DocumentRoot /var/www/test.dev.local/htdocs/ RewriteEngine on RewriteRule ^(.*) http://127.0.0.1:8000/$1 [proxy]
(The code in this post is released in the Public Domain).