Python Web Applications: The basics of WSGI

Adam Bard
Adam Bard
Share
The basics of WSGI

Beneath Django, Flask, Bottle, and every other Python web framework, lies the Web Server Gateway Interface, or WSGI for short. WSGI is to Python what Servlets are to Java — a common specification for web servers that allows different web servers and application frameworks to interact based on a common API. However, as with most things, the Python version is considerably simpler.

WSGI is defined in PEP 3333, which I encourage you to read as a reference if you want more information after this quick intro.

This article will introduce you to the WSGI spec from an application developer’s perspective, and show you how to work directly with WSGI to create applications (if you so desire).

Your First WSGI App

Here’s the most basic Python web app possible:

def app(environ, start_fn):
    start_fn('200 OK', [('Content-Type', 'text/plain')])
    return ["Hello World!\n"]

That’s it! The whole file. Call it app.py and run it with any WSGI-compatible server and you’ll get a Hello World response with a 200 status. You can use gunicorn for this; just install it via pip (pip install gunicorn) and run it with gunicorn app:app. This command tells gunicorn to get the WSGI callable from the app variable in the app module.

Right now, you should be pretty excited. Just 3 lines for a running application? That must be some sort of record (barring PHP, because mod_php is cheating). I bet you’re just raring to know more.

So what are the essential parts of a WSGI application?

  • A WSGI application is a Python callable, such as a function, a class, or a class instance with a __call__ method
  • The application callable must accept two arguments: the environ, which is a Python dict containing the request data, and start_fn, itself a callable.
  • The application must call the start_fn with two arguments: the status code (as a string), and a list of headers expressed as 2-tuples.
  • The application returns an iterable containing the bytes in the response body in handy, streamable chunks — in this case, a list of strings containing just "Hello, World!". (If app is a class, this can be accomplished in the __iter__ method.)

By way of example, these next two examples are equivalent to the first:

class app(object):

    def __init__(self, environ, start_fn):
        self.environ = environ
        self.start_fn = start_fn

    def __iter__(self):
        self.start_fn('200 OK', [('Content-Type', 'text/plain')])
        yield "Hello World!\n"
class Application(object):
    def __call__(self, environ, start_fn):
        start_fn('200 OK', [('Content-Type', 'text/plain')])
        yield "Hello World!\n"

app = Application()

You might already be thinking of ways that you can use this information, but probably the most relevant one is writing middlewares.

Jazzing It Up

Middlewares are an easy way to extend the functionality of WSGI apps. Since you need only provide a callable, you can wrap it up in other functions however you please.

For example, say we want to examine the contents of environ. We can easily create a middleware to do so, as in this example:

import pprint


def handler(environ, start_fn):
    start_fn('200 OK', [('Content-Type', 'text/plain')])
    return ["Hello World!\n"]


def log_environ(handler):
    def _inner(environ, start_fn):
        pprint.pprint(environ)
        return handler(environ, start_fn)
    return _inner


app = log_environ(handler)

Here, log_environ is a function that returns a function, which pretty-prints the environ argument before deferring to the original callback.

The advantage of writing middlewares this way is that the middleware and the handler don’t have to know or care about each other. You could easily bolt log_environ onto a Flask application, for example, since Flask apps are WSGI apps.

A few other useful middleware ideas:

import pprint


def handle_error(handler):
    def _inner(environ, start_fn):
        try:
            return handler(environ, start_fn)
        except Exception as e:
            print e  # Log error
            start_fn('500 Server Error', [('Content-Type', 'text/plain')])
            return ['500 Server Error']
    return _inner


def wrap_query_params(handler):
    def _inner(environ, start_fn):
        qs = environ.get('QUERY_STRING')
        environ['QUERY_PARAMS'] = urlparse.parse_qs(qs)
        return handler(environ, start_fn)
    return _inner

You can use reduce to apply a bunch of middleware at once if you don’t want to make a big pyramid a the bottom of your file:

# Applied from bottom to top on the way in, then top to bottom on the way out
MIDDLEWARES = [wrap_query_params,
               log_environ,
               handle_error]

app = reduce(lambda h, m: m(h), MIDDLEWARES, handler)

You can also write middleware that modifies the response, by taking advantage of the start_fn argument. Here’s a middleware that reverses the output if the Content-Type header is text/plain:

def reverser(handler):

    # A reverse function
    rev = lambda it: it[::-1]

    def _inner(environ, start_fn):
        do_reverse = []  # Must be a reference type such as a list

        # Override start_fn to check the content type and set a flag
        def start_reverser(status, headers):
            for name, value in headers:
                if (name.lower() == 'content-type'
                        and value.lower() == 'text/plain'):
                    do_reverse.append(True)
                    break

            # Remember to call `start_fn`
            start_fn(status, headers)

        response = handler(environ, start_reverser)

        try:
            if do_reverse:
                return list(rev(map(rev, response)))

            return response
        finally:
            if hasattr(response, 'close'):
                response.close()
    return _inner

It’s a little more tangled thanks to the separation of start_fn and response, but still perfectly workable.

