Repository: aaugustin/websockets
Pull request #1176 (2022-05-15 08:33:37)

Fix Python 3.11 test failures
Fix the test failures reported in #1175.

hroncok: Thank you.

Patch:
diff --git a/docs/faq/asyncio.rst b/docs/faq/asyncio.rst
new file mode 100644
index 0000000..7db43e1
--- /dev/null
+++ b/docs/faq/asyncio.rst
@@ -0,0 +1,73 @@
+asyncio usage
+=============
+
+.. currentmodule:: websockets
+
+How do I run two coroutines in parallel?
+----------------------------------------
+
+You must start two tasks, which the event loop will run concurrently. You can
+achieve this with :func:`asyncio.gather` or :func:`asyncio.create_task`.
+
+Keep track of the tasks and make sure they terminate or you cancel them when
+the connection terminates.
+
+Why does my program never receive any messages?
+-----------------------------------------------
+
+Your program runs a coroutine that never yields control to the event loop. The
+coroutine that receives messages never gets a chance to run.
+
+Putting an ``await`` statement in a ``for`` or a ``while`` loop isn't enough
+to yield control. Awaiting a coroutine may yield control, but there's no
+guarantee that it will.
+
+For example, :meth:`~legacy.protocol.WebSocketCommonProtocol.send` only yields
+control when send buffers are full, which never happens in most practical
+cases.
+
+If you run a loop that contains only synchronous operations and
+a :meth:`~legacy.protocol.WebSocketCommonProtocol.send` call, you must yield
+control explicitly with :func:`asyncio.sleep`::
+
+    async def producer(websocket):
+        message = generate_next_message()
+        await websocket.send(message)
+        await asyncio.sleep(0)  # yield control to the event loop
+
+:func:`asyncio.sleep` always suspends the current task, allowing other tasks
+to run. This behavior is documented precisely because it isn't expected from
+every coroutine.
+
+See `issue 867`_.
+
+.. _issue 867: https://github.com/aaugustin/websockets/issues/867
+
+Why am I having problems with threads?
+--------------------------------------
+
+You shouldn't use threads. Use tasks instead.
+
+Indeed, when you chose websockets, you chose :mod:`asyncio` as the primary
+framework to handle concurrency. This choice is mutually exclusive with
+:mod:`threading`.
+
+If you believe that you need to run websockets in a thread and some logic in
+another thread, you should run that logic in a :class:`~asyncio.Task` instead.
+
+If you believe that you cannot run that logic in the same event loop because it
+will block websockets, :meth:`~asyncio.loop.run_in_executor` may help.
+
+This question is really about :mod:`asyncio`. Please review the advice about
+:ref:`asyncio-multithreading` in the Python documentation.
+
+Why does my simple program misbehave mysteriously?
+--------------------------------------------------
+
+You are using :func:`time.sleep` instead of :func:`asyncio.sleep`, which
+blocks the event loop and prevents asyncio from operating normally.
+
+This may lead to messages getting send but not received, to connection
+timeouts, and to unexpected results of shotgun debugging e.g. adding an
+unnecessary call to :meth:`~legacy.protocol.WebSocketCommonProtocol.send`
+makes the program functional.
diff --git a/docs/faq/client.rst b/docs/faq/client.rst
new file mode 100644
index 0000000..5bbbd6d
--- /dev/null
+++ b/docs/faq/client.rst
@@ -0,0 +1,86 @@
+Client
+======
+
+.. currentmodule:: websockets
+
+Why does the client close the connection prematurely?
+-----------------------------------------------------
+
+You're exiting the context manager prematurely. Wait for the work to be
+finished before exiting.
+
+For example, if your code has a structure similar to::
+
+    async with connect(...) as websocket:
+        asyncio.create_task(do_some_work())
+
+change it to::
+
+    async with connect(...) as websocket:
+        await do_some_work()
+
+How do I access HTTP headers?
+-----------------------------
+
+Once the connection is established, HTTP headers are available in
+:attr:`~client.WebSocketClientProtocol.request_headers` and
+:attr:`~client.WebSocketClientProtocol.response_headers`.
+
+How do I set HTTP headers?
+--------------------------
+
+To set the ``Origin``, ``Sec-WebSocket-Extensions``, or
+``Sec-WebSocket-Protocol`` headers in the WebSocket handshake request, use the
+``origin``, ``extensions``, or ``subprotocols`` arguments of
+:func:`~client.connect`.
+
+To override the ``User-Agent`` header, use the ``user_agent_header`` argument.
+Set it to :obj:`None` to remove the header.
+
+To set other HTTP headers, for example the ``Authorization`` header, use the
+``extra_headers`` argument::
+
+    async with connect(..., extra_headers={"Authorization": ...}) as websocket:
+        ...
+
+How do I close a connection?
+----------------------------
+
+The easiest is to use :func:`~client.connect` as a context manager::
+
+    async with connect(...) as websocket:
+        ...
+
+The connection is closed when exiting the context manager.
+
+How do I reconnect when the connection drops?
+---------------------------------------------
+
+Use :func:`~client.connect` as an asynchronous iterator::
+
+    async for websocket in websockets.connect(...):
+        try:
+            ...
+        except websockets.ConnectionClosed:
+            continue
+
+Make sure you handle exceptions in the ``async for`` loop. Uncaught exceptions
+will break out of the loop.
+
+How do I stop a client that is processing messages in a loop?
+-------------------------------------------------------------
+
+You can close the connection.
+
+Here's an example that terminates cleanly when it receives SIGTERM on Unix:
+
+.. literalinclude:: ../../example/shutdown_client.py
+    :emphasize-lines: 10-13
+
+How do I disable TLS/SSL certificate verification?
+--------------------------------------------------
+
+Look at the ``ssl`` argument of :meth:`~asyncio.loop.create_connection`.
+
+:func:`~client.connect` accepts the same arguments as
+:meth:`~asyncio.loop.create_connection`.
diff --git a/docs/faq/common.rst b/docs/faq/common.rst
new file mode 100644
index 0000000..dff64f6
--- /dev/null
+++ b/docs/faq/common.rst
@@ -0,0 +1,151 @@
+Both sides
+==========
+
+.. currentmodule:: websockets
+
+What does ``ConnectionClosedError: no close frame received or sent`` mean?
+--------------------------------------------------------------------------
+
+If you're seeing this traceback in the logs of a server:
+
+.. code-block:: pytb
+
+    connection handler failed
+    Traceback (most recent call last):
+      ...
+    asyncio.exceptions.IncompleteReadError: 0 bytes read on a total of 2 expected bytes
+
+    The above exception was the direct cause of the following exception:
+
+    Traceback (most recent call last):
+      ...
+    websockets.exceptions.ConnectionClosedError: no close frame received or sent
+
+or if a client crashes with this traceback:
+
+.. code-block:: pytb
+
+    Traceback (most recent call last):
+      ...
+    ConnectionResetError: [Errno 54] Connection reset by peer
+
+    The above exception was the direct cause of the following exception:
+
+    Traceback (most recent call last):
+      ...
+    websockets.exceptions.ConnectionClosedError: no close frame received or sent
+
+it means that the TCP connection was lost. As a consequence, the WebSocket
+connection was closed without receiving and sending a close frame, which is
+abnormal.
+
+You can catch and handle :exc:`~exceptions.ConnectionClosed` to prevent it
+from being logged.
+
+There are several reasons why long-lived connections may be lost:
+
+* End-user devices tend to lose network connectivity often and unpredictably
+  because they can move out of wireless network coverage, get unplugged from
+  a wired network, enter airplane mode, be put to sleep, etc.
+* HTTP load balancers or proxies that aren't configured for long-lived
+  connections may terminate connections after a short amount of time, usually
+  30 seconds, despite websockets' keepalive mechanism.
+
+If you're facing a reproducible issue, :ref:`enable debug logs <debugging>` to
+see when and how connections are closed.
+
+What does ``ConnectionClosedError: sent 1011 (unexpected error) keepalive ping timeout; no close frame received`` mean?
+-----------------------------------------------------------------------------------------------------------------------
+
+If you're seeing this traceback in the logs of a server:
+
+.. code-block:: pytb
+
+    connection handler failed
+    Traceback (most recent call last):
+      ...
+    asyncio.exceptions.CancelledError
+
+    The above exception was the direct cause of the following exception:
+
+    Traceback (most recent call last):
+      ...
+    websockets.exceptions.ConnectionClosedError: sent 1011 (unexpected error) keepalive ping timeout; no close frame received
+
+or if a client crashes with this traceback:
+
+.. code-block:: pytb
+
+    Traceback (most recent call last):
+      ...
+    asyncio.exceptions.CancelledError
+
+    The above exception was the direct cause of the following exception:
+
+    Traceback (most recent call last):
+      ...
+    websockets.exceptions.ConnectionClosedError: sent 1011 (unexpected error) keepalive ping timeout; no close frame received
+
+it means that the WebSocket connection suffered from excessive latency and was
+closed after reaching the timeout of websockets' keepalive mechanism.
+
+You can catch and handle :exc:`~exceptions.ConnectionClosed` to prevent it
+from being logged.
+
+There are two main reasons why latency may increase:
+
+* Poor network connectivity.
+* More traffic than the recipient can handle.
+
+See the discussion of :doc:`timeouts <../topics/timeouts>` for details.
+
+If websockets' default timeout of 20 seconds is too short for your use case,
+you can adjust it with the ``ping_timeout`` argument.
+
+How do I set a timeout on :meth:`~legacy.protocol.WebSocketCommonProtocol.recv`?
+--------------------------------------------------------------------------------
+
+Use :func:`~asyncio.wait_for`::
+
+    await asyncio.wait_for(websocket.recv(), timeout=10)
+
+This technique works for most APIs, except for asynchronous context managers.
+See `issue 574`_.
+
+.. _issue 574: https://github.com/aaugustin/websockets/issues/574
+
+How can I pass arguments to a custom protocol subclass?
+-------------------------------------------------------
+
+You can bind additional arguments to the protocol factory with
+:func:`functools.partial`::
+
+    import asyncio
+    import functools
+    import websockets
+
+    class MyServerProtocol(websockets.WebSocketServerProtocol):
+        def __init__(self, extra_argument, *args, **kwargs):
+            super().__init__(*args, **kwargs)
+            # do something with extra_argument
+
+    create_protocol = functools.partial(MyServerProtocol, extra_argument='spam')
+    start_server = websockets.serve(..., create_protocol=create_protocol)
+
+This example was for a server. The same pattern applies on a client.
+
+How do I keep idle connections open?
+------------------------------------
+
+websockets sends pings at 20 seconds intervals to keep the connection open.
+
+It closes the connection if it doesn't get a pong within 20 seconds.
+
+You can adjust this behavior with ``ping_interval`` and ``ping_timeout``.
+
+See :doc:`../topics/timeouts` for details.
+
+How do I respond to pings?
+--------------------------
+
+Don't bother; websockets takes care of responding to pings with pongs.
diff --git a/docs/faq/index.rst b/docs/faq/index.rst
new file mode 100644
index 0000000..9d5b0d5
--- /dev/null
+++ b/docs/faq/index.rst
@@ -0,0 +1,21 @@
+Frequently asked questions
+==========================
+
+.. currentmodule:: websockets
+
+.. admonition:: Many questions asked in websockets' issue tracker are really
+    about :mod:`asyncio`.
+    :class: seealso
+
+    Python's documentation about `developing with asyncio`_ is a good
+    complement.
+
+    .. _developing with asyncio: https://docs.python.org/3/library/asyncio-dev.html
+
+.. toctree::
+
+    server
+    client
+    common
+    asyncio
+    misc
diff --git a/docs/faq/misc.rst b/docs/faq/misc.rst
new file mode 100644
index 0000000..9ef07ef
--- /dev/null
+++ b/docs/faq/misc.rst
@@ -0,0 +1,39 @@
+Miscellaneous
+=============
+
+.. currentmodule:: websockets
+
+Can I use websockets without ``async`` and ``await``?
+.....................................................
+
+No, there is no convenient way to do this. You should use another library.
+
+Are there ``onopen``, ``onmessage``, ``onerror``, and ``onclose`` callbacks?
+............................................................................
+
+No, there aren't.
+
+websockets provides high-level, coroutine-based APIs. Compared to callbacks,
+coroutines make it easier to manage control flow in concurrent code.
+
+If you prefer callback-based APIs, you should use another library.
+
+Why do I get the error: ``module 'websockets' has no attribute '...'``?
+.......................................................................
+
+Often, this is because you created a script called ``websockets.py`` in your
+current working directory. Then ``import websockets`` imports this module
+instead of the websockets library.
+
+Why is websockets slower than another Python library in my benchmark?
+.....................................................................
+
+Not all libraries are as feature-complete as websockets. For a fair benchmark,
+you should disable features that the other library doesn't provide. Typically,
+you may need to disable:
+
+* Compression: set ``compression=None``
+* Keepalive: set ``ping_interval=None``
+* UTF-8 decoding: send ``bytes`` rather than ``str``
+
+If websockets is still slower than another Python library, please file a bug.
diff --git a/docs/faq/server.rst b/docs/faq/server.rst
new file mode 100644
index 0000000..68490d7
--- /dev/null
+++ b/docs/faq/server.rst
@@ -0,0 +1,282 @@
+Server
+======
+
+.. currentmodule:: websockets
+
+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)
+
+*Don't feel bad if this happens to you — it's the most common question in
+websockets' issue tracker :-)*
+
+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, if you have::
+
+    async def handler(websocket):
+        time.sleep(1)
+
+change it to::
+
+    async def handler(websocket):
+        await asyncio.sleep(1)
+
+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:`~websockets.broadcast`::
+
+    import websockets
+
+    def message_all(message):
+        websockets.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:`~legacy.protocol.WebSocketCommonProtocol.send`::
+
+    async def message_user(user_id, message):
+        websocket = CONNECTIONS[user_id]  # raises KeyError if user disconnected
+        await websocket.send(message)  # may raise websockets.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
+connects 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 asyncio
+    import functools
+    import websockets
+
+    async def handler(websocket, extra_argument):
+        ...
+
+    bound_handler = functools.partial(handler, extra_argument='spam')
+    start_server = websockets.serve(bound_handler, ...)
+
+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:`~server.WebSocketServerProtocol.path` attribute.
+
+You may route a connection to different handlers depending on the request path::
+
+    async def handler(websocket):
+        if websocket.path == "/blue":
+            await blue_handler(websocket)
+        elif websocket.path == "/green":
+            await green_handler(websocket)
+        else:
+            # No handler for this path; close the connection.
+            return
+
+You may also route the connection based on the first message received from the
+client, as shown in the :doc:`tutorial <../intro/tutorial2>`. When you want to
+authenticate the connection before routing it, this is usually more convenient.
+
+Generally speaking, there is far less emphasis on the request path in WebSocket
+servers than in HTTP servers. When a WebSockt server provides a single endpoint,
+it may ignore the request path entirely.
+
+How do I access HTTP headers?
+-----------------------------
+
+To access HTTP headers during the WebSocket handshake, you can override
+:attr:`~server.WebSocketServerProtocol.process_request`::
+
+    async def process_request(self, path, request_headers):
+        authorization = request_headers["Authorization"]
+
+Once the connection is established, HTTP headers are available in
+:attr:`~server.WebSocketServerProtocol.request_headers` and
+:attr:`~server.WebSocketServerProtocol.response_headers`::
+
+    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:`~server.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, use the ``extra_headers`` argument.
+
+How do I get the IP address of the client?
+------------------------------------------
+
+It's available in :attr:`~legacy.protocol.WebSocketCommonProtocol.remote_address`::
+
+    async def handler(websocket):
+        remote_ip = websocket.remote_address[0]
+
+How do I set the IP addresses my server listens on?
+---------------------------------------------------
+
+Look at the ``host`` argument of :meth:`~asyncio.loop.create_server`.
+
+:func:`~server.serve` accepts the same arguments as
+:meth:`~asyncio.loop.create_server`.
+
+What does ``OSError: [Errno 99] error while attempting to bind on address ('::1', 80, 0, 0): address not available`` mean?
+--------------------------------------------------------------------------------------------------------------------------
+
+You are calling :func:`~server.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:`~server.serve` context manager.
+
+Here's an example that terminates cleanly when it receives SIGTERM on Unix:
+
+.. literalinclude:: ../../example/shutdown_server.py
+    :emphasize-lines: 12-15,18
+
+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 a 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
+:attr:`~server.WebSocketServerProtocol.process_request` hook.
+
+If you need more, pick a HTTP server and run it separately.
+
+Alternatively, pick a HTTP framework that builds on top of ``websockets`` to
+support WebSocket connections, like Sanic_.
+
+.. _Sanic: https://sanicframework.org/en/
diff --git a/docs/howto/autoreload.rst b/docs/howto/autoreload.rst
index edd87d0..fc736a5 100644
--- a/docs/howto/autoreload.rst
+++ b/docs/howto/autoreload.rst
@@ -16,7 +16,7 @@ Install watchdog_ with the ``watchmedo`` shell utility:
 
 .. code-block:: console
 
