summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/tools/third_party/websockets/docs/howto/patterns.rst
blob: c6f325d21372f3903114ba93ed09ba568ad868f0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
Patterns
========

.. currentmodule:: websockets

Here are typical patterns for processing messages in a WebSocket server or
client. You will certainly implement some of them in your application.

This page gives examples of connection handlers for a server. However, they're
also applicable to a client, simply by assuming that ``websocket`` is a
connection created with :func:`~client.connect`.

WebSocket connections are long-lived. You will usually write a loop to process
several messages during the lifetime of a connection.

Consumer
--------

To receive messages from the WebSocket connection::

    async def consumer_handler(websocket):
        async for message in websocket:
            await consumer(message)

In this example, ``consumer()`` is a coroutine implementing your business
logic for processing a message received on the WebSocket connection. Each
message may be :class:`str` or :class:`bytes`.

Iteration terminates when the client disconnects.

Producer
--------

To send messages to the WebSocket connection::

    async def producer_handler(websocket):
        while True:
            message = await producer()
            await websocket.send(message)

In this example, ``producer()`` is a coroutine implementing your business
logic for generating the next message to send on the WebSocket connection.
Each message must be :class:`str` or :class:`bytes`.

Iteration terminates when the client disconnects
because :meth:`~server.WebSocketServerProtocol.send` raises a
:exc:`~exceptions.ConnectionClosed` exception,
which breaks out of the ``while  True`` loop.

Consumer and producer
---------------------

You can receive and send messages on the same WebSocket connection by
combining the consumer and producer patterns. This requires running two tasks
in parallel::

    async def handler(websocket):
        await asyncio.gather(
            consumer_handler(websocket),
            producer_handler(websocket),
        )

If a task terminates, :func:`~asyncio.gather` doesn't cancel the other task.
This can result in a situation where the producer keeps running after the
consumer finished, which may leak resources.

Here's a way to exit and close the WebSocket connection as soon as a task
terminates, after canceling the other task::

    async def handler(websocket):
        consumer_task = asyncio.create_task(consumer_handler(websocket))
        producer_task = asyncio.create_task(producer_handler(websocket))
        done, pending = await asyncio.wait(
            [consumer_task, producer_task],
            return_when=asyncio.FIRST_COMPLETED,
        )
        for task in pending:
            task.cancel()

Registration
------------

To keep track of currently connected clients, you can register them when they
connect and unregister them when they disconnect::

    connected = set()

    async def handler(websocket):
        # Register.
        connected.add(websocket)
        try:
            # Broadcast a message to all connected clients.
            websockets.broadcast(connected, "Hello!")
            await asyncio.sleep(10)
        finally:
            # Unregister.
            connected.remove(websocket)

This example maintains the set of connected clients in memory. This works as
long as you run a single process. It doesn't scale to multiple processes.

Publish–subscribe
-----------------

If you plan to run multiple processes and you want to communicate updates
between processes, then you must deploy a messaging system. You may find
publish-subscribe functionality useful.

A complete implementation of this idea with Redis is described in
the :doc:`Django integration guide <../howto/django>`.