diff options
Diffstat (limited to 'testing/web-platform/tests/tools/third_party/websockets/docs/topics')
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��Rd��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">& 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� �ǂbd\}� +��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. |