Skip to content

WebSockets

What is a WebSocket?

In web development, a WebSocket is a two-way communication channel used on websites. Read more about them here.

WebSocket Routers

Like other routers, the websocket router has both a standard and direct variation, with the same API. Unlike other routers, a WebSocket comes with one input out of the box, that being the actual WebSocket object.

Other Inputs

You can add query inputs and path parameters to a websocket route, but not a body input.

A WebSocket route does also not care what you return. In fact, a type checker expects that routes decorated with websocket return None.

For example, a WebSocket that does nothing is as follows:

from view import new_app, WebSocket

app = new_app()

@app.websocket("/")
async def websocket_route(ws: WebSocket):
    ...

app.run()

!! warning

If you installed `uvicorn` manually, make sure to install `websockets` or `wsproto` if you plan on using WebSockets:

```
$ pip install websockets
```

Handshakes

If you used the above code, it wouldn't actually work as a WebSocket from the client, since we don't accept the connection.

Like other libraries, view.py does not automatically decide the lifecycle of your WebSocket handshake, meaning you have to manually accept and close it. For example, adding on to our above example that does nothing:

from view import new_app, WebSocket

app = new_app()

@app.websocket("/")
async def websocket_route(ws: WebSocket):
    await ws.accept()
    await ws.close()

app.run()

Now, we could actually use a WebSocket client to access this route. However, you should use a context manager instead of manually calling lifecycle methods:

from view import new_app, WebSocket

app = new_app()

@app.websocket("/")
async def websocket_route(ws: WebSocket):
    async with ws:
        ...

app.run()

Note

A WebSocketHandshakeError is raised if the client disconnects before the server calls close.

Sending and Receiving

Now, let's make our WebSocket do something! We can use send and receive to send and receive data.

The best way to understand these methods is visually, so a simple chat application could look like:

from view import new_app, WebSocket
import aiofiles  # For asynchronous input()

app = new_app()

@app.websocket("/")
async def websocket_route(ws: WebSocket):
    await ws.accept()  # We shouldn't ever need to exit, so no need for a context manager
    while True:
        await ws.send(await aiofiles.stdin.readline())
        print("Them:", await ws.receive())

app.run()

Receiving Types

Using view.py's type-casting system, you can specify a type to receive from the client by passing tp to receive. The supported types are:

  • str
  • int
  • bool
  • bytes
  • dict

For example, if you wanted to receive JSON data:

from view import new_app, WebSocket

app = new_app()

@app.websocket("/")
async def websocket_route(ws: WebSocket):
    async with ws:
        json = await ws.receive(tp=dict)

app.run()

Message Pairs

In many cases, such as with our chat app from above, we want a 1:1 ratio of messages from the server to the client. view.py gives you the pair method, to remove some boilerplate. It simply sends a message, then returns a received message. For example, with our chat app from above:

from view import new_app, WebSocket
import aiofiles

app = new_app()

@app.websocket("/")
async def websocket_route(ws: WebSocket):
    await ws.accept()
    while True:
        print("Them:", await ws.pair(await aiofiles.stdin.readline()))

app.run()

As stated above, pair sends the message before receiving, but you can reverse this by passing recv_first=True:

print("Them:", await ws.pair(await aiofiles.stdin.readline(), recv_first=True))

This would receive from the client, then send a message, and then return that received message.

Expecting Messages

In some cases, you might just want the client to send some data to ensure compliance with a protocol, or perhaps for a ping-pong. You can use the expect method, which ensures that the client send some data, and then discards the message. For example:

from view import new_app, WebSocket

app = new_app()

@app.websocket("/")
async def websocket_route(ws: WebSocket):
    async with ws:
        await ws.expect("MYPROTOCOL V1.1")
        await ws.send("ACK")
        # ...

app.run()

Review

WebSocket routes always have at least one route input, that being a WebSocket object representing the connection. view.py does not handle the connection lifetime, so calling accept, close, or using the context manager is up to the user.

Data can be sent and received via send and receive (who would have guessed!), and certain types can be expected from the client via the tp parameter. You can also use the pair method to eliminate some boilerplate when it comes to 1:1 message correspondence, as well as use the expect method to expect that the client sends some data.