Also note that, to be strictly spec-compliant with WSGI, we must check for a close method on the response and call it if present. Legacy WSGI applications may also return a write function instead of an iterable upon calling handler; if you want your middleware to support older applications, you may need to handle this case.

Once you start playing with raw WSGI a little bit, you start to understand why Python has literally dozens of web frameworks. WSGI makes it pretty simple to build something up starting from scratch. For example, you might be considering the problem of routing:

routes = {
    '/': home_handler,
    '/about': about_handler,
}


class Application(object):
    def __init__(self, routes):
        self.routes = routes

    def not_found(self, environ, start_fn):
        start_fn('404 Not Found', [('Content-Type', 'text/plain')])
        return ['404 Not Found']

    def __call__(self, environ, start_fn):
        handler = self.routes.get(environ.get('PATH_INFO')) or self.not_found
        return handler(environ, start_fn)

Working with WSGI directly can be nice if you enjoy the flexibility of assembling libraries over

  • Template libraries: just drop in any template library you like (e.g. Jinja2, Pystashe) and return the rendered template from your handler!
  • Help your routing with a library like Routes or perhaps Werkzeug’s routing. Actually, take a look at Werkzeug if you want to use an ever-so-slight abstraction over WSGI.
  • Use any database/migration libraries as you would with Flask or similar.

Of course, for non-specialized applications, you’ll probably still want to use a framework just so that edge cases are properly handled and whatnot.

But What About Servers?

There are a bunch of ways to serve WSGI apps. We already talked about Gunicorn, which is a decent option. uWSGI is another great option. Just make sure you set up something like nginx in front of these to serve static assets and you should have a solid starting point.

And that’s all there is to it!

Frequently Asked Questions (FAQs) about Python Web Applications and WSGI

What is the role of WSGI in Python web applications?

WSGI, or Web Server Gateway Interface, is a standard interface between web servers and web applications. It plays a crucial role in Python web applications as it allows the application and the web server to communicate and send requests to each other. WSGI acts as a bridge, enabling the web server to forward requests from a client (like a web browser) to a web application. The application then processes the request and sends a response back to the client via the web server.

How does WSGI work in a Python web application?

WSGI works by defining a common interface that allows web servers to communicate with web applications. When a client sends a request to a web server, the server uses the WSGI interface to pass that request to the web application. The application processes the request and returns a response, which the server then sends back to the client. This process allows for a consistent and reliable way for web servers and applications to interact, regardless of their specific implementations.

What are some popular WSGI servers for Python?

There are several popular WSGI servers available for Python, each with its own strengths and weaknesses. Some of the most commonly used ones include Gunicorn, uWSGI, and mod_wsgi. Gunicorn is known for its simplicity and ease of use, while uWSGI is praised for its speed and efficiency. Mod_wsgi, on the other hand, is a module for Apache servers and is often used in enterprise environments.

How do I deploy a Python web application using WSGI?

Deploying a Python web application using WSGI involves several steps. First, you need to install a WSGI server, such as Gunicorn or uWSGI. Next, you need to configure your web server to use the WSGI server as a proxy for handling requests to your application. This involves modifying your server’s configuration file to include the necessary WSGI settings. Finally, you need to start your WSGI server and point it to your application’s WSGI file.

Can I use WSGI with Django?

Yes, you can use WSGI with Django. In fact, Django comes with a built-in WSGI application that you can use to deploy your Django projects. The Django WSGI application acts as a bridge between your Django project and the web server, allowing them to communicate and exchange requests and responses.

What is the difference between WSGI and ASGI?

WSGI and ASGI are both interfaces for Python web applications, but they serve different purposes. WSGI is a synchronous interface that allows for communication between a web server and a web application. ASGI, or Asynchronous Server Gateway Interface, is an extension of WSGI that supports asynchronous operations. This means that with ASGI, your application can handle multiple requests at the same time without blocking, making it more efficient for real-time applications.

How do I create a WSGI application in Python?

Creating a WSGI application in Python involves defining a function that accepts two arguments: an environment dictionary and a start_response callable. The environment dictionary contains information about the incoming request, while the start_response callable is used to start the response to the request. Your function should return an iterable that produces the body of the response.

What are the benefits of using WSGI in Python web applications?

Using WSGI in Python web applications offers several benefits. First, it provides a standard interface for web servers and applications, making it easier to develop and deploy Python web applications. Second, it allows for greater flexibility, as you can choose from a variety of WSGI-compatible servers and applications. Finally, it promotes code reusability, as you can use the same WSGI application with different servers and middleware.

Can I use WSGI with Flask?

Yes, you can use WSGI with Flask. In fact, Flask applications are WSGI applications by default. When you create a Flask application, you’re actually creating a WSGI application that the Flask framework will use to handle incoming requests and send responses.

What is a WSGI middleware?

A WSGI middleware is a component that sits between a WSGI server and a WSGI application. It can process requests before they reach the application and responses before they’re sent back to the client. Middleware can be used to implement a variety of functionalities, such as session management, URL routing, and authentication.