summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/tools/third_party/websockets/docs/topics
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/tools/third_party/websockets/docs/topics')
-rw-r--r--testing/web-platform/tests/tools/third_party/websockets/docs/topics/authentication.rst348
-rw-r--r--testing/web-platform/tests/tools/third_party/websockets/docs/topics/authentication.svg63
-rw-r--r--testing/web-platform/tests/tools/third_party/websockets/docs/topics/broadcast.rst348
-rw-r--r--testing/web-platform/tests/tools/third_party/websockets/docs/topics/compression.rst222
-rw-r--r--testing/web-platform/tests/tools/third_party/websockets/docs/topics/data-flow.svg63
-rw-r--r--testing/web-platform/tests/tools/third_party/websockets/docs/topics/deployment.rst181
-rw-r--r--testing/web-platform/tests/tools/third_party/websockets/docs/topics/deployment.svg63
-rw-r--r--testing/web-platform/tests/tools/third_party/websockets/docs/topics/design.rst572
-rw-r--r--testing/web-platform/tests/tools/third_party/websockets/docs/topics/index.rst18
-rw-r--r--testing/web-platform/tests/tools/third_party/websockets/docs/topics/lifecycle.graffle25
-rw-r--r--testing/web-platform/tests/tools/third_party/websockets/docs/topics/lifecycle.svg3
-rw-r--r--testing/web-platform/tests/tools/third_party/websockets/docs/topics/logging.rst245
-rw-r--r--testing/web-platform/tests/tools/third_party/websockets/docs/topics/memory.rst48
-rw-r--r--testing/web-platform/tests/tools/third_party/websockets/docs/topics/performance.rst20
-rw-r--r--testing/web-platform/tests/tools/third_party/websockets/docs/topics/protocol.graffle37
-rw-r--r--testing/web-platform/tests/tools/third_party/websockets/docs/topics/protocol.svg3
-rw-r--r--testing/web-platform/tests/tools/third_party/websockets/docs/topics/security.rst41
-rw-r--r--testing/web-platform/tests/tools/third_party/websockets/docs/topics/timeouts.rst116
18 files changed, 2416 insertions, 0 deletions
diff --git a/testing/web-platform/tests/tools/third_party/websockets/docs/topics/authentication.rst b/testing/web-platform/tests/tools/third_party/websockets/docs/topics/authentication.rst
new file mode 100644
index 0000000000..31bc8e6da8
--- /dev/null
+++ b/testing/web-platform/tests/tools/third_party/websockets/docs/topics/authentication.rst
@@ -0,0 +1,348 @@
+Authentication
+==============
+
+The WebSocket protocol was designed for creating web applications that need
+bidirectional communication between clients running in browsers and servers.
+
+In most practical use cases, WebSocket servers need to authenticate clients in
+order to route communications appropriately and securely.
+
+:rfc:`6455` stays elusive when it comes to authentication:
+
+ This protocol doesn't prescribe any particular way that servers can
+ authenticate clients during the WebSocket handshake. The WebSocket
+ server can use any client authentication mechanism available to a
+ generic HTTP server, such as cookies, HTTP authentication, or TLS
+ authentication.
+
+None of these three mechanisms works well in practice. Using cookies is
+cumbersome, HTTP authentication isn't supported by all mainstream browsers,
+and TLS authentication in a browser is an esoteric user experience.
+
+Fortunately, there are better alternatives! Let's discuss them.
+
+System design
+-------------
+
+Consider a setup where the WebSocket server is separate from the HTTP server.
+
+Most servers built with websockets to complement a web application adopt this
+design because websockets doesn't aim at supporting HTTP.
+
+The following diagram illustrates the authentication flow.
+
+.. image:: authentication.svg
+
+Assuming the current user is authenticated with the HTTP server (1), the
+application needs to obtain credentials from the HTTP server (2) in order to
+send them to the WebSocket server (3), who can check them against the database
+of user accounts (4).
+
+Usernames and passwords aren't a good choice of credentials here, if only
+because passwords aren't available in clear text in the database.
+
+Tokens linked to user accounts are a better choice. These tokens must be
+impossible to forge by an attacker. For additional security, they can be
+short-lived or even single-use.
+
+Sending credentials
+-------------------
+
+Assume the web application obtained authentication credentials, likely a
+token, from the HTTP server. There's four options for passing them to the
+WebSocket server.
+
+1. **Sending credentials as the first message in the WebSocket connection.**
+
+ This is fully reliable and the most secure mechanism in this discussion. It
+ has two minor downsides:
+
+ * Authentication is performed at the application layer. Ideally, it would
+ be managed at the protocol layer.
+
+ * Authentication is performed after the WebSocket handshake, making it
+ impossible to monitor authentication failures with HTTP response codes.
+
+2. **Adding credentials to the WebSocket URI in a query parameter.**
+
+ This is also fully reliable but less secure. Indeed, it has a major
+ downside:
+
+ * URIs end up in logs, which leaks credentials. Even if that risk could be
+ lowered with single-use tokens, it is usually considered unacceptable.
+
+ Authentication is still performed at the application layer but it can
+ happen before the WebSocket handshake, which improves separation of
+ concerns and enables responding to authentication failures with HTTP 401.
+
+3. **Setting a cookie on the domain of the WebSocket URI.**
+
+ Cookies are undoubtedly the most common and hardened mechanism for sending
+ credentials from a web application to a server. In an HTTP application,
+ credentials would be a session identifier or a serialized, signed session.
+
+ Unfortunately, when the WebSocket server runs on a different domain from
+ the web application, this idea bumps into the `Same-Origin Policy`_. For
+ security reasons, setting a cookie on a different origin is impossible.
+
+ The proper workaround consists in:
+
+ * creating a hidden iframe_ served from the domain of the WebSocket server
+ * sending the token to the iframe with postMessage_
+ * setting the cookie in the iframe
+
+ before opening the WebSocket connection.
+
+ Sharing a parent domain (e.g. example.com) between the HTTP server (e.g.
+ www.example.com) and the WebSocket server (e.g. ws.example.com) and setting
+ the cookie on that parent domain would work too.
+
+ However, the cookie would be shared with all subdomains of the parent
+ domain. For a cookie containing credentials, this is unacceptable.
+
+.. _Same-Origin Policy: https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy
+.. _iframe: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe
+.. _postMessage: https://developer.mozilla.org/en-US/docs/Web/API/MessagePort/postMessage
+
+4. **Adding credentials to the WebSocket URI in user information.**
+
+ Letting the browser perform HTTP Basic Auth is a nice idea in theory.
+
+ In practice it doesn't work due to poor support in browsers.
+
+ As of May 2021:
+
+ * Chrome 90 behaves as expected.
+
+ * Firefox 88 caches credentials too aggressively.
+
+ When connecting again to the same server with new credentials, it reuses
+ the old credentials, which may be expired, resulting in an HTTP 401. Then
+ the next connection succeeds. Perhaps errors clear the cache.
+
+ When tokens are short-lived or single-use, this bug produces an
+ interesting effect: every other WebSocket connection fails.
+
+ * Safari 14 ignores credentials entirely.
+
+Two other options are off the table:
+
+1. **Setting a custom HTTP header**
+
+ This would be the most elegant mechanism, solving all issues with the options
+ discussed above.
+
+ Unfortunately, it doesn't work because the `WebSocket API`_ doesn't support
+ `setting custom headers`_.
+
+.. _WebSocket API: https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API
+.. _setting custom headers: https://github.com/whatwg/html/issues/3062
+
+2. **Authenticating with a TLS certificate**
+
+ While this is suggested by the RFC, installing a TLS certificate is too far
+ from the mainstream experience of browser users. This could make sense in
+ high security contexts. I hope developers working on such projects don't
+ take security advice from the documentation of random open source projects.
+
+Let's experiment!
+-----------------
+
+The `experiments/authentication`_ directory demonstrates these techniques.
+
+Run the experiment in an environment where websockets is installed:
+
+.. _experiments/authentication: https://github.com/python-websockets/websockets/tree/main/experiments/authentication
+
+.. code-block:: console
+
+ $ python experiments/authentication/app.py
+ Running on http://localhost:8000/
+
+When you browse to the HTTP server at http://localhost:8000/ and you submit a
+username, the server creates a token and returns a testing web page.
+
+This page opens WebSocket connections to four WebSocket servers running on
+four different origins. It attempts to authenticate with the token in four
+different ways.
+
+First message
+.............
+
+As soon as the connection is open, the client sends a message containing the
+token:
+
+.. code-block:: javascript
+
+ const websocket = new WebSocket("ws://.../");
+ websocket.onopen = () => websocket.send(token);
+
+ // ...
+
+At the beginning of the connection handler, the server receives this message
+and authenticates the user. If authentication fails, the server closes the
+connection:
+
+.. code-block:: python
+
+ async def first_message_handler(websocket):
+ token = await websocket.recv()
+ user = get_user(token)
+ if user is None:
+ await websocket.close(CloseCode.INTERNAL_ERROR, "authentication failed")
+ return
+
+ ...
+
+Query parameter
+...............
+
+The client adds the token to the WebSocket URI in a query parameter before
+opening the connection:
+
+.. code-block:: javascript
+
+ const uri = `ws://.../?token=${token}`;
+ const websocket = new WebSocket(uri);
+
+ // ...
+
+The server intercepts the HTTP request, extracts the token and authenticates
+the user. If authentication fails, it returns an HTTP 401:
+
+.. code-block:: python
+
+ class QueryParamProtocol(websockets.WebSocketServerProtocol):
+ async def process_request(self, path, headers):
+ token = get_query_parameter(path, "token")
+ if token is None:
+ return http.HTTPStatus.UNAUTHORIZED, [], b"Missing token\n"
+
+ user = get_user(token)
+ if user is None:
+ return http.HTTPStatus.UNAUTHORIZED, [], b"Invalid token\n"
+
+ self.user = user
+
+ async def query_param_handler(websocket):
+ user = websocket.user
+
+ ...
+
+Cookie
+......
+
+The client sets a cookie containing the token before opening the connection.
+
+The cookie must be set by an iframe loaded from the same origin as the
+WebSocket server. This requires passing the token to this iframe.
+
+.. code-block:: javascript
+
+ // in main window
+ iframe.contentWindow.postMessage(token, "http://...");
+
+ // in iframe
+ document.cookie = `token=${data}; SameSite=Strict`;
+
+ // in main window
+ const websocket = new WebSocket("ws://.../");
+
+ // ...
+
+This sequence must be synchronized between the main window and the iframe.
+This involves several events. Look at the full implementation for details.
+
+The server intercepts the HTTP request, extracts the token and authenticates
+the user. If authentication fails, it returns an HTTP 401:
+
+.. code-block:: python
+
+ class CookieProtocol(websockets.WebSocketServerProtocol):
+ async def process_request(self, path, headers):
+ # Serve iframe on non-WebSocket requests
+ ...
+
+ token = get_cookie(headers.get("Cookie", ""), "token")
+ if token is None:
+ return http.HTTPStatus.UNAUTHORIZED, [], b"Missing token\n"
+
+ user = get_user(token)
+ if user is None:
+ return http.HTTPStatus.UNAUTHORIZED, [], b"Invalid token\n"
+
+ self.user = user
+
+ async def cookie_handler(websocket):
+ user = websocket.user
+
+ ...
+
+User information
+................
+
+The client adds the token to the WebSocket URI in user information before
+opening the connection:
+
+.. code-block:: javascript
+
+ const uri = `ws://token:${token}@.../`;
+ const websocket = new WebSocket(uri);
+
+ // ...
+
+Since HTTP Basic Auth is designed to accept a username and a password rather
+than a token, we send ``token`` as username and the token as password.
+
+The server intercepts the HTTP request, extracts the token and authenticates
+the user. If authentication fails, it returns an HTTP 401:
+
+.. code-block:: python
+
+ class UserInfoProtocol(websockets.BasicAuthWebSocketServerProtocol):
+ async def check_credentials(self, username, password):
+ if username != "token":
+ return False
+
+ user = get_user(password)
+ if user is None:
+ return False
+
+ self.user = user
+ return True
+
+ async def user_info_handler(websocket):
+ user = websocket.user
+
+ ...
+
+Machine-to-machine authentication
+---------------------------------
+
+When the WebSocket client is a standalone program rather than a script running
+in a browser, there are far fewer constraints. HTTP Authentication is the best
+solution in this scenario.
+
+To authenticate a websockets client with HTTP Basic Authentication
+(:rfc:`7617`), include the credentials in the URI:
+
+.. code-block:: python
+
+ async with websockets.connect(
+ f"wss://{username}:{password}@example.com",
+ ) as websocket:
+ ...
+
+(You must :func:`~urllib.parse.quote` ``username`` and ``password`` if they
+contain unsafe characters.)
+
+To authenticate a websockets client with HTTP Bearer Authentication
+(:rfc:`6750`), add a suitable ``Authorization`` header:
+
+.. code-block:: python
+
+ async with websockets.connect(
+ "wss://example.com",
+ extra_headers={"Authorization": f"Bearer {token}"}
+ ) as websocket:
+ ...
diff --git a/testing/web-platform/tests/tools/third_party/websockets/docs/topics/authentication.svg b/testing/web-platform/tests/tools/third_party/websockets/docs/topics/authentication.svg
new file mode 100644
index 0000000000..79b5fb8d56
--- /dev/null
+++ b/testing/web-platform/tests/tools/third_party/websockets/docs/topics/authentication.svg
@@ -0,0 +1,63 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="-514 74 644 380" width="644px" height="380px"><defs><style>@font-face{
+ font-family:"DIN Next";
+ font-weight: 400;
+ src:url("https://whimsical.com/fonts/139e8e25-0eea-4cb4-a5d4-79048803e73d.eot?#iefix");
+ src:url("https://whimsical.com/fonts/139e8e25-0eea-4cb4-a5d4-79048803e73d.eot?#iefix") format("eot"),url("https://whimsical.com/fonts/094b15e3-94bd-435b-a595-d40edfde661a.woff2") format("woff2"),url("https://whimsical.com/fonts/7e5fbe11-4858-4bd1-9ec6-a1d9f9d227aa.woff") format("woff"),url("https://whimsical.com/fonts/0f11eff9-9f05-46f5-9703-027c510065d7.ttf") format("truetype"),url("https://whimsical.com/fonts/48b61978-3f30-4274-823c-5cdcd1876918.svg#48b61978-3f30-4274-823c-5cdcd1876918") format("svg");
+}
+@font-face{
+ font-family:"DIN Next";
+ font-weight: 400;
+ font-style: italic;
+ src:url("https://whimsical.com/fonts/df24d5e8-5087-42fd-99b1-b16042d666c8.eot?#iefix");
+ src:url("https://whimsical.com/fonts/df24d5e8-5087-42fd-99b1-b16042d666c8.eot?#iefix") format("eot"),url("https://whimsical.com/fonts/46251881-ffe9-4bfb-99c7-d6ce3bebaf3e.woff2") format("woff2"),url("https://whimsical.com/fonts/790ebbf2-62c5-4a32-946f-99d405f9243e.woff") format("woff"),url("https://whimsical.com/fonts/d28199e6-0f4a-42df-97f4-606701c6f75a.ttf") format("truetype"),url("https://whimsical.com/fonts/37a462c0-d86e-492c-b9ab-35e6bd417f6c.svg#37a462c0-d86e-492c-b9ab-35e6bd417f6c") format("svg");
+}
+@font-face{
+ font-weight: 500;
+ font-family:"DIN Next";
+ src:url("https://whimsical.com/fonts/c5b058fc-55ce-4e06-a175-5c7d9a7e90f4.eot?#iefix");
+ src:url("https://whimsical.com/fonts/c5b058fc-55ce-4e06-a175-5c7d9a7e90f4.eot?#iefix") format("eot"),url("https://whimsical.com/fonts/7b29ae40-30ff-4f99-a2b9-cde88669fa2f.woff2") format("woff2"),url("https://whimsical.com/fonts/bf73077c-e354-4562-a085-f4703eb1d653.woff") format("woff"),url("https://whimsical.com/fonts/0ffa6947-5317-4d07-b525-14d08a028821.ttf") format("truetype"),url("https://whimsical.com/fonts/9e423e45-5450-4991-a157-dbe6cf61eb4e.svg#9e423e45-5450-4991-a157-dbe6cf61eb4e") format("svg");
+}
+@font-face{
+ font-family:"DIN Next";
+ font-weight: 500;
+ font-style: italic;
+ src:url("https://whimsical.com/fonts/9897c008-fd65-48a4-afc7-36de2fea97b9.eot?#iefix");
+ src:url("https://whimsical.com/fonts/9897c008-fd65-48a4-afc7-36de2fea97b9.eot?#iefix") format("eot"),url("https://whimsical.com/fonts/c7717981-647d-4b76-8817-33062e42d11f.woff2") format("woff2"),url("https://whimsical.com/fonts/b852cd4c-1255-40b1-a2be-73a9105b0155.woff") format("woff"),url("https://whimsical.com/fonts/821b00ad-e741-4e2d-af1a-85594367c8a2.ttf") format("truetype"),url("https://whimsical.com/fonts/d3e3b689-a6b0-45f2-b279-f5e194f87409.svg#d3e3b689-a6b0-45f2-b279-f5e194f87409") format("svg");
+}
+@font-face{
+ font-weight: 700;
+ font-family:"DIN Next";
+ src:url("https://whimsical.com/fonts/81cd3b08-fd39-4ae3-8d05-9d24709eba84.eot?#iefix");
+ src:url("https://whimsical.com/fonts/81cd3b08-fd39-4ae3-8d05-9d24709eba84.eot?#iefix") format("eot"),url("https://whimsical.com/fonts/31704504-4671-47a6-a61e-397f07410d91.woff2") format("woff2"),url("https://whimsical.com/fonts/b8a280da-481f-44a0-8d9c-1bc64bd7227c.woff") format("woff"),url("https://whimsical.com/fonts/276d122a-0fab-447b-9fc0-5d7fb0eafce2.ttf") format("truetype"),url("https://whimsical.com/fonts/8fb8273a-8213-4928-808b-b5bfaf3fd7e9.svg#8fb8273a-8213-4928-808b-b5bfaf3fd7e9") format("svg");
+}
+@font-face{
+ font-family:"DIN Next";
+ font-weight: 700;
+ font-style: italic;
+ src:url("https://whimsical.com/fonts/00cc6df3-ed32-4004-8dd8-1c576600a408.eot?#iefix");
+ src:url("https://whimsical.com/fonts/00cc6df3-ed32-4004-8dd8-1c576600a408.eot?#iefix") format("eot"),url("https://whimsical.com/fonts/4132c4c8-680c-4d6d-9251-a2da38503bbd.woff2") format("woff2"),url("https://whimsical.com/fonts/366401fe-6df4-47be-8f55-8a411cff0dd2.woff") format("woff"),url("https://whimsical.com/fonts/dbe4f7ba-fc16-44a6-a577-571620e9edaf.ttf") format("truetype"),url("https://whimsical.com/fonts/f874edca-ee87-4ccf-8b1d-921fbc3c1c36.svg#f874edca-ee87-4ccf-8b1d-921fbc3c1c36") format("svg");
+}
+
+@font-face {
+ font-family: 'PFDINMonoPro';
+ src:url('https://whimsical.com/fonts/PFDINMonoPro-Regular.woff') format('woff');
+ font-weight: 400;
+}
+@font-face {
+ font-family: 'PFDINMonoPro';
+ src:url('https://whimsical.com/fonts/PFDINMonoPro-Italic.woff') format('woff');
+ font-style: italic;
+ font-weight: 400;
+}
+@font-face {
+ font-family: 'PFDINMonoPro';
+ src:url('https://whimsical.com/fonts/PFDINMonoPro-Bold.woff') format('woff');
+ font-weight: 700;
+}
+@font-face {
+ font-family: 'PFDINMonoPro';
+ src:url('https://whimsical.com/fonts/PFDINMonoPro-BoldItalic.woff') format('woff');
+ font-style: italic;
+ font-weight: 700;
+}
+* {-webkit-font-smoothing: auto; -moz-osx-font-smoothing: auto;}@media print { svg { width: 100%; height: 100%; } }</style><filter id="fill-shadow" x="-100%" y="-100%" width="300%" height="300%"><feGaussianBlur in="SourceAlpha" stdDeviation="6"/><feOffset result="offsetblur" dx="0" dy="2"/><feComponentTransfer result="s0"><feFuncA type="linear" slope="0.16"/></feComponentTransfer><feMerge><feMergeNode in="s0"/><feMergeNode in="SourceGraphic"/></feMerge></filter><filter id="fill-light-shadow" x="-100%" y="-100%" width="300%" height="300%"><feGaussianBlur in="SourceAlpha" stdDeviation="6"/><feOffset result="offsetblur" dx="0" dy="2"/><feComponentTransfer result="s0"><feFuncA type="linear" slope="0.04"/></feComponentTransfer><feMerge><feMergeNode in="s0"/><feMergeNode in="SourceGraphic"/></feMerge></filter><filter id="image-shadow" x="-100%" y="-100%" width="300%" height="300%"><feGaussianBlur in="SourceAlpha" stdDeviation="8"/><feOffset result="offsetblur" dx="0" dy="3"/><feComponentTransfer result="s0"><feFuncA type="linear" slope="0.06"/></feComponentTransfer><feMerge><feMergeNode in="s0"/><feMergeNode in="SourceGraphic"/></feMerge></filter><filter id="frame-shadow" x="-100%" y="-100%" width="300%" height="300%"><feGaussianBlur in="SourceAlpha" stdDeviation="8"/><feOffset result="offsetblur" dx="0" dy="3"/><feComponentTransfer result="s0"><feFuncA type="linear" slope="0.06"/></feComponentTransfer><feMerge><feMergeNode in="s0"/><feMergeNode in="SourceGraphic"/></feMerge></filter><filter id="badge-shadow" x="-100%" y="-100%" width="300%" height="300%"><feGaussianBlur in="SourceAlpha" stdDeviation="6"/><feOffset result="offsetblur" dx="0" dy="2"/><feComponentTransfer result="s0"><feFuncA type="linear" slope="0.08"/></feComponentTransfer><feMerge><feMergeNode in="s0"/><feMergeNode in="SourceGraphic"/></feMerge></filter></defs><g><g><rect x="-504" y="228" width="192" height="72" rx="3" ry="3" fill="#cfeeeb"/><rect y="228" rx="3" stroke="#1AAE9F" fill="none" stroke-linejoin="round" width="192" stroke-linecap="round" stroke-width="2" x="-504" ry="3" height="72"/><text y="240" font-style="normal" font-size="18" font-weight="normal" fill="#293845" width="168" xml:space="preserve" x="-492" font-family="DIN Next, sans-serif" height="48"><tspan x="-408" y="258" text-anchor="middle" style="white-space:pre;">HTTP</tspan><tspan x="-408" y="282" text-anchor="middle" style="white-space:pre;">server</tspan></text></g></g><g><g><rect x="-72" y="228" width="192" height="72" rx="3" ry="3" fill="#d3e6f7"/><rect y="228" rx="3" stroke="#2C88D9" fill="none" stroke-linejoin="round" width="192" stroke-linecap="round" stroke-width="2" x="-72" ry="3" height="72"/><text y="240" font-style="normal" font-size="18" font-weight="normal" fill="#293845" width="168" xml:space="preserve" x="-60" font-family="DIN Next, sans-serif" height="48"><tspan x="24" y="258" text-anchor="middle" style="white-space:pre;">WebSocket</tspan><tspan x="24" y="282" text-anchor="middle" style="white-space:pre;">server</tspan></text></g></g><g><g><rect x="-288" y="84" width="192" height="72" rx="3" ry="3" fill="#d9dde0"/><rect y="84" rx="3" stroke="#4B5C6B" fill="none" stroke-linejoin="round" width="192" stroke-linecap="round" stroke-width="2" x="-288" ry="3" height="72"/><text y="96" font-style="normal" font-size="18" font-weight="normal" fill="#293845" width="168" xml:space="preserve" x="-276" font-family="DIN Next, sans-serif" height="48"><tspan x="-192" y="114" text-anchor="middle" style="white-space:pre;">web app</tspan><tspan x="-192" y="138" text-anchor="middle" style="white-space:pre;">in browser</tspan></text></g></g><g><g><path d="M-96,372c0,6.62741 -42.98074,12 -96,12c-53.01926,0 -96,-5.37259 -96,-12c0,-6.62741 42.98074,-12 96,-12c53.01926,0 96,5.37259 96,12c0,6.62741 0,53.37259 0,60c0,6.62741 -42.98074,12 -96,12c-53.01926,0 -96,-5.37259 -96,-12c0,-6.62741 0,-60 0,-60" fill="#e2cdf2"/><path d="M-96,372c0,6.62741 -42.98074,12 -96,12c-53.01926,0 -96,-5.37259 -96,-12c0,-6.62741 42.98074,-12 96,-12c53.01926,0 96,5.37259 96,12c0,6.62741 0,53.37259 0,60c0,6.62741 -42.98074,12 -96,12c-53.01926,0 -96,-5.37259 -96,-12c0,-6.62741 0,-60 0,-60" fill="none" stroke="#730FC3" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/><text y="396" font-style="normal" font-size="18" font-weight="normal" fill="#293845" width="168" xml:space="preserve" x="-276" font-family="DIN Next, sans-serif" height="24"><tspan x="-192" y="414" text-anchor="middle" style="white-space:pre;">user accounts</tspan></text></g></g><g><g><path d="M-400.46606,302.69069l37.26606,13.30931M-284.8,344l33.4991,11.96396" fill="none" stroke="#1AAE9F" stroke-linecap="round" stroke-linejoin="round" stroke-width="4"/><polygon points="-252.34924,349.70583 -247.53394,357.30931 -256.07648,360.14213" fill="#1AAE9F" stroke="#1AAE9F" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><text y="318" font-style="normal" font-size="18" font-weight="normal" fill="#293845" width="160" xml:space="preserve" x="-404" font-family="DIN Next, sans-serif" height="24"><tspan x="-324" y="336" text-anchor="middle" style="white-space:pre;">(1) authenticate user</tspan></text></g></g><g><g><path d="M-247.35316,159.15135l-43.98017,18.84865M-356.66667,206l-40.30359,17.27297" fill="none" stroke="#1AAE9F" stroke-linecap="round" stroke-linejoin="round" stroke-width="4"/><polygon points="-391.94549,227.14787 -400.64684,224.84865 -396.31086,216.96199" fill="#1AAE9F" stroke="#1AAE9F" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><text y="180" font-style="normal" font-size="18" font-weight="normal" fill="#293845" width="164" xml:space="preserve" x="-406" font-family="DIN Next, sans-serif" height="24"><tspan x="-324" y="198" text-anchor="middle" style="white-space:pre;">(2) obtain credentials</tspan></text></g></g><g><g><path d="M-136.64684,159.15135l43.98017,18.84865M-27.33333,206l40.30359,17.27297" fill="none" stroke="#2C88D9" stroke-linecap="round" stroke-linejoin="round" stroke-width="4"/><polygon points="12.31086,216.96199 16.64684,224.84865 7.94549,227.14787" fill="#2C88D9" stroke="#2C88D9" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><text y="180" font-style="normal" font-size="18" font-weight="normal" fill="#293845" width="153" xml:space="preserve" x="-136.5" font-family="DIN Next, sans-serif" height="24"><tspan x="-60" y="198" text-anchor="middle" style="white-space:pre;">(3) send credentials</tspan></text></g></g><g><g><path d="M16.46606,302.69069l-37.26606,13.30931M-99.2,344l-33.4991,11.96396" fill="none" stroke="#2C88D9" stroke-linecap="round" stroke-linejoin="round" stroke-width="4"/><polygon points="-127.92352,360.14213 -136.46606,357.30931 -131.65076,349.70583" fill="#2C88D9" stroke="#2C88D9" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><text y="318" font-style="normal" font-size="18" font-weight="normal" fill="#293845" width="160" xml:space="preserve" x="-140" font-family="DIN Next, sans-serif" height="24"><tspan x="-60" y="336" text-anchor="middle" style="white-space:pre;">(4) authenticate user</tspan></text></g></g></svg>
diff --git a/testing/web-platform/tests/tools/third_party/websockets/docs/topics/broadcast.rst b/testing/web-platform/tests/tools/third_party/websockets/docs/topics/broadcast.rst
new file mode 100644
index 0000000000..1acb372d4f
--- /dev/null
+++ b/testing/web-platform/tests/tools/third_party/websockets/docs/topics/broadcast.rst
@@ -0,0 +1,348 @@
+Broadcasting messages
+=====================
+
+.. currentmodule:: websockets
+
+
+.. admonition:: If you just want to send a message to all connected clients,
+ use :func:`broadcast`.
+ :class: tip
+
+ If you want to learn about its design in depth, continue reading this
+ document.
+
+WebSocket servers often send the same message to all connected clients or to a
+subset of clients for which the message is relevant.
+
+Let's explore options for broadcasting a message, explain the design
+of :func:`broadcast`, and discuss alternatives.
+
+For each option, we'll provide a connection handler called ``handler()`` and a
+function or coroutine called ``broadcast()`` that sends a message to all
+connected clients.
+
+Integrating them is left as an exercise for the reader. You could start with::
+
+ import asyncio
+ import websockets
+
+ async def handler(websocket):
+ ...
+
+ async def broadcast(message):
+ ...
+
+ async def broadcast_messages():
+ while True:
+ await asyncio.sleep(1)
+ message = ... # your application logic goes here
+ await broadcast(message)
+
+ async def main():
+ async with websockets.serve(handler, "localhost", 8765):
+ await broadcast_messages() # runs forever
+
+ if __name__ == "__main__":
+ asyncio.run(main())
+
+``broadcast_messages()`` must yield control to the event loop between each
+message, or else it will never let the server run. That's why it includes
+``await asyncio.sleep(1)``.
+
+A complete example is available in the `experiments/broadcast`_ directory.
+
+.. _experiments/broadcast: https://github.com/python-websockets/websockets/tree/main/experiments/broadcast
+
+The naive way
+-------------
+
+The most obvious way to send a message to all connected clients consists in
+keeping track of them and sending the message to each of them.
+
+Here's a connection handler that registers clients in a global variable::
+
+ CLIENTS = set()
+
+ async def handler(websocket):
+ CLIENTS.add(websocket)
+ try:
+ await websocket.wait_closed()
+ finally:
+ CLIENTS.remove(websocket)
+
+This implementation assumes that the client will never send any messages. If
+you'd rather not make this assumption, you can change::
+
+ await websocket.wait_closed()
+
+to::
+
+ async for _ in websocket:
+ pass
+
+Here's a coroutine that broadcasts a message to all clients::
+
+ async def broadcast(message):
+ for websocket in CLIENTS.copy():
+ try:
+ await websocket.send(message)
+ except websockets.ConnectionClosed:
+ pass
+
+There are two tricks in this version of ``broadcast()``.
+
+First, it makes a copy of ``CLIENTS`` before iterating it. Else, if a client
+connects or disconnects while ``broadcast()`` is running, the loop would fail
+with::
+
+ RuntimeError: Set changed size during iteration
+
+Second, it ignores :exc:`~exceptions.ConnectionClosed` exceptions because a
+client could disconnect between the moment ``broadcast()`` makes a copy of
+``CLIENTS`` and the moment it sends a message to this client. This is fine: a
+client that disconnected doesn't belongs to "all connected clients" anymore.
+
+The naive way can be very fast. Indeed, if all connections have enough free
+space in their write buffers, ``await websocket.send(message)`` writes the
+message and returns immediately, as it doesn't need to wait for the buffer to
+drain. In this case, ``broadcast()`` doesn't yield control to the event loop,
+which minimizes overhead.
+
+The naive way can also fail badly. If the write buffer of a connection reaches
+``write_limit``, ``broadcast()`` waits for the buffer to drain before sending
+the message to other clients. This can cause a massive drop in performance.
+
+As a consequence, this pattern works only when write buffers never fill up,
+which is usually outside of the control of the server.
+
+If you know for sure that you will never write more than ``write_limit`` bytes
+within ``ping_interval + ping_timeout``, then websockets will terminate slow
+connections before the write buffer has time to fill up.
+
+Don't set extreme ``write_limit``, ``ping_interval``, and ``ping_timeout``
+values to ensure that this condition holds. Set reasonable values and use the
+built-in :func:`broadcast` function instead.
+
+The concurrent way
+------------------
+
+The naive way didn't work well because it serialized writes, while the whole
+point of asynchronous I/O is to perform I/O concurrently.
+
+Let's modify ``broadcast()`` to send messages concurrently::
+
+ async def send(websocket, message):
+ try:
+ await websocket.send(message)
+ except websockets.ConnectionClosed:
+ pass
+
+ def broadcast(message):
+ for websocket in CLIENTS:
+ asyncio.create_task(send(websocket, message))
+
+We move the error handling logic in a new coroutine and we schedule
+a :class:`~asyncio.Task` to run it instead of executing it immediately.
+
+Since ``broadcast()`` no longer awaits coroutines, we can make it a function
+rather than a coroutine and do away with the copy of ``CLIENTS``.
+
+This version of ``broadcast()`` makes clients independent from one another: a
+slow client won't block others. As a side effect, it makes messages
+independent from one another.
+
+If you broadcast several messages, there is no strong guarantee that they will
+be sent in the expected order. Fortunately, the event loop runs tasks in the
+order in which they are created, so the order is correct in practice.
+
+Technically, this is an implementation detail of the event loop. However, it
+seems unlikely for an event loop to run tasks in an order other than FIFO.
+
+If you wanted to enforce the order without relying this implementation detail,
+you could be tempted to wait until all clients have received the message::
+
+ async def broadcast(message):
+ if CLIENTS: # asyncio.wait doesn't accept an empty list
+ await asyncio.wait([
+ asyncio.create_task(send(websocket, message))
+ for websocket in CLIENTS
+ ])
+
+However, this doesn't really work in practice. Quite often, it will block
+until the slowest client times out.
+
+Backpressure meets broadcast
+----------------------------
+
+At this point, it becomes apparent that backpressure, usually a good practice,
+doesn't work well when broadcasting a message to thousands of clients.
+
+When you're sending messages to a single client, you don't want to send them
+faster than the network can transfer them and the client accept them. This is
+why :meth:`~server.WebSocketServerProtocol.send` checks if the write buffer
+is full and, if it is, waits until it drain, giving the network and the
+client time to catch up. This provides backpressure.
+
+Without backpressure, you could pile up data in the write buffer until the
+server process runs out of memory and the operating system kills it.
+
+The :meth:`~server.WebSocketServerProtocol.send` API is designed to enforce
+backpressure by default. This helps users of websockets write robust programs
+even if they never heard about backpressure.
+
+For comparison, :class:`asyncio.StreamWriter` requires users to understand
+backpressure and to await :meth:`~asyncio.StreamWriter.drain` explicitly
+after each :meth:`~asyncio.StreamWriter.write`.
+
+When broadcasting messages, backpressure consists in slowing down all clients
+in an attempt to let the slowest client catch up. With thousands of clients,
+the slowest one is probably timing out and isn't going to receive the message
+anyway. So it doesn't make sense to synchronize with the slowest client.
+
+How do we avoid running out of memory when slow clients can't keep up with the
+broadcast rate, then? The most straightforward option is to disconnect them.
+
+If a client gets too far behind, eventually it reaches the limit defined by
+``ping_timeout`` and websockets terminates the connection. You can read the
+discussion of :doc:`keepalive and timeouts <./timeouts>` for details.
+
+How :func:`broadcast` works
+---------------------------
+
+The built-in :func:`broadcast` function is similar to the naive way. The main
+difference is that it doesn't apply backpressure.
+
+This provides the best performance by avoiding the overhead of scheduling and
+running one task per client.
+
+Also, when sending text messages, encoding to UTF-8 happens only once rather
+than once per client, providing a small performance gain.
+
+Per-client queues
+-----------------
+
+At this point, we deal with slow clients rather brutally: we disconnect then.
+
+Can we do better? For example, we could decide to skip or to batch messages,
+depending on how far behind a client is.
+
+To implement this logic, we can create a queue of messages for each client and
+run a task that gets messages from the queue and sends them to the client::
+
+ import asyncio
+
+ CLIENTS = set()
+
+ async def relay(queue, websocket):
+ while True:
+ # Implement custom logic based on queue.qsize() and
+ # websocket.transport.get_write_buffer_size() here.
+ message = await queue.get()
+ await websocket.send(message)
+
+ async def handler(websocket):
+ queue = asyncio.Queue()
+ relay_task = asyncio.create_task(relay(queue, websocket))
+ CLIENTS.add(queue)
+ try:
+ await websocket.wait_closed()
+ finally:
+ CLIENTS.remove(queue)
+ relay_task.cancel()
+
+Then we can broadcast a message by pushing it to all queues::
+
+ def broadcast(message):
+ for queue in CLIENTS:
+ queue.put_nowait(message)
+
+The queues provide an additional buffer between the ``broadcast()`` function
+and clients. This makes it easier to support slow clients without excessive
+memory usage because queued messages aren't duplicated to write buffers
+until ``relay()`` processes them.
+
+Publish–subscribe
+-----------------
+
+Can we avoid centralizing the list of connected clients in a global variable?
+
+If each client subscribes to a stream a messages, then broadcasting becomes as
+simple as publishing a message to the stream.
+
+Here's a message stream that supports multiple consumers::
+
+ class PubSub:
+ def __init__(self):
+ self.waiter = asyncio.Future()
+
+ def publish(self, value):
+ waiter, self.waiter = self.waiter, asyncio.Future()
+ waiter.set_result((value, self.waiter))
+
+ async def subscribe(self):
+ waiter = self.waiter
+ while True:
+ value, waiter = await waiter
+ yield value
+
+ __aiter__ = subscribe
+
+ PUBSUB = PubSub()
+
+The stream is implemented as a linked list of futures. It isn't necessary to
+synchronize consumers. They can read the stream at their own pace,
+independently from one another. Once all consumers read a message, there are
+no references left, therefore the garbage collector deletes it.
+
+The connection handler subscribes to the stream and sends messages::
+
+ async def handler(websocket):
+ async for message in PUBSUB:
+ await websocket.send(message)
+
+The broadcast function publishes to the stream::
+
+ def broadcast(message):
+ PUBSUB.publish(message)
+
+Like per-client queues, this version supports slow clients with limited memory
+usage. Unlike per-client queues, it makes it difficult to tell how far behind
+a client is. The ``PubSub`` class could be extended or refactored to provide
+this information.
+
+The ``for`` loop is gone from this version of the ``broadcast()`` function.
+However, there's still a ``for`` loop iterating on all clients hidden deep
+inside :mod:`asyncio`. When ``publish()`` sets the result of the ``waiter``
+future, :mod:`asyncio` loops on callbacks registered with this future and
+schedules them. This is how connection handlers receive the next value from
+the asynchronous iterator returned by ``subscribe()``.
+
+Performance considerations
+--------------------------
+
+The built-in :func:`broadcast` function sends all messages without yielding
+control to the event loop. So does the naive way when the network and clients
+are fast and reliable.
+
+For each client, a WebSocket frame is prepared and sent to the network. This
+is the minimum amount of work required to broadcast a message.
+
+It would be tempting to prepare a frame and reuse it for all connections.
+However, this isn't possible in general for two reasons:
+
+* Clients can negotiate different extensions. You would have to enforce the
+ same extensions with the same parameters. For example, you would have to
+ select some compression settings and reject clients that cannot support
+ these settings.
+
+* Extensions can be stateful, producing different encodings of the same
+ message depending on previous messages. For example, you would have to
+ disable context takeover to make compression stateless, resulting in poor
+ compression rates.
+
+All other patterns discussed above yield control to the event loop once per
+client because messages are sent by different tasks. This makes them slower
+than the built-in :func:`broadcast` function.
+
+There is no major difference between the performance of per-client queues and
+publish–subscribe.
diff --git a/testing/web-platform/tests/tools/third_party/websockets/docs/topics/compression.rst b/testing/web-platform/tests/tools/third_party/websockets/docs/topics/compression.rst
new file mode 100644
index 0000000000..eaf99070db
--- /dev/null
+++ b/testing/web-platform/tests/tools/third_party/websockets/docs/topics/compression.rst
@@ -0,0 +1,222 @@
+Compression
+===========
+
+.. currentmodule:: websockets.extensions.permessage_deflate
+
+Most WebSocket servers exchange JSON messages because they're convenient to
+parse and serialize in a browser. These messages contain text data and tend to
+be repetitive.
+
+This makes the stream of messages highly compressible. Enabling compression
+can reduce network traffic by more than 80%.
+
+There's a standard for compressing messages. :rfc:`7692` defines WebSocket
+Per-Message Deflate, a compression extension based on the Deflate_ algorithm.
+
+.. _Deflate: https://en.wikipedia.org/wiki/Deflate
+
+Configuring compression
+-----------------------
+
+:func:`~websockets.client.connect` and :func:`~websockets.server.serve` enable
+compression by default because the reduction in network bandwidth is usually
+worth the additional memory and CPU cost.
+
+If you want to disable compression, set ``compression=None``::
+
+ import websockets
+
+ websockets.connect(..., compression=None)
+
+ websockets.serve(..., compression=None)
+
+If you want to customize compression settings, you can enable the Per-Message
+Deflate extension explicitly with :class:`ClientPerMessageDeflateFactory` or
+:class:`ServerPerMessageDeflateFactory`::
+
+ import websockets
+ from websockets.extensions import permessage_deflate
+
+ websockets.connect(
+ ...,
+ extensions=[
+ permessage_deflate.ClientPerMessageDeflateFactory(
+ server_max_window_bits=11,
+ client_max_window_bits=11,
+ compress_settings={"memLevel": 4},
+ ),
+ ],
+ )
+
+ websockets.serve(
+ ...,
+ extensions=[
+ permessage_deflate.ServerPerMessageDeflateFactory(
+ server_max_window_bits=11,
+ client_max_window_bits=11,
+ compress_settings={"memLevel": 4},
+ ),
+ ],
+ )
+
+The Window Bits and Memory Level values in these examples reduce memory usage
+at the expense of compression rate.
+
+Compression settings
+--------------------
+
+When a client and a server enable the Per-Message Deflate extension, they
+negotiate two parameters to guarantee compatibility between compression and
+decompression. These parameters affect the trade-off between compression rate
+and memory usage for both sides.
+
+* **Context Takeover** means that the compression context is retained between
+ messages. In other words, compression is applied to the stream of messages
+ rather than to each message individually.
+
+ Context takeover should remain enabled to get good performance on
+ applications that send a stream of messages with similar structure,
+ that is, most applications.
+
+ This requires retaining the compression context and state between messages,
+ which increases the memory footprint of a connection.
+
+* **Window Bits** controls the size of the compression context. It must be
+ an integer between 9 (lowest memory usage) and 15 (best compression).
+ Setting it to 8 is possible but rejected by some versions of zlib.
+
+ On the server side, websockets defaults to 12. Specifically, the compression
+ window size (server to client) is always 12 while the decompression window
+ (client to server) size may be 12 or 15 depending on whether the client
+ supports configuring it.
+
+ On the client side, websockets lets the server pick a suitable value, which
+ has the same effect as defaulting to 15.
+
+:mod:`zlib` offers additional parameters for tuning compression. They control
+the trade-off between compression rate, memory usage, and CPU usage only for
+compressing. They're transparent for decompressing. Unless mentioned
+otherwise, websockets inherits defaults of :func:`~zlib.compressobj`.
+
+* **Memory Level** controls the size of the compression state. It must be an
+ integer between 1 (lowest memory usage) and 9 (best compression).
+
+ websockets defaults to 5. This is lower than zlib's default of 8. Not only
+ does a lower memory level reduce memory usage, but it can also increase
+ speed thanks to memory locality.
+
+* **Compression Level** controls the effort to optimize compression. It must
+ be an integer between 1 (lowest CPU usage) and 9 (best compression).
+
+* **Strategy** selects the compression strategy. The best choice depends on
+ the type of data being compressed.
+
+
+Tuning compression
+------------------
+
+For servers
+...........
+
+By default, websockets enables compression with conservative settings that
+optimize memory usage at the cost of a slightly worse compression rate:
+Window Bits = 12 and Memory Level = 5. This strikes a good balance for small
+messages that are typical of WebSocket servers.
+
+Here's how various compression settings affect memory usage of a single
+connection on a 64-bit system, as well a benchmark of compressed size and
+compression time for a corpus of small JSON documents.
+
+=========== ============ ============ ================ ================
+Window Bits Memory Level Memory usage Size vs. default Time vs. default
+=========== ============ ============ ================ ================
+15 8 322 KiB -4.0% +15%
+14 7 178 KiB -2.6% +10%
+13 6 106 KiB -1.4% +5%
+**12** **5** **70 KiB** **=** **=**
+11 4 52 KiB +3.7% -5%
+10 3 43 KiB +90% +50%
+9 2 39 KiB +160% +100%
+— — 19 KiB +452% —
+=========== ============ ============ ================ ================
+
+Window Bits and Memory Level don't have to move in lockstep. However, other
+combinations don't yield significantly better results than those shown above.
+
+Compressed size and compression time depend heavily on the kind of messages
+exchanged by the application so this example may not apply to your use case.
+
+You can adapt `compression/benchmark.py`_ by creating a list of typical
+messages and passing it to the ``_run`` function.
+
+Window Bits = 11 and Memory Level = 4 looks like the sweet spot in this table.
+
+websockets defaults to Window Bits = 12 and Memory Level = 5 to stay away from
+Window Bits = 10 or Memory Level = 3 where performance craters, raising doubts
+on what could happen at Window Bits = 11 and Memory Level = 4 on a different
+corpus.
+
+Defaults must be safe for all applications, hence a more conservative choice.
+
+.. _compression/benchmark.py: https://github.com/python-websockets/websockets/blob/main/experiments/compression/benchmark.py
+
+The benchmark focuses on compression because it's more expensive than
+decompression. Indeed, leaving aside small allocations, theoretical memory
+usage is:
+
+* ``(1 << (windowBits + 2)) + (1 << (memLevel + 9))`` for compression;
+* ``1 << windowBits`` for decompression.
+
+CPU usage is also higher for compression than decompression.
+
+While it's always possible for a server to use a smaller window size for
+compressing outgoing messages, using a smaller window size for decompressing
+incoming messages requires collaboration from clients.
+
+When a client doesn't support configuring the size of its compression window,
+websockets enables compression with the largest possible decompression window.
+In most use cases, this is more efficient than disabling compression both ways.
+
+If you are very sensitive to memory usage, you can reverse this behavior by
+setting the ``require_client_max_window_bits`` parameter of
+:class:`ServerPerMessageDeflateFactory` to ``True``.
+
+For clients
+...........
+
+By default, websockets enables compression with Memory Level = 5 but leaves
+the Window Bits setting up to the server.
+
+There's two good reasons and one bad reason for not optimizing the client side
+like the server side:
+
+1. If the maintainers of a server configured some optimized settings, we don't
+ want to override them with more restrictive settings.
+
+2. Optimizing memory usage doesn't matter very much for clients because it's
+ uncommon to open thousands of client connections in a program.
+
+3. On a more pragmatic note, some servers misbehave badly when a client
+ configures compression settings. `AWS API Gateway`_ is the worst offender.
+
+ .. _AWS API Gateway: https://github.com/python-websockets/websockets/issues/1065
+
+ Unfortunately, even though websockets is right and AWS is wrong, many users
+ jump to the conclusion that websockets doesn't work.
+
+ Until the ecosystem levels up, interoperability with buggy servers seems
+ more valuable than optimizing memory usage.
+
+
+Further reading
+---------------
+
+This `blog post by Ilya Grigorik`_ provides more details about how compression
+settings affect memory usage and how to optimize them.
+
+.. _blog post by Ilya Grigorik: https://www.igvita.com/2013/11/27/configuring-and-optimizing-websocket-compression/
+
+This `experiment by Peter Thorson`_ recommends Window Bits = 11 and Memory
+Level = 4 for optimizing memory usage.
+
+.. _experiment by Peter Thorson: https://mailarchive.ietf.org/arch/msg/hybi/F9t4uPufVEy8KBLuL36cZjCmM_Y/
diff --git a/testing/web-platform/tests/tools/third_party/websockets/docs/topics/data-flow.svg b/testing/web-platform/tests/tools/third_party/websockets/docs/topics/data-flow.svg
new file mode 100644
index 0000000000..22a198ed83
--- /dev/null
+++ b/testing/web-platform/tests/tools/third_party/websockets/docs/topics/data-flow.svg
@@ -0,0 +1,63 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="-382 -214 500 464" width="500px" height="464px"><defs><style>@font-face{
+ font-family:"DIN Next";
+ font-weight: 400;
+ src:url("https://whimsical.com/fonts/139e8e25-0eea-4cb4-a5d4-79048803e73d.eot?#iefix");
+ src:url("https://whimsical.com/fonts/139e8e25-0eea-4cb4-a5d4-79048803e73d.eot?#iefix") format("eot"),url("https://whimsical.com/fonts/094b15e3-94bd-435b-a595-d40edfde661a.woff2") format("woff2"),url("https://whimsical.com/fonts/7e5fbe11-4858-4bd1-9ec6-a1d9f9d227aa.woff") format("woff"),url("https://whimsical.com/fonts/0f11eff9-9f05-46f5-9703-027c510065d7.ttf") format("truetype"),url("https://whimsical.com/fonts/48b61978-3f30-4274-823c-5cdcd1876918.svg#48b61978-3f30-4274-823c-5cdcd1876918") format("svg");
+}
+@font-face{
+ font-family:"DIN Next";
+ font-weight: 400;
+ font-style: italic;
+ src:url("https://whimsical.com/fonts/df24d5e8-5087-42fd-99b1-b16042d666c8.eot?#iefix");
+ src:url("https://whimsical.com/fonts/df24d5e8-5087-42fd-99b1-b16042d666c8.eot?#iefix") format("eot"),url("https://whimsical.com/fonts/46251881-ffe9-4bfb-99c7-d6ce3bebaf3e.woff2") format("woff2"),url("https://whimsical.com/fonts/790ebbf2-62c5-4a32-946f-99d405f9243e.woff") format("woff"),url("https://whimsical.com/fonts/d28199e6-0f4a-42df-97f4-606701c6f75a.ttf") format("truetype"),url("https://whimsical.com/fonts/37a462c0-d86e-492c-b9ab-35e6bd417f6c.svg#37a462c0-d86e-492c-b9ab-35e6bd417f6c") format("svg");
+}
+@font-face{
+ font-weight: 500;
+ font-family:"DIN Next";
+ src:url("https://whimsical.com/fonts/c5b058fc-55ce-4e06-a175-5c7d9a7e90f4.eot?#iefix");
+ src:url("https://whimsical.com/fonts/c5b058fc-55ce-4e06-a175-5c7d9a7e90f4.eot?#iefix") format("eot"),url("https://whimsical.com/fonts/7b29ae40-30ff-4f99-a2b9-cde88669fa2f.woff2") format("woff2"),url("https://whimsical.com/fonts/bf73077c-e354-4562-a085-f4703eb1d653.woff") format("woff"),url("https://whimsical.com/fonts/0ffa6947-5317-4d07-b525-14d08a028821.ttf") format("truetype"),url("https://whimsical.com/fonts/9e423e45-5450-4991-a157-dbe6cf61eb4e.svg#9e423e45-5450-4991-a157-dbe6cf61eb4e") format("svg");
+}
+@font-face{
+ font-family:"DIN Next";
+ font-weight: 500;
+ font-style: italic;
+ src:url("https://whimsical.com/fonts/9897c008-fd65-48a4-afc7-36de2fea97b9.eot?#iefix");
+ src:url("https://whimsical.com/fonts/9897c008-fd65-48a4-afc7-36de2fea97b9.eot?#iefix") format("eot"),url("https://whimsical.com/fonts/c7717981-647d-4b76-8817-33062e42d11f.woff2") format("woff2"),url("https://whimsical.com/fonts/b852cd4c-1255-40b1-a2be-73a9105b0155.woff") format("woff"),url("https://whimsical.com/fonts/821b00ad-e741-4e2d-af1a-85594367c8a2.ttf") format("truetype"),url("https://whimsical.com/fonts/d3e3b689-a6b0-45f2-b279-f5e194f87409.svg#d3e3b689-a6b0-45f2-b279-f5e194f87409") format("svg");
+}
+@font-face{
+ font-weight: 700;
+ font-family:"DIN Next";
+ src:url("https://whimsical.com/fonts/81cd3b08-fd39-4ae3-8d05-9d24709eba84.eot?#iefix");
+ src:url("https://whimsical.com/fonts/81cd3b08-fd39-4ae3-8d05-9d24709eba84.eot?#iefix") format("eot"),url("https://whimsical.com/fonts/31704504-4671-47a6-a61e-397f07410d91.woff2") format("woff2"),url("https://whimsical.com/fonts/b8a280da-481f-44a0-8d9c-1bc64bd7227c.woff") format("woff"),url("https://whimsical.com/fonts/276d122a-0fab-447b-9fc0-5d7fb0eafce2.ttf") format("truetype"),url("https://whimsical.com/fonts/8fb8273a-8213-4928-808b-b5bfaf3fd7e9.svg#8fb8273a-8213-4928-808b-b5bfaf3fd7e9") format("svg");
+}
+@font-face{
+ font-family:"DIN Next";
+ font-weight: 700;
+ font-style: italic;
+ src:url("https://whimsical.com/fonts/00cc6df3-ed32-4004-8dd8-1c576600a408.eot?#iefix");
+ src:url("https://whimsical.com/fonts/00cc6df3-ed32-4004-8dd8-1c576600a408.eot?#iefix") format("eot"),url("https://whimsical.com/fonts/4132c4c8-680c-4d6d-9251-a2da38503bbd.woff2") format("woff2"),url("https://whimsical.com/fonts/366401fe-6df4-47be-8f55-8a411cff0dd2.woff") format("woff"),url("https://whimsical.com/fonts/dbe4f7ba-fc16-44a6-a577-571620e9edaf.ttf") format("truetype"),url("https://whimsical.com/fonts/f874edca-ee87-4ccf-8b1d-921fbc3c1c36.svg#f874edca-ee87-4ccf-8b1d-921fbc3c1c36") format("svg");
+}
+
+@font-face {
+ font-family: 'PFDINMonoPro';
+ src:url('https://whimsical.com/fonts/PFDINMonoPro-Regular.woff') format('woff');
+ font-weight: 400;
+}
+@font-face {
+ font-family: 'PFDINMonoPro';
+ src:url('https://whimsical.com/fonts/PFDINMonoPro-Italic.woff') format('woff');
+ font-style: italic;
+ font-weight: 400;
+}
+@font-face {
+ font-family: 'PFDINMonoPro';
+ src:url('https://whimsical.com/fonts/PFDINMonoPro-Bold.woff') format('woff');
+ font-weight: 700;
+}
+@font-face {
+ font-family: 'PFDINMonoPro';
+ src:url('https://whimsical.com/fonts/PFDINMonoPro-BoldItalic.woff') format('woff');
+ font-style: italic;
+ font-weight: 700;
+}
+* {-webkit-font-smoothing: auto; -moz-osx-font-smoothing: auto;}@media print { svg { width: 100%; height: 100%; } }</style><filter id="fill-shadow" x="-100%" y="-100%" width="300%" height="300%"><feGaussianBlur in="SourceAlpha" stdDeviation="6"/><feOffset result="offsetblur" dx="0" dy="2"/><feComponentTransfer result="s0"><feFuncA type="linear" slope="0.16"/></feComponentTransfer><feMerge><feMergeNode in="s0"/><feMergeNode in="SourceGraphic"/></feMerge></filter><filter id="fill-light-shadow" x="-100%" y="-100%" width="300%" height="300%"><feGaussianBlur in="SourceAlpha" stdDeviation="6"/><feOffset result="offsetblur" dx="0" dy="2"/><feComponentTransfer result="s0"><feFuncA type="linear" slope="0.04"/></feComponentTransfer><feMerge><feMergeNode in="s0"/><feMergeNode in="SourceGraphic"/></feMerge></filter><filter id="image-shadow" x="-100%" y="-100%" width="300%" height="300%"><feGaussianBlur in="SourceAlpha" stdDeviation="8"/><feOffset result="offsetblur" dx="0" dy="3"/><feComponentTransfer result="s0"><feFuncA type="linear" slope="0.06"/></feComponentTransfer><feMerge><feMergeNode in="s0"/><feMergeNode in="SourceGraphic"/></feMerge></filter><filter id="frame-shadow" x="-100%" y="-100%" width="300%" height="300%"><feGaussianBlur in="SourceAlpha" stdDeviation="8"/><feOffset result="offsetblur" dx="0" dy="3"/><feComponentTransfer result="s0"><feFuncA type="linear" slope="0.06"/></feComponentTransfer><feMerge><feMergeNode in="s0"/><feMergeNode in="SourceGraphic"/></feMerge></filter><filter id="badge-shadow" x="-100%" y="-100%" width="300%" height="300%"><feGaussianBlur in="SourceAlpha" stdDeviation="6"/><feOffset result="offsetblur" dx="0" dy="2"/><feComponentTransfer result="s0"><feFuncA type="linear" slope="0.08"/></feComponentTransfer><feMerge><feMergeNode in="s0"/><feMergeNode in="SourceGraphic"/></feMerge></filter></defs><g><g><rect x="-372" y="-12" width="480" height="72" rx="3" ry="3" fill="#f6d8dd"/><rect y="-12" rx="3" stroke="#D3455B" fill="none" stroke-linejoin="round" width="480" stroke-linecap="round" stroke-width="2" x="-372" ry="3" height="72"/><text y="12" font-style="normal" font-size="18" font-weight="normal" fill="#293845" width="456" xml:space="preserve" x="-360" font-family="DIN Next, sans-serif" height="24"><tspan x="-132" y="30" text-anchor="middle" style="white-space:pre;">Integration layer</tspan></text></g></g><g><g><rect x="-372" y="168" width="480" height="72" rx="3" ry="3" fill="#d3e6f7"/><rect y="168" rx="3" stroke="#2C88D9" fill="none" stroke-linejoin="round" width="480" stroke-linecap="round" stroke-width="2" x="-372" ry="3" height="72"/><text y="192" font-style="normal" font-size="18" font-weight="normal" fill="#293845" width="456" xml:space="preserve" x="-360" font-family="DIN Next, sans-serif" height="24"><tspan x="-132" y="210" text-anchor="middle" style="white-space:pre;">Sans-I/O layer</tspan></text></g></g><g><g><rect x="-372" y="-192" width="216" height="72" rx="3" ry="3" fill="#cfeeeb"/><rect y="-192" rx="3" stroke="#1AAE9F" fill="none" stroke-linejoin="round" width="216" stroke-linecap="round" stroke-width="2" x="-372" ry="3" height="72"/><text y="-168" font-style="normal" font-size="18" font-weight="normal" fill="#293845" width="192" xml:space="preserve" x="-360" font-family="DIN Next, sans-serif" height="24"><tspan x="-264" y="-150" text-anchor="middle" style="white-space:pre;">Application</tspan></text></g></g><g><g><path d="M-324,-20v-20M-324,-92v-16" fill="none" stroke="#788896" stroke-linecap="round" stroke-linejoin="round" stroke-width="4"/><polygon points="-329.54095,-104.9079 -324,-112 -318.45905,-104.9079" fill="#788896" stroke="#788896" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><text y="-90" font-style="normal" font-size="18" font-weight="normal" fill="#293845" width="79" xml:space="preserve" x="-363.5" font-family="DIN Next, sans-serif" height="48"><tspan x="-324" y="-72" text-anchor="middle" style="white-space:pre;">receive</tspan><tspan x="-324" y="-48" text-anchor="middle" style="white-space:pre;">messages</tspan></text></g></g><g><g><path d="M-204,-112l0,20M-204,-40v16" fill="none" stroke="#788896" stroke-linecap="round" stroke-linejoin="round" stroke-width="4"/><polygon points="-198.45905,-27.0921 -204,-20 -209.54095,-27.0921" fill="#788896" stroke="#788896" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><text y="-90" font-style="normal" font-size="18" font-weight="normal" fill="#293845" width="79" xml:space="preserve" x="-243.5" font-family="DIN Next, sans-serif" height="48"><tspan x="-204" y="-72" text-anchor="middle" style="white-space:pre;">send</tspan><tspan x="-204" y="-48" text-anchor="middle" style="white-space:pre;">messages</tspan></text></g></g><g><g><path d="M96.94345,-173.14258c4.53106,1.57532 8.08271,4.56716 9.84726,8.29515c1.76456,3.72799 1.59205,7.87526 -0.4783,11.4987c1.926,5.62898 0.07743,11.66356 -4.87418,15.91158c-4.95161,4.24802 -12.28578,6.09138 -19.33826,4.86046c-2.37232,7.09867 -9.03221,12.73751 -17.68526,14.97387c-8.65305,2.23636 -18.11659,0.76458 -25.13032,-3.90829c-8.39761,8.37233 -20.93327,13.315 -34.25257,13.5054c-13.3193,0.1904 -26.06267,-4.39089 -34.82012,-12.51799c-15.68591,7.18472 -35.58915,2.88148 -44.76245,-9.678c-7.38426,1.34031 -15.11657,-0.22307 -20.85552,-4.21672c-5.73895,-3.99365 -8.80965,-9.94793 -8.28226,-16.05983c-3.457,-2.81303 -4.97104,-6.82861 -4.04383,-10.72516c0.92721,-3.89655 4.17556,-7.16931 8.67598,-8.74119c1.08624,-6.50166 5.74572,-12.25693 12.67613,-15.65724c6.93042,-3.40031 15.38838,-4.08092 23.00992,-1.85161c7.11534,-8.80068 18.56556,-14.69575 31.42744,-16.1802c12.86188,-1.48445 25.89062,1.58538 35.76002,8.42577c6.64655,-4.34187 15.16482,-6.34654 23.65054,-5.56587c8.48572,0.78068 16.23125,4.28161 21.50507,9.72014c19.34894,-5.64384 40.71077,2.33211 47.97072,17.91102z" fill="#e3e6e9"/><path d="M96.94345,-173.14258c4.53106,1.57532 8.08271,4.56716 9.84726,8.29515c1.76456,3.72799 1.59205,7.87526 -0.4783,11.4987c1.926,5.62898 0.07743,11.66356 -4.87418,15.91158c-4.95161,4.24802 -12.28578,6.09138 -19.33826,4.86046c-2.37232,7.09867 -9.03221,12.73751 -17.68526,14.97387c-8.65305,2.23636 -18.11659,0.76458 -25.13032,-3.90829c-8.39761,8.37233 -20.93327,13.315 -34.25257,13.5054c-13.3193,0.1904 -26.06267,-4.39089 -34.82012,-12.51799c-15.68591,7.18472 -35.58915,2.88148 -44.76245,-9.678c-7.38426,1.34031 -15.11657,-0.22307 -20.85552,-4.21672c-5.73895,-3.99365 -8.80965,-9.94793 -8.28226,-16.05983c-3.457,-2.81303 -4.97104,-6.82861 -4.04383,-10.72516c0.92721,-3.89655 4.17556,-7.16931 8.67598,-8.74119c1.08624,-6.50166 5.74572,-12.25693 12.67613,-15.65724c6.93042,-3.40031 15.38838,-4.08092 23.00992,-1.85161c7.11534,-8.80068 18.56556,-14.69575 31.42744,-16.1802c12.86188,-1.48445 25.89062,1.58538 35.76002,8.42577c6.64655,-4.34187 15.16482,-6.34654 23.65054,-5.56587c8.48572,0.78068 16.23125,4.28161 21.50507,9.72014c19.34894,-5.64384 40.71077,2.33211 47.97072,17.91102z" fill="none" stroke="#788896" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/><text y="-168" font-style="normal" font-size="18" font-weight="normal" fill="#293845" width="192" xml:space="preserve" x="-96" font-family="DIN Next, sans-serif" height="24"><tspan x="0" y="-150" text-anchor="middle" style="white-space:pre;">Network</tspan></text></g></g><g><g><path d="M-60,-20v-20M-60,-92v-15.53727" fill="none" stroke="#788896" stroke-linecap="round" stroke-linejoin="round" stroke-width="4"/><polygon points="-65.54095,-104.44518 -60,-111.53727 -54.45905,-104.44518" fill="#788896" stroke="#788896" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><text y="-90" font-style="normal" font-size="18" font-weight="normal" fill="#293845" width="38" xml:space="preserve" x="-79" font-family="DIN Next, sans-serif" height="48"><tspan x="-60" y="-72" text-anchor="middle" style="white-space:pre;">send</tspan><tspan x="-60" y="-48" text-anchor="middle" style="white-space:pre;">data</tspan></text></g></g><g><g><path d="M60,-108.78722v18.78722M60,-38v14" fill="none" stroke="#788896" stroke-linecap="round" stroke-linejoin="round" stroke-width="4"/><polygon points="65.54095,-27.0921 60,-20 54.45905,-27.0921" fill="#788896" stroke="#788896" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><text y="-88" font-style="normal" font-size="18" font-weight="normal" fill="#293845" width="56" xml:space="preserve" x="32" font-family="DIN Next, sans-serif" height="48"><tspan x="60" y="-70" text-anchor="middle" style="white-space:pre;">receive</tspan><tspan x="60" y="-46" text-anchor="middle" style="white-space:pre;">data</tspan></text></g></g><g><g><path d="M60,68v20M60,140v16" fill="none" stroke="#788896" stroke-linecap="round" stroke-linejoin="round" stroke-width="4"/><polygon points="65.54095,152.9079 60,160 54.45905,152.9079" fill="#788896" stroke="#788896" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><text y="90" font-style="normal" font-size="18" font-weight="normal" fill="#293845" width="56" xml:space="preserve" x="32" font-family="DIN Next, sans-serif" height="48"><tspan x="60" y="108" text-anchor="middle" style="white-space:pre;">receive</tspan><tspan x="60" y="132" text-anchor="middle" style="white-space:pre;">bytes</tspan></text></g></g><g><g><path d="M-60,160v-20M-60,88v-16" fill="none" stroke="#788896" stroke-linecap="round" stroke-linejoin="round" stroke-width="4"/><polygon points="-65.54095,75.0921 -60,68 -54.45905,75.0921" fill="#788896" stroke="#788896" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><text y="90" font-style="normal" font-size="18" font-weight="normal" fill="#293845" width="42" xml:space="preserve" x="-81" font-family="DIN Next, sans-serif" height="48"><tspan x="-60" y="108" text-anchor="middle" style="white-space:pre;">send</tspan><tspan x="-60" y="132" text-anchor="middle" style="white-space:pre;">bytes</tspan></text></g></g><g><g><path d="M-212,42" fill="none" stroke="#788896" stroke-linecap="round" stroke-linejoin="round" stroke-width="4"/><polygon points="-212,42 -212,42 -212,42" fill="#788896" stroke="#788896" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></g></g><g><g><path d="M-204,68v20M-204,140v16" fill="none" stroke="#788896" stroke-linecap="round" stroke-linejoin="round" stroke-width="4"/><polygon points="-198.45905,152.9079 -204,160 -209.54095,152.9079" fill="#788896" stroke="#788896" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><text y="90" font-style="normal" font-size="18" font-weight="normal" fill="#293845" width="52" xml:space="preserve" x="-230" font-family="DIN Next, sans-serif" height="48"><tspan x="-204" y="108" text-anchor="middle" style="white-space:pre;">send</tspan><tspan x="-204" y="132" text-anchor="middle" style="white-space:pre;">events</tspan></text></g></g><g><g><path d="M-324,160l0,-18M-324,90v-14" fill="none" stroke="#788896" stroke-linecap="round" stroke-linejoin="round" stroke-width="4"/><polygon points="-329.54095,79.0921 -324,72 -318.45905,79.0921" fill="#788896" stroke="#788896" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><text y="92" font-style="normal" font-size="18" font-weight="normal" fill="#293845" width="56" xml:space="preserve" x="-352" font-family="DIN Next, sans-serif" height="48"><tspan x="-324" y="110" text-anchor="middle" style="white-space:pre;">receive</tspan><tspan x="-324" y="134" text-anchor="middle" style="white-space:pre;">events</tspan></text></g></g></svg>
diff --git a/testing/web-platform/tests/tools/third_party/websockets/docs/topics/deployment.rst b/testing/web-platform/tests/tools/third_party/websockets/docs/topics/deployment.rst
new file mode 100644
index 0000000000..2a1fe9a785
--- /dev/null
+++ b/testing/web-platform/tests/tools/third_party/websockets/docs/topics/deployment.rst
@@ -0,0 +1,181 @@
+Deployment
+==========
+
+.. currentmodule:: websockets
+
+When you deploy your websockets server to production, at a high level, your
+architecture will almost certainly look like the following diagram:
+
+.. image:: deployment.svg
+
+The basic unit for scaling a websockets server is "one server process". Each
+blue box in the diagram represents one server process.
+
+There's more variation in routing. While the routing layer is shown as one big
+box, it is likely to involve several subsystems.
+
+When you design a deployment, your should consider two questions:
+
+1. How will I run the appropriate number of server processes?
+2. How will I route incoming connections to these processes?
+
+These questions are strongly related. There's a wide range of acceptable
+answers, depending on your goals and your constraints.
+
+You can find a few concrete examples in the :ref:`deployment how-to guides
+<deployment-howto>`.
+
+Running server processes
+------------------------
+
+How many processes do I need?
+.............................
+
+Typically, one server process will manage a few hundreds or thousands
+connections, depending on the frequency of messages and the amount of work
+they require.
+
+CPU and memory usage increase with the number of connections to the server.
+
+Often CPU is the limiting factor. If a server process goes to 100% CPU, then
+you reached the limit. How much headroom you want to keep is up to you.
+
+Once you know how many connections a server process can manage and how many
+connections you need to handle, you can calculate how many processes to run.
+
+You can also automate this calculation by configuring an autoscaler to keep
+CPU usage or connection count within acceptable limits.
+
+Don't scale with threads. Threads doesn't make sense for a server built with
+:mod:`asyncio`.
+
+How do I run processes?
+.......................
+
+Most solutions for running multiple instances of a server process fall into
+one of these three buckets:
+
+1. Running N processes on a platform:
+
+ * a Kubernetes Deployment
+
+ * its equivalent on a Platform as a Service provider
+
+2. Running N servers:
+
+ * an AWS Auto Scaling group, a GCP Managed instance group, etc.
+
+ * a fixed set of long-lived servers
+
+3. Running N processes on a server:
+
+ * preferably via a process manager or supervisor
+
+Option 1 is easiest of you have access to such a platform.
+
+Option 2 almost always combines with option 3.
+
+How do I start a process?
+.........................
+
+Run a Python program that invokes :func:`~server.serve`. That's it.
+
+Don't run an ASGI server such as Uvicorn, Hypercorn, or Daphne. They're
+alternatives to websockets, not complements.
+
+Don't run a WSGI server such as Gunicorn, Waitress, or mod_wsgi. They aren't
+designed to run WebSocket applications.
+
+Applications servers handle network connections and expose a Python API. You
+don't need one because websockets handles network connections directly.
+
+How do I stop a process?
+........................
+
+Process managers send the SIGTERM signal to terminate processes. Catch this
+signal and exit the server to ensure a graceful shutdown.
+
+Here's an example:
+
+.. literalinclude:: ../../example/faq/shutdown_server.py
+ :emphasize-lines: 12-15,18
+
+When exiting the context manager, :func:`~server.serve` closes all connections
+with code 1001 (going away). As a consequence:
+
+* If the connection handler is awaiting
+ :meth:`~server.WebSocketServerProtocol.recv`, it receives a
+ :exc:`~exceptions.ConnectionClosedOK` exception. It can catch the exception
+ and clean up before exiting.
+
+* Otherwise, it should be waiting on
+ :meth:`~server.WebSocketServerProtocol.wait_closed`, so it can receive the
+ :exc:`~exceptions.ConnectionClosedOK` exception and exit.
+
+This example is easily adapted to handle other signals.
+
+If you override the default signal handler for SIGINT, which raises
+:exc:`KeyboardInterrupt`, be aware that you won't be able to interrupt a
+program with Ctrl-C anymore when it's stuck in a loop.
+
+Routing connections
+-------------------
+
+What does routing involve?
+..........................
+
+Since the routing layer is directly exposed to the Internet, it should provide
+appropriate protection against threats ranging from Internet background noise
+to targeted attacks.
+
+You should always secure WebSocket connections with TLS. Since the routing
+layer carries the public domain name, it should terminate TLS connections.
+
+Finally, it must route connections to the server processes, balancing new
+connections across them.
+
+How do I route connections?
+...........................
+
+Here are typical solutions for load balancing, matched to ways of running
+processes:
+
+1. If you're running on a platform, it comes with a routing layer:
+
+ * a Kubernetes Ingress and Service
+
+ * a service mesh: Istio, Consul, Linkerd, etc.
+
+ * the routing mesh of a Platform as a Service
+
+2. If you're running N servers, you may load balance with:
+
+ * a cloud load balancer: AWS Elastic Load Balancing, GCP Cloud Load
+ Balancing, etc.
+
+ * A software load balancer: HAProxy, NGINX, etc.
+
+3. If you're running N processes on a server, you may load balance with:
+
+ * A software load balancer: HAProxy, NGINX, etc.
+
+ * The operating system — all processes listen on the same port
+
+You may trust the load balancer to handle encryption and to provide security.
+You may add another layer in front of the load balancer for these purposes.
+
+There are many possibilities. Don't add layers that you don't need, though.
+
+How do I implement a health check?
+..................................
+
+Load balancers need a way to check whether server processes are up and running
+to avoid routing connections to a non-functional backend.
+
+websockets provide minimal support for responding to HTTP requests with the
+:meth:`~server.WebSocketServerProtocol.process_request` hook.
+
+Here's an example:
+
+.. literalinclude:: ../../example/faq/health_check_server.py
+ :emphasize-lines: 7-9,18
diff --git a/testing/web-platform/tests/tools/third_party/websockets/docs/topics/deployment.svg b/testing/web-platform/tests/tools/third_party/websockets/docs/topics/deployment.svg
new file mode 100644
index 0000000000..cb948d8d6b
--- /dev/null
+++ b/testing/web-platform/tests/tools/third_party/websockets/docs/topics/deployment.svg
@@ -0,0 +1,63 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="-490 -34 644 356" width="644px" height="356px"><defs><style>@font-face{
+ font-family:"DIN Next";
+ font-weight: 400;
+ src:url("https://whimsical.com/fonts/139e8e25-0eea-4cb4-a5d4-79048803e73d.eot?#iefix");
+ src:url("https://whimsical.com/fonts/139e8e25-0eea-4cb4-a5d4-79048803e73d.eot?#iefix") format("eot"),url("https://whimsical.com/fonts/094b15e3-94bd-435b-a595-d40edfde661a.woff2") format("woff2"),url("https://whimsical.com/fonts/7e5fbe11-4858-4bd1-9ec6-a1d9f9d227aa.woff") format("woff"),url("https://whimsical.com/fonts/0f11eff9-9f05-46f5-9703-027c510065d7.ttf") format("truetype"),url("https://whimsical.com/fonts/48b61978-3f30-4274-823c-5cdcd1876918.svg#48b61978-3f30-4274-823c-5cdcd1876918") format("svg");
+}
+@font-face{
+ font-family:"DIN Next";
+ font-weight: 400;
+ font-style: italic;
+ src:url("https://whimsical.com/fonts/df24d5e8-5087-42fd-99b1-b16042d666c8.eot?#iefix");
+ src:url("https://whimsical.com/fonts/df24d5e8-5087-42fd-99b1-b16042d666c8.eot?#iefix") format("eot"),url("https://whimsical.com/fonts/46251881-ffe9-4bfb-99c7-d6ce3bebaf3e.woff2") format("woff2"),url("https://whimsical.com/fonts/790ebbf2-62c5-4a32-946f-99d405f9243e.woff") format("woff"),url("https://whimsical.com/fonts/d28199e6-0f4a-42df-97f4-606701c6f75a.ttf") format("truetype"),url("https://whimsical.com/fonts/37a462c0-d86e-492c-b9ab-35e6bd417f6c.svg#37a462c0-d86e-492c-b9ab-35e6bd417f6c") format("svg");
+}
+@font-face{
+ font-weight: 500;
+ font-family:"DIN Next";
+ src:url("https://whimsical.com/fonts/c5b058fc-55ce-4e06-a175-5c7d9a7e90f4.eot?#iefix");
+ src:url("https://whimsical.com/fonts/c5b058fc-55ce-4e06-a175-5c7d9a7e90f4.eot?#iefix") format("eot"),url("https://whimsical.com/fonts/7b29ae40-30ff-4f99-a2b9-cde88669fa2f.woff2") format("woff2"),url("https://whimsical.com/fonts/bf73077c-e354-4562-a085-f4703eb1d653.woff") format("woff"),url("https://whimsical.com/fonts/0ffa6947-5317-4d07-b525-14d08a028821.ttf") format("truetype"),url("https://whimsical.com/fonts/9e423e45-5450-4991-a157-dbe6cf61eb4e.svg#9e423e45-5450-4991-a157-dbe6cf61eb4e") format("svg");
+}
+@font-face{
+ font-family:"DIN Next";
+ font-weight: 500;
+ font-style: italic;
+ src:url("https://whimsical.com/fonts/9897c008-fd65-48a4-afc7-36de2fea97b9.eot?#iefix");
+ src:url("https://whimsical.com/fonts/9897c008-fd65-48a4-afc7-36de2fea97b9.eot?#iefix") format("eot"),url("https://whimsical.com/fonts/c7717981-647d-4b76-8817-33062e42d11f.woff2") format("woff2"),url("https://whimsical.com/fonts/b852cd4c-1255-40b1-a2be-73a9105b0155.woff") format("woff"),url("https://whimsical.com/fonts/821b00ad-e741-4e2d-af1a-85594367c8a2.ttf") format("truetype"),url("https://whimsical.com/fonts/d3e3b689-a6b0-45f2-b279-f5e194f87409.svg#d3e3b689-a6b0-45f2-b279-f5e194f87409") format("svg");
+}
+@font-face{
+ font-weight: 700;
+ font-family:"DIN Next";
+ src:url("https://whimsical.com/fonts/81cd3b08-fd39-4ae3-8d05-9d24709eba84.eot?#iefix");
+ src:url("https://whimsical.com/fonts/81cd3b08-fd39-4ae3-8d05-9d24709eba84.eot?#iefix") format("eot"),url("https://whimsical.com/fonts/31704504-4671-47a6-a61e-397f07410d91.woff2") format("woff2"),url("https://whimsical.com/fonts/b8a280da-481f-44a0-8d9c-1bc64bd7227c.woff") format("woff"),url("https://whimsical.com/fonts/276d122a-0fab-447b-9fc0-5d7fb0eafce2.ttf") format("truetype"),url("https://whimsical.com/fonts/8fb8273a-8213-4928-808b-b5bfaf3fd7e9.svg#8fb8273a-8213-4928-808b-b5bfaf3fd7e9") format("svg");
+}
+@font-face{
+ font-family:"DIN Next";
+ font-weight: 700;
+ font-style: italic;
+ src:url("https://whimsical.com/fonts/00cc6df3-ed32-4004-8dd8-1c576600a408.eot?#iefix");
+ src:url("https://whimsical.com/fonts/00cc6df3-ed32-4004-8dd8-1c576600a408.eot?#iefix") format("eot"),url("https://whimsical.com/fonts/4132c4c8-680c-4d6d-9251-a2da38503bbd.woff2") format("woff2"),url("https://whimsical.com/fonts/366401fe-6df4-47be-8f55-8a411cff0dd2.woff") format("woff"),url("https://whimsical.com/fonts/dbe4f7ba-fc16-44a6-a577-571620e9edaf.ttf") format("truetype"),url("https://whimsical.com/fonts/f874edca-ee87-4ccf-8b1d-921fbc3c1c36.svg#f874edca-ee87-4ccf-8b1d-921fbc3c1c36") format("svg");
+}
+
+@font-face {
+ font-family: 'PFDINMonoPro';
+ src:url('https://whimsical.com/fonts/PFDINMonoPro-Regular.woff') format('woff');
+ font-weight: 400;
+}
+@font-face {
+ font-family: 'PFDINMonoPro';
+ src:url('https://whimsical.com/fonts/PFDINMonoPro-Italic.woff') format('woff');
+ font-style: italic;
+ font-weight: 400;
+}
+@font-face {
+ font-family: 'PFDINMonoPro';
+ src:url('https://whimsical.com/fonts/PFDINMonoPro-Bold.woff') format('woff');
+ font-weight: 700;
+}
+@font-face {
+ font-family: 'PFDINMonoPro';
+ src:url('https://whimsical.com/fonts/PFDINMonoPro-BoldItalic.woff') format('woff');
+ font-style: italic;
+ font-weight: 700;
+}
+* {-webkit-font-smoothing: auto; -moz-osx-font-smoothing: auto;}@media print { svg { width: 100%; height: 100%; } }</style><filter id="fill-shadow" x="-100%" y="-100%" width="300%" height="300%"><feGaussianBlur in="SourceAlpha" stdDeviation="6"/><feOffset result="offsetblur" dx="0" dy="2"/><feComponentTransfer result="s0"><feFuncA type="linear" slope="0.16"/></feComponentTransfer><feMerge><feMergeNode in="s0"/><feMergeNode in="SourceGraphic"/></feMerge></filter><filter id="fill-light-shadow" x="-100%" y="-100%" width="300%" height="300%"><feGaussianBlur in="SourceAlpha" stdDeviation="6"/><feOffset result="offsetblur" dx="0" dy="2"/><feComponentTransfer result="s0"><feFuncA type="linear" slope="0.04"/></feComponentTransfer><feMerge><feMergeNode in="s0"/><feMergeNode in="SourceGraphic"/></feMerge></filter><filter id="image-shadow" x="-100%" y="-100%" width="300%" height="300%"><feGaussianBlur in="SourceAlpha" stdDeviation="8"/><feOffset result="offsetblur" dx="0" dy="3"/><feComponentTransfer result="s0"><feFuncA type="linear" slope="0.06"/></feComponentTransfer><feMerge><feMergeNode in="s0"/><feMergeNode in="SourceGraphic"/></feMerge></filter><filter id="frame-shadow" x="-100%" y="-100%" width="300%" height="300%"><feGaussianBlur in="SourceAlpha" stdDeviation="8"/><feOffset result="offsetblur" dx="0" dy="3"/><feComponentTransfer result="s0"><feFuncA type="linear" slope="0.06"/></feComponentTransfer><feMerge><feMergeNode in="s0"/><feMergeNode in="SourceGraphic"/></feMerge></filter><filter id="badge-shadow" x="-100%" y="-100%" width="300%" height="300%"><feGaussianBlur in="SourceAlpha" stdDeviation="6"/><feOffset result="offsetblur" dx="0" dy="2"/><feComponentTransfer result="s0"><feFuncA type="linear" slope="0.08"/></feComponentTransfer><feMerge><feMergeNode in="s0"/><feMergeNode in="SourceGraphic"/></feMerge></filter></defs><g><g><path d="M132.40052,16.91125c8.20504,1.34238 13.19097,6.77278 11.13766,12.13055c-0.76086,3.6926 -4.09828,6.94687 -9.03771,8.81254c-4.93944,1.86567 -10.9238,2.13232 -16.20563,0.72209c-3.50792,6.79759 -12.12825,11.93728 -22.81409,13.60245c-10.68585,1.66516 -21.93194,-0.37877 -29.7633,-5.40937c-7.5447,11.32261 -23.72737,19.17936 -42.50646,20.63705c-18.77909,1.45769 -37.33189,-3.70278 -48.732,-13.55484c-11.00862,8.39145 -26.81112,13.46368 -43.71109,14.03023c-16.89997,0.56655 -33.41499,-3.42228 -45.68197,-11.03344c-12.09168,9.57066 -30.08677,15.12494 -49.08843,15.1514c-19.00166,0.02646 -37.03287,-5.47764 -49.18697,-15.01453c-32.22671,18.71922 -81.52408,17.01732 -110.54782,-3.81645c-9.61896,5.63808 -22.48701,8.30277 -35.32441,7.3149c-12.8374,-0.98787 -24.40407,-5.53286 -31.75198,-12.47659c-8.38039,4.12685 -19.07889,5.69428 -29.35634,4.30095c-10.27744,-1.39333 -19.13592,-5.61211 -24.30736,-11.5762c-6.03705,3.01428 -14.12058,3.62226 -21.04917,1.58316c-6.92858,-2.0391 -11.58448,-6.39632 -12.12376,-11.34603c-4.11078,-3.88947 -2.69127,-9.21224 3.19106,-11.96556c2.8597,-6.26181 10.13013,-11.25433 19.56423,-13.4345c9.43409,-2.18018 19.89581,-1.28549 28.15172,2.40755c4.56946,-5.60994 12.84561,-9.53066 22.43123,-10.62652c9.58562,-1.09585 19.40964,0.75561 26.62651,5.0181c13.21632,-10.89584 32.7134,-17.76363 53.91728,-18.99222c21.20388,-1.22858 42.24851,3.29017 58.19687,12.49616c11.75754,-9.7851 29.59785,-15.62692 48.64043,-15.92732c19.04258,-0.30041 37.29416,4.97204 49.76173,14.37498c12.30654,-11.80391 32.97339,-18.70238 54.83436,-18.30339c21.86097,0.39899 41.90535,8.04051 53.18277,20.27486c10.58585,-9.80304 28.36041,-15.18816 46.66019,-14.13654c18.29978,1.05162 34.36046,8.38113 42.16106,19.24077c7.63845,-6.15855 19.75125,-9.16677 31.72723,-7.87948c11.97598,1.2873 21.97263,6.67206 26.18437,14.10438c7.68618,-3.10406 17.06471,-3.86266 25.67861,-2.07706c8.6139,1.78561 15.60478,5.93747 19.14117,11.3679z" fill="#e3e6e9"/><path d="M132.40052,16.91125c8.20504,1.34238 13.19097,6.77278 11.13766,12.13055c-0.76086,3.6926 -4.09828,6.94687 -9.03771,8.81254c-4.93944,1.86567 -10.9238,2.13232 -16.20563,0.72209c-3.50792,6.79759 -12.12825,11.93728 -22.81409,13.60245c-10.68585,1.66516 -21.93194,-0.37877 -29.7633,-5.40937c-7.5447,11.32261 -23.72737,19.17936 -42.50646,20.63705c-18.77909,1.45769 -37.33189,-3.70278 -48.732,-13.55484c-11.00862,8.39145 -26.81112,13.46368 -43.71109,14.03023c-16.89997,0.56655 -33.41499,-3.42228 -45.68197,-11.03344c-12.09168,9.57066 -30.08677,15.12494 -49.08843,15.1514c-19.00166,0.02646 -37.03287,-5.47764 -49.18697,-15.01453c-32.22671,18.71922 -81.52408,17.01732 -110.54782,-3.81645c-9.61896,5.63808 -22.48701,8.30277 -35.32441,7.3149c-12.8374,-0.98787 -24.40407,-5.53286 -31.75198,-12.47659c-8.38039,4.12685 -19.07889,5.69428 -29.35634,4.30095c-10.27744,-1.39333 -19.13592,-5.61211 -24.30736,-11.5762c-6.03705,3.01428 -14.12058,3.62226 -21.04917,1.58316c-6.92858,-2.0391 -11.58448,-6.39632 -12.12376,-11.34603c-4.11078,-3.88947 -2.69127,-9.21224 3.19106,-11.96556c2.8597,-6.26181 10.13013,-11.25433 19.56423,-13.4345c9.43409,-2.18018 19.89581,-1.28549 28.15172,2.40755c4.56946,-5.60994 12.84561,-9.53066 22.43123,-10.62652c9.58562,-1.09585 19.40964,0.75561 26.62651,5.0181c13.21632,-10.89584 32.7134,-17.76363 53.91728,-18.99222c21.20388,-1.22858 42.24851,3.29017 58.19687,12.49616c11.75754,-9.7851 29.59785,-15.62692 48.64043,-15.92732c19.04258,-0.30041 37.29416,4.97204 49.76173,14.37498c12.30654,-11.80391 32.97339,-18.70238 54.83436,-18.30339c21.86097,0.39899 41.90535,8.04051 53.18277,20.27486c10.58585,-9.80304 28.36041,-15.18816 46.66019,-14.13654c18.29978,1.05162 34.36046,8.38113 42.16106,19.24077c7.63845,-6.15855 19.75125,-9.16677 31.72723,-7.87948c11.97598,1.2873 21.97263,6.67206 26.18437,14.10438c7.68618,-3.10406 17.06471,-3.86266 25.67861,-2.07706c8.6139,1.78561 15.60478,5.93747 19.14117,11.3679z" fill="none" stroke="#788896" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/><text y="12" font-style="normal" font-size="18" font-weight="normal" fill="#293845" width="600" xml:space="preserve" x="-468" font-family="DIN Next, sans-serif" height="24"><tspan x="-168" y="30" text-anchor="middle" style="white-space:pre;">Internet</tspan></text></g></g><g><g><rect x="-480" y="240" width="144" height="72" rx="3" ry="3" fill="#d3e6f7"/><rect y="240" rx="3" stroke="#2C88D9" fill="none" stroke-linejoin="round" width="144" stroke-linecap="round" stroke-width="2" x="-480" ry="3" height="72"/><text y="264" font-style="normal" font-size="18" font-weight="normal" fill="#293845" width="120" xml:space="preserve" x="-468" font-family="DIN Next, sans-serif" height="24"><tspan x="-408" y="282" text-anchor="middle" style="white-space:pre;">websockets</tspan></text></g></g><g><g><rect x="-312" y="240" width="144" height="72" rx="3" ry="3" fill="#d3e6f7"/><rect y="240" rx="3" stroke="#2C88D9" fill="none" stroke-linejoin="round" width="144" stroke-linecap="round" stroke-width="2" x="-312" ry="3" height="72"/><text y="264" font-style="normal" font-size="18" font-weight="normal" fill="#293845" width="120" xml:space="preserve" x="-300" font-family="DIN Next, sans-serif" height="24"><tspan x="-240" y="282" text-anchor="middle" style="white-space:pre;">websockets</tspan></text></g></g><g><g><rect x="0" y="240" width="144" height="72" rx="3" ry="3" fill="#d3e6f7"/><rect y="240" rx="3" stroke="#2C88D9" fill="none" stroke-linejoin="round" width="144" stroke-linecap="round" stroke-width="2" x="0" ry="3" height="72"/><text y="264" font-style="normal" font-size="18" font-weight="normal" fill="#293845" width="120" xml:space="preserve" x="12" font-family="DIN Next, sans-serif" height="24"><tspan x="72" y="282" text-anchor="middle" style="white-space:pre;">websockets</tspan></text></g></g><g><g><rect x="-144" y="258" width="24" height="36" rx="1.5" ry="1.5" fill="#d3e6f7"/><rect y="258" rx="1.5" stroke="#2C88D9" fill="none" stroke-linejoin="round" width="24" stroke-linecap="round" stroke-width="2" x="-144" ry="1.5" height="36"/></g></g><g><g><rect x="-48" y="258" width="24" height="36" rx="1.5" ry="1.5" fill="#d3e6f7"/><rect y="258" rx="1.5" stroke="#2C88D9" fill="none" stroke-linejoin="round" width="24" stroke-linecap="round" stroke-width="2" x="-48" ry="1.5" height="36"/></g></g><g><g><rect x="-96" y="258" width="24" height="36" rx="1.5" ry="1.5" fill="#d3e6f7"/><rect y="258" rx="1.5" stroke="#2C88D9" fill="none" stroke-linejoin="round" width="24" stroke-linecap="round" stroke-width="2" x="-96" ry="1.5" height="36"/></g></g><g><g><rect x="-480" y="120" width="624" height="72" rx="3" ry="3" fill="#cfeeeb"/><rect y="120" rx="3" stroke="#1AAE9F" fill="none" stroke-linejoin="round" width="624" stroke-linecap="round" stroke-width="2" x="-480" ry="3" height="72"/><text y="144" font-style="normal" font-size="18" font-weight="normal" fill="#293845" width="600" xml:space="preserve" x="-468" font-family="DIN Next, sans-serif" height="24"><tspan x="-168" y="162" text-anchor="middle" style="white-space:pre;">routing</tspan></text></g></g><g><g><line stroke="#788896" fill="none" stroke-linejoin="round" y1="199.99999999999997" stroke-linecap="round" stroke-width="4" x1="-240" y2="227.99999999999994" x2="-240"/><polygon points="-234.45905,224.9079 -240,232 -245.54095,224.9079" fill="#788896" stroke="#788896" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></g></g><g><g><line stroke="#788896" fill="none" stroke-linejoin="round" y1="199.99999999999997" stroke-linecap="round" stroke-width="4" x1="-407.99999999999994" y2="227.99999999999994" x2="-407.99999999999994"/><polygon points="-402.45905,224.9079 -408,232 -413.54095,224.9079" fill="#788896" stroke="#788896" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></g></g><g><g><line stroke="#788896" fill="none" stroke-linejoin="round" y1="200.00000000000003" stroke-linecap="round" stroke-width="4" x1="72" y2="228" x2="72"/><polygon points="77.54095,224.9079 72,232 66.45905,224.9079" fill="#788896" stroke="#788896" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></g></g><g><g><line stroke="#788896" fill="none" stroke-linejoin="round" y1="79.92376615249583" stroke-linecap="round" stroke-width="4" x1="-168" y2="107.99999999999994" x2="-168"/><polygon points="-162.45905,104.9079 -168,112 -173.54095,104.9079" fill="#788896" stroke="#788896" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></g></g></svg>
diff --git a/testing/web-platform/tests/tools/third_party/websockets/docs/topics/design.rst b/testing/web-platform/tests/tools/third_party/websockets/docs/topics/design.rst
new file mode 100644
index 0000000000..f164d29905
--- /dev/null
+++ b/testing/web-platform/tests/tools/third_party/websockets/docs/topics/design.rst
@@ -0,0 +1,572 @@
+Design
+======
+
+.. currentmodule:: websockets
+
+This document describes the design of websockets. It assumes familiarity with
+the specification of the WebSocket protocol in :rfc:`6455`.
+
+It's primarily intended at maintainers. It may also be useful for users who
+wish to understand what happens under the hood.
+
+.. warning::
+
+ Internals described in this document may change at any time.
+
+ Backwards compatibility is only guaranteed for :doc:`public APIs
+ <../reference/index>`.
+
+Lifecycle
+---------
+
+State
+.....
+
+WebSocket connections go through a trivial state machine:
+
+- ``CONNECTING``: initial state,
+- ``OPEN``: when the opening handshake is complete,
+- ``CLOSING``: when the closing handshake is started,
+- ``CLOSED``: when the TCP connection is closed.
+
+Transitions happen in the following places:
+
+- ``CONNECTING -> OPEN``: in
+ :meth:`~legacy.protocol.WebSocketCommonProtocol.connection_open` which runs when
+ the :ref:`opening handshake <opening-handshake>` completes and the WebSocket
+ connection is established — not to be confused with
+ :meth:`~asyncio.BaseProtocol.connection_made` which runs when the TCP connection
+ is established;
+- ``OPEN -> CLOSING``: in
+ :meth:`~legacy.protocol.WebSocketCommonProtocol.write_frame` immediately before
+ sending a close frame; since receiving a close frame triggers sending a
+ close frame, this does the right thing regardless of which side started the
+ :ref:`closing handshake <closing-handshake>`; also in
+ :meth:`~legacy.protocol.WebSocketCommonProtocol.fail_connection` which duplicates
+ a few lines of code from ``write_close_frame()`` and ``write_frame()``;
+- ``* -> CLOSED``: in
+ :meth:`~legacy.protocol.WebSocketCommonProtocol.connection_lost` which is always
+ called exactly once when the TCP connection is closed.
+
+Coroutines
+..........
+
+The following diagram shows which coroutines are running at each stage of the
+connection lifecycle on the client side.
+
+.. image:: lifecycle.svg
+ :target: _images/lifecycle.svg
+
+The lifecycle is identical on the server side, except inversion of control
+makes the equivalent of :meth:`~client.connect` implicit.
+
+Coroutines shown in green are called by the application. Multiple coroutines
+may interact with the WebSocket connection concurrently.
+
+Coroutines shown in gray manage the connection. When the opening handshake
+succeeds, :meth:`~legacy.protocol.WebSocketCommonProtocol.connection_open` starts
+two tasks:
+
+- :attr:`~legacy.protocol.WebSocketCommonProtocol.transfer_data_task` runs
+ :meth:`~legacy.protocol.WebSocketCommonProtocol.transfer_data` which handles
+ incoming data and lets :meth:`~legacy.protocol.WebSocketCommonProtocol.recv`
+ consume it. It may be canceled to terminate the connection. It never exits
+ with an exception other than :exc:`~asyncio.CancelledError`. See :ref:`data
+ transfer <data-transfer>` below.
+
+- :attr:`~legacy.protocol.WebSocketCommonProtocol.keepalive_ping_task` runs
+ :meth:`~legacy.protocol.WebSocketCommonProtocol.keepalive_ping` which sends Ping
+ frames at regular intervals and ensures that corresponding Pong frames are
+ received. It is canceled when the connection terminates. It never exits
+ with an exception other than :exc:`~asyncio.CancelledError`.
+
+- :attr:`~legacy.protocol.WebSocketCommonProtocol.close_connection_task` runs
+ :meth:`~legacy.protocol.WebSocketCommonProtocol.close_connection` which waits for
+ the data transfer to terminate, then takes care of closing the TCP
+ connection. It must not be canceled. It never exits with an exception. See
+ :ref:`connection termination <connection-termination>` below.
+
+Besides, :meth:`~legacy.protocol.WebSocketCommonProtocol.fail_connection` starts
+the same :attr:`~legacy.protocol.WebSocketCommonProtocol.close_connection_task` when
+the opening handshake fails, in order to close the TCP connection.
+
+Splitting the responsibilities between two tasks makes it easier to guarantee
+that websockets can terminate connections:
+
+- within a fixed timeout,
+- without leaking pending tasks,
+- without leaking open TCP connections,
+
+regardless of whether the connection terminates normally or abnormally.
+
+:attr:`~legacy.protocol.WebSocketCommonProtocol.transfer_data_task` completes when no
+more data will be received on the connection. Under normal circumstances, it
+exits after exchanging close frames.
+
+:attr:`~legacy.protocol.WebSocketCommonProtocol.close_connection_task` completes when
+the TCP connection is closed.
+
+
+.. _opening-handshake:
+
+Opening handshake
+-----------------
+
+websockets performs the opening handshake when establishing a WebSocket
+connection. On the client side, :meth:`~client.connect` executes it
+before returning the protocol to the caller. On the server side, it's executed
+before passing the protocol to the ``ws_handler`` coroutine handling the
+connection.
+
+While the opening handshake is asymmetrical — the client sends an HTTP Upgrade
+request and the server replies with an HTTP Switching Protocols response —
+websockets aims at keeping the implementation of both sides consistent with
+one another.
+
+On the client side, :meth:`~client.WebSocketClientProtocol.handshake`:
+
+- builds an HTTP request based on the ``uri`` and parameters passed to
+ :meth:`~client.connect`;
+- writes the HTTP request to the network;
+- reads an HTTP response from the network;
+- checks the HTTP response, validates ``extensions`` and ``subprotocol``, and
+ configures the protocol accordingly;
+- moves to the ``OPEN`` state.
+
+On the server side, :meth:`~server.WebSocketServerProtocol.handshake`:
+
+- reads an HTTP request from the network;
+- calls :meth:`~server.WebSocketServerProtocol.process_request` which may
+ abort the WebSocket handshake and return an HTTP response instead; this
+ hook only makes sense on the server side;
+- checks the HTTP request, negotiates ``extensions`` and ``subprotocol``, and
+ configures the protocol accordingly;
+- builds an HTTP response based on the above and parameters passed to
+ :meth:`~server.serve`;
+- writes the HTTP response to the network;
+- moves to the ``OPEN`` state;
+- returns the ``path`` part of the ``uri``.
+
+The most significant asymmetry between the two sides of the opening handshake
+lies in the negotiation of extensions and, to a lesser extent, of the
+subprotocol. The server knows everything about both sides and decides what the
+parameters should be for the connection. The client merely applies them.
+
+If anything goes wrong during the opening handshake, websockets :ref:`fails
+the connection <connection-failure>`.
+
+
+.. _data-transfer:
+
+Data transfer
+-------------
+
+Symmetry
+........
+
+Once the opening handshake has completed, the WebSocket protocol enters the
+data transfer phase. This part is almost symmetrical. There are only two
+differences between a server and a client:
+
+- `client-to-server masking`_: the client masks outgoing frames; the server
+ unmasks incoming frames;
+- `closing the TCP connection`_: the server closes the connection immediately;
+ the client waits for the server to do it.
+
+.. _client-to-server masking: https://www.rfc-editor.org/rfc/rfc6455.html#section-5.3
+.. _closing the TCP connection: https://www.rfc-editor.org/rfc/rfc6455.html#section-5.5.1
+
+These differences are so minor that all the logic for `data framing`_, for
+`sending and receiving data`_ and for `closing the connection`_ is implemented
+in the same class, :class:`~legacy.protocol.WebSocketCommonProtocol`.
+
+.. _data framing: https://www.rfc-editor.org/rfc/rfc6455.html#section-5
+.. _sending and receiving data: https://www.rfc-editor.org/rfc/rfc6455.html#section-6
+.. _closing the connection: https://www.rfc-editor.org/rfc/rfc6455.html#section-7
+
+The :attr:`~legacy.protocol.WebSocketCommonProtocol.is_client` attribute tells which
+side a protocol instance is managing. This attribute is defined on the
+:attr:`~server.WebSocketServerProtocol` and
+:attr:`~client.WebSocketClientProtocol` classes.
+
+Data flow
+.........
+
+The following diagram shows how data flows between an application built on top
+of websockets and a remote endpoint. It applies regardless of which side is
+the server or the client.
+
+.. image:: protocol.svg
+ :target: _images/protocol.svg
+
+Public methods are shown in green, private methods in yellow, and buffers in
+orange. Methods related to connection termination are omitted; connection
+termination is discussed in another section below.
+
+Receiving data
+..............
+
+The left side of the diagram shows how websockets receives data.
+
+Incoming data is written to a :class:`~asyncio.StreamReader` in order to
+implement flow control and provide backpressure on the TCP connection.
+
+:attr:`~legacy.protocol.WebSocketCommonProtocol.transfer_data_task`, which is started
+when the WebSocket connection is established, processes this data.
+
+When it receives data frames, it reassembles fragments and puts the resulting
+messages in the :attr:`~legacy.protocol.WebSocketCommonProtocol.messages` queue.
+
+When it encounters a control frame:
+
+- if it's a close frame, it starts the closing handshake;
+- if it's a ping frame, it answers with a pong frame;
+- if it's a pong frame, it acknowledges the corresponding ping (unless it's an
+ unsolicited pong).
+
+Running this process in a task guarantees that control frames are processed
+promptly. Without such a task, websockets would depend on the application to
+drive the connection by having exactly one coroutine awaiting
+:meth:`~legacy.protocol.WebSocketCommonProtocol.recv` at any time. While this
+happens naturally in many use cases, it cannot be relied upon.
+
+Then :meth:`~legacy.protocol.WebSocketCommonProtocol.recv` fetches the next message
+from the :attr:`~legacy.protocol.WebSocketCommonProtocol.messages` queue, with some
+complexity added for handling backpressure and termination correctly.
+
+Sending data
+............
+
+The right side of the diagram shows how websockets sends data.
+
+:meth:`~legacy.protocol.WebSocketCommonProtocol.send` writes one or several data
+frames containing the message. While sending a fragmented message, concurrent
+calls to :meth:`~legacy.protocol.WebSocketCommonProtocol.send` are put on hold until
+all fragments are sent. This makes concurrent calls safe.
+
+:meth:`~legacy.protocol.WebSocketCommonProtocol.ping` writes a ping frame and
+yields a :class:`~asyncio.Future` which will be completed when a matching pong
+frame is received.
+
+:meth:`~legacy.protocol.WebSocketCommonProtocol.pong` writes a pong frame.
+
+:meth:`~legacy.protocol.WebSocketCommonProtocol.close` writes a close frame and
+waits for the TCP connection to terminate.
+
+Outgoing data is written to a :class:`~asyncio.StreamWriter` in order to
+implement flow control and provide backpressure from the TCP connection.
+
+.. _closing-handshake:
+
+Closing handshake
+.................
+
+When the other side of the connection initiates the closing handshake,
+:meth:`~legacy.protocol.WebSocketCommonProtocol.read_message` receives a close
+frame while in the ``OPEN`` state. It moves to the ``CLOSING`` state, sends a
+close frame, and returns :obj:`None`, causing
+:attr:`~legacy.protocol.WebSocketCommonProtocol.transfer_data_task` to terminate.
+
+When this side of the connection initiates the closing handshake with
+:meth:`~legacy.protocol.WebSocketCommonProtocol.close`, it moves to the ``CLOSING``
+state and sends a close frame. When the other side sends a close frame,
+:meth:`~legacy.protocol.WebSocketCommonProtocol.read_message` receives it in the
+``CLOSING`` state and returns :obj:`None`, also causing
+:attr:`~legacy.protocol.WebSocketCommonProtocol.transfer_data_task` to terminate.
+
+If the other side doesn't send a close frame within the connection's close
+timeout, websockets :ref:`fails the connection <connection-failure>`.
+
+The closing handshake can take up to ``2 * close_timeout``: one
+``close_timeout`` to write a close frame and one ``close_timeout`` to receive
+a close frame.
+
+Then websockets terminates the TCP connection.
+
+
+.. _connection-termination:
+
+Connection termination
+----------------------
+
+:attr:`~legacy.protocol.WebSocketCommonProtocol.close_connection_task`, which is
+started when the WebSocket connection is established, is responsible for
+eventually closing the TCP connection.
+
+First :attr:`~legacy.protocol.WebSocketCommonProtocol.close_connection_task` waits
+for :attr:`~legacy.protocol.WebSocketCommonProtocol.transfer_data_task` to terminate,
+which may happen as a result of:
+
+- a successful closing handshake: as explained above, this exits the infinite
+ loop in :attr:`~legacy.protocol.WebSocketCommonProtocol.transfer_data_task`;
+- a timeout while waiting for the closing handshake to complete: this cancels
+ :attr:`~legacy.protocol.WebSocketCommonProtocol.transfer_data_task`;
+- a protocol error, including connection errors: depending on the exception,
+ :attr:`~legacy.protocol.WebSocketCommonProtocol.transfer_data_task` :ref:`fails the
+ connection <connection-failure>` with a suitable code and exits.
+
+:attr:`~legacy.protocol.WebSocketCommonProtocol.close_connection_task` is separate
+from :attr:`~legacy.protocol.WebSocketCommonProtocol.transfer_data_task` to make it
+easier to implement the timeout on the closing handshake. Canceling
+:attr:`~legacy.protocol.WebSocketCommonProtocol.transfer_data_task` creates no risk
+of canceling :attr:`~legacy.protocol.WebSocketCommonProtocol.close_connection_task`
+and failing to close the TCP connection, thus leaking resources.
+
+Then :attr:`~legacy.protocol.WebSocketCommonProtocol.close_connection_task` cancels
+:meth:`~legacy.protocol.WebSocketCommonProtocol.keepalive_ping`. This task has no
+protocol compliance responsibilities. Terminating it to avoid leaking it is
+the only concern.
+
+Terminating the TCP connection can take up to ``2 * close_timeout`` on the
+server side and ``3 * close_timeout`` on the client side. Clients start by
+waiting for the server to close the connection, hence the extra
+``close_timeout``. Then both sides go through the following steps until the
+TCP connection is lost: half-closing the connection (only for non-TLS
+connections), closing the connection, aborting the connection. At this point
+the connection drops regardless of what happens on the network.
+
+
+.. _connection-failure:
+
+Connection failure
+------------------
+
+If the opening handshake doesn't complete successfully, websockets fails the
+connection by closing the TCP connection.
+
+Once the opening handshake has completed, websockets fails the connection by
+canceling :attr:`~legacy.protocol.WebSocketCommonProtocol.transfer_data_task`
+and sending a close frame if appropriate.
+
+:attr:`~legacy.protocol.WebSocketCommonProtocol.transfer_data_task` exits, unblocking
+:attr:`~legacy.protocol.WebSocketCommonProtocol.close_connection_task`, which closes
+the TCP connection.
+
+
+.. _server-shutdown:
+
+Server shutdown
+---------------
+
+:class:`~websockets.server.WebSocketServer` closes asynchronously like
+:class:`asyncio.Server`. The shutdown happen in two steps:
+
+1. Stop listening and accepting new connections;
+2. Close established connections with close code 1001 (going away) or, if
+ the opening handshake is still in progress, with HTTP status code 503
+ (Service Unavailable).
+
+The first call to :class:`~websockets.server.WebSocketServer.close` starts a
+task that performs this sequence. Further calls are ignored. This is the
+easiest way to make :class:`~websockets.server.WebSocketServer.close` and
+:class:`~websockets.server.WebSocketServer.wait_closed` idempotent.
+
+
+.. _cancellation:
+
+Cancellation
+------------
+
+User code
+.........
+
+websockets provides a WebSocket application server. It manages connections and
+passes them to user-provided connection handlers. This is an *inversion of
+control* scenario: library code calls user code.
+
+If a connection drops, the corresponding handler should terminate. If the
+server shuts down, all connection handlers must terminate. Canceling
+connection handlers would terminate them.
+
+However, using cancellation for this purpose would require all connection
+handlers to handle it properly. For example, if a connection handler starts
+some tasks, it should catch :exc:`~asyncio.CancelledError`, terminate or
+cancel these tasks, and then re-raise the exception.
+
+Cancellation is tricky in :mod:`asyncio` applications, especially when it
+interacts with finalization logic. In the example above, what if a handler
+gets interrupted with :exc:`~asyncio.CancelledError` while it's finalizing
+the tasks it started, after detecting that the connection dropped?
+
+websockets considers that cancellation may only be triggered by the caller of
+a coroutine when it doesn't care about the results of that coroutine anymore.
+(Source: `Guido van Rossum <https://groups.google.com/forum/#!msg
+/python-tulip/LZQe38CR3bg/7qZ1p_q5yycJ>`_). Since connection handlers run
+arbitrary user code, websockets has no way of deciding whether that code is
+still doing something worth caring about.
+
+For these reasons, websockets never cancels connection handlers. Instead it
+expects them to detect when the connection is closed, execute finalization
+logic if needed, and exit.
+
+Conversely, cancellation isn't a concern for WebSocket clients because they
+don't involve inversion of control.
+
+Library
+.......
+
+Most :doc:`public APIs <../reference/index>` of websockets are coroutines.
+They may be canceled, for example if the user starts a task that calls these
+coroutines and cancels the task later. websockets must handle this situation.
+
+Cancellation during the opening handshake is handled like any other exception:
+the TCP connection is closed and the exception is re-raised. This can only
+happen on the client side. On the server side, the opening handshake is
+managed by websockets and nothing results in a cancellation.
+
+Once the WebSocket connection is established, internal tasks
+:attr:`~legacy.protocol.WebSocketCommonProtocol.transfer_data_task` and
+:attr:`~legacy.protocol.WebSocketCommonProtocol.close_connection_task` mustn't get
+accidentally canceled if a coroutine that awaits them is canceled. In other
+words, they must be shielded from cancellation.
+
+:meth:`~legacy.protocol.WebSocketCommonProtocol.recv` waits for the next message in
+the queue or for :attr:`~legacy.protocol.WebSocketCommonProtocol.transfer_data_task`
+to terminate, whichever comes first. It relies on :func:`~asyncio.wait` for
+waiting on two futures in parallel. As a consequence, even though it's waiting
+on a :class:`~asyncio.Future` signaling the next message and on
+:attr:`~legacy.protocol.WebSocketCommonProtocol.transfer_data_task`, it doesn't
+propagate cancellation to them.
+
+:meth:`~legacy.protocol.WebSocketCommonProtocol.ensure_open` is called by
+:meth:`~legacy.protocol.WebSocketCommonProtocol.send`,
+:meth:`~legacy.protocol.WebSocketCommonProtocol.ping`, and
+:meth:`~legacy.protocol.WebSocketCommonProtocol.pong`. When the connection state is
+``CLOSING``, it waits for
+:attr:`~legacy.protocol.WebSocketCommonProtocol.transfer_data_task` but shields it to
+prevent cancellation.
+
+:meth:`~legacy.protocol.WebSocketCommonProtocol.close` waits for the data transfer
+task to terminate with :func:`~asyncio.timeout`. If it's canceled or if the
+timeout elapses, :attr:`~legacy.protocol.WebSocketCommonProtocol.transfer_data_task`
+is canceled, which is correct at this point.
+:meth:`~legacy.protocol.WebSocketCommonProtocol.close` then waits for
+:attr:`~legacy.protocol.WebSocketCommonProtocol.close_connection_task` but shields it
+to prevent cancellation.
+
+:meth:`~legacy.protocol.WebSocketCommonProtocol.close` and
+:meth:`~legacy.protocol.WebSocketCommonProtocol.fail_connection` are the only
+places where :attr:`~legacy.protocol.WebSocketCommonProtocol.transfer_data_task` may
+be canceled.
+
+:attr:`~legacy.protocol.WebSocketCommonProtocol.close_connection_task` starts by
+waiting for :attr:`~legacy.protocol.WebSocketCommonProtocol.transfer_data_task`. It
+catches :exc:`~asyncio.CancelledError` to prevent a cancellation of
+:attr:`~legacy.protocol.WebSocketCommonProtocol.transfer_data_task` from propagating
+to :attr:`~legacy.protocol.WebSocketCommonProtocol.close_connection_task`.
+
+.. _backpressure:
+
+Backpressure
+------------
+
+.. note::
+
+ This section discusses backpressure from the perspective of a server but
+ the concept applies to clients symmetrically.
+
+With a naive implementation, if a server receives inputs faster than it can
+process them, or if it generates outputs faster than it can send them, data
+accumulates in buffers, eventually causing the server to run out of memory and
+crash.
+
+The solution to this problem is backpressure. Any part of the server that
+receives inputs faster than it can process them and send the outputs
+must propagate that information back to the previous part in the chain.
+
+websockets is designed to make it easy to get backpressure right.
+
+For incoming data, websockets builds upon :class:`~asyncio.StreamReader` which
+propagates backpressure to its own buffer and to the TCP stream. Frames are
+parsed from the input stream and added to a bounded queue. If the queue fills
+up, parsing halts until the application reads a frame.
+
+For outgoing data, websockets builds upon :class:`~asyncio.StreamWriter` which
+implements flow control. If the output buffers grow too large, it waits until
+they're drained. That's why all APIs that write frames are asynchronous.
+
+Of course, it's still possible for an application to create its own unbounded
+buffers and break the backpressure. Be careful with queues.
+
+
+.. _buffers:
+
+Buffers
+-------
+
+.. note::
+
+ This section discusses buffers from the perspective of a server but it
+ applies to clients as well.
+
+An asynchronous systems works best when its buffers are almost always empty.
+
+For example, if a client sends data too fast for a server, the queue of
+incoming messages will be constantly full. The server will always be 32
+messages (by default) behind the client. This consumes memory and increases
+latency for no good reason. The problem is called bufferbloat.
+
+If buffers are almost always full and that problem cannot be solved by adding
+capacity — typically because the system is bottlenecked by the output and
+constantly regulated by backpressure — reducing the size of buffers minimizes
+negative consequences.
+
+By default websockets has rather high limits. You can decrease them according
+to your application's characteristics.
+
+Bufferbloat can happen at every level in the stack where there is a buffer.
+For each connection, the receiving side contains these buffers:
+
+- OS buffers: tuning them is an advanced optimization.
+- :class:`~asyncio.StreamReader` bytes buffer: the default limit is 64 KiB.
+ You can set another limit by passing a ``read_limit`` keyword argument to
+ :func:`~client.connect()` or :func:`~server.serve`.
+- Incoming messages :class:`~collections.deque`: its size depends both on
+ the size and the number of messages it contains. By default the maximum
+ UTF-8 encoded size is 1 MiB and the maximum number is 32. In the worst case,
+ after UTF-8 decoding, a single message could take up to 4 MiB of memory and
+ the overall memory consumption could reach 128 MiB. You should adjust these
+ limits by setting the ``max_size`` and ``max_queue`` keyword arguments of
+ :func:`~client.connect()` or :func:`~server.serve` according to your
+ application's requirements.
+
+For each connection, the sending side contains these buffers:
+
+- :class:`~asyncio.StreamWriter` bytes buffer: the default size is 64 KiB.
+ You can set another limit by passing a ``write_limit`` keyword argument to
+ :func:`~client.connect()` or :func:`~server.serve`.
+- OS buffers: tuning them is an advanced optimization.
+
+Concurrency
+-----------
+
+Awaiting any combination of :meth:`~legacy.protocol.WebSocketCommonProtocol.recv`,
+:meth:`~legacy.protocol.WebSocketCommonProtocol.send`,
+:meth:`~legacy.protocol.WebSocketCommonProtocol.close`
+:meth:`~legacy.protocol.WebSocketCommonProtocol.ping`, or
+:meth:`~legacy.protocol.WebSocketCommonProtocol.pong` concurrently is safe, including
+multiple calls to the same method, with one exception and one limitation.
+
+* **Only one coroutine can receive messages at a time.** This constraint
+ avoids non-deterministic behavior (and simplifies the implementation). If a
+ coroutine is awaiting :meth:`~legacy.protocol.WebSocketCommonProtocol.recv`,
+ awaiting it again in another coroutine raises :exc:`RuntimeError`.
+
+* **Sending a fragmented message forces serialization.** Indeed, the WebSocket
+ protocol doesn't support multiplexing messages. If a coroutine is awaiting
+ :meth:`~legacy.protocol.WebSocketCommonProtocol.send` to send a fragmented message,
+ awaiting it again in another coroutine waits until the first call completes.
+ This will be transparent in many cases. It may be a concern if the
+ fragmented message is generated slowly by an asynchronous iterator.
+
+Receiving frames is independent from sending frames. This isolates
+:meth:`~legacy.protocol.WebSocketCommonProtocol.recv`, which receives frames, from
+the other methods, which send frames.
+
+While the connection is open, each frame is sent with a single write. Combined
+with the concurrency model of :mod:`asyncio`, this enforces serialization. The
+only other requirement is to prevent interleaving other data frames in the
+middle of a fragmented message.
+
+After the connection is closed, sending a frame raises
+:exc:`~websockets.exceptions.ConnectionClosed`, which is safe.
diff --git a/testing/web-platform/tests/tools/third_party/websockets/docs/topics/index.rst b/testing/web-platform/tests/tools/third_party/websockets/docs/topics/index.rst
new file mode 100644
index 0000000000..120a3dd327
--- /dev/null
+++ b/testing/web-platform/tests/tools/third_party/websockets/docs/topics/index.rst
@@ -0,0 +1,18 @@
+Topic guides
+============
+
+Get a deeper understanding of how websockets is built and why.
+
+.. toctree::
+ :titlesonly:
+
+ deployment
+ logging
+ authentication
+ broadcast
+ compression
+ timeouts
+ design
+ memory
+ security
+ performance
diff --git a/testing/web-platform/tests/tools/third_party/websockets/docs/topics/lifecycle.graffle b/testing/web-platform/tests/tools/third_party/websockets/docs/topics/lifecycle.graffle
new file mode 100644
index 0000000000..bd888dcf31
--- /dev/null
+++ b/testing/web-platform/tests/tools/third_party/websockets/docs/topics/lifecycle.graffle
@@ -0,0 +1,25 @@
+��][W�8~~���}�G�%��a�.�0KC��N�����J���i�i���ߒs�5n����cUI咾������}틌b/ ~Y�:Z�d��� ~Y}��u�Z�����?v����j#ߋ����������j�G#_�Z;���`��� �Vk�pU[=O�ћV���R���CE���p$���2[�M�U(f�{A��zN����ƅ��l;��E�W2�\�u���B�$r �M�њ]�X�lϱ��t��Ɏ"[]��'<�&H����D�x�������}�7ZS�5�B'�OL�9ɑl�fYOD'a��s'�h,[��-۹PE.7:��9��٧d�"������
+�i�zM�&_�L,���*�mߎ������2K,�;�L�$�� Y'��eF9�x�Y�Yj.y��ٗ�X�`�Gyq@�sE̮���c�1 �K��m;�b�G�7�0Se��|Ef�/Y��k����a���AP� WeJ�;#ہ,�����M��"=�0K��V�0c�?�m��IE<�����:��o(~cp�����*�0*󷯆2��=@�U�*��x�ȷ�:��W��¡9C�m~]��WϗݫQ����i�f�:� �2��%�N�w��M
+�����<^ k`2}�0H��~���+��F�'�/2�T�s��\kS��*WO7��<&&{�v��s��(�yF[x�������J�%&Eq­���Ց�jXX:% ƅ!���f`�[#�-D-$�y]�݌���&���)Lr;wf�kM[E��톗yMe��m�}���pV�V�ݎ���a�fu1M͗3a=�۹*��m?NҜ�k{���Q]�<79/POZ��rͶ�l�f����`�=�LkM#��[Y&�%8��*ܤ��s�X)������a��0Ma0
+6�2�B�JKb@i���i"!8����u�;��H�����py2:��ǝwݥ��nz=(�P�������E����������֫�w�b�����QlC�)&$g��n�n1*A�Ж���{sF��,؊��٤��� O����7�X�b� Ģ&C�04�ʽ�ۻ�0
+dtb��8�� �Bp�X ��h��&Q~M7��K��%*�]�%Sd/J��g���8�&���� m��8����g�#���K����>����+���w��(� ���d}+��wݟ���Q`��{�t�+�����8���)տz���\����~odGn/��8�_�Iz�:Bӓ���0RC�$��$ �$��N�$Pc(�׋;G�����78OdзU�C++�1�z��P����v!����/��Tx}[��
+���:���GQr�����H�� �O��g�=u�i�V�})3tF
+Q
+� ��%«��c/�u�gO���O�t�>^ǂ�����.�͏a�wLę���R�D��#F����I��@J���dfE>��s�X����t�(� �C0�"��V)8���aA�+h��kL˄��&LsMc\�b�b!L�xAn��D�z`��D|W��'�OH���"Ne�ځ��#�,np��֒�ӗQo��p��������v�Cӹ�w�'Øb�#bppO ��2����
+�Y�c�YF:�����j`������ �:�A�V
+� �nzD܂S�8�X�B��qk6�mp��q�d�0O�ʦ^L��s����*���7��h�=�0�i>'a�k(�қ�kZE�;��rKW>àG�֌$�ʑ�����2lQ]�hnRaLM�b�*_�|�E���e��(*d��2�����%4�c�sv���z����)���H:_�����ӊR���4���4��ҁ/�=b��b�
+u�`����]�jʔ���]
+n\�]]
+rW�WӸ�׸�b:������R�fg��
+E:f&5-�H�ݯ�7���
+Ͳ��[4��zՑ����-r��O�o��&�P�u�b���~-�9�H+���P�z�1�[��5�7��q=��iV��t5[��I-{���{�[^ۂ�C�'M��.sS��U�_��o�{O��I������O���{#ǰt�N̤�p<��rj��4�yA���ENi�eYwANw��9/9�z̽�GNw��97��Vq",A *M�~����N��"���}!������,��Ŀ�a��&��:�;�8�Zywy9�>��Z������/���o��1] �0��Fj�nh�ԗ�ԣ���DŽ)�q��9�T��
+N_�G=:<���.�TK�W��<7?ZSܓZ���-����V�YWy����0�e�w�Q�n���e��*{Y��۩la� �Q�
+��/n'�v�2>���&.����{g�}�KŤ#!��@ƕ���[��#܍��E��R��aq7����dDEm�ߺ�z�,�ɭ�����S�h�J~վ�Z��i���#���8����.j� m8N7�v�
+s�k/$%u�� �y�Nn���X�Uj�)mGv��K�5KRz?s7xAU7L*�jOo5{ �An�jV�l)xOD���R�)�L.Ñ�}l ��"�T�6]u���9��UT��?GŻ����O,�F�ZG�:fo{c��m�'�;�~&r�':�$�`C&�/��2M[PM��
+͋qs��
+�$����V����Y�}?��"����Y�I {a���^W��;��T��ޭ-h���������#��-���^ئ�o����=o��G �}��w���=z�?����mH��e�.��������a��� 4�~�/���,���'Q�2*7� 1mT��5�ON����/����ZLa���4���o��7�aj�5S��[
+U��D����H���?QA�S�7=E�E��V�Q ��Xw�H�=
+���m�Ix��G3����������w��j�&�(*�!ԩ�[��n��%���Yv��;���c��5ox����
+/�I����f5�^]�Ŧ|{E��T��8�&Lߝ��>MT���2�;��
+�)�*}���)�k�4k�j�z���l��y����<r���̾-�8��9[T����o��I��R d��i�O�`��J�T���L�±���\Ө)��p���ɩ�Ŝ��Х�k�F��w
+�Β���01�٭�����ס6W�� �Ǵj
diff --git a/testing/web-platform/tests/tools/third_party/websockets/docs/topics/lifecycle.svg b/testing/web-platform/tests/tools/third_party/websockets/docs/topics/lifecycle.svg
new file mode 100644
index 0000000000..0a9818d293
--- /dev/null
+++ b/testing/web-platform/tests/tools/third_party/websockets/docs/topics/lifecycle.svg
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xl="http://www.w3.org/1999/xlink" version="1.1" viewBox="-14.3464565 112.653543 624.6929 372.69291" width="624.6929pt" height="372.69291pt" xmlns:dc="http://purl.org/dc/elements/1.1/"><metadata> Produced by OmniGraffle 6.6.2 <dc:date>2018-07-29 15:25:34 +0000</dc:date></metadata><defs><font-face font-family="Courier New" font-size="12" panose-1="2 7 6 9 2 2 5 2 4 4" units-per-em="1000" underline-position="-232.91016" underline-thickness="100.097656" slope="0" x-height="443.35938" cap-height="591.79688" ascent="832.51953" descent="-300.29297" font-weight="bold"><font-face-src><font-face-name name="CourierNewPS-BoldMT"/></font-face-src></font-face><font-face font-family="Courier New" font-size="12" panose-1="2 7 3 9 2 2 5 2 4 4" units-per-em="1000" underline-position="-232.91016" underline-thickness="41.015625" slope="0" x-height="422.85156" cap-height="571.28906" ascent="832.51953" descent="-300.29297" font-weight="500"><font-face-src><font-face-name name="CourierNewPSMT"/></font-face-src></font-face><marker orient="auto" overflow="visible" markerUnits="strokeWidth" id="StickArrow_Marker" viewBox="-1 -4 8 8" markerWidth="8" markerHeight="8" color="black"><g><path d="M 5.8666667 0 L 0 0 M 0 -2.2 L 5.8666667 0 L 0 2.2" fill="none" stroke="currentColor" stroke-width="1"/></g></marker><font-face font-family="Verdana" font-size="12" panose-1="2 11 6 4 3 5 4 4 2 4" units-per-em="1000" underline-position="-87.890625" underline-thickness="58.59375" slope="0" x-height="545.41016" cap-height="727.0508" ascent="1005.3711" descent="-209.96094" font-weight="500"><font-face-src><font-face-name name="Verdana"/></font-face-src></font-face></defs><g stroke="none" stroke-opacity="1" stroke-dasharray="none" fill="none" fill-opacity="1"><title>Canvas 1</title><g><title>Layer 1</title><text transform="translate(19.173228 148.90551)" fill="black"><tspan font-family="Courier New" font-size="12" font-weight="bold" x="1.5138254" y="10" textLength="72.01172">CONNECTING</tspan></text><text transform="translate(160.90551 148.90551)" fill="black"><tspan font-family="Courier New" font-size="12" font-weight="bold" x="23.117341" y="10" textLength="28.804688">OPEN</tspan></text><text transform="translate(359.3307 148.90551)" fill="black"><tspan font-family="Courier New" font-size="12" font-weight="bold" x="12.315583" y="10" textLength="50.408203">CLOSING</tspan></text><text transform="translate(501.063 148.90551)" fill="black"><tspan font-family="Courier New" font-size="12" font-weight="bold" x="15.916169" y="10" textLength="43.20703">CLOSED</tspan></text><line x1="198.4252" y1="170.07874" x2="198.4252" y2="453.5433" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke-dasharray="1,3"/><line x1="396.8504" y1="170.07874" x2="396.8504" y2="453.5433" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke-dasharray="1,3"/><line x1="538.58267" y1="170.07874" x2="538.58267" y2="453.5433" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke-dasharray="1,3"/><line x1="56.692913" y1="170.07874" x2="56.692913" y2="453.5433" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke-dasharray="1,3"/><path d="M 240.94488 240.94488 L 411.02362 240.94488 C 418.85128 240.94488 425.19685 247.29045 425.19685 255.11811 L 425.19685 255.11811 C 425.19685 262.94577 418.85128 269.29134 411.02362 269.29134 L 240.94488 269.29134 C 233.11722 269.29134 226.77165 262.94577 226.77165 255.11811 L 226.77165 255.11811 C 226.77165 247.29045 233.11722 240.94488 240.94488 240.94488 Z" fill="#dadada"/><path d="M 240.94488 240.94488 L 411.02362 240.94488 C 418.85128 240.94488 425.19685 247.29045 425.19685 255.11811 L 425.19685 255.11811 C 425.19685 262.94577 418.85128 269.29134 411.02362 269.29134 L 240.94488 269.29134 C 233.11722 269.29134 226.77165 262.94577 226.77165 255.11811 L 226.77165 255.11811 C 226.77165 247.29045 233.11722 240.94488 240.94488 240.94488 Z" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><text transform="translate(226.77165 248.11811)" fill="black"><tspan font-family="Courier New" font-size="12" font-weight="bold" x="52.40498" y="10" textLength="93.615234">transfer_data</tspan></text><path d="M 240.94488 354.3307 L 552.7559 354.3307 C 560.58356 354.3307 566.92913 360.67628 566.92913 368.50393 L 566.92913 368.50393 C 566.92913 376.3316 560.58356 382.67716 552.7559 382.67716 L 240.94488 382.67716 C 233.11722 382.67716 226.77165 376.3316 226.77165 368.50393 L 226.77165 368.50393 C 226.77165 360.67628 233.11722 354.3307 240.94488 354.3307 Z" fill="#dadada"/><path d="M 240.94488 354.3307 L 552.7559 354.3307 C 560.58356 354.3307 566.92913 360.67628 566.92913 368.50393 L 566.92913 368.50393 C 566.92913 376.3316 560.58356 382.67716 552.7559 382.67716 L 240.94488 382.67716 C 233.11722 382.67716 226.77165 376.3316 226.77165 368.50393 L 226.77165 368.50393 C 226.77165 360.67628 233.11722 354.3307 240.94488 354.3307 Z" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><text transform="translate(231.77165 361.50393)" fill="black"><tspan font-family="Courier New" font-size="12" font-weight="bold" x="107.469364" y="10" textLength="115.21875">close_connection</tspan></text><path d="M 99.2126 184.25197 L 155.90551 184.25197 C 163.73317 184.25197 170.07874 190.59754 170.07874 198.4252 L 170.07874 198.4252 C 170.07874 206.25285 163.73317 212.59842 155.90551 212.59842 L 99.2126 212.59842 C 91.38494 212.59842 85.03937 206.25285 85.03937 198.4252 L 85.03937 198.4252 C 85.03937 190.59754 91.38494 184.25197 99.2126 184.25197 Z" fill="#6f6"/><path d="M 99.2126 184.25197 L 155.90551 184.25197 C 163.73317 184.25197 170.07874 190.59754 170.07874 198.4252 L 170.07874 198.4252 C 170.07874 206.25285 163.73317 212.59842 155.90551 212.59842 L 99.2126 212.59842 C 91.38494 212.59842 85.03937 206.25285 85.03937 198.4252 L 85.03937 198.4252 C 85.03937 190.59754 91.38494 184.25197 99.2126 184.25197 Z" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><text transform="translate(90.03937 191.4252)" fill="black"><tspan font-family="Courier New" font-size="12" font-weight="bold" x="12.315583" y="10" textLength="50.408203">connect</tspan></text><path d="M 240.94488 184.25197 L 496.063 184.25197 C 503.89065 184.25197 510.23622 190.59754 510.23622 198.4252 L 510.23622 198.4252 C 510.23622 206.25285 503.89065 212.59842 496.063 212.59842 L 240.94488 212.59842 C 233.11722 212.59842 226.77165 206.25285 226.77165 198.4252 L 226.77165 198.4252 C 226.77165 190.59754 233.11722 184.25197 240.94488 184.25197 Z" fill="#6f6"/><path d="M 240.94488 184.25197 L 496.063 184.25197 C 503.89065 184.25197 510.23622 190.59754 510.23622 198.4252 L 510.23622 198.4252 C 510.23622 206.25285 503.89065 212.59842 496.063 212.59842 L 240.94488 212.59842 C 233.11722 212.59842 226.77165 206.25285 226.77165 198.4252 L 226.77165 198.4252 C 226.77165 190.59754 233.11722 184.25197 240.94488 184.25197 Z" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><text transform="translate(231.77165 191.4252)" fill="black"><tspan font-family="Courier New" font-size="12" font-weight="bold" x="17.912947" y="10" textLength="100.816406">recv / send / </tspan><tspan font-family="Courier New" font-size="12" font-weight="500" x="118.72935" y="10" textLength="93.615234">ping / pong /</tspan><tspan font-family="Courier New" font-size="12" font-weight="bold" x="212.34459" y="10" textLength="50.408203"> close </tspan></text><path d="M 170.07874 198.4252 L 183.97874 198.4252 L 198.4252 198.4252 L 198.4252 283.46457 L 198.4252 368.50393 L 212.87165 368.50393 L 215.37165 368.50393" marker-end="url(#StickArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><text transform="translate(75.86614 410.19685)" fill="black"><tspan font-family="Verdana" font-size="12" font-weight="500" x="27.760296" y="12" textLength="52.083984">opening </tspan><tspan font-family="Verdana" font-size="12" font-weight="500" x="19.164593" y="27" textLength="58.02539">handshak</tspan><tspan font-family="Verdana" font-size="12" font-weight="500" x="77.072796" y="27" textLength="7.1484375">e</tspan></text><text transform="translate(416.02362 410.19685)" fill="black"><tspan font-family="Verdana" font-size="12" font-weight="500" x="19.182171" y="12" textLength="65.021484">connection</tspan><tspan font-family="Verdana" font-size="12" font-weight="500" x="16.861858" y="27" textLength="69.66211">termination</tspan></text><text transform="translate(217.59842 410.19685)" fill="black"><tspan font-family="Verdana" font-size="12" font-weight="500" x="41.03058" y="12" textLength="40.6875">data tr</tspan><tspan font-family="Verdana" font-size="12" font-weight="500" x="81.507143" y="12" textLength="37.541016">ansfer</tspan><tspan font-family="Verdana" font-size="12" font-weight="500" x="18.211245" y="27" textLength="116.625">&amp; closing handshak</tspan><tspan font-family="Verdana" font-size="12" font-weight="500" x="134.71906" y="27" textLength="7.1484375">e</tspan></text><path d="M 425.19685 255.11811 L 439.09685 255.11811 L 453.5433 255.11811 L 453.5433 342.9307" marker-end="url(#StickArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><path d="M 240.94488 297.6378 L 411.02362 297.6378 C 418.85128 297.6378 425.19685 303.98336 425.19685 311.81102 L 425.19685 311.81102 C 425.19685 319.63868 418.85128 325.98425 411.02362 325.98425 L 240.94488 325.98425 C 233.11722 325.98425 226.77165 319.63868 226.77165 311.81102 L 226.77165 311.81102 C 226.77165 303.98336 233.11722 297.6378 240.94488 297.6378 Z" fill="#dadada"/><path d="M 240.94488 297.6378 L 411.02362 297.6378 C 418.85128 297.6378 425.19685 303.98336 425.19685 311.81102 L 425.19685 311.81102 C 425.19685 319.63868 418.85128 325.98425 411.02362 325.98425 L 240.94488 325.98425 C 233.11722 325.98425 226.77165 319.63868 226.77165 311.81102 L 226.77165 311.81102 C 226.77165 303.98336 233.11722 297.6378 240.94488 297.6378 Z" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><text transform="translate(226.77165 304.81102)" fill="black"><tspan font-family="Courier New" font-size="12" font-weight="bold" x="48.804395" y="10" textLength="100.816406">keepalive_ping</tspan></text><line x1="198.4252" y1="255.11811" x2="214.62165" y2="255.11811" marker-end="url(#StickArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><line x1="198.4252" y1="311.81102" x2="215.37165" y2="311.81102" marker-end="url(#StickArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/></g></g></svg>
diff --git a/testing/web-platform/tests/tools/third_party/websockets/docs/topics/logging.rst b/testing/web-platform/tests/tools/third_party/websockets/docs/topics/logging.rst
new file mode 100644
index 0000000000..e7abd96ce5
--- /dev/null
+++ b/testing/web-platform/tests/tools/third_party/websockets/docs/topics/logging.rst
@@ -0,0 +1,245 @@
+Logging
+=======
+
+.. currentmodule:: websockets
+
+Logs contents
+-------------
+
+When you run a WebSocket client, your code calls coroutines provided by
+websockets.
+
+If an error occurs, websockets tells you by raising an exception. For example,
+it raises a :exc:`~exceptions.ConnectionClosed` exception if the other side
+closes the connection.
+
+When you run a WebSocket server, websockets accepts connections, performs the
+opening handshake, runs the connection handler coroutine that you provided,
+and performs the closing handshake.
+
+Given this `inversion of control`_, if an error happens in the opening
+handshake or if the connection handler crashes, there is no way to raise an
+exception that you can handle.
+
+.. _inversion of control: https://en.wikipedia.org/wiki/Inversion_of_control
+
+Logs tell you about these errors.
+
+Besides errors, you may want to record the activity of the server.
+
+In a request/response protocol such as HTTP, there's an obvious way to record
+activity: log one event per request/response. Unfortunately, this solution
+doesn't work well for a bidirectional protocol such as WebSocket.
+
+Instead, when running as a server, websockets logs one event when a
+`connection is established`_ and another event when a `connection is
+closed`_.
+
+.. _connection is established: https://www.rfc-editor.org/rfc/rfc6455.html#section-4
+.. _connection is closed: https://www.rfc-editor.org/rfc/rfc6455.html#section-7.1.4
+
+By default, websockets doesn't log an event for every message. That would be
+excessive for many applications exchanging small messages at a fast rate. If
+you need this level of detail, you could add logging in your own code.
+
+Finally, you can enable debug logs to get details about everything websockets
+is doing. This can be useful when developing clients as well as servers.
+
+See :ref:`log levels <log-levels>` below for a list of events logged by
+websockets logs at each log level.
+
+Configure logging
+-----------------
+
+websockets relies on the :mod:`logging` module from the standard library in
+order to maximize compatibility and integrate nicely with other libraries::
+
+ import logging
+
+websockets logs to the ``"websockets.client"`` and ``"websockets.server"``
+loggers.
+
+websockets doesn't provide a default logging configuration because
+requirements vary a lot depending on the environment.
+
+Here's a basic configuration for a server in production::
+
+ logging.basicConfig(
+ format="%(asctime)s %(message)s",
+ level=logging.INFO,
+ )
+
+Here's how to enable debug logs for development::
+
+ logging.basicConfig(
+ format="%(message)s",
+ level=logging.DEBUG,
+ )
+
+Furthermore, websockets adds a ``websocket`` attribute to log records, so you
+can include additional information about the current connection in logs.
+
+You could attempt to add information with a formatter::
+
+ # this doesn't work!
+ logging.basicConfig(
+ format="{asctime} {websocket.id} {websocket.remote_address[0]} {message}",
+ level=logging.INFO,
+ style="{",
+ )
+
+However, this technique runs into two problems:
+
+* The formatter applies to all records. It will crash if it receives a record
+ without a ``websocket`` attribute. For example, this happens when logging
+ that the server starts because there is no current connection.
+
+* Even with :meth:`str.format` style, you're restricted to attribute and index
+ lookups, which isn't enough to implement some fairly simple requirements.
+
+There's a better way. :func:`~client.connect` and :func:`~server.serve` accept
+a ``logger`` argument to override the default :class:`~logging.Logger`. You
+can set ``logger`` to a :class:`~logging.LoggerAdapter` that enriches logs.
+
+For example, if the server is behind a reverse
+proxy, :attr:`~legacy.protocol.WebSocketCommonProtocol.remote_address` gives
+the IP address of the proxy, which isn't useful. IP addresses of clients are
+provided in an HTTP header set by the proxy.
+
+Here's how to include them in logs, assuming they're in the
+``X-Forwarded-For`` header::
+
+ logging.basicConfig(
+ format="%(asctime)s %(message)s",
+ level=logging.INFO,
+ )
+
+ class LoggerAdapter(logging.LoggerAdapter):
+ """Add connection ID and client IP address to websockets logs."""
+ def process(self, msg, kwargs):
+ try:
+ websocket = kwargs["extra"]["websocket"]
+ except KeyError:
+ return msg, kwargs
+ xff = websocket.request_headers.get("X-Forwarded-For")
+ return f"{websocket.id} {xff} {msg}", kwargs
+
+ async with websockets.serve(
+ ...,
+ # Python < 3.10 requires passing None as the second argument.
+ logger=LoggerAdapter(logging.getLogger("websockets.server"), None),
+ ):
+ ...
+
+Logging to JSON
+---------------
+
+Even though :mod:`logging` predates structured logging, it's still possible to
+output logs as JSON with a bit of effort.
+
+First, we need a :class:`~logging.Formatter` that renders JSON:
+
+.. literalinclude:: ../../example/logging/json_log_formatter.py
+
+Then, we configure logging to apply this formatter::
+
+ handler = logging.StreamHandler()
+ handler.setFormatter(formatter)
+
+ logger = logging.getLogger()
+ logger.addHandler(handler)
+ logger.setLevel(logging.INFO)
+
+Finally, we populate the ``event_data`` custom attribute in log records with
+a :class:`~logging.LoggerAdapter`::
+
+ class LoggerAdapter(logging.LoggerAdapter):
+ """Add connection ID and client IP address to websockets logs."""
+ def process(self, msg, kwargs):
+ try:
+ websocket = kwargs["extra"]["websocket"]
+ except KeyError:
+ return msg, kwargs
+ kwargs["extra"]["event_data"] = {
+ "connection_id": str(websocket.id),
+ "remote_addr": websocket.request_headers.get("X-Forwarded-For"),
+ }
+ return msg, kwargs
+
+ async with websockets.serve(
+ ...,
+ # Python < 3.10 requires passing None as the second argument.
+ logger=LoggerAdapter(logging.getLogger("websockets.server"), None),
+ ):
+ ...
+
+Disable logging
+---------------
+
+If your application doesn't configure :mod:`logging`, Python outputs messages
+of severity ``WARNING`` and higher to :data:`~sys.stderr`. As a consequence,
+you will see a message and a stack trace if a connection handler coroutine
+crashes or if you hit a bug in websockets.
+
+If you want to disable this behavior for websockets, you can add
+a :class:`~logging.NullHandler`::
+
+ logging.getLogger("websockets").addHandler(logging.NullHandler())
+
+Additionally, if your application configures :mod:`logging`, you must disable
+propagation to the root logger, or else its handlers could output logs::
+
+ logging.getLogger("websockets").propagate = False
+
+Alternatively, you could set the log level to ``CRITICAL`` for the
+``"websockets"`` logger, as the highest level currently used is ``ERROR``::
+
+ logging.getLogger("websockets").setLevel(logging.CRITICAL)
+
+Or you could configure a filter to drop all messages::
+
+ logging.getLogger("websockets").addFilter(lambda record: None)
+
+.. _log-levels:
+
+Log levels
+----------
+
+Here's what websockets logs at each level.
+
+``ERROR``
+.........
+
+* Exceptions raised by connection handler coroutines in servers
+* Exceptions resulting from bugs in websockets
+
+``WARNING``
+...........
+
+* Failures in :func:`~websockets.broadcast`
+
+``INFO``
+........
+
+* Server starting and stopping
+* Server establishing and closing connections
+* Client reconnecting automatically
+
+``DEBUG``
+.........
+
+* Changes to the state of connections
+* Handshake requests and responses
+* All frames sent and received
+* Steps to close a connection
+* Keepalive pings and pongs
+* Errors handled transparently
+
+Debug messages have cute prefixes that make logs easier to scan:
+
+* ``>`` - send something
+* ``<`` - receive something
+* ``=`` - set connection state
+* ``x`` - shut down connection
+* ``%`` - manage pings and pongs
+* ``!`` - handle errors and timeouts
diff --git a/testing/web-platform/tests/tools/third_party/websockets/docs/topics/memory.rst b/testing/web-platform/tests/tools/third_party/websockets/docs/topics/memory.rst
new file mode 100644
index 0000000000..e44247a77c
--- /dev/null
+++ b/testing/web-platform/tests/tools/third_party/websockets/docs/topics/memory.rst
@@ -0,0 +1,48 @@
+Memory usage
+============
+
+.. currentmodule:: websockets
+
+In most cases, memory usage of a WebSocket server is proportional to the
+number of open connections. When a server handles thousands of connections,
+memory usage can become a bottleneck.
+
+Memory usage of a single connection is the sum of:
+
+1. the baseline amount of memory websockets requires for each connection,
+2. the amount of data held in buffers before the application processes it,
+3. any additional memory allocated by the application itself.
+
+Baseline
+--------
+
+Compression settings are the main factor affecting the baseline amount of
+memory used by each connection.
+
+With websockets' defaults, on the server side, a single connections uses
+70 KiB of memory.
+
+Refer to the :doc:`topic guide on compression <../topics/compression>` to
+learn more about tuning compression settings.
+
+Buffers
+-------
+
+Under normal circumstances, buffers are almost always empty.
+
+Under high load, if a server receives more messages than it can process,
+bufferbloat can result in excessive memory usage.
+
+By default websockets has generous limits. It is strongly recommended to adapt
+them to your application. When you call :func:`~server.serve`:
+
+- Set ``max_size`` (default: 1 MiB, UTF-8 encoded) to the maximum size of
+ messages your application generates.
+- Set ``max_queue`` (default: 32) to the maximum number of messages your
+ application expects to receive faster than it can process them. The queue
+ provides burst tolerance without slowing down the TCP connection.
+
+Furthermore, you can lower ``read_limit`` and ``write_limit`` (default:
+64 KiB) to reduce the size of buffers for incoming and outgoing data.
+
+The design document provides :ref:`more details about buffers <buffers>`.
diff --git a/testing/web-platform/tests/tools/third_party/websockets/docs/topics/performance.rst b/testing/web-platform/tests/tools/third_party/websockets/docs/topics/performance.rst
new file mode 100644
index 0000000000..45e23b2390
--- /dev/null
+++ b/testing/web-platform/tests/tools/third_party/websockets/docs/topics/performance.rst
@@ -0,0 +1,20 @@
+Performance
+===========
+
+Here are tips to optimize performance.
+
+uvloop
+------
+
+You can make a websockets application faster by running it with uvloop_.
+
+(This advice isn't specific to websockets. It applies to any :mod:`asyncio`
+application.)
+
+.. _uvloop: https://github.com/MagicStack/uvloop
+
+broadcast
+---------
+
+:func:`~websockets.broadcast` is the most efficient way to send a message to
+many clients.
diff --git a/testing/web-platform/tests/tools/third_party/websockets/docs/topics/protocol.graffle b/testing/web-platform/tests/tools/third_party/websockets/docs/topics/protocol.graffle
new file mode 100644
index 0000000000..04a9e7acb5
--- /dev/null
+++ b/testing/web-platform/tests/tools/third_party/websockets/docs/topics/protocol.graffle
@@ -0,0 +1,37 @@
+��]ks�6����
+���[Ӹ��q�t�n�xc'�v<ӡ%X�"U���f���J�x-J���ۉl�A��\q��_FQ�N�0�|�=����~2�Ꮟ���#���'�����ѯ/z�(�����g�_������Ǒ��}~��w����Q���}��q��Y����=??�C����!�v�d����5t�
+�A>x ���^|;���G�=��/�����~\��U<�_��o�b�z�ӧ���寗M��a?ȡ�Ӟ獂4
+�/�=���)��KFq8L���{��+
+NO#-���HJ�X(�P>��D�d����&y�7��$���Ӊ޽��,�2��p��Y؟�͞}Ff(��k�a|����o���b���=����2`�b?
+�f��$
+w�5y�|A9��<�S���"���1O/B�ɧ�������ip��b`oޖ�3:�����L�0ӿ%ɨÊ���A�6
+��b-\Nf1��W�hq����9o�J��(ދ�a�n���>}�bN�� zJ����'\*�d��p��0��y�7H�w��A��� �@E��~�5�$i����H�a��7��5Vm�<��Qpq��ƽ�����~���w�S飋q�qLS�-��?�8�s�|jIcng<3�c��ꊭ2�|<��X[e�I���O��un(,����>���猯J��|W�r�t�w_��.�C���R$�U�)�Qʐ���|I)�Xq�}�J� �ǂbd\}�
+��5#��JH�+�S /o�N+�4f5;�yy�v|i��\ڑ���K �Ҵ2���[�4�Z�Ϣi�gJo��W<���A��B|DY!C�DGA��ن�1�g�! ϯ���Κ@\g���* *Ʃ���1��+*��1�=)�/��1�2#�$�UT(.a�()a��Q&��>RJ�oߚ��¾�g�X6����$
+u�F��rԙ���\,,\��;N�(�&޸U��������g�5>�+�%o�劀/�2T}c3�
+�� +�h�q�A�i~���8���!&���~�K������=�z|
++7?��O���(�4>>�i�sԫ��|�~%)4��q����a�ul~9�&>���/������?��8���1&Ň������ U c �M[�����7L� ��Ad����Y��ӠZz��x����q��N�`��o~������"��+7t�J�K"|��`R��t��:t���"�v�{p�@��4?K�IDς�xlF`S�K�/]66�œ���'9<��WT16b�OC>�o) �p����y�Խ|�\~��f�Ad�������Ǖd�*�K�|�f�ՍB_�#5 �n� �M�P�kZm�N��ᵲ��������a
+.���f�̸=���*v,�����٤?�a�!BV�c_����?gYk��y��"�2���P�J���&�4f�/<N�""���Ķ�9����V�Oa׾*�|)�pG��BQ�)�P��%yO� �6}����Xs�ږkY��$��
+�><�DǏn�U��� tS&�5fQS{��a�����H� F�X>�v=��f����ĩ�v6�-�p+��5L8·�*�j]��%l-�� )@G�m�ȅ�eqΰ5|ykǍ���8Y�;B�M���"&����S˜����֎;F\��M
+��� +#.i��������Xu:)�ڛggnqS淍��``mb)}čJ�6�m��$W0H�" 1_��P�,�
+��W��!{�
+�}���m��l�����j{ɖ�@1,i��1�@n;\|�!_�Ta!�tr�H;ϒh�=�TŤNhl�� c��WV���֋��O�|m�yqUY$�7��
+��(�$'��sՄ�鏘}V�_��CKKl�K�w�ӎ1V�_h5�[��������D�f�wwCH��bE'&Z�fѢ(���e[�և\�4�
+wr��X�'VA�1���@�v�mp�F3
+֐�I'�K:�b��A�p?%�B.��xx�b�h�XI��¤s)�:���rb��'�X�W,�� {>%DA%gJ:�L�2��A���<`�L���ʀ��B�|ݐ?S�C�(�e�0
+r�OT�d�A-K�z����IM�@��w&u����6�4��+�u�,�c�����1]�����XpJ|�̨X��,��n3�$�c�{�+�Z�Y#�����š�ԝ�-����4�hl�� uO�ϖ.�+��yɱ�}���t'י�u���N�ui}���x�\殺
+��|A}_ N)�;�V.Q�"�3��8ج
+��[����1Y���pmKҟW�߅7��\_��n]� ��E�\t�E�pt-����][K����!7�{�{F�T�3�8�fG�:��<�b�>�Ew`|��G�+0�[���[�GZ��:�sK�����;�|P
+0����_V=�/���=�L�Fj��Jj�M�E�=�VqYر�
+��x;͗������u�4�e�j����F�Z3�m�f���:��1t�S{���HgY0���MB�6,||R�h9�L��u,A�\��f���Q`N}�*��AA�|y �Z�H*�m�B�7��#�杫���*�I�R��0��u�8�i]�u�]���ǹ��sm���:L��Xv=a��B��u�,���&���7��]�C���YE��ݺ�D�#��SX��_�xh`[U�z��OX��Y\_$����֊{��\�H�� O���RM 偙!1�����C�mtmR�pv5��
+:o�c=��� ��L��:�r�iy$�P����0��<��HR%WBZ�6�=�'8��J�"���d���,�R��И�������3.��/�X��0׿'sm���m[����N�� ,u�»�ݩ�ypC'�wǻ�;�aw��u���t�=�Y�9�s@瀮#Н\䷈s������}�܄m`G\P�&tn‡�/1�>K�ޠ�Њ�ͤV��m8�v�;�u8��p��47��Am=�i��HK�:�uH����&���g��N�a��ʠ,�ԫ"*PJ�h��
+�x������Aׇ��jٕ%��Ʒd��[��5��y�8_#�f �#�j�N+8vi���� s�.���0���W�1\�a`>G�pj�bk|�mk�8���xz��J�y�~��cE��\wJ��$#�4)@�Y�;׽�!��*��G�Ŵ��IX�n��曳��7߶
+��7L|SHh+�v�Hh�2,���ۥ���p
+�Զ�n��"ugQ��,|eJ�տj�Rw�r����?�-aXT`�HܽJY���-����O�X��A8ɚ 3u胪L�l�c�<��A{/�w���&v�ᙧ0��:���� �4+lֻ��j�#U�`�j���G����s�֐���}��[�X�µ���� ��˟��R)����q�e�e�)
+�q)�,�����X��T�LF��M�n�,�ܒKzsn�N��݉�eq ��afw�3�aƲ�#��:��*��� ��WU�KeA��G �K0�i���1��qRA����D�l5��&����ܮ&���}MB�٘+ՏH�%y��R},F�*u�qJ�`0�(�}��"�8��D��N$߁�ֻ+���s"��H����̷��(��ڜ���\���b��Zw�$ҫ���}]��]r��q0o@�%ð���6�lV�i�JKm�-�J�VK7 �C��8�i
+%��>ɒ�'0�����G}rX|���FI|�&9�at�I5\���]T8����K�Ψp��=N�s*�}B�T��\�t<�l��
+���8�<ʠ31���������dՖ],����Ijm>��֐|�ܓ�̴Z�۳���q0Ύ��/�jt�ڈ�yC+��?�Yx�����X��9�C�P;BP�۴A����z|����(����L��B��;���5�h|�odJ�&Y���mz�I~�Z�uX����Y�a|���n�8�6���K|/GA���+2W�a�O~ ��a��ء|�ւ�P�V]VZA�|ø9�A�7� O�ވ�:�����6���9'��nX�_a��y2;�W�
+����{��6�Ѭ@����R`g���\�K2OgF��қ�� �v�������Y�nf�q�����.�]�t�{{��$�ø��
+�s�'z����9��v���ӹsM��5}�:�nn�7�ϒ<OF��0,-�RTk��i�y��g�3\��bM��2I�?ͮ.w��]�F}���=�����9��#F��Ϣ�^�L������ዳ�����'���}�{��=�����h���GG��?~������b�����Kt������Z��ez �\���i�-�O?����N��1l�MŒ�ݦ�n��W�����%\H0�TuÜ�f��ӟA�iK܇����m������ut_���E������Ä�����rLy��W*�^��iMu�J��4g*#`�Q�7�o?�?YEQ���N] ø���^�ī:hwEc�q_���}�1������q�WM������0#���'i
+3Q�dSO���/f&���).�q�;�X�xZ��2@��"�' �������A�)��X<��n-�r�^�I��rռ]\dyz`��(8y�
+�� Jkj�L4���
+z-�|<n �"�G�ݝ�)�4��$��V��!���Y����ba��,��|/e0j��s�<}�������
diff --git a/testing/web-platform/tests/tools/third_party/websockets/docs/topics/protocol.svg b/testing/web-platform/tests/tools/third_party/websockets/docs/topics/protocol.svg
new file mode 100644
index 0000000000..51bfd982be
--- /dev/null
+++ b/testing/web-platform/tests/tools/third_party/websockets/docs/topics/protocol.svg
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xl="http://www.w3.org/1999/xlink" version="1.1" viewBox="0 0 624.34646 822.34646" width="624.34646pt" height="822.34646pt" xmlns:dc="http://purl.org/dc/elements/1.1/"><metadata> Produced by OmniGraffle 6.6.2 <dc:date>2019-07-07 08:38:24 +0000</dc:date></metadata><defs><font-face font-family="Verdana" font-size="12" panose-1="2 11 6 4 3 5 4 4 2 4" units-per-em="1000" underline-position="-87.890625" underline-thickness="58.59375" slope="0" x-height="545.41016" cap-height="727.0508" ascent="1005.3711" descent="-209.96094" font-weight="500"><font-face-src><font-face-name name="Verdana"/></font-face-src></font-face><font-face font-family="Courier New" font-size="12" panose-1="2 7 3 9 2 2 5 2 4 4" units-per-em="1000" underline-position="-232.91016" underline-thickness="41.015625" slope="0" x-height="422.85156" cap-height="571.28906" ascent="832.51953" descent="-300.29297" font-weight="500"><font-face-src><font-face-name name="CourierNewPSMT"/></font-face-src></font-face><font-face font-family="Courier New" font-size="12" panose-1="2 7 6 9 2 2 5 2 4 4" units-per-em="1000" underline-position="-232.91016" underline-thickness="100.097656" slope="0" x-height="443.35938" cap-height="591.79688" ascent="832.51953" descent="-300.29297" font-weight="bold"><font-face-src><font-face-name name="CourierNewPS-BoldMT"/></font-face-src></font-face><font-face font-family="Courier New" font-size="10" panose-1="2 7 3 9 2 2 5 2 4 4" units-per-em="1000" underline-position="-232.91016" underline-thickness="41.015625" slope="0" x-height="422.85156" cap-height="571.28906" ascent="832.51953" descent="-300.29297" font-weight="500"><font-face-src><font-face-name name="CourierNewPSMT"/></font-face-src></font-face><marker orient="auto" overflow="visible" markerUnits="strokeWidth" id="StickArrow_Marker" viewBox="-1 -4 8 8" markerWidth="8" markerHeight="8" color="black"><g><path d="M 5.8666667 0 L 0 0 M 0 -2.2 L 5.8666667 0 L 0 2.2" fill="none" stroke="currentColor" stroke-width="1"/></g></marker><radialGradient cx="0" cy="0" r="1" id="Gradient" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="white"/><stop offset="1" stop-color="#a5a5a5"/></radialGradient><radialGradient id="Obj_Gradient" xl:href="#Gradient" gradientTransform="translate(311.81102 708.6614) scale(145.75703)"/><marker orient="auto" overflow="visible" markerUnits="strokeWidth" id="StickArrow_Marker_2" viewBox="-1 -6 14 12" markerWidth="14" markerHeight="12" color="black"><g><path d="M 12 0 L 0 0 M 0 -4.5 L 12 0 L 0 4.5" fill="none" stroke="currentColor" stroke-width="1"/></g></marker><marker orient="auto" overflow="visible" markerUnits="strokeWidth" id="StickArrow_Marker_3" viewBox="-1 -4 8 8" markerWidth="8" markerHeight="8" color="black"><g><path d="M 5.9253333 0 L 0 0 M 0 -2.222 L 5.9253333 0 L 0 2.222" fill="none" stroke="currentColor" stroke-width="1"/></g></marker></defs><g stroke="none" stroke-opacity="1" stroke-dasharray="none" fill="none" fill-opacity="1"><title>Canvas 1</title><rect fill="white" width="1314" height="1698"/><g><title>Layer 1</title><rect x="28.346457" y="765.35433" width="566.92913" height="28.346457" fill="#6cf"/><rect x="28.346457" y="765.35433" width="566.92913" height="28.346457" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><text transform="translate(33.346457 772.02755)" fill="black"><tspan font-family="Verdana" font-size="12" font-weight="500" x="228.50753" y="12" textLength="99.91406">remote endpoint</tspan></text><rect x="28.346457" y="85.03937" width="566.92913" height="566.92913" fill="white"/><rect x="28.346457" y="85.03937" width="566.92913" height="566.92913" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><text transform="translate(33.346457 90.03937)" fill="black"><tspan font-family="Verdana" font-size="12" font-weight="500" x="243.79171" y="12" textLength="51.333984">websock</tspan><tspan font-family="Verdana" font-size="12" font-weight="500" x="295.00851" y="12" textLength="18.128906">ets</tspan><tspan font-family="Courier New" font-size="12" font-weight="500" x="195.65109" y="25" textLength="165.62695">WebSocketCommonProtocol</tspan></text><rect x="28.346457" y="28.346457" width="566.92913" height="28.346457" fill="#6f6"/><rect x="28.346457" y="28.346457" width="566.92913" height="28.346457" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><text transform="translate(33.346457 35.019685)" fill="black"><tspan font-family="Verdana" font-size="12" font-weight="500" x="230.0046" y="12" textLength="96.91992">application logic</tspan></text><path d="M 102.047243 586.77165 L 238.11023 586.77165 C 247.49858 586.77165 255.11811 596.93102 255.11811 609.4488 C 255.11811 621.9666 247.49858 632.12598 238.11023 632.12598 L 102.047243 632.12598 C 92.658897 632.12598 85.03937 621.9666 85.03937 609.4488 C 85.03937 596.93102 92.658897 586.77165 102.047243 586.77165" fill="#fc6"/><path d="M 102.047243 586.77165 L 238.11023 586.77165 C 247.49858 586.77165 255.11811 596.93102 255.11811 609.4488 C 255.11811 621.9666 247.49858 632.12598 238.11023 632.12598 L 102.047243 632.12598 C 92.658897 632.12598 85.03937 621.9666 85.03937 609.4488 C 85.03937 596.93102 92.658897 586.77165 102.047243 586.77165 M 238.11023 586.77165 C 228.72189 586.77165 221.10236 596.93102 221.10236 609.4488 C 221.10236 621.9666 228.72189 632.12598 238.11023 632.12598" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><text transform="translate(125.33071 596.9488)" fill="black"><tspan font-family="Courier New" font-size="12" font-weight="bold" x="14.896484" y="10" textLength="43.20703">reader</tspan><tspan font-family="Courier New" font-size="10" font-weight="500" x=".49414062" y="22" textLength="72.01172">StreamReader</tspan></text><path d="M 385.5118 586.77165 L 521.5748 586.77165 C 530.96315 586.77165 538.58267 596.93102 538.58267 609.4488 C 538.58267 621.9666 530.96315 632.12598 521.5748 632.12598 L 385.5118 632.12598 C 376.12346 632.12598 368.50393 621.9666 368.50393 609.4488 C 368.50393 596.93102 376.12346 586.77165 385.5118 586.77165" fill="#fc6"/><path d="M 385.5118 586.77165 L 521.5748 586.77165 C 530.96315 586.77165 538.58267 596.93102 538.58267 609.4488 C 538.58267 621.9666 530.96315 632.12598 521.5748 632.12598 L 385.5118 632.12598 C 376.12346 632.12598 368.50393 621.9666 368.50393 609.4488 C 368.50393 596.93102 376.12346 586.77165 385.5118 586.77165 M 521.5748 586.77165 C 512.18645 586.77165 504.56693 596.93102 504.56693 609.4488 C 504.56693 621.9666 512.18645 632.12598 521.5748 632.12598" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><text transform="translate(408.79527 596.9488)" fill="black"><tspan font-family="Courier New" font-size="12" font-weight="bold" x="14.896484" y="10" textLength="43.20703">writer</tspan><tspan font-family="Courier New" font-size="10" font-weight="500" x=".49414062" y="22" textLength="72.01172">StreamWriter</tspan></text><path d="M 481.88976 419.52756 L 481.88976 374.17323 C 481.88976 371.04378 469.19055 368.50393 453.5433 368.50393 C 437.89606 368.50393 425.19685 371.04378 425.19685 374.17323 L 425.19685 419.52756 C 425.19685 422.657 437.89606 425.19685 453.5433 425.19685 C 469.19055 425.19685 481.88976 422.657 481.88976 419.52756" fill="#fecc66"/><path d="M 481.88976 419.52756 L 481.88976 374.17323 C 481.88976 371.04378 469.19055 368.50393 453.5433 368.50393 C 437.89606 368.50393 425.19685 371.04378 425.19685 374.17323 L 425.19685 419.52756 C 425.19685 422.657 437.89606 425.19685 453.5433 425.19685 C 469.19055 425.19685 481.88976 422.657 481.88976 419.52756 M 481.88976 374.17323 C 481.88976 377.30267 469.19055 379.84252 453.5433 379.84252 C 437.89606 379.84252 425.19685 377.30267 425.19685 374.17323" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width=".75"/><text transform="translate(429.19685 387.18504)" fill="black"><tspan font-family="Courier New" font-size="12" font-weight="500" x="6.343527" y="10" textLength="36.00586">pings</tspan><tspan font-family="Courier New" font-size="10" font-weight="500" x="12.3445034" y="22" textLength="24.003906">dict</tspan></text><path d="M 85.039413 283.46457 L 255.11806 283.46457 C 270.7734 283.46457 283.46457 296.15573 283.46457 311.81107 L 283.46457 481.88972 C 283.46457 497.54506 270.7734 510.23622 255.11806 510.23622 L 85.039413 510.23622 C 69.384074 510.23622 56.692913 497.54506 56.692913 481.88972 L 56.692913 311.81107 C 56.692913 296.15573 69.384074 283.46457 85.039413 283.46457 Z" fill="#dadada"/><path d="M 85.039413 283.46457 L 255.11806 283.46457 C 270.7734 283.46457 283.46457 296.15573 283.46457 311.81107 L 283.46457 481.88972 C 283.46457 497.54506 270.7734 510.23622 255.11806 510.23622 L 85.039413 510.23622 C 69.384074 510.23622 56.692913 497.54506 56.692913 481.88972 L 56.692913 311.81107 C 56.692913 296.15573 69.384074 283.46457 85.039413 283.46457 Z" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><text transform="translate(61.692913 288.46457)" fill="black"><tspan font-family="Courier New" font-size="12" font-weight="bold" x="43.57528" y="10" textLength="129.62109">transfer_data_task</tspan><tspan font-family="Courier New" font-size="10" font-weight="500" x="96.383873" y="22" textLength="24.003906">Task</tspan></text><path d="M 297.6378 765.35433 L 297.6378 609.4488 L 255.11811 609.4488 L 269.01811 609.4488 L 266.51811 609.4488" marker-end="url(#StickArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><path d="M 368.50393 609.4488 L 354.60393 609.4488 L 325.98425 609.4488 L 325.98425 753.95433" marker-end="url(#StickArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><path d="M 207.03401 712.3154 C 161.22047 708.6614 179.48976 677.90097 252.5726 683.1496 C 259.35307 672.91835 344.33858 674.579 343.783 683.1496 C 397.0715 672.1877 465.17102 694.04553 419.49354 705.00744 C 474.30425 710.32206 418.80189 738.9565 373.8189 734.17322 C 370.2189 742.14584 289.80283 744.9358 282.74457 734.17322 C 237.20882 745.66715 142.25953 727.9946 207.03401 712.3154 Z" fill="url(#Obj_Gradient)"/><path d="M 207.03401 712.3154 C 161.22047 708.6614 179.48976 677.90097 252.5726 683.1496 C 259.35307 672.91835 344.33858 674.579 343.783 683.1496 C 397.0715 672.1877 465.17102 694.04553 419.49354 705.00744 C 474.30425 710.32206 418.80189 738.9565 373.8189 734.17322 C 370.2189 742.14584 289.80283 744.9358 282.74457 734.17322 C 237.20882 745.66715 142.25953 727.9946 207.03401 712.3154 Z" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><text transform="translate(217.59842 701.1614)" fill="black"><tspan font-family="Verdana" font-size="12" font-weight="500" x="69.81416" y="12" textLength="48.796875">network</tspan></text><rect x="85.03937" y="453.5433" width="170.07874" height="28.346457" fill="#ff6"/><rect x="85.03937" y="453.5433" width="170.07874" height="28.346457" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><text transform="translate(90.03937 460.71653)" fill="black"><tspan font-family="Courier New" font-size="12" font-weight="bold" x="44.03351" y="10" textLength="72.01172">read_frame</tspan></text><rect x="85.03937" y="396.8504" width="170.07874" height="28.346457" fill="#ff6"/><rect x="85.03937" y="396.8504" width="170.07874" height="28.346457" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><text transform="translate(90.03937 404.02362)" fill="black"><tspan font-family="Courier New" font-size="12" font-weight="bold" x="26.03058" y="10" textLength="108.01758">read_data_frame</tspan></text><rect x="85.03937" y="340.15748" width="170.07874" height="28.346457" fill="#ff6"/><rect x="85.03937" y="340.15748" width="170.07874" height="28.346457" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><text transform="translate(90.03937 347.3307)" fill="black"><tspan font-family="Courier New" font-size="12" font-weight="bold" x="36.832338" y="10" textLength="86.41406">read_message</tspan></text><text transform="translate(178.07874 490.563)" fill="black"><tspan font-family="Courier New" font-size="10" font-weight="500" x="0" y="8" textLength="30.004883">bytes</tspan></text><text transform="translate(178.07874 433.87008)" fill="black"><tspan font-family="Courier New" font-size="10" font-weight="500" x="0" y="8" textLength="36.00586">frames</tspan></text><text transform="translate(178.07874 371.67716)" fill="black"><tspan font-family="Courier New" font-size="10" font-weight="500" x="0" y="8" textLength="24.003906">data</tspan><tspan font-family="Courier New" font-size="10" font-weight="500" x="0" y="19" textLength="36.00586">frames</tspan></text><rect x="368.50393" y="510.23622" width="170.07874" height="28.346457" fill="#ff6"/><rect x="368.50393" y="510.23622" width="170.07874" height="28.346457" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><text transform="translate(373.50393 517.40945)" fill="black"><tspan font-family="Courier New" font-size="12" font-weight="bold" x="40.432924" y="10" textLength="79.21289">write_frame</tspan></text><path d="M 85.03937 609.4488 L 71.13937 609.4488 L 56.692913 609.4488 L 56.692913 595.2756 L 56.692913 566.92913 L 113.385826 566.92913 L 170.07874 566.92913 L 170.07874 495.78976 L 170.07874 494.03976" marker-end="url(#StickArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><path d="M 453.5433 539.33267 L 453.5433 552.48267 L 453.5433 566.92913 L 510.23622 566.92913 L 569.76378 566.92913 L 569.76378 595.2756 L 569.76378 609.4488 L 552.48267 609.4488 L 549.98267 609.4488" marker-end="url(#StickArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><line x1="170.07874" y1="453.5433" x2="170.07874" y2="437.34685" marker-end="url(#StickArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><line x1="170.07874" y1="396.8504" x2="170.07874" y2="380.65393" marker-end="url(#StickArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><path d="M 102.047243 204.09449 L 238.11023 204.09449 C 247.49858 204.09449 255.11811 214.25386 255.11811 226.77165 C 255.11811 239.28945 247.49858 249.44882 238.11023 249.44882 L 102.047243 249.44882 C 92.658897 249.44882 85.03937 239.28945 85.03937 226.77165 C 85.03937 214.25386 92.658897 204.09449 102.047243 204.09449" fill="#fc6"/><path d="M 102.047243 204.09449 L 238.11023 204.09449 C 247.49858 204.09449 255.11811 214.25386 255.11811 226.77165 C 255.11811 239.28945 247.49858 249.44882 238.11023 249.44882 L 102.047243 249.44882 C 92.658897 249.44882 85.03937 239.28945 85.03937 226.77165 C 85.03937 214.25386 92.658897 204.09449 102.047243 204.09449 M 238.11023 204.09449 C 228.72189 204.09449 221.10236 214.25386 221.10236 226.77165 C 221.10236 239.28945 228.72189 249.44882 238.11023 249.44882" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><text transform="translate(132.33071 214.27165)" fill="black"><tspan font-family="Courier New" font-size="12" font-weight="bold" x=".1953125" y="10" textLength="57.609375">messages</tspan><tspan font-family="Courier New" font-size="10" font-weight="500" x="13.997559" y="22" textLength="30.004883">deque</tspan></text><path d="M 255.11811 354.3307 L 269.01811 354.3307 L 297.6378 354.3307 L 297.6378 328.8189 L 297.6378 226.77165 L 269.01811 226.77165 L 266.51811 226.77165" marker-end="url(#StickArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><rect x="85.03937" y="141.73228" width="170.07874" height="28.346457" fill="#cf6"/><rect x="85.03937" y="141.73228" width="170.07874" height="28.346457" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><text transform="translate(90.03937 148.90551)" fill="black"><tspan font-family="Courier New" font-size="12" font-weight="bold" x="65.637026" y="10" textLength="28.804688">recv</tspan></text><path d="M 85.03937 226.77165 L 71.13937 226.77165 L 42.519685 226.77165 L 42.519685 209.76378 L 42.519685 155.90551 L 71.13937 155.90551 L 73.63937 155.90551" marker-end="url(#StickArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><line x1="170.07874" y1="141.73228" x2="170.07874" y2="68.092913" marker-end="url(#StickArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><line x1="453.5433" y1="56.692913" x2="453.5433" y2="130.33228" marker-end="url(#StickArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><line x1="467.71653" y1="56.692913" x2="467.71653" y2="187.8752" marker-end="url(#StickArrow_Marker_2)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width=".75"/><line x1="481.88976" y1="56.692913" x2="481.88976" y2="244.56811" marker-end="url(#StickArrow_Marker_2)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width=".75"/><line x1="496.063" y1="56.692913" x2="496.063" y2="300.32302" marker-end="url(#StickArrow_Marker_3)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><rect x="368.50393" y="141.73228" width="170.07874" height="28.346457" fill="#cf6"/><rect x="368.50393" y="141.73228" width="170.07874" height="28.346457" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><text transform="translate(373.50393 148.90551)" fill="black"><tspan font-family="Courier New" font-size="12" font-weight="bold" x="65.637026" y="10" textLength="28.804688">send</tspan></text><rect x="368.50393" y="198.4252" width="170.07874" height="28.346457" fill="#cf6"/><rect x="368.50393" y="198.4252" width="170.07874" height="28.346457" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width=".75"/><text transform="translate(373.50393 205.59842)" fill="black"><tspan font-family="Courier New" font-size="12" font-weight="500" x="65.637026" y="10" textLength="28.804688">ping</tspan></text><rect x="368.50393" y="255.11811" width="170.07874" height="28.346457" fill="#cf6"/><rect x="368.50393" y="255.11811" width="170.07874" height="28.346457" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width=".75"/><text transform="translate(373.50393 262.29134)" fill="black"><tspan font-family="Courier New" font-size="12" font-weight="500" x="65.637026" y="10" textLength="28.804688">pong</tspan></text><rect x="368.50393" y="311.81102" width="170.07874" height="28.346457" fill="#cf6"/><rect x="368.50393" y="311.81102" width="170.07874" height="28.346457" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><text transform="translate(373.50393 318.98425)" fill="black"><tspan font-family="Courier New" font-size="12" font-weight="bold" x="62.03644" y="10" textLength="36.00586">close</tspan></text><path d="M 538.58267 155.90551 L 552.48267 155.90551 L 566.92913 155.90551 L 566.92913 481.88976 L 453.5433 481.88976 L 453.5433 496.33622 L 453.5433 498.08622" marker-end="url(#StickArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><line x1="538.58267" y1="212.59842" x2="566.92913" y2="212.59842" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width=".75"/><line x1="538.58267" y1="269.29134" x2="566.92913" y2="269.29134" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width=".75"/><line x1="538.58267" y1="325.98425" x2="566.92913" y2="325.98425" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><path d="M 255.86811 411.02362 L 262.61811 411.02362 L 340.15748 411.02362 L 340.15748 481.88976 L 453.5433 481.88976" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><text transform="translate(291.94527 399.02362)" fill="black"><tspan font-family="Courier New" font-size="10" font-weight="500" x="0" y="8" textLength="42.006836">control</tspan><tspan font-family="Courier New" font-size="10" font-weight="500" x="0" y="21" textLength="36.00586">frames</tspan></text><line x1="340.15748" y1="411.02362" x2="414.64685" y2="411.02362" marker-end="url(#StickArrow_Marker_2)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width=".75"/><path d="M 368.50393 212.59842 L 361.75393 212.59842 L 340.15748 212.59842 L 340.15748 340.15748 L 340.15748 382.67716" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width=".75"/><text transform="translate(461.5433 547.2559)" fill="black"><tspan font-family="Courier New" font-size="10" font-weight="500" x="0" y="8" textLength="30.004883">bytes</tspan></text><text transform="translate(461.5433 490.563)" fill="black"><tspan font-family="Courier New" font-size="10" font-weight="500" x="0" y="8" textLength="36.00586">frames</tspan></text><line x1="340.15748" y1="382.67716" x2="414.64685" y2="382.67716" marker-end="url(#StickArrow_Marker_2)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width=".75"/></g></g></svg>
diff --git a/testing/web-platform/tests/tools/third_party/websockets/docs/topics/security.rst b/testing/web-platform/tests/tools/third_party/websockets/docs/topics/security.rst
new file mode 100644
index 0000000000..d3dec21bd1
--- /dev/null
+++ b/testing/web-platform/tests/tools/third_party/websockets/docs/topics/security.rst
@@ -0,0 +1,41 @@
+Security
+========
+
+Encryption
+----------
+
+For production use, a server should require encrypted connections.
+
+See this example of :ref:`encrypting connections with TLS
+<secure-server-example>`.
+
+Memory usage
+------------
+
+.. warning::
+
+ An attacker who can open an arbitrary number of connections will be able
+ to perform a denial of service by memory exhaustion. If you're concerned
+ by denial of service attacks, you must reject suspicious connections
+ before they reach websockets, typically in a reverse proxy.
+
+With the default settings, opening a connection uses 70 KiB of memory.
+
+Sending some highly compressed messages could use up to 128 MiB of memory with
+an amplification factor of 1000 between network traffic and memory usage.
+
+Configuring a server to :doc:`optimize memory usage <memory>` will improve
+security in addition to improving performance.
+
+Other limits
+------------
+
+websockets implements additional limits on the amount of data it accepts in
+order to minimize exposure to security vulnerabilities.
+
+In the opening handshake, websockets limits the number of HTTP headers to 256
+and the size of an individual header to 4096 bytes. These limits are 10 to 20
+times larger than what's expected in standard use cases. They're hard-coded.
+
+If you need to change these limits, you can monkey-patch the constants in
+``websockets.http11``.
diff --git a/testing/web-platform/tests/tools/third_party/websockets/docs/topics/timeouts.rst b/testing/web-platform/tests/tools/third_party/websockets/docs/topics/timeouts.rst
new file mode 100644
index 0000000000..633fc1ab43
--- /dev/null
+++ b/testing/web-platform/tests/tools/third_party/websockets/docs/topics/timeouts.rst
@@ -0,0 +1,116 @@
+Timeouts
+========
+
+.. currentmodule:: websockets
+
+Long-lived connections
+----------------------
+
+Since the WebSocket protocol is intended for real-time communications over
+long-lived connections, it is desirable to ensure that connections don't
+break, and if they do, to report the problem quickly.
+
+Connections can drop as a consequence of temporary network connectivity issues,
+which are very common, even within data centers.
+
+Furthermore, WebSocket builds on top of HTTP/1.1 where connections are
+short-lived, even with ``Connection: keep-alive``. Typically, HTTP/1.1
+infrastructure closes idle connections after 30 to 120 seconds.
+
+As a consequence, proxies may terminate WebSocket connections prematurely when
+no message was exchanged in 30 seconds.
+
+.. _keepalive:
+
+Keepalive in websockets
+-----------------------
+
+To avoid these problems, websockets runs a keepalive and heartbeat mechanism
+based on WebSocket Ping_ and Pong_ frames, which are designed for this purpose.
+
+.. _Ping: https://www.rfc-editor.org/rfc/rfc6455.html#section-5.5.2
+.. _Pong: https://www.rfc-editor.org/rfc/rfc6455.html#section-5.5.3
+
+It loops through these steps:
+
+1. Wait 20 seconds.
+2. Send a Ping frame.
+3. Receive a corresponding Pong frame within 20 seconds.
+
+If the Pong frame isn't received, websockets considers the connection broken and
+closes it.
+
+This mechanism serves two purposes:
+
+1. It creates a trickle of traffic so that the TCP connection isn't idle and
+ network infrastructure along the path keeps it open ("keepalive").
+2. It detects if the connection drops or becomes so slow that it's unusable in
+ practice ("heartbeat"). In that case, it terminates the connection and your
+ application gets a :exc:`~exceptions.ConnectionClosed` exception.
+
+Timings are configurable with the ``ping_interval`` and ``ping_timeout``
+arguments of :func:`~client.connect` and :func:`~server.serve`. Shorter values
+will detect connection drops faster but they will increase network traffic and
+they will be more sensitive to latency.
+
+Setting ``ping_interval`` to :obj:`None` disables the whole keepalive and
+heartbeat mechanism.
+
+Setting ``ping_timeout`` to :obj:`None` disables only timeouts. This enables
+keepalive, to keep idle connections open, and disables heartbeat, to support large
+latency spikes.
+
+.. admonition:: Why doesn't websockets rely on TCP keepalive?
+ :class: hint
+
+ TCP keepalive is disabled by default on most operating systems. When
+ enabled, the default interval is two hours or more, which is far too much.
+
+Keepalive in browsers
+---------------------
+
+Browsers don't enable a keepalive mechanism like websockets by default. As a
+consequence, they can fail to notice that a WebSocket connection is broken for
+an extended period of time, until the TCP connection times out.
+
+In this scenario, the ``WebSocket`` object in the browser doesn't fire a
+``close`` event. If you have a reconnection mechanism, it doesn't kick in
+because it believes that the connection is still working.
+
+If your browser-based app mysteriously and randomly fails to receive events,
+this is a likely cause. You need a keepalive mechanism in the browser to avoid
+this scenario.
+
+Unfortunately, the WebSocket API in browsers doesn't expose the native Ping and
+Pong functionality in the WebSocket protocol. You have to roll your own in the
+application layer.
+
+Latency issues
+--------------
+
+Latency between a client and a server may increase for two reasons:
+
+* Network connectivity is poor. When network packets are lost, TCP attempts to
+ retransmit them, which manifests as latency. Excessive packet loss makes
+ the connection unusable in practice. At some point, timing out is a
+ reasonable choice.
+
+* Traffic is high. For example, if a client sends messages on the connection
+ faster than a server can process them, this manifests as latency as well,
+ because data is waiting in flight, mostly in OS buffers.
+
+ If the server is more than 20 seconds behind, it doesn't see the Pong before
+ the default timeout elapses. As a consequence, it closes the connection.
+ This is a reasonable choice to prevent overload.
+
+ If traffic spikes cause unwanted timeouts and you're confident that the server
+ will catch up eventually, you can increase ``ping_timeout`` or you can set it
+ to :obj:`None` to disable heartbeat entirely.
+
+ The same reasoning applies to situations where the server sends more traffic
+ than the client can accept.
+
+The latency measured during the last exchange of Ping and Pong frames is
+available in the :attr:`~legacy.protocol.WebSocketCommonProtocol.latency`
+attribute. Alternatively, you can measure the latency at any time with the
+:attr:`~legacy.protocol.WebSocketCommonProtocol.ping` method.