-    $ pip install watchdog[watchmedo]
+    $ pip install 'watchdog[watchmedo]'
 
 .. _watchdog: https://pypi.org/project/watchdog/
 
diff --git a/docs/howto/django.rst b/docs/howto/django.rst
index 34cce58..5bb2e29 100644
--- a/docs/howto/django.rst
+++ b/docs/howto/django.rst
@@ -113,10 +113,10 @@ your settings module.
 
 The connection handler reads the first message received from the client, which
 is expected to contain a django-sesame token. Then it authenticates the user
-with ``get_user()``, the API for `authentication outside views`_. If
+with ``get_user()``, the API for `authentication outside a view`_. If
 authentication fails, it closes the connection and exits.
 
-.. _authentication outside views: https://github.com/aaugustin/django-sesame#authentication-outside-views
+.. _authentication outside a view: https://django-sesame.readthedocs.io/en/stable/howto.html#outside-a-view
 
 When we call an API that makes a database query such as ``get_user()``, we
 wrap the call in :func:`~asyncio.to_thread`. Indeed, the Django ORM doesn't
diff --git a/docs/howto/extensions.rst b/docs/howto/extensions.rst
index 2baead3..3c8a7d7 100644
--- a/docs/howto/extensions.rst
+++ b/docs/howto/extensions.rst
@@ -1,5 +1,5 @@
-Writing an extension
-====================
+Write an extension
+==================
 
 .. currentmodule:: websockets.extensions
 
