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.