patterns.rst (3670B)
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>`.