@@ -28,5 +28,3 @@ As a consequence, writing an extension requires implementing several classes:
 websockets provides base classes for extension factories and extensions.
 See :class:`ClientExtensionFactory`, :class:`ServerExtensionFactory`,
 and :class:`Extension` for details.
-
-
diff --git a/docs/howto/faq.rst b/docs/howto/faq.rst
deleted file mode 100644
index a103f67..0000000
--- a/docs/howto/faq.rst
+++ /dev/null
@@ -1,621 +0,0 @@
-FAQ
-===
-
-.. currentmodule:: websockets
-
-.. admonition:: Many questions asked in websockets' issue tracker are really
-    about :mod:`asyncio`.
-    :class: seealso
-
-    Python's documentation about `developing with asyncio`_ is a good
-    complement.
-
-    .. _developing with asyncio: https://docs.python.org/3/library/asyncio-dev.html
-
-Server side
------------
-
-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)
-
-*Don't feel bad if this happens to you — it's the most common question in
-websockets' issue tracker :-)*
-
-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, if you have::
-
-    async def handler(websocket):
-        time.sleep(1)
-
-change it to::
-
-    async def handler(websocket):
-        await asyncio.sleep(1)
-
-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:`~websockets.broadcast`::
-
-    import websockets
-
-    def message_all(message):
-        websockets.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:`~legacy.protocol.WebSocketCommonProtocol.send`::
-
-    async def message_user(user_id, message):
-        websocket = CONNECTIONS[user_id]  # raises KeyError if user disconnected
-        await websocket.send(message)  # may raise websockets.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
-connects 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:`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 asyncio
-    import functools
-    import websockets
-
-    async def handler(websocket, extra_argument):
-        ...
-
-    bound_handler = functools.partial(handler, extra_argument='spam')
-    start_server = websockets.serve(bound_handler, ...)
-
-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:`~server.WebSocketServerProtocol.path` attribute.
-
-You may route a connection to different handlers depending on the request path::
-
-    async def handler(websocket):
-        if websocket.path == "/blue":
-            await blue_handler(websocket)
-        elif websocket.path == "/green":
-            await green_handler(websocket)
-        else:
-            # No handler for this path; close the connection.
-            return
-
-You may also route the connection based on the first message received from the
-client, as shown in the :doc:`tutorial <../intro/tutorial2>`. When you want to
-authenticate the connection before routing it, this is usually more convenient.
-
-Generally speaking, there is far less emphasis on the request path in WebSocket
-servers than in HTTP servers. When a WebSockt server provides a single endpoint,
-it may ignore the request path entirely.
-
-How do I access HTTP headers?
-.............................
-
-To access HTTP headers during the WebSocket handshake, you can override
-:attr:`~server.WebSocketServerProtocol.process_request`::
-
-    async def process_request(self, path, request_headers):
-        authorization = request_headers["Authorization"]
-
-Once the connection is established, HTTP headers are available in
-:attr:`~server.WebSocketServerProtocol.request_headers` and
-:attr:`~server.WebSocketServerProtocol.response_headers`::
-
-    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:`~server.serve`.
-
-To set other HTTP headers, use the ``extra_headers`` argument.
-
-How do I get the IP address of the client?
-..........................................
-
-It's available in :attr:`~legacy.protocol.WebSocketCommonProtocol.remote_address`::
-
-    async def handler(websocket):
-        remote_ip = websocket.remote_address[0]
-
-How do I set the IP addresses my server listens on?
-...................................................
-
-Look at the ``host`` argument of :meth:`~asyncio.loop.create_server`.
-
-:func:`~server.serve` accepts the same arguments as
-:meth:`~asyncio.loop.create_server`.
-
-What does ``OSError: [Errno 99] error while attempting to bind on address ('::1', 80, 0, 0): address not available`` mean?
-..........................................................................................................................
-
-You are calling :func:`~server.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:`~server.serve` context manager.
-
-Here's an example that terminates cleanly when it receives SIGTERM on Unix:
-
-.. literalinclude:: ../../example/shutdown_server.py
-    :emphasize-lines: 12-15,18
-
-
-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 a 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
-:attr:`~server.WebSocketServerProtocol.process_request` hook.
-
-If you need more, pick a HTTP server and run it separately.
-
-Alternatively, pick a HTTP framework that builds on top of ``websockets`` to
-support WebSocket connections, like Sanic_.
-
-.. _Sanic: https://sanicframework.org/en/
-
-Client side
------------
-
-Why does the client close the connection prematurely?
-.....................................................
-
-You're exiting the context manager prematurely. Wait for the work to be
-finished before exiting.
-
-For example, if your code has a structure similar to::
-
-    async with connect(...) as websocket:
-        asyncio.create_task(do_some_work())
-
-change it to::
-
-    async with connect(...) as websocket:
-        await do_some_work()
-
-How do I access HTTP headers?
-.............................
-
-Once the connection is established, HTTP headers are available in
-:attr:`~client.WebSocketClientProtocol.request_headers` and
-:attr:`~client.WebSocketClientProtocol.response_headers`.
-
-How do I set HTTP headers?
-..........................
-
-To set the ``Origin``, ``Sec-WebSocket-Extensions``, or
-``Sec-WebSocket-Protocol`` headers in the WebSocket handshake request, use the
-``origin``, ``extensions``, or ``subprotocols`` arguments of
-:func:`~client.connect`.
-
-To set other HTTP headers, for example the ``Authorization`` header, use the
-``extra_headers`` argument::
-
-    async with connect(..., extra_headers={"Authorization": ...}) as websocket:
-        ...
-
-How do I close a connection?
-............................
-
-The easiest is to use :func:`~client.connect` as a context manager::
-
-    async with connect(...) as websocket:
-        ...
-
-The connection is closed when exiting the context manager.
-
-How do I reconnect when the connection drops?
-.............................................
-
-Use :func:`~client.connect` as an asynchronous iterator::
-
-    async for websocket in websockets.connect(...):
-        try:
-            ...
-        except websockets.ConnectionClosed:
-            continue
-
-Make sure you handle exceptions in the ``async for`` loop. Uncaught exceptions
-will break out of the loop.
-
-How do I stop a client that is processing messages in a loop?
-.............................................................
-
-You can close the connection.
-
-Here's an example that terminates cleanly when it receives SIGTERM on Unix:
-
-.. literalinclude:: ../../example/shutdown_client.py
-    :emphasize-lines: 10-13
-
-How do I disable TLS/SSL certificate verification?
-..................................................
-
-Look at the ``ssl`` argument of :meth:`~asyncio.loop.create_connection`.
-
-:func:`~client.connect` accepts the same arguments as
-:meth:`~asyncio.loop.create_connection`.
-
-asyncio usage
--------------
-
-How do I run two coroutines in parallel?
-........................................
-
-You must start two tasks, which the event loop will run concurrently. You can
-achieve this with :func:`asyncio.gather` or :func:`asyncio.create_task`.
-
-Keep track of the tasks and make sure they terminate or you cancel them when
-the connection terminates.
-
-Why does my program never receive any messages?
-...............................................
-
-Your program runs a coroutine that never yields control to the event loop. The
-coroutine that receives messages never gets a chance to run.
-
-Putting an ``await`` statement in a ``for`` or a ``while`` loop isn't enough
-to yield control. Awaiting a coroutine may yield control, but there's no
-guarantee that it will.
-
-For example, :meth:`~legacy.protocol.WebSocketCommonProtocol.send` only yields
-control when send buffers are full, which never happens in most practical
-cases.
-
-If you run a loop that contains only synchronous operations and
-a :meth:`~legacy.protocol.WebSocketCommonProtocol.send` call, you must yield
-control explicitly with :func:`asyncio.sleep`::
-
-    async def producer(websocket):
-        message = generate_next_message()
-        await websocket.send(message)
-        await asyncio.sleep(0)  # yield control to the event loop
-
-:func:`asyncio.sleep` always suspends the current task, allowing other tasks
-to run. This behavior is documented precisely because it isn't expected from
-every coroutine.
-
-See `issue 867`_.
-
-.. _issue 867: https://github.com/aaugustin/websockets/issues/867
-
-Why does my simple program misbehave mysteriously?
-..................................................
-
-You are using :func:`time.sleep` instead of :func:`asyncio.sleep`, which
-blocks the event loop and prevents asyncio from operating normally.
-
-This may lead to messages getting send but not received, to connection
-timeouts, and to unexpected results of shotgun debugging e.g. adding an
-unnecessary call to :meth:`~legacy.protocol.WebSocketCommonProtocol.send`
-makes the program functional.
-
-Both sides
-----------
-
-What does ``ConnectionClosedError: no close frame received or sent`` mean?
-..........................................................................
-
-If you're seeing this traceback in the logs of a server:
-
-.. code-block:: pytb
-
-    connection handler failed
-    Traceback (most recent call last):
-      ...
-    asyncio.exceptions.IncompleteReadError: 0 bytes read on a total of 2 expected bytes
-
-    The above exception was the direct cause of the following exception:
-
-    Traceback (most recent call last):
-      ...
-    websockets.exceptions.ConnectionClosedError: no close frame received or sent
-
-or if a client crashes with this traceback:
-
-.. code-block:: pytb
-
-    Traceback (most recent call last):
-      ...
-    ConnectionResetError: [Errno 54] Connection reset by peer
-
-    The above exception was the direct cause of the following exception:
-
-    Traceback (most recent call last):
-      ...
-    websockets.exceptions.ConnectionClosedError: no close frame received or sent
-
-it means that the TCP connection was lost. As a consequence, the WebSocket
-connection was closed without receiving and sending a close frame, which is
-abnormal.
-
-You can catch and handle :exc:`~exceptions.ConnectionClosed` to prevent it
-from being logged.
-
-There are several reasons why long-lived connections may be lost:
-
-* End-user devices tend to lose network connectivity often and unpredictably
-  because they can move out of wireless network coverage, get unplugged from
-  a wired network, enter airplane mode, be put to sleep, etc.
-* HTTP load balancers or proxies that aren't configured for long-lived
-  connections may terminate connections after a short amount of time, usually
-  30 seconds, despite websockets' keepalive mechanism.
-
-If you're facing a reproducible issue, :ref:`enable debug logs <debugging>` to
-see when and how connections are closed.
-
-What does ``ConnectionClosedError: sent 1011 (unexpected error) keepalive ping timeout; no close frame received`` mean?
-.......................................................................................................................
-
-If you're seeing this traceback in the logs of a server:
-
-.. code-block:: pytb
-
-    connection handler failed
-    Traceback (most recent call last):
-      ...
-    asyncio.exceptions.CancelledError
-
-    The above exception was the direct cause of the following exception:
-
-    Traceback (most recent call last):
-      ...
-    websockets.exceptions.ConnectionClosedError: sent 1011 (unexpected error) keepalive ping timeout; no close frame received
-
-or if a client crashes with this traceback:
-
-.. code-block:: pytb
-
-    Traceback (most recent call last):
-      ...
-    asyncio.exceptions.CancelledError
-
-    The above exception was the direct cause of the following exception:
-
-    Traceback (most recent call last):
-      ...
-    websockets.exceptions.ConnectionClosedError: sent 1011 (unexpected error) keepalive ping timeout; no close frame received
-
-it means that the WebSocket connection suffered from excessive latency and was
-closed after reaching the timeout of websockets' keepalive mechanism.
-
-You can catch and handle :exc:`~exceptions.ConnectionClosed` to prevent it
-from being logged.
-
-There are two main reasons why latency may increase:
-
-* Poor network connectivity.
-* More traffic than the recipient can handle.
-
-See the discussion of :doc:`timeouts <../topics/timeouts>` for details.
-
-If websockets' default timeout of 20 seconds is too short for your use case,
-you can adjust it with the ``ping_timeout`` argument.
-
-How do I set a timeout on :meth:`~legacy.protocol.WebSocketCommonProtocol.recv`?
-................................................................................
-
-Use :func:`~asyncio.wait_for`::
-
-    await asyncio.wait_for(websocket.recv(), timeout=10)
-
-This technique works for most APIs, except for asynchronous context managers.
-See `issue 574`_.
-
-.. _issue 574: https://github.com/aaugustin/websockets/issues/574
-
-How can I pass arguments to a custom protocol subclass?
-.......................................................
-
-You can bind additional arguments to the protocol factory with
-:func:`functools.partial`::
-
-    import asyncio
-    import functools
-    import websockets
-
-    class MyServerProtocol(websockets.WebSocketServerProtocol):
-        def __init__(self, extra_argument, *args, **kwargs):
-            super().__init__(*args, **kwargs)
-            # do something with extra_argument
-
-    create_protocol = functools.partial(MyServerProtocol, extra_argument='spam')
-    start_server = websockets.serve(..., create_protocol=create_protocol)
-
-This example was for a server. The same pattern applies on a client.
-
-How do I keep idle connections open?
-....................................
-
-websockets sends pings at 20 seconds intervals to keep the connection open.
-
-It closes the connection if it doesn't get a pong within 20 seconds.
-
-You can adjust this behavior with ``ping_interval`` and ``ping_timeout``.
-
-See :doc:`../topics/timeouts` for details.
-
-How do I respond to pings?
-..........................
-
-Don't bother; websockets takes care of responding to pings with pongs.
-
-Miscellaneous
--------------
-
-Can I use websockets without ``async`` and ``await``?
-.....................................................
-
-No, there is no convenient way to do this. You should use another library.
-
-Are there ``onopen``, ``onmessage``, ``onerror``, and ``onclose`` callbacks?
-............................................................................
-
-No, there aren't.
-
-websockets provides high-level, coroutine-based APIs. Compared to callbacks,
-coroutines make it easier to manage control flow in concurrent code.
-
-If you prefer callback-based APIs, you should use another library.
-
-Why do I get the error: ``module 'websockets' has no attribute '...'``?
-.......................................................................
-
-Often, this is because you created a script called ``websockets.py`` in your
-current working directory. Then ``import websockets`` imports this module
-instead of the websockets library.
-
-Why am I having problems with threads?
-......................................
-
-You shouldn't use threads. Use tasks instead.
-
-Indeed, when you chose websockets, you chose :mod:`asyncio` as the primary
-framework to handle concurrency. This choice is mutually exclusive with
-:mod:`threading`.
-
-If you believe that you need to run websockets in a thread and some logic in
-another thread, you should run that logic in a :class:`~asyncio.Task` instead.
-
-If you believe that you cannot run that logic in the same event loop because it
-will block websockets, :meth:`~asyncio.loop.run_in_executor` may help.
-
-This question is really about :mod:`asyncio`. Please review the advice about
-:ref:`asyncio-multithreading` in the Python documentation.
diff --git a/docs/howto/index.rst b/docs/howto/index.rst
index d399ceb..dafb723 100644
--- a/docs/howto/index.rst
+++ b/docs/howto/index.rst
@@ -1,12 +1,18 @@
 How-to guides
 =============
 
