Skip to content

Commit 0fafda8

Browse files
committed
Extract patterns to a howto guide.
Improve this guide. Fix #1022.
1 parent b9e1112 commit 0fafda8

File tree

3 files changed

+111
-82
lines changed

3 files changed

+111
-82
lines changed

docs/howto/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ If you're stuck, perhaps you'll find the answer here.
88

99
faq
1010
cheatsheet
11+
patterns
1112
autoreload
1213

1314
This guide will help you integrate websockets into a broader system.

docs/howto/patterns.rst

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
Patterns
2+
========
3+
4+
.. currentmodule:: websockets
5+
6+
Here are typical patterns for processing messages in a WebSocket server or
7+
client. You will certainly implement some of them in your application.
8+
9+
This page gives examples of connection handlers for a server. However, they're
10+
also applicable to a client, simply by assuming that ``websocket`` is a
11+
connection created with :func:`~client.connect`.
12+
13+
WebSocket connections are long-lived. You will usually write a loop to process
14+
several messages during the lifetime of a connection.
15+
16+
Consumer
17+
--------
18+
19+
To receive messages from the WebSocket connection::
20+
21+
async def consumer_handler(websocket):
22+
async for message in websocket:
23+
await consumer(message)
24+
25+
In this example, ``consumer()`` is a coroutine implementing your business
26+
logic for processing a message received on the WebSocket connection. Each
27+
message may be :class:`str` or :class:`bytes`.
28+
29+
Iteration terminates when the client disconnects.
30+
31+
Producer
32+
--------
33+
34+
To send messages to the WebSocket connection::
35+
36+
async def producer_handler(websocket):
37+
while True:
38+
message = await producer()
39+
await websocket.send(message)
40+
41+
In this example, ``producer()`` is a coroutine implementing your business
42+
logic for generating the next message to send on the WebSocket connection.
43+
Each message must be :class:`str` or :class:`bytes`.
44+
45+
Iteration terminates when the client disconnects
46+
because :meth:`~server.WebSocketServerProtocol.send` raises a
47+
:exc:`~exceptions.ConnectionClosed` exception,
48+
which breaks out of the ``while True`` loop.
49+
50+
Consumer and producer
51+
---------------------
52+
53+
You can receive and send messages on the same WebSocket connection by
54+
combining the consumer and producer patterns. This requires running two tasks
55+
in parallel::
56+
57+
async def handler(websocket):
58+
await asyncio.gather(
59+
consumer_handler(websocket),
60+
producer_handler(websocket),
61+
)
62+
63+
If a task terminates, :func:`~asyncio.gather` doesn't cancel the other task.
64+
This can result in a situation where the producer keeps running after the
65+
consumer finished, which may leak resources.
66+
67+
Here's a way to exit and close the WebSocket connection as soon as a task
68+
terminates, after canceling the other task::
69+
70+
async def handler(websocket):
71+
consumer_task = asyncio.create_task(consumer_handler(websocket))
72+
producer_task = asyncio.create_task(producer_handler(websocket))
73+
done, pending = await asyncio.wait(
74+
[consumer_task, producer_task],
75+
return_when=asyncio.FIRST_COMPLETED,
76+
)
77+
for task in pending:
78+
task.cancel()
79+
80+
Registration
81+
------------
82+
83+
To keep track of currently connected clients, you can register them when they
84+
connect and unregister them when they disconnect::
85+
86+
connected = set()
87+
88+
async def handler(websocket):
89+
# Register.
90+
connected.add(websocket)
91+
try:
92+
# Broadcast a message to all connected clients.
93+
websockets.broadcast(connected, "Hello!")
94+
await asyncio.sleep(10)
95+
finally:
96+
# Unregister.
97+
connected.remove(websocket)
98+
99+
This example maintains the set of connected clients in memory. This works as
100+
long as you run a single process. It doesn't scale to multiple processes.
101+
102+
Publish–subscribe
103+
-----------------
104+
105+
If you plan to run multiple processes and you want to communicate updates
106+
between processes, then you must deploy a messaging system. You may find
107+
publish-subscribe functionality useful.
108+
109+
A complete implementation of this idea with Redis is described in
110+
the :doc:`Django integration guide <../howto/django>`.

docs/intro/index.rst

Lines changed: 0 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -118,85 +118,3 @@ Then open this HTML file in several browsers.
118118

119119
.. literalinclude:: ../../example/counter.html
120120
:language: html
121-
122-
Common patterns
123-
---------------
124-
125-
You will usually want to process several messages during the lifetime of a
126-
connection. Therefore you must write a loop. Here are the basic patterns for
127-
building a WebSocket server.
128-
129-
Consumer
130-
........
131-
132-
For receiving messages and passing them to a ``consumer`` coroutine::
133-
134-
async def consumer_handler(websocket):
135-
async for message in websocket:
136-
await consumer(message)
137-
138-
In this example, ``consumer`` represents your business logic for processing
139-
messages received on the WebSocket connection.
140-
141-
Iteration terminates when the client disconnects.
142-
143-
Producer
144-
........
145-
146-
For getting messages from a ``producer`` coroutine and sending them::
147-
148-
async def producer_handler(websocket):
149-
while True:
150-
message = await producer()
151-
await websocket.send(message)
152-
153-
In this example, ``producer`` represents your business logic for generating
154-
messages to send on the WebSocket connection.
155-
156-
:meth:`~legacy.protocol.WebSocketCommonProtocol.send` raises a
157-
:exc:`~exceptions.ConnectionClosed` exception when the client disconnects,
158-
which breaks out of the ``while True`` loop.
159-
160-
Both sides
161-
..........
162-
163-
You can read and write messages on the same connection by combining the two
164-
patterns shown above and running the two tasks in parallel::
165-
166-
async def handler(websocket):
167-
consumer_task = asyncio.ensure_future(
168-
consumer_handler(websocket))
169-
producer_task = asyncio.ensure_future(
170-
producer_handler(websocket))
171-
done, pending = await asyncio.wait(
172-
[consumer_task, producer_task],
173-
return_when=asyncio.FIRST_COMPLETED,
174-
)
175-
for task in pending:
176-
task.cancel()
177-
178-
Registration
179-
............
180-
181-
As shown in the synchronization example above, if you need to maintain a list
182-
of currently connected clients, you must register them when they connect and
183-
unregister them when they disconnect.
184-
185-
::
186-
187-
connected = set()
188-
189-
async def handler(websocket):
190-
# Register.
191-
connected.add(websocket)
192-
try:
193-
# Broadcast a message to all connected clients.
194-
websockets.broadcast(connected, "Hello!")
195-
await asyncio.sleep(10)
196-
finally:
197-
# Unregister.
198-
connected.remove(websocket)
199-
200-
This simplistic example keeps track of connected clients in memory. This only
201-
works as long as you run a single process. In a practical application, the
202-
handler may subscribe to some channels on a message broker, for example.

0 commit comments

Comments
 (0)