Werkzeug

How WSGI Works

Navigation

Contents

New to Werkzeug or even WSGI? This part of the documentation covers the low level parts of Werkzeug that are in fact just plain WSGI.

What is WSGI?

Basically WSGI is an interface between web servers and web applications. We'll explain the mechanics of WSGI below, but to sum it up WSGI lets you develop rich web application without the need to know about your server environment. A WSGI application runs on standalone servers, on any webserver out there, via mod_python, FastCGI, CGI, basically everything that runs Python.

But there's much more; WSGI is more than just HTTP! You and other developers can extend WSGI by adding new features to it, filtering input or output, adding rich debugging systems, automatically send e-mails to your mailer as soon as an unhandled application error occurs. You can add session features to it etc.

There are few things you cannot do with WSGI, and Werkzeug itself is just a tiny wrapper around WSGI, thus you don't lose any functionally unlike some other implementations that abstract more. If you don't require all the power of WSGI you can choose a higher level implementation like Django.

Writing a WSGI Application

The first part is about how to use WSGI without Werkzeug. You can read the PEP (:pep:333) that defines it, but we'll do a very brief summary:

  • a WSGI application is just a callable object that is passed an environ - a dict that contains request data, and a start_response function that is called to start sending the response.

    In order to send data to the server all you have to do is to call start_response and return an iterable.

  • environ is a plain old CGI environment (contains values like PATH_INFO) in form of a python dict with some extensions like wsgi.input that represent the input stream.

  • middlewares (we'll cover them later) can add additional data to the environ.

So, here's a simple application:

def application(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/html')])
    return ['Hello World!']

Now you have an application, but how can you start it? From Python 2.5, the stdlib contains a WSGI server called wsgiref, for Python 2.4 and lower you have to install that on your own. There are also other standalone implementations which you can find online at wsgi.org.

So to start the application as standalone server all you have to do is adding this start hook to the file:

if __name__ == '__main__':
    from wsgiref.simple_server import make_server
    srv = make_server('localhost', 5000, application)
    srv.serve_forever()

When you now start the application you will see Hello World! on http://localhost:5000/.

The __name__ hook is a good idea so that you can use the module for other modules like flup (provides a bridge to Apache and other webservers via FastCGI and similar protocols) without altering the code.

Adding Interactive Elements

Let's extend the example from above so that we say Hello John if the user visits http://localhost:5000/?name=John:

from cgi import parse_qs, escape

def application(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/html')])
    query = parse_qs(environ.get('QUERY_STRING', ''))
    return ['Hello %s!' % escape(query.get('name', 'World'))]

Basically we just combined the python CGI module with our WSGI application.

That Sucks!

Indeed it does. It's neither fun to work with nor does it look clean and simple. And that's where utilities like Werkzeug come into play. Werkzeug for example lets you easily create unicode aware applications (in fact it forces you to use unicode internally) and avoid repetitive work.

In this example we want to connect / with an index function and /about with an about function, both returning different content. To keep the example simple we just want to use a dict that maps to that and take advantage or some of the Werkzeug features:

from cgi import escape
from werkzeug import BaseRequest, BaseResponse

class Request(BaseRequest):
    pass

class Response(BaseResponse):
    pass

def index(req):
    return Response(u'''<h1>Index Page</h1>
    <p>What\'s your name?</p>
    <form action="hello" method="post">
        <input type="text" name="name" value="My Name">
        <input type="submit" value="Greet Me!">
    </form>
    ''', mimetype='text/html')

def hello(req):
    name = req.form.get('name') or 'Nobody'
    return Response(u'<h1>Hello %s!</h1>' % escape(name),
                    mimetype='text/html')

def about(req):
    return Response(u'''<h1>About This Page</h1>
        <p>This page is just a small example page for Werkzeug</p>
    ''', mimetype='text/html')

def not_found(req):
    return Response(u'<h1>Page Not Found</h1>', status=404,
                    mimetype='text/html')

views = {
    '/':        index,
    '/hello':   hello,
    '/about':   about
}

def application(environ, start_response):
    req = Request(environ)
    if req.path not in views:
        resp = not_found(req)
    else:
        resp = views[req.path](req)
    return resp(environ, start_response)

Alright. That's quite a lot of code but let's go step by step through it.

The first thing we do is importing the stuff we use. In that example we need the escape function from the cgi module that SGML escapes strings. This is required to avoid XSS attacks to the application. The next import gives us the BaseRequest and BaseResponse objects we then subclass so that we can extend them later in one go if we want extra features.

Then we define a bunch of callback functions that just return HTML responses. One of them - not_found - is called when we don't have a matching URL, the others are bound to urls in the views dict.

The WSGI application itself just creates a request object and looks up the callbacks or proceeds with the not_found function if there is no matching URL. The return value of the response is then called as WSGI application.

Because Response objects behave like WSGI applications this is possible.

Of course this example is still bad code, in real applications you would use one of the template engines that are available for Python. But for the simple example that should be enough.

Debugging It

You probably have had a typo in one of the examples above. In that case you don't have to use the error output from the wsgiref server. There are some excellent debugging systems for WSGI, one of them is shipped with werkzeug and explained in the debug system docs.