Electricmonk

Ferry Boender

Programmer, DevOpper, Open Source enthusiast.

Blog

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 % ( """
User:
Text:
Save:
"""), '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).

The text of all posts on this blog, unless specificly mentioned otherwise, are licensed under this license.