+In a hurry? Check out these examples.
+
+.. toctree::
+   :titlesonly:
+
+   quickstart
+
 If you're stuck, perhaps you'll find the answer here.
 
 .. toctree::
    :titlesonly:
 
-   faq
    cheatsheet
    patterns
    autoreload
diff --git a/docs/intro/quickstart.rst b/docs/howto/quickstart.rst
similarity index 100%
rename from docs/intro/quickstart.rst
rename to docs/howto/quickstart.rst
diff --git a/docs/index.rst b/docs/index.rst
index 07835a8..00a9b59 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -52,13 +52,14 @@ Also, websockets provides an interactive client:
     < Hello world!
     Connection closed: 1000 (OK).
 
-Do you like it? Let's dive in!
+Do you like it? :doc:`Let's dive in! <intro/index>`
 
 .. toctree::
    :hidden:
 
    intro/index
    howto/index
+   faq/index
    reference/index
    topics/index
    project/index
diff --git a/docs/intro/index.rst b/docs/intro/index.rst
index 2c66dea..fe4e704 100644
--- a/docs/intro/index.rst
+++ b/docs/intro/index.rst
@@ -43,8 +43,4 @@ Learn how to build an real-time web application with websockets.
 In a hurry?
 -----------
 
-Check out these examples.
-
-.. toctree::
-
-   quickstart
+Look at the :doc:`quick start guide <../howto/quickstart>`.
diff --git a/docs/reference/client.rst b/docs/reference/client.rst
index 3797653..3016e85 100644
--- a/docs/reference/client.rst
+++ b/docs/reference/client.rst
@@ -9,16 +9,16 @@ asyncio
 Opening a connection
 ....................
 
