Tutorial: Websockets¶
This tutorial will guide you through building the websocket server
present in the examples/websocket
directory. This is a very simple
app that simply echos back all messages received over the websocket.
Running the example¶
To run the example, in examples/websocket
the following should start
the server, (see Installation first),
$ export QUART_APP=websocket:app
$ quart run
the websocket is then available at http://localhost:5000/.
1: Structure¶
Quart by default expects the code to be structured in a certain way in order for templates and static file to be found. This means that you should structure the websocket as follows,
websocket/
websocket/static/
websocket/static/js/
websocket/static/css/
websocket/templates/
doing so will also make your project familiar to others, as you follow the same convention.
2: Installation¶
It is always best to run python projects with a pipenv, which should be created and activated as follows,
$ cd websocket
$ pipenv install quart
for this websocket we will only need Quart. Now pipenv can be activated,
$ pipenv shell
3: Websockets¶
Websocket connections allow for continuous two way communication between a client and a server without having to reopen or negotiate a connection. Chat systems and games are two examples with continuous communication that are well suited to websockets.
Quart natively supports websockets, and a simple websocket echo route is,
from quart import websocket
@app.websocket('/ws')
async def ws():
while True:
data = await websocket.receive()
await websocket.send(f"echo {data}")
Quart also makes testing websockets easy, as so,
@pytest.mark.asyncio
async def test_websocket(app):
test_client = app.test_client()
data = b'bob'
async with test_client.websocket('/ws') as test_websocket:
await test_websocket.send(data)
result = await test_websocket.receive()
assert result == data
4: Javascript¶
To connect to and communicate with a websocket in Javascript a
WebSocket
object must be used,
var ws = new WebSocket('ws://' + document.domain + ':' + location.port + '/ws');
ws.onmessage = function (event) {
console.log(event.data);
};
ws.send('bob');
5: Broadcasting¶
A common task is to have one part of the application send messages to all connected clients. This could come from a POST, a websocket, or even another asyncio server.
To broadcast to all connected websockets, we are going to create a queue for each connected websocket, and then send the message to all queues. A decorator can be used to allocate and add queues to a set on connection and remove them on disconnect, as so,
connected_websockets = set()
def collect_websocket(func):
@wraps(func)
async def wrapper(*args, **kwargs):
global connected_websockets
queue = asyncio.Queue()
connected_websockets.add(queue)
try:
return await func(queue, *args, **kwargs)
finally:
connected_websockets.remove(queue)
return wrapper
The websocket endpoint in your application then just needs to receive data on that queue and send it to the client, as so,
@app.websocket('/api/v2/ws')
@collect_websocket
async def ws(queue):
while True:
data = await queue.get()
await websocket.send(data)
And finally, broadcast the message, as so,
def broadcast(message):
for queue in connected_websockets:
await queue.put(message)
6: Conclusion¶
The example files contain this entire tutorial and a little more, so they are now worth a read. Hopefully you can now go ahead and create your own apps that use websockets.