Hypercorn is now a Trio ASGI Server

As well as supporting asyncio and uvloop

Philip Jones
Nov 15, 2018 · 2 min read

The Python community has a long history of adopting many different libraries for concurrent programming. In the Python 2 era Twisted, Tornado, Eventlet, and Gevent were among the most popular. With Python 3 they have been joined by asyncio (in the standard library), Curio, and Trio. Which library to use depends on many factors, and often comes down to a personal choice. Fortunately WSGI servers such as uWSGI (Tornado, Gevent) and Gunicorn (Tornado, Eventlet, Gevent) made this choice possible for WSGI frameworks. Hypercorn, 0.4.0, now makes this possible for ASGI frameworks, as it supports asyncio and Trio.

The Hypercorn and Trio logos alongside

To use Hypercorn with the Trio worker class simply add --worker-class trio to the command line e.g.

hypercorn --worker-class trio [module]:[Application]

Performance comparison

Given that Hypercorn can run with either an asyncio, uvloop, or trio worker class it seems natural to compare the performance of each. Running a simple echo application with wrk as described yields the following,

get requests/second
####################################################################
███████ 1483 Hypercorn-trio
██████████████████████ 4444 Hypercorn-asyncio
███████████████████████████ 5409 Hypercorn-uvloop
post requests/second
####################################################################
██████ 1257 Hypercorn-trio
██████████████████ 3436 Hypercorn-asyncio
██████████████████████ 4305 Hypercorn-uvloop

Clearly Trio doesn’t perform as well as asyncio or uvloop, however I don’t think Trio’s development has reached a stage whereby performance is a focus. Rather Trio’s developers seem focused on getting the API right.

A simple Trio ASGI application

By way of example of what is now possible here is a Trio ASGI application that returns the current time from the world clock API or a 404,

# pip install asks hypercorn[trio]
# hypercorn -k trio [this file]:App

import asks
import trio
asks.init(trio)


class App:

def __init__(self, scope):
self.scope = scope

async def __call__(self, receive, send):
while True:
event = await receive()
if (
event['type'] == 'http.request' and
not event.get('more_body', False)
):
if self.scope['path'] == '/':
await self.send(send)
else:
await self.send_404(send)
elif event['type'] == 'http.disconnect':
break
elif event['type'] == 'lifespan.startup':
await send({'type': 'lifespan.startup.complete'})
elif event['type'] == 'lifespan.cleanup':
await send({'type': 'lifespan.cleanup.complete'})
elif event['type'] == 'lifespan.shutdown':
await send({'type': 'lifespan.shutdown.complete'})

async def send(self, send):
response = await asks.get(
'http://worldclockapi.com/api/json/utc/now'
)
data = response.json()['currentDateTime'].encode('ascii')
await send({
'type': 'http.response.start',
'status': 200,
'headers': [(b'content-length', b"%d" % len(data))],
})
await send({
'type': 'http.response.body',
'body': data,
'more_body': False,
})

async def send_404(self, send):
await send({
'type': 'http.response.start',
'status': 404,
'headers': [(b'content-length', b'0')],
})
await send({
'type': 'http.response.body',
'body': b'',
'more_body': False,
})

Philip Jones

Written by

Head of Engineering at Octopus Wealth. Maintainer of Quart, Hypercorn and various other Python HTTP projects.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade