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 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343
|
Server
======
.. currentmodule:: websockets.asyncio.server
.. admonition:: This FAQ is written for the new :mod:`asyncio` implementation.
:class: tip
Answers are also valid for the legacy :mod:`asyncio` implementation.
They translate to the :mod:`threading` implementation by removing ``await``
and ``async`` keywords and by using a :class:`~threading.Thread` instead of
a :class:`~asyncio.Task` for concurrent execution.
Why does the server close the connection prematurely?
-----------------------------------------------------
Your connection handler exits prematurely. Wait for the work to be finished
before returning.
For example, if your handler has a structure similar to::
async def handler(websocket):
asyncio.create_task(do_some_work())
change it to::
async def handler(websocket):
await do_some_work()
Why does the server close the connection after one message?
-----------------------------------------------------------
Your connection handler exits after processing one message. Write a loop to
process multiple messages.
For example, if your handler looks like this::
async def handler(websocket):
print(websocket.recv())
change it like this::
async def handler(websocket):
async for message in websocket:
print(message)
If you have prior experience with an API that relies on callbacks, you may
assume that ``handler()`` is executed every time a message is received. The API
of websockets relies on coroutines instead.
The handler coroutine is started when a new connection is established. Then, it
is responsible for receiving or sending messages throughout the lifetime of that
connection.
Why can only one client connect at a time?
------------------------------------------
Your connection handler blocks the event loop. Look for blocking calls.
Any call that may take some time must be asynchronous.
For example, this connection handler prevents the event loop from running during
one second::
async def handler(websocket):
time.sleep(1)
...
Change it to::
async def handler(websocket):
await asyncio.sleep(1)
...
In addition, calling a coroutine doesn't guarantee that it will yield control to
the event loop.
For example, this connection handler blocks the event loop by sending messages
continuously::
async def handler(websocket):
while True:
await websocket.send("firehose!")
:meth:`~ServerConnection.send` completes synchronously as long as there's space
in send buffers. The event loop never runs. (This pattern is uncommon in
real-world applications. It occurs mostly in toy programs.)
You can avoid the issue by yielding control to the event loop explicitly::
async def handler(websocket):
while True:
await websocket.send("firehose!")
await asyncio.sleep(0)
All this is part of learning asyncio. It isn't specific to websockets.
See also Python's documentation about `running blocking code`_.
.. _running blocking code: https://docs.python.org/3/library/asyncio-dev.html#running-blocking-code
.. _send-message-to-all-users:
How do I send a message to all users?
-------------------------------------
Record all connections in a global variable::
CONNECTIONS = set()
async def handler(websocket):
CONNECTIONS.add(websocket)
try:
await websocket.wait_closed()
finally:
CONNECTIONS.remove(websocket)
Then, call :func:`broadcast`::
from websockets.asyncio.server import broadcast
def message_all(message):
broadcast(CONNECTIONS, message)
If you're running multiple server processes, make sure you call ``message_all``
in each process.
.. _send-message-to-single-user:
How do I send a message to a single user?
-----------------------------------------
Record connections in a global variable, keyed by user identifier::
CONNECTIONS = {}
async def handler(websocket):
user_id = ... # identify user in your app's context
CONNECTIONS[user_id] = websocket
try:
await websocket.wait_closed()
finally:
del CONNECTIONS[user_id]
Then, call :meth:`~ServerConnection.send`::
async def message_user(user_id, message):
websocket = CONNECTIONS[user_id] # raises KeyError if user disconnected
await websocket.send(message) # may raise websockets.exceptions.ConnectionClosed
Add error handling according to the behavior you want if the user disconnected
before the message could be sent.
This example supports only one connection per user. To support concurrent
connections by the same user, you can change ``CONNECTIONS`` to store a set of
connections for each user.
If you're running multiple server processes, call ``message_user`` in each
process. The process managing the user's connection sends the message; other
processes do nothing.
When you reach a scale where server processes cannot keep up with the stream of
all messages, you need a better architecture. For example, you could deploy an
external publish / subscribe system such as Redis_. Server processes would
subscribe their clients. Then, they would receive messages only for the
connections that they're managing.
.. _Redis: https://redis.io/
How do I send a message to a channel, a topic, or some users?
-------------------------------------------------------------
websockets doesn't provide built-in publish / subscribe functionality.
Record connections in a global variable, keyed by user identifier, as shown in
:ref:`How do I send a message to a single user?<send-message-to-single-user>`
Then, build the set of recipients and broadcast the message to them, as shown in
:ref:`How do I send a message to all users?<send-message-to-all-users>`
:doc:`../howto/django` contains a complete implementation of this pattern.
Again, as you scale, you may reach the performance limits of a basic in-process
implementation. You may need an external publish / subscribe system like Redis_.
.. _Redis: https://redis.io/
How do I pass arguments to the connection handler?
--------------------------------------------------
You can bind additional arguments to the connection handler with
:func:`functools.partial`::
import functools
async def handler(websocket, extra_argument):
...
bound_handler = functools.partial(handler, extra_argument=42)
Another way to achieve this result is to define the ``handler`` coroutine in
a scope where the ``extra_argument`` variable exists instead of injecting it
through an argument.
How do I access the request path?
---------------------------------
It is available in the :attr:`~ServerConnection.request` object.
Refer to the :doc:`routing guide <../topics/routing>` for details on how to
route connections to different handlers depending on the request path.
How do I access HTTP headers?
-----------------------------
You can access HTTP headers during the WebSocket handshake by providing a
``process_request`` callable or coroutine::
def process_request(connection, request):
authorization = request.headers["Authorization"]
...
async with serve(handler, process_request=process_request):
...
Once the connection is established, HTTP headers are available in the
:attr:`~ServerConnection.request` and :attr:`~ServerConnection.response`
objects::
async def handler(websocket):
authorization = websocket.request.headers["Authorization"]
How do I set HTTP headers?
--------------------------
To set the ``Sec-WebSocket-Extensions`` or ``Sec-WebSocket-Protocol`` headers in
the WebSocket handshake response, use the ``extensions`` or ``subprotocols``
arguments of :func:`~serve`.
To override the ``Server`` header, use the ``server_header`` argument. Set it to
:obj:`None` to remove the header.
To set other HTTP headers, provide a ``process_response`` callable or
coroutine::
def process_response(connection, request, response):
response.headers["X-Blessing"] = "May the network be with you"
async with serve(handler, process_response=process_response):
...
How do I get the IP address of the client?
------------------------------------------
It's available in :attr:`~ServerConnection.remote_address`::
async def handler(websocket):
remote_ip = websocket.remote_address[0]
How do I set the IP addresses that my server listens on?
--------------------------------------------------------
Use the ``host`` argument of :meth:`~serve`::
async with serve(handler, host="192.168.0.1", port=8080):
...
:func:`~serve` accepts the same arguments as
:meth:`~asyncio.loop.create_server` and passes them through.
What does ``OSError: [Errno 99] error while attempting to bind on address ('::1', 80, 0, 0): address not available`` mean?
--------------------------------------------------------------------------------------------------------------------------
You are calling :func:`~serve` without a ``host`` argument in a context where
IPv6 isn't available.
To listen only on IPv4, specify ``host="0.0.0.0"`` or ``family=socket.AF_INET``.
Refer to the documentation of :meth:`~asyncio.loop.create_server` for details.
How do I close a connection?
----------------------------
websockets takes care of closing the connection when the handler exits.
How do I stop a server?
-----------------------
Exit the :func:`~serve` context manager.
Here's an example that terminates cleanly when it receives SIGTERM on Unix:
.. literalinclude:: ../../example/faq/shutdown_server.py
:emphasize-lines: 14-16
How do I stop a server while keeping existing connections open?
---------------------------------------------------------------
Call the server's :meth:`~Server.close` method with ``close_connections=False``.
Here's how to adapt the example just above::
async def server():
...
server = await serve(echo, "localhost", 8765)
await stop
server.close(close_connections=False)
await server.wait_closed()
How do I implement a health check?
----------------------------------
Intercept requests with the ``process_request`` hook. When a request is sent to
the health check endpoint, treat is as an HTTP request and return a response:
.. literalinclude:: ../../example/faq/health_check_server.py
:emphasize-lines: 7-9,16
:meth:`~ServerConnection.respond` makes it easy to send a plain text response.
You can also construct a :class:`~websockets.http11.Response` object directly.
How do I run HTTP and WebSocket servers on the same port?
---------------------------------------------------------
You don't.
HTTP and WebSocket have widely different operational characteristics. Running
them with the same server becomes inconvenient when you scale.
Providing an HTTP server is out of scope for websockets. It only aims at
providing a WebSocket server.
There's limited support for returning HTTP responses with the
``process_request`` hook.
If you need more, pick an HTTP server and run it separately.
Alternatively, pick an HTTP framework that builds on top of ``websockets`` to
support WebSocket connections, like Sanic_.
.. _Sanic: https://sanicframework.org/en/
|