-.. autofunction:: connect(uri, *, create_protocol=None, logger=None, compression="deflate", origin=None, extensions=None, subprotocols=None, extra_headers=None, open_timeout=10, ping_interval=20, ping_timeout=20, close_timeout=10, max_size=2 ** 20, max_queue=2 ** 5, read_limit=2 ** 16, write_limit=2 ** 16, **kwds)
+.. autofunction:: connect(uri, *, create_protocol=None, logger=None, compression="deflate", origin=None, extensions=None, subprotocols=None, extra_headers=None, user_agent_header="Python/x.y.z websockets/X.Y", open_timeout=10, ping_interval=20, ping_timeout=20, close_timeout=10, max_size=2 ** 20, max_queue=2 ** 5, read_limit=2 ** 16, write_limit=2 ** 16, **kwds)
     :async:
 
-.. autofunction:: unix_connect(path, uri="ws://localhost/", *, create_protocol=None, logger=None, compression="deflate", origin=None, extensions=None, subprotocols=None, extra_headers=None, open_timeout=10, ping_interval=20, ping_timeout=20, close_timeout=10, max_size=2 ** 20, max_queue=2 ** 5, read_limit=2 ** 16, write_limit=2 ** 16, **kwds)
+.. autofunction:: unix_connect(path, uri="ws://localhost/", *, create_protocol=None, logger=None, compression="deflate", origin=None, extensions=None, subprotocols=None, extra_headers=None, user_agent_header="Python/x.y.z websockets/X.Y", open_timeout=10, ping_interval=20, ping_timeout=20, close_timeout=10, max_size=2 ** 20, max_queue=2 ** 5, read_limit=2 ** 16, write_limit=2 ** 16, **kwds)
     :async:
 
 Using a connection
 ..................
 
-.. autoclass:: WebSocketClientProtocol(*, logger=None, origin=None, extensions=None, subprotocols=None, extra_headers=None, ping_interval=20, ping_timeout=20, close_timeout=10, max_size=2 ** 20, max_queue=2 ** 5, read_limit=2 ** 16, write_limit=2 ** 16)
+.. autoclass:: WebSocketClientProtocol(*, logger=None, origin=None, extensions=None, subprotocols=None, extra_headers=None, user_agent_header="Python/x.y.z websockets/X.Y", ping_interval=20, ping_timeout=20, close_timeout=10, max_size=2 ** 20, max_queue=2 ** 5, read_limit=2 ** 16, write_limit=2 ** 16)
 
     .. automethod:: recv
 
diff --git a/docs/reference/index.rst b/docs/reference/index.rst
index a5ee578..5f51a1c 100644
--- a/docs/reference/index.rst
+++ b/docs/reference/index.rst
@@ -43,8 +43,8 @@ and server connections, common methods are documented in a "Both sides" page.
 .. toctree::
    :titlesonly:
 
-   client
    server
+   client
    common
    utilities
    exceptions
diff --git a/docs/reference/server.rst b/docs/reference/server.rst
index 0e446f3..65f9884 100644
--- a/docs/reference/server.rst
+++ b/docs/reference/server.rst
@@ -9,10 +9,10 @@ asyncio
 Starting a server
 .................
 
-.. autofunction:: serve(ws_handler, host=None, port=None, *, create_protocol=None, logger=None, compression="deflate", origins=None, extensions=None, subprotocols=None, extra_headers=None, process_request=None, select_subprotocol=None, ping_interval=20, ping_timeout=20, close_timeout=10, max_size=2 ** 20, max_queue=2 ** 5, read_limit=2 ** 16, write_limit=2 ** 16, **kwds)
+.. autofunction:: serve(ws_handler, host=None, port=None, *, create_protocol=None, logger=None, compression="deflate", origins=None, extensions=None, subprotocols=None, extra_headers=None, server_header="Python/x.y.z websockets/X.Y", process_request=None, select_subprotocol=None, ping_interval=20, ping_timeout=20, close_timeout=10, max_size=2 ** 20, max_queue=2 ** 5, read_limit=2 ** 16, write_limit=2 ** 16, **kwds)
     :async:
 
-.. autofunction:: unix_serve(ws_handler, path=None, *, create_protocol=None, logger=None, compression="deflate", origins=None, extensions=None, subprotocols=None, extra_headers=None, process_request=None, select_subprotocol=None, ping_interval=20, ping_timeout=20, close_timeout=10, max_size=2 ** 20, max_queue=2 ** 5, read_limit=2 ** 16, write_limit=2 ** 16, **kwds)
+.. autofunction:: unix_serve(ws_handler, path=None, *, create_protocol=None, logger=None, compression="deflate", origins=None, extensions=None, subprotocols=None, extra_headers=None, server_header="Python/x.y.z websockets/X.Y", process_request=None, select_subprotocol=None, ping_interval=20, ping_timeout=20, close_timeout=10, max_size=2 ** 20, max_queue=2 ** 5, read_limit=2 ** 16, write_limit=2 ** 16, **kwds)
     :async:
 
 Stopping a server
@@ -37,7 +37,7 @@ Stopping a server
 Using a connection
 ..................
 
-.. autoclass:: WebSocketServerProtocol(ws_handler, ws_server, *, logger=None, origins=None, extensions=None, subprotocols=None, extra_headers=None, process_request=None, select_subprotocol=None, ping_interval=20, ping_timeout=20, close_timeout=10, max_size=2 ** 20, max_queue=2 ** 5, read_limit=2 ** 16, write_limit=2 ** 16)
+.. autoclass:: WebSocketServerProtocol(ws_handler, ws_server, *, logger=None, origins=None, extensions=None, subprotocols=None, extra_headers=None, server_header="Python/x.y.z websockets/X.Y", process_request=None, select_subprotocol=None, ping_interval=20, ping_timeout=20, close_timeout=10, max_size=2 ** 20, max_queue=2 ** 5, read_limit=2 ** 16, write_limit=2 ** 16)
 
     .. automethod:: recv
 
