File: patterns.rst

package info (click to toggle)
python-websockets 15.0.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,948 kB
  • sloc: python: 25,105; javascript: 350; ansic: 148; makefile: 43
file content (124 lines) | stat: -rw-r--r-- 4,078 bytes parent folder | download
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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
Design a WebSocket application
==============================

.. currentmodule:: websockets

WebSocket server or client applications follow common patterns. This guide
describes patterns that you're likely to implement in your application.

All examples are connection handlers for a server. However, they would also
apply to a client, assuming that ``websocket`` is a connection created with
:func:`~asyncio.client.connect`.

.. admonition:: WebSocket connections are long-lived.
    :class: tip

    You need a loop to process several messages during the lifetime of a
    connection.

Consumer pattern
----------------

To receive messages from the WebSocket connection::

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

In this example, ``consume()`` is a coroutine implementing your business logic
for processing a message received on the WebSocket connection.

Iteration terminates when the client disconnects.

Producer pattern
----------------

To send messages to the WebSocket connection::

    from websockets.exceptions import ConnectionClosed

    async def producer_handler(websocket):
        while True:
            try:
                message = await produce()
                await websocket.send(message)
            except ConnectionClosed:
                break

In this example, ``produce()`` is a coroutine implementing your business logic
for generating the next message to send on the WebSocket connection.

Iteration terminates when the client disconnects because
:meth:`~asyncio.server.ServerConnection.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. The simplest option offered by
:mod:`asyncio` is::

    import asyncio

    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.
            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.

If you just need the set of connected clients, as in this example, use the
:attr:`~asyncio.server.Server.connections` property of the server. This pattern
is needed only when recording additional information about each client.

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>`.