Getting Started with bareASGI

The next generation of Python web servers will be powered by ASGI, which is a low level standard for asynchronous web servers. A number of frameworks have been written to support this standard. I will talk about how to get started with bareASGI, a framework I have written and have been using for about 6 months.

ASGI Servers

To use ASGI you need a server. There are several to choose from. Here we will use hypercorn.

Hello, World!

Let’s get started!

Run the program and browse to http://localhost:9009/hello-world to check it works.

Now let’s take this apart.

The HTTP Request Handler

As it’s name suggests the function handles an HTTP request. It’s arguments form the request, and it returns a response. Unlike other frameworks I chose not to wrap these in an object. There aren’t many, and I took the view that the less work done that isn’t strictly necessary the faster the framework would be.

This example doesn’t use any of the input parameters, so I’ll introduce them later, but it does return a whole lot of stuff for the response.

The HTTP Response

The first element of the response tuple is the 200 HTTP response code.

The second element are the headers. These are optional (we could have passed in ), but we’re returning plain text so I set the accordingly. Note that the headers are a list of tuples of bytes. Also the header name is in lower case. This is all part of the ASGI standard, and it means this list can be passed directly through to the server without the framework doing any extra work.

The third element is the body of the response. This looks a bit tricksy, but it’s one of the key concepts in the framework, so we’ll take a little time to understand it. Here’s the actual code for .

This is an asynchronous generator. It (optionally) splits the response into chunks which are sent to the client in sequence. We do this for a number of reasons.

First it makes the generation of the response asynchronous. Lets say you’re sending an image file. If the file is read asynchronously in chunks the asyncio coroutines will be able to yield during the file chunk reading and when sending the chunk over the network to the client. This means your web server will be able to respond to other requests while the IO is waiting.

Second it means the response can be cancelled. If the client browses away from the page before the image has been uploaded, the image data will stop being read and the server will stop sending it.

Third we can support streaming content: for example a ticking price or a twitter feed.

The last element of the response is a list of push request supported by the HTTP 2 protocol which I won’t discuss that here.

Routing

In the above example routing was implemented with a decorator:

The first argument is a set of HTTP methods that are supported by the handler, and the second is the path to which the handler will respond.

I used the decorator routing style for simplicity, however it could have been done in the following manner:

This is my preferred method, as it allows more control over the creation of the object.

In the examples above we used a literal path. We could also have used a parameterised path:

Note how the parameters can optionally have types, and some types can have parse patterns. There is also the special parameter which captures all remaining path elements.

The captured parameters are passed into the HTTP request handler as the argument which is a of the parameter names as keys for the matched values.

REST

The next example sends and receives data using REST. You will need something like postman to try it out.

To try this out make a request to with the header set to . It should response with a of and body of . Sending a to the same endpoint with the body and a of should respond with a 204 status code. A subsequent should return .

In this example we start to use some of the request parameters. The scope is passed directly from the ASGI server, and is used to fetch the headers. The content is the complement of the body in the response. It is an asynchronous generator to read the body of the request. The helper function is used to retrieve the body (note this is awaited). The info is a user supplied argument to the application, and is used to share information between the handlers.

The handlers respond with 500 if the request was incorrect. We can see that it is not necessary to provide all the elements of the response, where all elements to the right would be .

Wrapping Up

If you’ve got here you have my sympathy. That was a whole lot of explanation for two example programs! I hope I’ve given you some ideas about how I’ve designed the framework.

There’s a small, but growing eco-system of code around the framework including CORS, static file serving, GraphQL, Jjinja2 templating, prometheus instrumentation middleware, and a complimentary client. If you’d like to find out more check out the documentation.