diff --git a/src/websockets/client.py b/src/websockets/client.py
index df8e534..1c33fae 100644
--- a/src/websockets/client.py
+++ b/src/websockets/client.py
@@ -64,6 +64,9 @@ class ClientConnection(Connection):
         logger: logger for this connection;
             defaults to ``logging.getLogger("websockets.client")``;
             see the :doc:`logging guide <../topics/logging>` for details.
+        user_agent_header: value of  the ``User-Agent`` request header;
+            defauts to ``"Python/x.y.z websockets/X.Y"``;
+            :obj:`None` removes the header.
 
     """
 
@@ -73,6 +76,7 @@ class ClientConnection(Connection):
         origin: Optional[Origin] = None,
         extensions: Optional[Sequence[ClientExtensionFactory]] = None,
         subprotocols: Optional[Sequence[Subprotocol]] = None,
+        user_agent_header: Optional[str] = USER_AGENT,
         state: State = CONNECTING,
         max_size: Optional[int] = 2**20,
         logger: Optional[LoggerLike] = None,
@@ -87,6 +91,7 @@ class ClientConnection(Connection):
         self.origin = origin
         self.available_extensions = extensions
         self.available_subprotocols = subprotocols
+        self.user_agent_header = user_agent_header
         self.key = generate_key()
 
     def connect(self) -> Request:  # noqa: F811
@@ -131,7 +136,8 @@ class ClientConnection(Connection):
             protocol_header = build_subprotocol(self.available_subprotocols)
             headers["Sec-WebSocket-Protocol"] = protocol_header
 
-        headers["User-Agent"] = USER_AGENT
+        if self.user_agent_header is not None:
+            headers["User-Agent"] = self.user_agent_header
 
         return Request(self.wsuri.resource_name, headers)
 
diff --git a/src/websockets/legacy/client.py b/src/websockets/legacy/client.py
index 29550b6..93566b8 100644
--- a/src/websockets/legacy/client.py
+++ b/src/websockets/legacy/client.py
@@ -70,7 +70,8 @@ class WebSocketClientProtocol(WebSocketCommonProtocol):
     is closed with any other code.
 
     See :func:`connect` for the documentation of ``logger``, ``origin``,
-    ``extensions``, ``subprotocols``, and ``extra_headers``.
+    ``extensions``, ``subprotocols``, ``extra_headers``, and
+    ``user_agent_header``.
 
     See :class:`~websockets.legacy.protocol.WebSocketCommonProtocol` for the
     documentation of ``ping_interval``, ``ping_timeout``, ``close_timeout``,
@@ -89,6 +90,7 @@ class WebSocketClientProtocol(WebSocketCommonProtocol):
         extensions: Optional[Sequence[ClientExtensionFactory]] = None,
         subprotocols: Optional[Sequence[Subprotocol]] = None,
         extra_headers: Optional[HeadersLike] = None,
+        user_agent_header: Optional[str] = USER_AGENT,
         **kwargs: Any,
     ) -> None:
         if logger is None:
@@ -98,6 +100,7 @@ class WebSocketClientProtocol(WebSocketCommonProtocol):
         self.available_extensions = extensions
         self.available_subprotocols = subprotocols
         self.extra_headers = extra_headers
+        self.user_agent_header = user_agent_header
 
     def write_http_request(self, path: str, headers: Headers) -> None:
         """
@@ -315,7 +318,8 @@ class WebSocketClientProtocol(WebSocketCommonProtocol):
         if self.extra_headers is not None:
             request_headers.update(self.extra_headers)
 
-        request_headers.setdefault("User-Agent", USER_AGENT)
+        if self.user_agent_header is not None:
+            request_headers.setdefault("User-Agent", self.user_agent_header)
 
         self.write_http_request(wsuri.resource_name, request_headers)
 
@@ -393,6 +397,9 @@ class Connect:
         subprotocols: list of supported subprotocols, in order of decreasing
             preference.
         extra_headers: arbitrary HTTP headers to add to the request.
+        user_agent_header: value of  the ``User-Agent`` request header;
+            defauts to ``"Python/x.y.z websockets/X.Y"``;
+            :obj:`None` removes the header.
         open_timeout: timeout for opening the connection in seconds;
             :obj:`None` to disable the timeout
 
@@ -438,6 +445,7 @@ class Connect:
         extensions: Optional[Sequence[ClientExtensionFactory]] = None,
         subprotocols: Optional[Sequence[Subprotocol]] = None,
         extra_headers: Optional[HeadersLike] = None,
+        user_agent_header: Optional[str] = USER_AGENT,
         open_timeout: Optional[float] = 10,
         ping_interval: Optional[float] = 20,
         ping_timeout: Optional[float] = 20,
@@ -503,6 +511,7 @@ class Connect:
             extensions=extensions,
             subprotocols=subprotocols,
             extra_headers=extra_headers,
+            user_agent_header=user_agent_header,
             ping_interval=ping_interval,
             ping_timeout=ping_timeout,
             close_timeout=close_timeout,
diff --git a/src/websockets/legacy/protocol.py b/src/websockets/legacy/protocol.py
index 651ff82..0472603 100644
--- a/src/websockets/legacy/protocol.py
+++ b/src/websockets/legacy/protocol.py
@@ -846,11 +846,12 @@ class WebSocketCommonProtocol(asyncio.Protocol):
         while data is None or data in self.pings:
             data = struct.pack("!I", random.getrandbits(32))
 
-        self.pings[data] = self.loop.create_future()
+        ping_future = self.loop.create_future()
+        self.pings[data] = ping_future
 
         await self.write_frame(True, OP_PING, data)
 
-        return asyncio.shield(self.pings[data])
+        return asyncio.shield(ping_future)
 
     async def pong(self, data: Data = b"") -> None:
         """
@@ -1302,7 +1303,7 @@ class WebSocketCommonProtocol(asyncio.Protocol):
             if self.is_client and hasattr(self, "transfer_data_task"):
                 if await self.wait_for_connection_lost():
                     # Coverage marks this line as a partially executed branch.
-                    # I supect a bug in coverage. Ignore it for now.
+                    # I suspect a bug in coverage. Ignore it for now.
                     return  # pragma: no cover
                 if self.debug:
                     self.logger.debug("! timed out waiting for TCP close")
@@ -1322,7 +1323,7 @@ class WebSocketCommonProtocol(asyncio.Protocol):
 
                 if await self.wait_for_connection_lost():
                     # Coverage marks this line as a partially executed branch.
-                    # I supect a bug in coverage. Ignore it for now.
+                    # I suspect a bug in coverage. Ignore it for now.
                     return  # pragma: no cover
                 if self.debug:
                     self.logger.debug("! timed out waiting for TCP close")
@@ -1360,7 +1361,7 @@ class WebSocketCommonProtocol(asyncio.Protocol):
 
         # connection_lost() is called quickly after aborting.
         # Coverage marks this line as a partially executed branch.
-        # I supect a bug in coverage. Ignore it for now.
+        # I suspect a bug in coverage. Ignore it for now.
         await self.wait_for_connection_lost()  # pragma: no cover
 
     async def wait_for_connection_lost(self) -> bool:
diff --git a/src/websockets/legacy/server.py b/src/websockets/legacy/server.py
index 3e51db1..836496b 100644
--- a/src/websockets/legacy/server.py
+++ b/src/websockets/legacy/server.py
@@ -84,7 +84,7 @@ class WebSocketServerProtocol(WebSocketCommonProtocol):
         ws_server: WebSocket server that created this connection.
 
     See :func:`serve` for the documentation of ``ws_handler``, ``logger``, ``origins``,
-    ``extensions``, ``subprotocols``, and ``extra_headers``.
+    ``extensions``, ``subprotocols``, ``extra_headers``, and ``server_header``.
 
     See :class:`~websockets.legacy.protocol.WebSocketCommonProtocol` for the
     documentation of ``ping_interval``, ``ping_timeout``, ``close_timeout``,
@@ -108,6 +108,7 @@ class WebSocketServerProtocol(WebSocketCommonProtocol):
         extensions: Optional[Sequence[ServerExtensionFactory]] = None,
         subprotocols: Optional[Sequence[Subprotocol]] = None,
         extra_headers: Optional[HeadersLikeOrCallable] = None,
+        server_header: Optional[str] = USER_AGENT,
         process_request: Optional[
             Callable[[str, Headers], Awaitable[Optional[HTTPResponse]]]
         ] = None,
@@ -132,6 +133,7 @@ class WebSocketServerProtocol(WebSocketCommonProtocol):
         self.available_extensions = extensions
         self.available_subprotocols = subprotocols
         self.extra_headers = extra_headers
+        self.server_header = server_header
         self._process_request = process_request
         self._select_subprotocol = select_subprotocol
 
@@ -153,7 +155,7 @@ class WebSocketServerProtocol(WebSocketCommonProtocol):
         Handle the lifecycle of a WebSocket connection.
 
         Since this method doesn't have a caller able to handle exceptions, it
-        attemps to log relevant ones and guarantees that the TCP connection is
+        attempts to log relevant ones and guarantees that the TCP connection is
         closed before exiting.
 
         """
