Python servers and WSGI

Tags

  • eng
  • py

Python servers (e.g. Gunicorn) nowadays I guess mostly implement the WSGI protocol spec. WSGI was originally motivated by the need to consolidate a growing list of Python web frameworks, kinda similar to Java’s servlet API, and apparently succeeded in its goals.

WSGI flow: (1) client sends HTTP requests (does it have to be HTTP? Not sure) over the internet; (2) for each request, server calls app(env, start_response); (3) Web application responds by calling start_response(status, headers, exc=None) and then yields response strings.

Running a simple WSGI server will yield a working response. Luckily wsgiref is a part of Python since 2.5 (also cf. Django). The simplest server implementation:

from wsgiref.simple_server import make_server

def app(environ, start_response):
  start_response('200 OK', [('Content-type', 'text/plain; charset=utf-8')])
  yield 'Heyo'.encode('utf-8')
  yield from (f'{k}:{v}\n'.encode("utf-8") for k, v in environ.items())

class PointOutVimMiddleware:
  def __init__(self, app):
    self.app = app
  def __call__(self, env, start_response):
    for part in self.app(env, start_response):
      yield part + ('<---' if 'vim' in part else '')

app = PointOutVimMiddleware(app)
with make_server('', 8000, app) as httpd:
  httpd.serve_forever()

Ta-daa, there is a WSGI webserver. Similar description in Django’s official docs.

For server internals, cf. server.py. The serve_forever method of socketserver.py does what you’d expect it to.

GUnicorn

Seems Green Unicorn is one of the more popular threading servers for Python.

From the code. Worker class loaded from config. BaseApplication calls the worker-managing Arbiter. Arbiter spawns workers while jobs are available. Cf. its master loop, which does periodic checkups on worker health. If no workers are available, master goes to sleep and waits on pipe input (cf. wakeup) Workers that exceed some predefined timeout threshold are sent the kill signal and aborted. Then the size of the currently active threadpool is adjusted. E.g. spawn new or kill extra workers. NOTE: seems weird they sort and pop from start of list. Could possibly be extremely inefficient here.

In practice

I guess vanilla Python server implementations might use something like supervisor or systemd to manage and monitor, nginx to proxy, gunicorn as server, and then a framework such as Django for the actual business logic implementation.

That’s kind of what Django docs seem to advocate, anyway. OK cool.