Skip to content
On this page

WSGI Interface

Having understood the HTTP protocol and HTML documents, we essentially grasp that the essence of a web application is:

  1. The browser sends an HTTP request.
  2. The server receives the request and generates an HTML document.
  3. The server sends the HTML document as the body of the HTTP response to the browser.
  4. The browser receives the HTTP response and extracts the HTML document from the HTTP body to display it.

Therefore, the simplest web application can be achieved by saving HTML in a file and using existing HTTP server software to handle user requests, read HTML from the file, and return it. Common static servers like Apache, Nginx, and Lighttpd perform this task.

If we want to dynamically generate HTML, we need to implement the steps mentioned above ourselves. However, receiving HTTP requests, parsing them, and sending HTTP responses can be cumbersome work. If we write this low-level code ourselves, we may spend months studying the HTTP specifications before we even start writing dynamic HTML.

The correct approach is to let dedicated server software handle the low-level code, allowing us to focus on generating HTML in Python. Since we don’t want to deal with TCP connections or raw HTTP request and response formats, we need a unified interface that allows us to concentrate on writing web business logic in Python.

This interface is WSGI: Web Server Gateway Interface.

The WSGI interface definition is very simple; it only requires web developers to implement a function to respond to HTTP requests. Let’s look at a basic web version of "Hello, web!":

python
def application(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/html')])
    return [b'<h1>Hello, web!</h1>']

The application() function above is a WSGI-compliant HTTP handler function that takes two parameters:

  • environ: A dictionary object containing all the HTTP request information.
  • start_response: A function to send the HTTP response.

In the application() function, we call:

python
start_response('200 OK', [('Content-Type', 'text/html')])

This sends the HTTP response header. Note that headers can only be sent once, meaning start_response() can only be called once. The start_response() function takes two parameters: one is the HTTP response code, and the other is a list of HTTP headers, where each header is represented as a tuple containing two strings.

Typically, we should always send the Content-Type header to the browser, along with many other commonly used HTTP headers.

Then, the return value b'<h1>Hello, web!</h1>' will be sent as the body of the HTTP response to the browser.

With WSGI, our focus is on how to extract HTTP request information from the environ dictionary, construct HTML, send headers using start_response(), and finally return the body.

The entire application() function itself does not involve any HTTP parsing, meaning we do not need to write any low-level code. We only need to consider how to respond to requests at a higher level.

However, wait a moment—how is this application() function called? If we call it ourselves, we cannot provide the two parameters environ and start_response, and the returned bytes cannot be sent to the browser.

So, the application() function must be called by a WSGI server. There are many WSGI-compliant servers available for us to choose from. However, right now, we just want to quickly test whether our application() function can actually output HTML to the browser, so we need to find the simplest WSGI server to run our web application.

The good news is that Python includes a built-in WSGI server called wsgiref, which is a reference implementation of a WSGI server written in pure Python. A "reference implementation" means this implementation fully complies with the WSGI standard but does not consider performance; it is only for development and testing purposes.

Running a WSGI Service

First, we create a hello.py file to implement the WSGI handler function for our web application:

python
# hello.py

def application(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/html')])
    return [b'<h1>Hello, web!</h1>']

Next, we write a server.py file that starts the WSGI server and loads the application() function:

python
# server.py
# Import from the wsgiref module:
from wsgiref.simple_server import make_server
# Import our own application function:
from hello import application

# Create a server with an empty IP address, port 8000, and the application function:
httpd = make_server('', 8000, application)
print('Serving HTTP on port 8000...')
# Start listening for HTTP requests:
httpd.serve_forever()

Make sure the above two files are in the same directory. Then, run python server.py in the command line to start the WSGI server:

server.jpg

Note: If port 8000 is already in use by another program, the startup will fail. Please change to a different port.

After successful startup, open a browser and enter http://localhost:8000/ to see the result:

result.webp

In the command line, you can see the log information printed by wsgiref:

log.webp

Press Ctrl+C to terminate the server.

If you feel that this web application is too simple, you can modify it slightly to read PATH_INFO from environ, allowing for more dynamic content display:

python
# hello.py

def application(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/html')])
    body = '<h1>Hello, %s!</h1>' % (environ['PATH_INFO'][1:] or 'web')
    return [body.encode('utf-8')]

You can input a username as part of the URL in the address bar, and it will return Hello, xxx!:

wsgi-hello.webp

Doesn’t it feel a bit like a web app now?

Summary

Regardless of how complex a web application is, the entry point is always a WSGI handler function. All input information from the HTTP request can be obtained through environ, and all output for the HTTP response can be sent through start_response() combined with the function return value as the body.

For complex web applications, handling everything with a single WSGI function is still too low-level; we need to abstract a web framework above WSGI to further simplify web development.

WSGI Interface has loaded