Your first aiohttp web server

aiohttp · Essentials · 11 min

aiohttp's server side is a compact async web framework suitable for APIs, proxies and WebSocket services.

The server lives in aiohttp.web. A handler is a coroutine that takes a web.Request and returns a web.Response:

async def hello(request):
    return web.Response(text="Hello")

Register handlers on a web.Application, either with app.add_routes([web.get("/", hello), web.post("/items", create)]) or with a web.RouteTableDef, which provides Flask-like decorators: @routes.get("/"). Then start the server with web.run_app(app, host="127.0.0.1", port=8080), which runs the event loop until interrupted.

Responses:

  • web.Response(text=..., status=..., headers=...) for text, or body= for bytes,
  • web.json_response(obj, status=...) for JSON,
  • raise web.HTTPNotFound() and the other web.HTTPException subclasses (HTTPBadRequest, HTTPFound(location=...) for redirects) to end a request with an error or redirect.

Path variables are written in braces, /users/{id}, and read with request.match_info["id"] (always a string). A regex can constrain them: /users/{id:\d+}.

Because handlers are coroutines, everything inside must be non-blocking: use async database drivers and await I/O. Calling blocking code (a synchronous DB driver, time.sleep, heavy CPU work) stalls every other request; push such work to loop.run_in_executor or asyncio.to_thread.

Example

import asyncio
from aiohttp import web
from aiohttp.test_utils import TestClient, TestServer

routes = web.RouteTableDef()
@routes.get("/")
async def index(request):
    return web.Response(text="Hello, aiohttp")

@routes.get(r"/users/{id:\d+}")
async def get_user(request):
    if request.match_info["id"] != "1":
        raise web.HTTPNotFound(text="no such user")
    return web.json_response({"id": 1, "name": "Ada"})

app = web.Application()
app.add_routes(routes)
# web.run_app(app, port=8080)   # serve for real at http://localhost:8080/

async def demo():                # or exercise it in-process:
    async with TestClient(TestServer(app)) as client:
        print(await (await client.get("/")).text())         # Hello, aiohttp
        print(await (await client.get("/users/1")).json())  # {'id': 1, 'name': 'Ada'}
        print((await client.get("/users/2")).status)        # 404
asyncio.run(demo())

Remember

  • Handlers are coroutines: Request in, Response out
  • RouteTableDef decorators or app.add_routes register routes; run_app serves
  • Raise web.HTTPNotFound and friends for error responses

Watch out for

  • Calling blocking libraries inside handlers, which freezes the whole server.

Check yourself

  1. How do you read the id from a route defined as /users/{id}?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.aiohttp.org/en/stable/web_quickstart.html