Let’s roll with something practical, namely a simple chat application using Sanic framework mentioned in previous post.

Sanic supports websockets out of the box thanks to the websockets library. It’s super easy to write a handler function by using decorator (#1):

This is what code looks like for a simple echo server. No error handling or communication with external world is done here.

In order to provide a functional chat service, we need a little more. Our clients should be able to talk to themselves. A concept that represents a virtual place where chat talks are carried out is called a room. A room can be joined and left. In my example it also takes new messages and broadcasts them to all room members.

This is how room class scaffolding might look like. For a little convenience (mainly during tests) I added  __len__ method, so we can easily ask room for its number of members using pythonic idiom – len(room).

The most interesting part is the send_message method:

We simply iterate over connected folks (#1), sending them message passed as an argument (#2).

We must not forget about error handling – (#3). Disconnection just a nanosecond before sending a message is an ordinary situation. In such case we remove a flawed client from our room (#4). Please take a note that operation of removing member of a collection (self.clients) during iteration may be dangerous and will throw exceptions. In this concrete implementation I used a list. It does not complain about removing subsequent items.

One vital part of chat code remains to be uncovered – websocket handler function. Here it is:

At the beginning (#1) we add every new guy to our room, so he/she can receive messages from the moment of opening the websocket connection. In the meantime we wait for any incoming message (#2). We need the same error handling (#3) as seen before in send_message implementation. This may look like a redundancy, but it covers completely different use case – for example there is only one client that enters empty room and after a while leaves. This would raise exception in #2. So we break the loop (#3) allowing Sanic to finalize connection. Otherwise, we broadcast received message to everyone in the room (#5).

Further considerations:

We might not want to wait in line marked #5 for all other room’s members to receive our message, since one slow client would block processing incoming messages. Wrapping this stuff with asyncio.Task would help.

Full sources: here