@@ -216,7 +218,9 @@ class WebSocketServerProtocol(WebSocketCommonProtocol):
                     )
 
                 headers.setdefault("Date", email.utils.formatdate(usegmt=True))
-                headers.setdefault("Server", USER_AGENT)
+                if self.server_header is not None:
+                    headers.setdefault("Server", self.server_header)
+
                 headers.setdefault("Content-Length", str(len(body)))
                 headers.setdefault("Content-Type", "text/plain")
                 headers.setdefault("Connection", "close")
@@ -635,7 +639,8 @@ class WebSocketServerProtocol(WebSocketCommonProtocol):
             response_headers.update(extra_headers)
 
         response_headers.setdefault("Date", email.utils.formatdate(usegmt=True))
-        response_headers.setdefault("Server", USER_AGENT)
+        if self.server_header is not None:
+            response_headers.setdefault("Server", self.server_header)
 
         self.write_http_response(http.HTTPStatus.SWITCHING_PROTOCOLS, response_headers)
 
@@ -833,6 +838,13 @@ class WebSocketServer:
         """
         See :meth:`asyncio.Server.start_serving`.
 
+        Typical use::
+
+            server = await serve(..., start_serving=False)
+            # perform additional setup here...
+            # ... then start the server
+            await server.start_serving()
+
         """
         await self.server.start_serving()  # pragma: no cover
 
@@ -840,6 +852,17 @@ class WebSocketServer:
         """
         See :meth:`asyncio.Server.serve_forever`.
 
+        Typical use::
+
+            server = await serve(...)
+            # this coroutine doesn't return
+            # canceling it stops the server
+            await server.serve_forever()
+
+        This is an alternative to using :func:`serve` as an asynchronous context
+        manager. Shutdown is triggered by canceling :meth:`serve_forever`
+        instead of exiting a :func:`serve` context.
+
         """
         await self.server.serve_forever()  # pragma: no cover
 
@@ -920,6 +943,9 @@ class Serve:
             a :data:`~websockets.datastructures.HeadersLike` or a callable
             taking the request path and headers in arguments and returning
             a :data:`~websockets.datastructures.HeadersLike`.
+        server_header: value of  the ``Server`` response header;
+            defauts to ``"Python/x.y.z websockets/X.Y"``;
+            :obj:`None` removes the header.
         process_request (Optional[Callable[[str, Headers], \
             Awaitable[Optional[Tuple[http.HTTPStatus, HeadersLike, bytes]]]]]):
             intercept HTTP request before the opening handshake;
@@ -962,6 +988,7 @@ class Serve:
         extensions: Optional[Sequence[ServerExtensionFactory]] = None,
         subprotocols: Optional[Sequence[Subprotocol]] = None,
         extra_headers: Optional[HeadersLikeOrCallable] = None,
+        server_header: Optional[str] = USER_AGENT,
         process_request: Optional[
             Callable[[str, Headers], Awaitable[Optional[HTTPResponse]]]
         ] = None,
@@ -1043,6 +1070,7 @@ class Serve:
             extensions=extensions,
             subprotocols=subprotocols,
             extra_headers=extra_headers,
+            server_header=server_header,
             process_request=process_request,
             select_subprotocol=select_subprotocol,
             logger=logger,
diff --git a/src/websockets/server.py b/src/websockets/server.py
index 5dad50b..2057d9f 100644
--- a/src/websockets/server.py
+++ b/src/websockets/server.py
@@ -64,6 +64,9 @@ class ServerConnection(Connection):
         logger: logger for this connection;
             defaults to ``logging.getLogger("websockets.client")``;
             see the :doc:`logging guide <../topics/logging>` for details.
+        server_header: value of  the ``Server`` response header;
+            defauts to ``"Python/x.y.z websockets/X.Y"``;
+            :obj:`None` removes the header.
 
     """
 
@@ -72,6 +75,7 @@ class ServerConnection(Connection):
         origins: Optional[Sequence[Optional[Origin]]] = None,
         extensions: Optional[Sequence[ServerExtensionFactory]] = None,
         subprotocols: Optional[Sequence[Subprotocol]] = None,
+        server_header: Optional[str] = USER_AGENT,
         state: State = CONNECTING,
         max_size: Optional[int] = 2**20,
         logger: Optional[LoggerLike] = None,
@@ -85,6 +89,7 @@ class ServerConnection(Connection):
         self.origins = origins
         self.available_extensions = extensions
         self.available_subprotocols = subprotocols
+        self.server_header = server_header
 
     def accept(self, request: Request) -> Response:
         """
@@ -170,7 +175,8 @@ class ServerConnection(Connection):
         if protocol_header is not None:
             headers["Sec-WebSocket-Protocol"] = protocol_header
 
-        headers["Server"] = USER_AGENT
+        if self.server_header is not None:
+            headers["Server"] = self.server_header
 
         self.logger.info("connection open")
         return Response(101, "Switching Protocols", headers)
@@ -469,9 +475,11 @@ class ServerConnection(Connection):
                 ("Connection", "close"),
                 ("Content-Length", str(len(body))),
                 ("Content-Type", "text/plain; charset=utf-8"),
-                ("Server", USER_AGENT),
             ]
         )
+        if self.server_header is not None:
+            headers["Server"] = self.server_header
+
         response = Response(status.value, status.phrase, headers, body)
         # When reject() is called from accept(), handshake_exc is already set.
         # If a user calls reject(), set handshake_exc to guarantee invariant:
diff --git a/src/websockets/uri.py b/src/websockets/uri.py
index fff0c38..385090f 100644
--- a/src/websockets/uri.py
+++ b/src/websockets/uri.py
@@ -33,8 +33,8 @@ class WebSocketURI:
     port: int
     path: str
     query: str
-    username: Optional[str]
-    password: Optional[str]
+    username: Optional[str] = None
+    password: Optional[str] = None
 
     @property
     def resource_name(self) -> str:


Test patch:
diff --git a/tests/legacy/test_client_server.py b/tests/legacy/test_client_server.py
index f9de70c..8d0f872 100644
--- a/tests/legacy/test_client_server.py
+++ b/tests/legacy/test_client_server.py
@@ -422,10 +422,14 @@ class CommonClientServerTests:
                 self.used_for_write = False
                 super().__init__(*args, **kwargs)
 
-            def recv(self, *args, **kwargs):
+            def recv(self, *args, **kwargs):  # pragma: no cover
                 self.used_for_read = True
                 return super().recv(*args, **kwargs)
 
+            def recv_into(self, *args, **kwargs):  # pragma: no cover
+                self.used_for_read = True
+                return super().recv_into(*args, **kwargs)
+
             def send(self, *args, **kwargs):
                 self.used_for_write = True
                 return super().send(*args, **kwargs)
@@ -655,12 +659,27 @@ class CommonClientServerTests:
         self.assertIn("('X-Spam', 'Eggs')", req_headers)
 
     @with_server()
