WebSockets: client and server

aiohttp · Advanced · 13 min

Real-time features such as chat, live dashboards and streaming updates need persistent two-way connections.

aiohttp has first-class WebSocket support on both sides.

Server: inside a normal GET handler, create a web.WebSocketResponse(), call await ws.prepare(request) to perform the upgrade handshake, then iterate over incoming messages:

async for msg in ws:
    if msg.type == aiohttp.WSMsgType.TEXT: ...

Send with await ws.send_str(...), send_bytes, or send_json. The loop ends when the client closes; return ws from the handler.

Client: async with session.ws_connect(url) as ws: opens a connection; then the same send_* methods and async for msg in ws (or await ws.receive()) work.

Message types to handle: TEXT, BINARY, CLOSE/CLOSED/CLOSING, and ERROR (check ws.exception()). Ping/pong is automatic; heartbeat=30 on either side sends pings and closes dead connections, which is important behind load balancers that drop idle connections.

To broadcast (chat rooms, live updates), keep a set of open WebSocketResponse objects in app state, add on connect, remove in a finally block, and send to each. One slow client should not block others, so consider per-client queues for heavy traffic.

Graceful shutdown matters: open WebSockets keep the server from stopping. Close them in an on_shutdown handler with await ws.close(code=WSCloseCode.GOING_AWAY).

Example

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

async def echo(request):
    ws = web.WebSocketResponse(heartbeat=30)
    await ws.prepare(request)                       # upgrade the connection
    async for msg in ws:
        if msg.type == aiohttp.WSMsgType.TEXT:
            if msg.data == "bye":
                await ws.close()
            else:
                await ws.send_json({"echo": msg.data.upper()})
    return ws
async def main():
    app = web.Application()
    app.router.add_get("/ws", echo)
    async with TestServer(app) as server, aiohttp.ClientSession() as session:
        async with session.ws_connect(server.make_url("/ws")) as ws:
            await ws.send_str("hello")
            print(await ws.receive_json())          # {'echo': 'HELLO'}
            await ws.send_str("bye")
            print((await ws.receive()).type == aiohttp.WSMsgType.CLOSE)  # True
asyncio.run(main())

Remember

  • Server: WebSocketResponse, await ws.prepare(request), async for msg in ws
  • Client: async with session.ws_connect(url) as ws
  • Use heartbeat= to detect dead connections; close sockets on shutdown

Watch out for

  • Forgetting to remove closed connections from a broadcast set, leaking memory and erroring on send.

Check yourself

  1. What must a WebSocket handler call before sending or receiving messages?

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#websockets