tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

server.rst (11182B)


      1 Server
      2 ======
      3 
      4 .. currentmodule:: websockets
      5 
      6 Why does the server close the connection prematurely?
      7 -----------------------------------------------------
      8 
      9 Your connection handler exits prematurely. Wait for the work to be finished
     10 before returning.
     11 
     12 For example, if your handler has a structure similar to::
     13 
     14    async def handler(websocket):
     15        asyncio.create_task(do_some_work())
     16 
     17 change it to::
     18 
     19    async def handler(websocket):
     20        await do_some_work()
     21 
     22 Why does the server close the connection after one message?
     23 -----------------------------------------------------------
     24 
     25 Your connection handler exits after processing one message. Write a loop to
     26 process multiple messages.
     27 
     28 For example, if your handler looks like this::
     29 
     30    async def handler(websocket):
     31        print(websocket.recv())
     32 
     33 change it like this::
     34 
     35    async def handler(websocket):
     36        async for message in websocket:
     37            print(message)
     38 
     39 *Don't feel bad if this happens to you — it's the most common question in
     40 websockets' issue tracker :-)*
     41 
     42 Why can only one client connect at a time?
     43 ------------------------------------------
     44 
     45 Your connection handler blocks the event loop. Look for blocking calls.
     46 
     47 Any call that may take some time must be asynchronous.
     48 
     49 For example, this connection handler prevents the event loop from running during
     50 one second::
     51 
     52    async def handler(websocket):
     53        time.sleep(1)
     54        ...
     55 
     56 Change it to::
     57 
     58    async def handler(websocket):
     59        await asyncio.sleep(1)
     60        ...
     61 
     62 In addition, calling a coroutine doesn't guarantee that it will yield control to
     63 the event loop.
     64 
     65 For example, this connection handler blocks the event loop by sending messages
     66 continuously::
     67 
     68    async def handler(websocket):
     69        while True:
     70            await websocket.send("firehose!")
     71 
     72 :meth:`~legacy.protocol.WebSocketCommonProtocol.send` completes synchronously as
     73 long as there's space in send buffers. The event loop never runs. (This pattern
     74 is uncommon in real-world applications. It occurs mostly in toy programs.)
     75 
     76 You can avoid the issue by yielding control to the event loop explicitly::
     77 
     78    async def handler(websocket):
     79        while True:
     80            await websocket.send("firehose!")
     81            await asyncio.sleep(0)
     82 
     83 All this is part of learning asyncio. It isn't specific to websockets.
     84 
     85 See also Python's documentation about `running blocking code`_.
     86 
     87 .. _running blocking code: https://docs.python.org/3/library/asyncio-dev.html#running-blocking-code
     88 
     89 .. _send-message-to-all-users:
     90 
     91 How do I send a message to all users?
     92 -------------------------------------
     93 
     94 Record all connections in a global variable::
     95 
     96    CONNECTIONS = set()
     97 
     98    async def handler(websocket):
     99        CONNECTIONS.add(websocket)
    100        try:
    101            await websocket.wait_closed()
    102        finally:
    103            CONNECTIONS.remove(websocket)
    104 
    105 Then, call :func:`~websockets.broadcast`::
    106 
    107    import websockets
    108 
    109    def message_all(message):
    110        websockets.broadcast(CONNECTIONS, message)
    111 
    112 If you're running multiple server processes, make sure you call ``message_all``
    113 in each process.
    114 
    115 .. _send-message-to-single-user:
    116 
    117 How do I send a message to a single user?
    118 -----------------------------------------
    119 
    120 Record connections in a global variable, keyed by user identifier::
    121 
    122    CONNECTIONS = {}
    123 
    124    async def handler(websocket):
    125        user_id = ...  # identify user in your app's context
    126        CONNECTIONS[user_id] = websocket
    127        try:
    128            await websocket.wait_closed()
    129        finally:
    130            del CONNECTIONS[user_id]
    131 
    132 Then, call :meth:`~legacy.protocol.WebSocketCommonProtocol.send`::
    133 
    134    async def message_user(user_id, message):
    135        websocket = CONNECTIONS[user_id]  # raises KeyError if user disconnected
    136        await websocket.send(message)  # may raise websockets.ConnectionClosed
    137 
    138 Add error handling according to the behavior you want if the user disconnected
    139 before the message could be sent.
    140 
    141 This example supports only one connection per user. To support concurrent
    142 connections by the same user, you can change ``CONNECTIONS`` to store a set of
    143 connections for each user.
    144 
    145 If you're running multiple server processes, call ``message_user`` in each
    146 process. The process managing the user's connection sends the message; other
    147 processes do nothing.
    148 
    149 When you reach a scale where server processes cannot keep up with the stream of
    150 all messages, you need a better architecture. For example, you could deploy an
    151 external publish / subscribe system such as Redis_. Server processes would
    152 subscribe their clients. Then, they would receive messages only for the
    153 connections that they're managing.
    154 
    155 .. _Redis: https://redis.io/
    156 
    157 How do I send a message to a channel, a topic, or some users?
    158 -------------------------------------------------------------
    159 
    160 websockets doesn't provide built-in publish / subscribe functionality.
    161 
    162 Record connections in a global variable, keyed by user identifier, as shown in
    163 :ref:`How do I send a message to a single user?<send-message-to-single-user>`
    164 
    165 Then, build the set of recipients and broadcast the message to them, as shown in
    166 :ref:`How do I send a message to all users?<send-message-to-all-users>`
    167 
    168 :doc:`../howto/django` contains a complete implementation of this pattern.
    169 
    170 Again, as you scale, you may reach the performance limits of a basic in-process
    171 implementation. You may need an external publish / subscribe system like Redis_.
    172 
    173 .. _Redis: https://redis.io/
    174 
    175 How do I pass arguments to the connection handler?
    176 --------------------------------------------------
    177 
    178 You can bind additional arguments to the connection handler with
    179 :func:`functools.partial`::
    180 
    181    import asyncio
    182    import functools
    183    import websockets
    184 
    185    async def handler(websocket, extra_argument):
    186        ...
    187 
    188    bound_handler = functools.partial(handler, extra_argument=42)
    189    start_server = websockets.serve(bound_handler, ...)
    190 
    191 Another way to achieve this result is to define the ``handler`` coroutine in
    192 a scope where the ``extra_argument`` variable exists instead of injecting it
    193 through an argument.
    194 
    195 How do I access the request path?
    196 ---------------------------------
    197 
    198 It is available in the :attr:`~server.WebSocketServerProtocol.path` attribute.
    199 
    200 You may route a connection to different handlers depending on the request path::
    201 
    202    async def handler(websocket):
    203        if websocket.path == "/blue":
    204            await blue_handler(websocket)
    205        elif websocket.path == "/green":
    206            await green_handler(websocket)
    207        else:
    208            # No handler for this path; close the connection.
    209            return
    210 
    211 You may also route the connection based on the first message received from the
    212 client, as shown in the :doc:`tutorial <../intro/tutorial2>`. When you want to
    213 authenticate the connection before routing it, this is usually more convenient.
    214 
    215 Generally speaking, there is far less emphasis on the request path in WebSocket
    216 servers than in HTTP servers. When a WebSocket server provides a single endpoint,
    217 it may ignore the request path entirely.
    218 
    219 How do I access HTTP headers?
    220 -----------------------------
    221 
    222 To access HTTP headers during the WebSocket handshake, you can override
    223 :attr:`~server.WebSocketServerProtocol.process_request`::
    224 
    225    async def process_request(self, path, request_headers):
    226        authorization = request_headers["Authorization"]
    227 
    228 Once the connection is established, HTTP headers are available in
    229 :attr:`~server.WebSocketServerProtocol.request_headers` and
    230 :attr:`~server.WebSocketServerProtocol.response_headers`::
    231 
    232    async def handler(websocket):
    233        authorization = websocket.request_headers["Authorization"]
    234 
    235 How do I set HTTP headers?
    236 --------------------------
    237 
    238 To set the ``Sec-WebSocket-Extensions`` or ``Sec-WebSocket-Protocol`` headers in
    239 the WebSocket handshake response, use the ``extensions`` or ``subprotocols``
    240 arguments of :func:`~server.serve`.
    241 
    242 To override the ``Server`` header, use the ``server_header`` argument. Set it to
    243 :obj:`None` to remove the header.
    244 
    245 To set other HTTP headers, use the ``extra_headers`` argument.
    246 
    247 How do I get the IP address of the client?
    248 ------------------------------------------
    249 
    250 It's available in :attr:`~legacy.protocol.WebSocketCommonProtocol.remote_address`::
    251 
    252    async def handler(websocket):
    253        remote_ip = websocket.remote_address[0]
    254 
    255 How do I set the IP addresses that my server listens on?
    256 --------------------------------------------------------
    257 
    258 Use the ``host`` argument of :meth:`~asyncio.loop.create_server`::
    259 
    260    await websockets.serve(handler, host="192.168.0.1", port=8080)
    261 
    262 :func:`~server.serve` accepts the same arguments as
    263 :meth:`~asyncio.loop.create_server`.
    264 
    265 What does ``OSError: [Errno 99] error while attempting to bind on address ('::1', 80, 0, 0): address not available`` mean?
    266 --------------------------------------------------------------------------------------------------------------------------
    267 
    268 You are calling :func:`~server.serve` without a ``host`` argument in a context
    269 where IPv6 isn't available.
    270 
    271 To listen only on IPv4, specify ``host="0.0.0.0"`` or ``family=socket.AF_INET``.
    272 
    273 Refer to the documentation of :meth:`~asyncio.loop.create_server` for details.
    274 
    275 How do I close a connection?
    276 ----------------------------
    277 
    278 websockets takes care of closing the connection when the handler exits.
    279 
    280 How do I stop a server?
    281 -----------------------
    282 
    283 Exit the :func:`~server.serve` context manager.
    284 
    285 Here's an example that terminates cleanly when it receives SIGTERM on Unix:
    286 
    287 .. literalinclude:: ../../example/faq/shutdown_server.py
    288    :emphasize-lines: 12-15,18
    289 
    290 How do I stop a server while keeping existing connections open?
    291 ---------------------------------------------------------------
    292 
    293 Call the server's :meth:`~server.WebSocketServer.close` method with
    294 ``close_connections=False``.
    295 
    296 Here's how to adapt the example just above::
    297 
    298    async def server():
    299        ...
    300 
    301        server = await websockets.serve(echo, "localhost", 8765)
    302        await stop
    303        await server.close(close_connections=False)
    304 
    305 How do I implement a health check?
    306 ----------------------------------
    307 
    308 Intercept WebSocket handshake requests with the
    309 :meth:`~server.WebSocketServerProtocol.process_request` hook.
    310 
    311 When a request is sent to the health check endpoint, treat is as an HTTP request
    312 and return a ``(status, headers, body)`` tuple, as in this example:
    313 
    314 .. literalinclude:: ../../example/faq/health_check_server.py
    315    :emphasize-lines: 7-9,18
    316 
    317 How do I run HTTP and WebSocket servers on the same port?
    318 ---------------------------------------------------------
    319 
    320 You don't.
    321 
    322 HTTP and WebSocket have widely different operational characteristics. Running
    323 them with the same server becomes inconvenient when you scale.
    324 
    325 Providing an HTTP server is out of scope for websockets. It only aims at
    326 providing a WebSocket server.
    327 
    328 There's limited support for returning HTTP responses with the
    329 :attr:`~server.WebSocketServerProtocol.process_request` hook.
    330 
    331 If you need more, pick an HTTP server and run it separately.
    332 
    333 Alternatively, pick an HTTP framework that builds on top of ``websockets`` to
    334 support WebSocket connections, like Sanic_.
    335 
    336 .. _Sanic: https://sanicframework.org/en/