Homepage of Jani Nurminen

This is a short technical note describing how to setup aiohttp-wsgi and aiohttp
to serve Django via WSGI while allowing WebSockets on the same port.

Hopefully this setup is useful to you for doing awesome things. Note, this is just a
technical note to use as a starting point and I have not benchmarked the
performance in any way. I work daily with (more or less) embedded systems, big
and small, and all this web stuff is just a hobby, so to speak.

What you need

Firstly, mise en place. You need:

Python 3

aiohttp

aiohttp-wsgi

your Django app

nginx

Why do we use these

Python 3.4+ is a must since you need the asyncio support. This gives you
asynchronous I/O, event loops, coroutines, and tasks. These basically let you
do co-operatively scheduled single-threaded concurrent code which is quite
useful when dealing with inputs/output handling from, say, the network.

The aiohttp is an (almost) pure Python
HTTP server (and client), using the asyncio support. It provides WebSocket
handling, among other things.

The aiohttp-wsgi is a WSGI adapter
for aiohttp. This is a bit more flexible than the built-in aiohttp.wsgi. The
lack of setup instructions in aiohttp-wsgi was actually the main motivation in
putting up this note.

Your Django app, this is any Django
application you want to make asyncio-capable, to add WebSocket support to, etc.

Nginx is a fast HTTP and reverse proxy server. You could
probably also use HAProxy or similar tools as well.

Prepare aiohttp-wsgi

Note: be sure to use version 0.1.1 or higher, as in 0.1.0 Django responses will
make aiohttp-wsgi nag with a RuntimeError.

The big picture

The satellite level view is as follows:

The client connects to port 8000.

Nginx auto-redirects HTTP (port 8000) to HTTPS (port 8443).

SSL handshake and setup are done, further traffic to and from 8443 is SSL protected.

Traffic coming to 8443 is decrypted internally and routed to aiohttp WSGI front-end at 127.0.0.1:8080.

Here the traffic goes either to WSGI app (Django) or WebSockets.

Response is sent to the client.

The ports are high ports to allow an unpriviledged user to test this - no need
to be root. You can easily map the ports to suitable priviledged ones like 80,
443 and so on.

The directory structure

Note: it is assumed that aiohttp and aiohttp_wsgi are installed system-wide.
It's an unimportant detail - you can install into virtualenv instead (I did).

Your Django app

The Django app, which is called "yourdjangoapp" from now on, is a regular Django app.
In this note, the app is stupendously simple and basically routes "/" to the
index-method, which serves up a templates/index.html file.

Note how this is an asyncio coroutine. You can also see the "yield from", which
means that at such a point the execution can go elsewhere until the stuff that
is yielded from finishes whatever it is doing. Once it does finish, the loop
continues from the yield from line. On the next iteration, the same thing
happens - there is no blocking for ws.receive_str() to finish.

Django setup for aiohttp is next. It's all in all quite simple - just 2-3
lines, thanks to aiohttp-wsgi. With the Django WSGI application instance, the
aiohttp server is configured with the chosen port and host and told to run the
Django WSGI application.

So what did we do so far? At this point we made "/" go to the Django app, via
WSGI. The "/ws" goes to the WebSocket testing method, which simply first sends a
message and then starts echoing all client messages back.

Without change, things stagnate. When things stagnate, they risk becoming
obsolete. Change for the sake of change is inefficient and just spins around
chasing its own tail - the change should have a purpose. Therefore: to avoid
risk of becoming obsolete, make purposeful changes.

That said, here is some purposeful change: a revamped incarnation of my blog.

The old posts were cleaned up and many were thrown away. I kept
the ones which were still interesting to me. You can find the old posts in the
Old-category at the top.