-    @with_client("/headers", extra_headers={"User-Agent": "Eggs"})
-    def test_protocol_custom_request_user_agent(self):
+    @with_client("/headers", extra_headers={"User-Agent": "websockets"})
+    def test_protocol_custom_user_agent_header_legacy(self):
         req_headers = self.loop.run_until_complete(self.client.recv())
         self.loop.run_until_complete(self.client.recv())
         self.assertEqual(req_headers.count("User-Agent"), 1)
-        self.assertIn("('User-Agent', 'Eggs')", req_headers)
+        self.assertIn("('User-Agent', 'websockets')", req_headers)
+
+    @with_server()
+    @with_client("/headers", user_agent_header=None)
+    def test_protocol_no_user_agent_header(self):
+        req_headers = self.loop.run_until_complete(self.client.recv())
+        self.loop.run_until_complete(self.client.recv())
+        self.assertNotIn("User-Agent", req_headers)
+
+    @with_server()
+    @with_client("/headers", user_agent_header="websockets")
+    def test_protocol_custom_user_agent_header(self):
+        req_headers = self.loop.run_until_complete(self.client.recv())
+        self.loop.run_until_complete(self.client.recv())
+        self.assertEqual(req_headers.count("User-Agent"), 1)
+        self.assertIn("('User-Agent', 'websockets')", req_headers)
 
     @with_server(extra_headers=lambda p, r: {"X-Spam": "Eggs"})
     @with_client("/headers")
@@ -682,13 +701,28 @@ class CommonClientServerTests:
         resp_headers = self.loop.run_until_complete(self.client.recv())
         self.assertIn("('X-Spam', 'Eggs')", resp_headers)
 
-    @with_server(extra_headers={"Server": "Eggs"})
+    @with_server(extra_headers={"Server": "websockets"})
     @with_client("/headers")
-    def test_protocol_custom_response_user_agent(self):
+    def test_protocol_custom_server_header_legacy(self):
         self.loop.run_until_complete(self.client.recv())
         resp_headers = self.loop.run_until_complete(self.client.recv())
         self.assertEqual(resp_headers.count("Server"), 1)
-        self.assertIn("('Server', 'Eggs')", resp_headers)
+        self.assertIn("('Server', 'websockets')", resp_headers)
+
+    @with_server(server_header=None)
+    @with_client("/headers")
+    def test_protocol_no_server_header(self):
+        self.loop.run_until_complete(self.client.recv())
+        resp_headers = self.loop.run_until_complete(self.client.recv())
+        self.assertNotIn("Server", resp_headers)
+
+    @with_server(server_header="websockets")
+    @with_client("/headers")
+    def test_protocol_custom_server_header(self):
+        self.loop.run_until_complete(self.client.recv())
+        resp_headers = self.loop.run_until_complete(self.client.recv())
+        self.assertEqual(resp_headers.count("Server"), 1)
+        self.assertIn("('Server', 'websockets')", resp_headers)
 
     @with_server(create_protocol=HealthCheckServerProtocol)
     def test_http_request_http_endpoint(self):
@@ -724,6 +758,16 @@ class CommonClientServerTests:
         self.loop.run_until_complete(self.client.recv())
         self.stop_client()
 
+    @with_server(create_protocol=HealthCheckServerProtocol, server_header=None)
+    def test_http_request_no_server_header(self):
+        response = self.loop.run_until_complete(self.make_http_request("/__health__/"))
+        self.assertNotIn("Server", response.headers)
+
+    @with_server(create_protocol=HealthCheckServerProtocol, server_header="websockets")
+    def test_http_request_custom_server_header(self):
+        response = self.loop.run_until_complete(self.make_http_request("/__health__/"))
+        self.assertEqual(response.headers["Server"], "websockets")
+
     def assert_client_raises_code(self, status_code):
         with self.assertRaises(InvalidStatusCode) as raised:
             self.start_client()
@@ -1299,6 +1343,9 @@ class ClientServerOriginTests(ClientServerTestsMixin, AsyncioTestCase):
         self.assertEqual(self.loop.run_until_complete(self.client.recv()), "Hello!")
 
 
+@unittest.skipIf(
+    sys.version_info[:2] >= (3, 11), "asyncio.coroutine has been removed in Python 3.11"
+)
 class YieldFromTests(ClientServerTestsMixin, AsyncioTestCase):
     @with_server()
     def test_client(self):
diff --git a/tests/legacy/test_protocol.py b/tests/legacy/test_protocol.py
index 1672ab1..ed64246 100644
--- a/tests/legacy/test_protocol.py
+++ b/tests/legacy/test_protocol.py
@@ -1188,7 +1188,7 @@ class CommonTests:
     def test_keepalive_ping_not_acknowledged_closes_connection(self):
         self.restart_protocol_with_keepalive_ping()
 
-        # Ping is sent at 3ms and not acknowleged.
+        # Ping is sent at 3ms and not acknowledged.
         self.loop.run_until_complete(asyncio.sleep(4 * MS))
         (ping_1,) = tuple(self.protocol.pings)
         self.assertOneFrameSent(True, OP_PING, ping_1)
@@ -1257,7 +1257,7 @@ class CommonTests:
     def test_keepalive_ping_with_no_ping_timeout(self):
         self.restart_protocol_with_keepalive_ping(ping_timeout=None)
 
-        # Ping is sent at 3ms and not acknowleged.
+        # Ping is sent at 3ms and not acknowledged.
         self.loop.run_until_complete(asyncio.sleep(4 * MS))
         (ping_1,) = tuple(self.protocol.pings)
         self.assertOneFrameSent(True, OP_PING, ping_1)
diff --git a/tests/legacy/utils.py b/tests/legacy/utils.py
index 1fa2b53..fd5dfc2 100644
--- a/tests/legacy/utils.py
+++ b/tests/legacy/utils.py
@@ -18,7 +18,7 @@ class AsyncioTestCase(unittest.TestCase):
         """
         Convert test coroutines to test functions.
 
-        This supports asychronous tests transparently.
+        This supports asynchronous tests transparently.
 
         """
         super().__init_subclass__(**kwargs)
diff --git a/tests/test_client.py b/tests/test_client.py
index a843d32..0504f79 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -574,6 +574,22 @@ class AcceptRejectTests(unittest.TestCase):
             raise client.handshake_exc
         self.assertEqual(str(raised.exception), "unsupported subprotocol: otherchat")
 
+    def test_no_user_agent_header(self):
+        client = ClientConnection(
+            parse_uri("wss://example.com/"),
+            user_agent_header=None,
+        )
+        request = client.connect()
+        self.assertNotIn("User-Agent", request.headers)
+
+    def test_custom_user_agent_header(self):
+        client = ClientConnection(
+            parse_uri("wss://example.com/"),
+            user_agent_header="websockets",
+        )
+        request = client.connect()
+        self.assertEqual(request.headers["User-Agent"], "websockets")
+
 
 class MiscTests(unittest.TestCase):
     def test_bypass_handshake(self):
diff --git a/tests/test_server.py b/tests/test_server.py
index e3e8022..c7f398c 100644
--- a/tests/test_server.py
+++ b/tests/test_server.py
@@ -608,6 +608,28 @@ class AcceptRejectTests(unittest.TestCase):
         self.assertNotIn("Sec-WebSocket-Protocol", response.headers)
         self.assertIsNone(server.subprotocol)
 
+    def test_no_server_header(self):
+        server = ServerConnection(server_header=None)
+        request = self.make_request()
+        response = server.accept(request)
+        self.assertNotIn("Server", response.headers)
+
+    def test_custom_server_header(self):
+        server = ServerConnection(server_header="websockets")
+        request = self.make_request()
+        response = server.accept(request)
+        self.assertEqual(response.headers["Server"], "websockets")
+
+    def test_reject_response_no_server_header(self):
+        server = ServerConnection(server_header=None)
+        response = server.reject(http.HTTPStatus.OK, "Hello world!\n")
+        self.assertNotIn("Server", response.headers)
+
+    def test_reject_response_custom_server_header(self):
+        server = ServerConnection(server_header="websockets")
+        response = server.reject(http.HTTPStatus.OK, "Hello world!\n")
+        self.assertEqual(response.headers["Server"], "websockets")
+
 
 class MiscTests(unittest.TestCase):
     def test_bypass_handshake(self):