A simple WSGI middleware dispatcher

By Nuno Mariz, on 8 June 2007 @ 10:41
The Web Server Gateway Interface (WSGI) is a standard interface between web server software and web applications written in Python. Having a standard interface makes it easy to use an application that supports WSGI with a number of different web servers.
One implementation is wsgiref that was added to Python 2.5 Standard Library.
Here is a simple web application that writes "Hello World":
def index(environ, start_response):
    start_response("200 Ok", [('content-type', 'text/html')])
    return ['Hello World!']

if __name__ == "__main__":
    from wsgiref.simple_server import make_server
    server = make_server(HOST, PORT, index)
    print 'Starting up HTTP server on port %i...' % PORT
    server.serve_forever()

This is nice, but is not very useful for us, so lets add some kind of controller(or url dispatcher):
from dispatcher import Dispatcher

dispatcher = Dispatcher()
dispatcher.add(r'^/$', 'views.index')
dispatcher.add(r'^/hello/(?P<username>\w+)/$', 'views.hello')

HOST = 'localhost'
PORT = 8000

if __name__ == "__main__": 
    from wsgiref.simple_server import make_server
    server = make_server(HOST, PORT, dispatcher)
    print 'Starting up HTTP server on port %i...' % PORT
    server.serve_forever()

As you can see I've used a regular expression for the url mapping, just like Django uses.
Here is the dispatcher(dispatcher.py):
import re

class Dispatcher(object):
    def __init__(self, handle404 = None):
        self.urls = dict()
        self.request_path = ''
        if handle404:
            self.handle404 = handle404
        else:
            self.handle404 = self._404

    def __call__(self, environ, start_response):
        self.request_path = environ.get('PATH_INFO', '')
        for url in self.urls:            
            regex = re.compile(url)
            if regex.match(self.request_path):
                m = regex.match(self.request_path)                        
                mod_name, func_name = self._get_mod_func(self.urls[url])                
                try:
                    callback = getattr(__import__(mod_name, {}, {}, ['']), func_name)
                except ImportError, e:
                    raise Exception, "Could not import %s. Error was: %s" % (mod_name, str(e))
                except AttributeError, e:
                    raise Exception, "Tried %s in module %s. Error was: %s" % (func_name, mod_name, str(e))                
                args = (environ, start_response)
                kwargs = dict()                
                for i in regex.groupindex:
                    kwargs[i] = m.group(i)
                # Run callback with environ, start_response and args
                return callback(*args, **kwargs)
        # No match with the defined urls
        return self.handle404(environ, start_response)
            
    def _get_mod_func(self, callback):
        """
        Converts 'path.to.module.funtion' to ['path.to.module', 'function']
        """
        try:
            d = callback.rindex('.')
        except ValueError:
            return callback, ''
        return callback[:d], callback[d+1:]
    
    def _404(self, environ, start_response):
        start_response("404 Not Found", [('content-type', 'text/html')])
        return ['Not Found']

    def add(self, regex, handler):
        self.urls[regex] = handler

And here is the views(views.py):
def index(environ, start_response):
    start_response("200 Ok", [('content-type', 'text/html')])
    return ['Index']

def hello(environ, start_response, username):
    start_response("200 Ok", [('content-type', 'text/html')])
    return ['Hello %s' % username]

Simple eh?
This is just a start, now we can add more features, like encapsulate the start_response method and add some kind of HttpResponse.
I don't what do reinvent the wheel here and add another web framework to Python, just like Joe Gregorio says in this article. But to prove how trivial is to make a simple framework with WSGI and don't worry about the deployment at development phase.

Comments

  • #1 By Evan Fosmark on 25 December 2008 @ 13:22
    Very cool. I was looking for something simple like this. I like how it works similar to Selector.
Comments are closed.