.\" Man page generated from reStructuredText. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "WEBSOCKETS" "1" "Apr 06, 2024" "12.0" "websockets" .SH NAME websockets \- websockets 12.0 .sp \fI\%licence\fP \fI\%version\fP \fI\%pyversions\fP \fI\%tests\fP \fI\%docs\fP \fI\%openssf\fP .sp websockets is a library for building \fI\%WebSocket\fP servers and clients in Python with a focus on correctness, simplicity, robustness, and performance. .sp It supports several network I/O and control flow paradigms: .INDENT 0.0 .IP 1. 3 The default implementation builds upon \fI\%asyncio\fP, Python\(aqs standard asynchronous I/O framework. It provides an elegant coroutine\-based API. It\(aqs ideal for servers that handle many clients concurrently. .IP 2. 3 The \fI\%threading\fP implementation is a good alternative for clients, especially if you aren\(aqt familiar with \fI\%asyncio\fP\&. It may also be used for servers that don\(aqt need to serve many clients. .IP 3. 3 The \fI\%Sans\-I/O\fP implementation is designed for integrating in third\-party libraries, typically application servers, in addition being used internally by websockets. .UNINDENT .sp Here\(aqs an echo server with the \fI\%asyncio\fP API: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C #!/usr/bin/env python import asyncio from websockets.server import serve async def echo(websocket): async for message in websocket: await websocket.send(message) async def main(): async with serve(echo, \(dqlocalhost\(dq, 8765): await asyncio.Future() # run forever asyncio.run(main()) .ft P .fi .UNINDENT .UNINDENT .sp Here\(aqs how a client sends and receives messages with the \fI\%threading\fP API: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C #!/usr/bin/env python import asyncio from websockets.sync.client import connect def hello(): with connect(\(dqws://localhost:8765\(dq) as websocket: websocket.send(\(dqHello world!\(dq) message = websocket.recv() print(f\(dqReceived: {message}\(dq) hello() .ft P .fi .UNINDENT .UNINDENT .sp Don\(aqt worry about the opening and closing handshakes, pings and pongs, or any other behavior described in the WebSocket specification. websockets takes care of this under the hood so you can focus on your application! .sp Also, websockets provides an interactive client: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ python \-m websockets ws://localhost:8765/ Connected to ws://localhost:8765/. > Hello world! < Hello world! Connection closed: 1000 (OK). .ft P .fi .UNINDENT .UNINDENT .sp Do you like it? \fI\%Let\(aqs dive in!\fP .SH GETTING STARTED .SS Requirements .sp websockets requires Python ≥ 3.8. .INDENT 0.0 .INDENT 3.5 .IP "Use the most recent Python release" .sp For each minor version (3.x), only the latest bugfix or security release (3.x.y) is officially supported. .UNINDENT .UNINDENT .sp It doesn\(aqt have any dependencies. .SS Installation .sp Install websockets with: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ pip install websockets .ft P .fi .UNINDENT .UNINDENT .sp Wheels are available for all platforms. .SS Tutorial .sp Learn how to build an real\-time web application with websockets. .SS Part 1 \- Send & receive .sp In this tutorial, you\(aqre going to build a web\-based \fI\%Connect Four\fP game. .sp The web removes the constraint of being in the same room for playing a game. Two players can connect over of the Internet, regardless of where they are, and play in their browsers. .sp When a player makes a move, it should be reflected immediately on both sides. This is difficult to implement over HTTP due to the request\-response style of the protocol. .sp Indeed, there is no good way to be notified when the other player makes a move. Workarounds such as polling or long\-polling introduce significant overhead. .sp Enter \fI\%WebSocket\fP\&. .sp The WebSocket protocol provides two\-way communication between a browser and a server over a persistent connection. That\(aqs exactly what you need to exchange moves between players, via a server. .INDENT 0.0 .INDENT 3.5 .IP "This is the first part of the tutorial." .INDENT 0.0 .IP \(bu 2 In this \fI\%first part\fP, you will create a server and connect one browser; you can play if you share the same browser. .IP \(bu 2 In the \fI\%second part\fP, you will connect a second browser; you can play from different browsers on a local network. .IP \(bu 2 In the \fI\%third part\fP, you will deploy the game to the web; you can play from any browser connected to the Internet. .UNINDENT .UNINDENT .UNINDENT .SS Prerequisites .sp This tutorial assumes basic knowledge of Python and JavaScript. .sp If you\(aqre comfortable with \fI\%virtual environments\fP, you can use one for this tutorial. Else, don\(aqt worry: websockets doesn\(aqt have any dependencies; it shouldn\(aqt create trouble in the default environment. .sp If you haven\(aqt installed websockets yet, do it now: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ pip install websockets .ft P .fi .UNINDENT .UNINDENT .sp Confirm that websockets is installed: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ python \-m websockets \-\-version .ft P .fi .UNINDENT .UNINDENT .INDENT 0.0 .INDENT 3.5 .IP "This tutorial is written for websockets 12.0\&." .sp If you installed another version, you should switch to the corresponding version of the documentation. .UNINDENT .UNINDENT .SS Download the starter kit .sp Create a directory and download these three files: \fBconnect4.js\fP, \fBconnect4.css\fP, and \fBconnect4.py\fP\&. .sp The JavaScript module, along with the CSS file, provides a web\-based user interface. Here\(aqs its API. .INDENT 0.0 .TP .B connect4\&.PLAYER1 Color of the first player. .UNINDENT .INDENT 0.0 .TP .B connect4\&.PLAYER2 Color of the second player. .UNINDENT .INDENT 0.0 .TP .B connect4\&.createBoard(board) Draw a board. .INDENT 7.0 .TP .B Arguments .INDENT 7.0 .IP \(bu 2 \fBboard\fP \-\- DOM element containing the board; must be initially empty. .UNINDENT .UNINDENT .UNINDENT .INDENT 0.0 .TP .B connect4\&.playMove(board, player, column, row) Play a move. .INDENT 7.0 .TP .B Arguments .INDENT 7.0 .IP \(bu 2 \fBboard\fP \-\- DOM element containing the board. .IP \(bu 2 \fBplayer\fP \-\- \fI\%PLAYER1\fP or \fI\%PLAYER2\fP\&. .IP \(bu 2 \fBcolumn\fP \-\- between \fB0\fP and \fB6\fP\&. .IP \(bu 2 \fBrow\fP \-\- between \fB0\fP and \fB5\fP\&. .UNINDENT .UNINDENT .UNINDENT .sp The Python module provides a class to record moves and tell when a player wins. Here\(aqs its API. .INDENT 0.0 .TP .B connect4.PLAYER1 = \(dqred\(dq Color of the first player. .UNINDENT .INDENT 0.0 .TP .B connect4.PLAYER2 = \(dqyellow\(dq Color of the second player. .UNINDENT .INDENT 0.0 .TP .B class connect4.Connect4 A Connect Four game. .INDENT 7.0 .TP .B play(player, column) Play a move. .INDENT 7.0 .TP .B Parameters .INDENT 7.0 .IP \(bu 2 \fBplayer\fP \-\- \fI\%PLAYER1\fP or \fI\%PLAYER2\fP\&. .IP \(bu 2 \fBcolumn\fP \-\- between \fB0\fP and \fB6\fP\&. .UNINDENT .TP .B Returns Row where the checker lands, between \fB0\fP and \fB5\fP\&. .TP .B Raises \fI\%RuntimeError\fP \-\- if the move is illegal. .UNINDENT .UNINDENT .INDENT 7.0 .TP .B moves List of moves played during this game, as \fB(player, column, row)\fP tuples. .UNINDENT .INDENT 7.0 .TP .B winner \fI\%PLAYER1\fP or \fI\%PLAYER2\fP if they won; \fI\%None\fP if the game is still ongoing. .UNINDENT .UNINDENT .SS Bootstrap the web UI .sp Create an \fBindex.html\fP file next to \fBconnect4.js\fP and \fBconnect4.css\fP with this content: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C Connect Four
.ft P .fi .UNINDENT .UNINDENT .sp This HTML page contains an empty \fB
\fP element where you will draw the Connect Four board. It loads a \fBmain.js\fP script where you will write all your JavaScript code. .sp Create a \fBmain.js\fP file next to \fBindex.html\fP\&. In this script, when the page loads, draw the board: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C import { createBoard, playMove } from \(dq./connect4.js\(dq; window.addEventListener(\(dqDOMContentLoaded\(dq, () => { // Initialize the UI. const board = document.querySelector(\(dq.board\(dq); createBoard(board); }); .ft P .fi .UNINDENT .UNINDENT .sp Open a shell, navigate to the directory containing these files, and start an HTTP server: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ python \-m http.server .ft P .fi .UNINDENT .UNINDENT .sp Open \fI\%http://localhost:8000/\fP in a web browser. The page displays an empty board with seven columns and six rows. You will play moves in this board later. .SS Bootstrap the server .sp Create an \fBapp.py\fP file next to \fBconnect4.py\fP with this content: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C #!/usr/bin/env python import asyncio import websockets async def handler(websocket): while True: message = await websocket.recv() print(message) async def main(): async with websockets.serve(handler, \(dq\(dq, 8001): await asyncio.Future() # run forever if __name__ == \(dq__main__\(dq: asyncio.run(main()) .ft P .fi .UNINDENT .UNINDENT .sp The entry point of this program is \fBasyncio.run(main())\fP\&. It creates an asyncio event loop, runs the \fBmain()\fP coroutine, and shuts down the loop. .sp The \fBmain()\fP coroutine calls \fI\%serve()\fP to start a websockets server. \fI\%serve()\fP takes three positional arguments: .INDENT 0.0 .IP \(bu 2 \fBhandler\fP is a coroutine that manages a connection. When a client connects, websockets calls \fBhandler\fP with the connection in argument. When \fBhandler\fP terminates, websockets closes the connection. .IP \(bu 2 The second argument defines the network interfaces where the server can be reached. Here, the server listens on all interfaces, so that other devices on the same local network can connect. .IP \(bu 2 The third argument is the port on which the server listens. .UNINDENT .sp Invoking \fI\%serve()\fP as an asynchronous context manager, in an \fBasync with\fP block, ensures that the server shuts down properly when terminating the program. .sp For each connection, the \fBhandler()\fP coroutine runs an infinite loop that receives messages from the browser and prints them. .sp Open a shell, navigate to the directory containing \fBapp.py\fP, and start the server: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ python app.py .ft P .fi .UNINDENT .UNINDENT .sp This doesn\(aqt display anything. Hopefully the WebSocket server is running. Let\(aqs make sure that it works. You cannot test the WebSocket server with a web browser like you tested the HTTP server. However, you can test it with websockets\(aq interactive client. .sp Open another shell and run this command: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ python \-m websockets ws://localhost:8001/ .ft P .fi .UNINDENT .UNINDENT .sp You get a prompt. Type a message and press \(dqEnter\(dq. Switch to the shell where the server is running and check that the server received the message. Good! .sp Exit the interactive client with Ctrl\-C or Ctrl\-D. .sp Now, if you look at the console where you started the server, you can see the stack trace of an exception: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C connection handler failed Traceback (most recent call last): ... File \(dqapp.py\(dq, line 22, in handler message = await websocket.recv() ... websockets.exceptions.ConnectionClosedOK: received 1000 (OK); then sent 1000 (OK) .ft P .fi .UNINDENT .UNINDENT .sp Indeed, the server was waiting for the next message with \fI\%recv()\fP when the client disconnected. When this happens, websockets raises a \fI\%ConnectionClosedOK\fP exception to let you know that you won\(aqt receive another message on this connection. .sp This exception creates noise in the server logs, making it more difficult to spot real errors when you add functionality to the server. Catch it in the \fBhandler()\fP coroutine: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C async def handler(websocket): while True: try: message = await websocket.recv() except websockets.ConnectionClosedOK: break print(message) .ft P .fi .UNINDENT .UNINDENT .sp Stop the server with Ctrl\-C and start it again: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ python app.py .ft P .fi .UNINDENT .UNINDENT .INDENT 0.0 .INDENT 3.5 .IP "You must restart the WebSocket server when you make changes." .sp The WebSocket server loads the Python code in \fBapp.py\fP then serves every WebSocket request with this version of the code. As a consequence, changes to \fBapp.py\fP aren\(aqt visible until you restart the server. .sp This is unlike the HTTP server that you started earlier with \fBpython \-m http.server\fP\&. For every request, this HTTP server reads the target file and sends it. That\(aqs why changes are immediately visible. .sp It is possible to \fI\%restart the WebSocket server automatically\fP but this isn\(aqt necessary for this tutorial. .UNINDENT .UNINDENT .sp Try connecting and disconnecting the interactive client again. The \fI\%ConnectionClosedOK\fP exception doesn\(aqt appear anymore. .sp This pattern is so common that websockets provides a shortcut for iterating over messages received on the connection until the client disconnects: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C async def handler(websocket): async for message in websocket: print(message) .ft P .fi .UNINDENT .UNINDENT .sp Restart the server and check with the interactive client that its behavior didn\(aqt change. .sp At this point, you bootstrapped a web application and a WebSocket server. Let\(aqs connect them. .SS Transmit from browser to server .sp In JavaScript, you open a WebSocket connection as follows: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C const websocket = new WebSocket(\(dqws://localhost:8001/\(dq); .ft P .fi .UNINDENT .UNINDENT .sp Before you exchange messages with the server, you need to decide their format. There is no universal convention for this. .sp Let\(aqs use JSON objects with a \fBtype\fP key identifying the type of the event and the rest of the object containing properties of the event. .sp Here\(aqs an event describing a move in the middle slot of the board: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C const event = {type: \(dqplay\(dq, column: 3}; .ft P .fi .UNINDENT .UNINDENT .sp Here\(aqs how to serialize this event to JSON and send it to the server: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C websocket.send(JSON.stringify(event)); .ft P .fi .UNINDENT .UNINDENT .sp Now you have all the building blocks to send moves to the server. .sp Add this function to \fBmain.js\fP: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C function sendMoves(board, websocket) { // When clicking a column, send a \(dqplay\(dq event for a move in that column. board.addEventListener(\(dqclick\(dq, ({ target }) => { const column = target.dataset.column; // Ignore clicks outside a column. if (column === undefined) { return; } const event = { type: \(dqplay\(dq, column: parseInt(column, 10), }; websocket.send(JSON.stringify(event)); }); } .ft P .fi .UNINDENT .UNINDENT .sp \fBsendMoves()\fP registers a listener for \fBclick\fP events on the board. The listener figures out which column was clicked, builds a event of type \fB\(dqplay\(dq\fP, serializes it, and sends it to the server. .sp Modify the initialization to open the WebSocket connection and call the \fBsendMoves()\fP function: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C window.addEventListener(\(dqDOMContentLoaded\(dq, () => { // Initialize the UI. const board = document.querySelector(\(dq.board\(dq); createBoard(board); // Open the WebSocket connection and register event handlers. const websocket = new WebSocket(\(dqws://localhost:8001/\(dq); sendMoves(board, websocket); }); .ft P .fi .UNINDENT .UNINDENT .sp Check that the HTTP server and the WebSocket server are still running. If you stopped them, here are the commands to start them again: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ python \-m http.server .ft P .fi .UNINDENT .UNINDENT .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ python app.py .ft P .fi .UNINDENT .UNINDENT .sp Refresh \fI\%http://localhost:8000/\fP in your web browser. Click various columns in the board. The server receives messages with the expected column number. .sp There isn\(aqt any feedback in the board because you haven\(aqt implemented that yet. Let\(aqs do it. .SS Transmit from server to browser .sp In JavaScript, you receive WebSocket messages by listening to \fBmessage\fP events. Here\(aqs how to receive a message from the server and deserialize it from JSON: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C websocket.addEventListener(\(dqmessage\(dq, ({ data }) => { const event = JSON.parse(data); // do something with event }); .ft P .fi .UNINDENT .UNINDENT .sp You\(aqre going to need three types of messages from the server to the browser: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C {type: \(dqplay\(dq, player: \(dqred\(dq, column: 3, row: 0} {type: \(dqwin\(dq, player: \(dqred\(dq} {type: \(dqerror\(dq, message: \(dqThis slot is full.\(dq} .ft P .fi .UNINDENT .UNINDENT .sp The JavaScript code receiving these messages will dispatch events depending on their type and take appropriate action. For example, it will react to an event of type \fB\(dqplay\(dq\fP by displaying the move on the board with the \fI\%playMove()\fP function. .sp Add this function to \fBmain.js\fP: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C function showMessage(message) { window.setTimeout(() => window.alert(message), 50); } function receiveMoves(board, websocket) { websocket.addEventListener(\(dqmessage\(dq, ({ data }) => { const event = JSON.parse(data); switch (event.type) { case \(dqplay\(dq: // Update the UI with the move. playMove(board, event.player, event.column, event.row); break; case \(dqwin\(dq: showMessage(\(gaPlayer ${event.player} wins!\(ga); // No further messages are expected; close the WebSocket connection. websocket.close(1000); break; case \(dqerror\(dq: showMessage(event.message); break; default: throw new Error(\(gaUnsupported event type: ${event.type}.\(ga); } }); } .ft P .fi .UNINDENT .UNINDENT .INDENT 0.0 .INDENT 3.5 .IP "Why does \fBshowMessage\fP use \fBwindow.setTimeout\fP?" .sp When \fI\%playMove()\fP modifies the state of the board, the browser renders changes asynchronously. Conversely, \fBwindow.alert()\fP runs synchronously and blocks rendering while the alert is visible. .sp If you called \fBwindow.alert()\fP immediately after \fI\%playMove()\fP, the browser could display the alert before rendering the move. You could get a \(dqPlayer red wins!\(dq alert without seeing red\(aqs last move. .sp We\(aqre using \fBwindow.alert()\fP for simplicity in this tutorial. A real application would display these messages in the user interface instead. It wouldn\(aqt be vulnerable to this problem. .UNINDENT .UNINDENT .sp Modify the initialization to call the \fBreceiveMoves()\fP function: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C window.addEventListener(\(dqDOMContentLoaded\(dq, () => { // Initialize the UI. const board = document.querySelector(\(dq.board\(dq); createBoard(board); // Open the WebSocket connection and register event handlers. const websocket = new WebSocket(\(dqws://localhost:8001/\(dq); receiveMoves(board, websocket); sendMoves(board, websocket); }); .ft P .fi .UNINDENT .UNINDENT .sp At this point, the user interface should receive events properly. Let\(aqs test it by modifying the server to send some events. .sp Sending an event from Python is quite similar to JavaScript: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C event = {\(dqtype\(dq: \(dqplay\(dq, \(dqplayer\(dq: \(dqred\(dq, \(dqcolumn\(dq: 3, \(dqrow\(dq: 0} await websocket.send(json.dumps(event)) .ft P .fi .UNINDENT .UNINDENT .INDENT 0.0 .INDENT 3.5 .IP "Don\(aqt forget to serialize the event with \fI\%json.dumps()\fP\&." .sp Else, websockets raises \fBTypeError: data is a dict\-like object\fP\&. .UNINDENT .UNINDENT .sp Modify the \fBhandler()\fP coroutine in \fBapp.py\fP as follows: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C import json from connect4 import PLAYER1, PLAYER2 async def handler(websocket): for player, column, row in [ (PLAYER1, 3, 0), (PLAYER2, 3, 1), (PLAYER1, 4, 0), (PLAYER2, 4, 1), (PLAYER1, 2, 0), (PLAYER2, 1, 0), (PLAYER1, 5, 0), ]: event = { \(dqtype\(dq: \(dqplay\(dq, \(dqplayer\(dq: player, \(dqcolumn\(dq: column, \(dqrow\(dq: row, } await websocket.send(json.dumps(event)) await asyncio.sleep(0.5) event = { \(dqtype\(dq: \(dqwin\(dq, \(dqplayer\(dq: PLAYER1, } await websocket.send(json.dumps(event)) .ft P .fi .UNINDENT .UNINDENT .sp Restart the WebSocket server and refresh \fI\%http://localhost:8000/\fP in your web browser. Seven moves appear at 0.5 second intervals. Then an alert announces the winner. .sp Good! Now you know how to communicate both ways. .sp Once you plug the game engine to process moves, you will have a fully functional game. .SS Add the game logic .sp In the \fBhandler()\fP coroutine, you\(aqre going to initialize a game: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C from connect4 import Connect4 async def handler(websocket): # Initialize a Connect Four game. game = Connect4() ... .ft P .fi .UNINDENT .UNINDENT .sp Then, you\(aqre going to iterate over incoming messages and take these steps: .INDENT 0.0 .IP \(bu 2 parse an event of type \fB\(dqplay\(dq\fP, the only type of event that the user interface sends; .IP \(bu 2 play the move in the board with the \fI\%play()\fP method, alternating between the two players; .IP \(bu 2 if \fI\%play()\fP raises \fI\%RuntimeError\fP because the move is illegal, send an event of type \fB\(dqerror\(dq\fP; .IP \(bu 2 else, send an event of type \fB\(dqplay\(dq\fP to tell the user interface where the checker lands; .IP \(bu 2 if the move won the game, send an event of type \fB\(dqwin\(dq\fP\&. .UNINDENT .sp Try to implement this by yourself! .sp Keep in mind that you must restart the WebSocket server and reload the page in the browser when you make changes. .sp When it works, you can play the game from a single browser, with players taking alternate turns. .INDENT 0.0 .INDENT 3.5 .IP "Enable debug logs to see all messages sent and received." .sp Here\(aqs how to enable debug logs: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C import logging logging.basicConfig(format=\(dq%(message)s\(dq, level=logging.DEBUG) .ft P .fi .UNINDENT .UNINDENT .UNINDENT .UNINDENT .sp If you\(aqre stuck, a solution is available at the bottom of this document. .SS Summary .sp In this first part of the tutorial, you learned how to: .INDENT 0.0 .IP \(bu 2 build and run a WebSocket server in Python with \fI\%serve()\fP; .IP \(bu 2 receive a message in a connection handler with \fI\%recv()\fP; .IP \(bu 2 send a message in a connection handler with \fI\%send()\fP; .IP \(bu 2 iterate over incoming messages with \fBasync for message in websocket: ...\fP; .IP \(bu 2 open a WebSocket connection in JavaScript with the \fBWebSocket\fP API; .IP \(bu 2 send messages in a browser with \fBWebSocket.send()\fP; .IP \(bu 2 receive messages in a browser by listening to \fBmessage\fP events; .IP \(bu 2 design a set of events to be exchanged between the browser and the server. .UNINDENT .sp You can now play a Connect Four game in a browser, communicating over a WebSocket connection with a server where the game logic resides! .sp However, the two players share a browser, so the constraint of being in the same room still applies. .sp Move on to the \fI\%second part\fP of the tutorial to break this constraint and play from separate browsers. .SS Solution .sp app.py .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C #!/usr/bin/env python import asyncio import itertools import json import websockets from connect4 import PLAYER1, PLAYER2, Connect4 async def handler(websocket): # Initialize a Connect Four game. game = Connect4() # Players take alternate turns, using the same browser. turns = itertools.cycle([PLAYER1, PLAYER2]) player = next(turns) async for message in websocket: # Parse a \(dqplay\(dq event from the UI. event = json.loads(message) assert event[\(dqtype\(dq] == \(dqplay\(dq column = event[\(dqcolumn\(dq] try: # Play the move. row = game.play(player, column) except RuntimeError as exc: # Send an \(dqerror\(dq event if the move was illegal. event = { \(dqtype\(dq: \(dqerror\(dq, \(dqmessage\(dq: str(exc), } await websocket.send(json.dumps(event)) continue # Send a \(dqplay\(dq event to update the UI. event = { \(dqtype\(dq: \(dqplay\(dq, \(dqplayer\(dq: player, \(dqcolumn\(dq: column, \(dqrow\(dq: row, } await websocket.send(json.dumps(event)) # If move is winning, send a \(dqwin\(dq event. if game.winner is not None: event = { \(dqtype\(dq: \(dqwin\(dq, \(dqplayer\(dq: game.winner, } await websocket.send(json.dumps(event)) # Alternate turns. player = next(turns) async def main(): async with websockets.serve(handler, \(dq\(dq, 8001): await asyncio.Future() # run forever if __name__ == \(dq__main__\(dq: asyncio.run(main()) .ft P .fi .UNINDENT .UNINDENT .sp index.html .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C Connect Four
.ft P .fi .UNINDENT .UNINDENT .sp main.js .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C import { createBoard, playMove } from \(dq./connect4.js\(dq; function showMessage(message) { window.setTimeout(() => window.alert(message), 50); } function receiveMoves(board, websocket) { websocket.addEventListener(\(dqmessage\(dq, ({ data }) => { const event = JSON.parse(data); switch (event.type) { case \(dqplay\(dq: // Update the UI with the move. playMove(board, event.player, event.column, event.row); break; case \(dqwin\(dq: showMessage(\(gaPlayer ${event.player} wins!\(ga); // No further messages are expected; close the WebSocket connection. websocket.close(1000); break; case \(dqerror\(dq: showMessage(event.message); break; default: throw new Error(\(gaUnsupported event type: ${event.type}.\(ga); } }); } function sendMoves(board, websocket) { // When clicking a column, send a \(dqplay\(dq event for a move in that column. board.addEventListener(\(dqclick\(dq, ({ target }) => { const column = target.dataset.column; // Ignore clicks outside a column. if (column === undefined) { return; } const event = { type: \(dqplay\(dq, column: parseInt(column, 10), }; websocket.send(JSON.stringify(event)); }); } window.addEventListener(\(dqDOMContentLoaded\(dq, () => { // Initialize the UI. const board = document.querySelector(\(dq.board\(dq); createBoard(board); // Open the WebSocket connection and register event handlers. const websocket = new WebSocket(\(dqws://localhost:8001/\(dq); receiveMoves(board, websocket); sendMoves(board, websocket); }); .ft P .fi .UNINDENT .UNINDENT .SS Part 2 \- Route & broadcast .INDENT 0.0 .INDENT 3.5 .IP "This is the second part of the tutorial." .INDENT 0.0 .IP \(bu 2 In the \fI\%first part\fP, you created a server and connected one browser; you could play if you shared the same browser. .IP \(bu 2 In this \fI\%second part\fP, you will connect a second browser; you can play from different browsers on a local network. .IP \(bu 2 In the \fI\%third part\fP, you will deploy the game to the web; you can play from any browser connected to the Internet. .UNINDENT .UNINDENT .UNINDENT .sp In the first part of the tutorial, you opened a WebSocket connection from a browser to a server and exchanged events to play moves. The state of the game was stored in an instance of the \fI\%Connect4\fP class, referenced as a local variable in the connection handler coroutine. .sp Now you want to open two WebSocket connections from two separate browsers, one for each player, to the same server in order to play the same game. This requires moving the state of the game to a place where both connections can access it. .SS Share game state .sp As long as you\(aqre running a single server process, you can share state by storing it in a global variable. .INDENT 0.0 .INDENT 3.5 .IP "What if you need to scale to multiple server processes?" .sp In that case, you must design a way for the process that handles a given connection to be aware of relevant events for that client. This is often achieved with a publish / subscribe mechanism. .UNINDENT .UNINDENT .sp How can you make two connection handlers agree on which game they\(aqre playing? When the first player starts a game, you give it an identifier. Then, you communicate the identifier to the second player. When the second player joins the game, you look it up with the identifier. .sp In addition to the game itself, you need to keep track of the WebSocket connections of the two players. Since both players receive the same events, you don\(aqt need to treat the two connections differently; you can store both in the same set. .sp Let\(aqs sketch this in code. .sp A module\-level \fI\%dict\fP enables lookups by identifier: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C JOIN = {} .ft P .fi .UNINDENT .UNINDENT .sp When the first player starts the game, initialize and store it: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C import secrets async def handler(websocket): ... # Initialize a Connect Four game, the set of WebSocket connections # receiving moves from this game, and secret access token. game = Connect4() connected = {websocket} join_key = secrets.token_urlsafe(12) JOIN[join_key] = game, connected try: ... finally: del JOIN[join_key] .ft P .fi .UNINDENT .UNINDENT .sp When the second player joins the game, look it up: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C async def handler(websocket): ... join_key = ... # TODO # Find the Connect Four game. game, connected = JOIN[join_key] # Register to receive moves from this game. connected.add(websocket) try: ... finally: connected.remove(websocket) .ft P .fi .UNINDENT .UNINDENT .sp Notice how we\(aqre carefully cleaning up global state with \fBtry: ... finally: ...\fP blocks. Else, we could leave references to games or connections in global state, which would cause a memory leak. .sp In both connection handlers, you have a \fBgame\fP pointing to the same \fI\%Connect4\fP instance, so you can interact with the game, and a \fBconnected\fP set of connections, so you can send game events to both players as follows: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C async def handler(websocket): ... for connection in connected: await connection.send(json.dumps(event)) ... .ft P .fi .UNINDENT .UNINDENT .sp Perhaps you spotted a major piece missing from the puzzle. How does the second player obtain \fBjoin_key\fP? Let\(aqs design new events to carry this information. .sp To start a game, the first player sends an \fB\(dqinit\(dq\fP event: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C {type: \(dqinit\(dq} .ft P .fi .UNINDENT .UNINDENT .sp The connection handler for the first player creates a game as shown above and responds with: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C {type: \(dqinit\(dq, join: \(dq\(dq} .ft P .fi .UNINDENT .UNINDENT .sp With this information, the user interface of the first player can create a link to \fBhttp://localhost:8000/?join=\fP\&. For the sake of simplicity, we will assume that the first player shares this link with the second player outside of the application, for example via an instant messaging service. .sp To join the game, the second player sends a different \fB\(dqinit\(dq\fP event: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C {type: \(dqinit\(dq, join: \(dq\(dq} .ft P .fi .UNINDENT .UNINDENT .sp The connection handler for the second player can look up the game with the join key as shown above. There is no need to respond. .sp Let\(aqs dive into the details of implementing this design. .SS Start a game .sp We\(aqll start with the initialization sequence for the first player. .sp In \fBmain.js\fP, define a function to send an initialization event when the WebSocket connection is established, which triggers an \fBopen\fP event: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C function initGame(websocket) { websocket.addEventListener(\(dqopen\(dq, () => { // Send an \(dqinit\(dq event for the first player. const event = { type: \(dqinit\(dq }; websocket.send(JSON.stringify(event)); }); } .ft P .fi .UNINDENT .UNINDENT .sp Update the initialization sequence to call \fBinitGame()\fP: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C window.addEventListener(\(dqDOMContentLoaded\(dq, () => { // Initialize the UI. const board = document.querySelector(\(dq.board\(dq); createBoard(board); // Open the WebSocket connection and register event handlers. const websocket = new WebSocket(\(dqws://localhost:8001/\(dq); initGame(websocket); receiveMoves(board, websocket); sendMoves(board, websocket); }); .ft P .fi .UNINDENT .UNINDENT .sp In \fBapp.py\fP, define a new \fBhandler\fP coroutine — keep a copy of the previous one to reuse it later: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C import secrets JOIN = {} async def start(websocket): # Initialize a Connect Four game, the set of WebSocket connections # receiving moves from this game, and secret access token. game = Connect4() connected = {websocket} join_key = secrets.token_urlsafe(12) JOIN[join_key] = game, connected try: # Send the secret access token to the browser of the first player, # where it\(aqll be used for building a \(dqjoin\(dq link. event = { \(dqtype\(dq: \(dqinit\(dq, \(dqjoin\(dq: join_key, } await websocket.send(json.dumps(event)) # Temporary \- for testing. print(\(dqfirst player started game\(dq, id(game)) async for message in websocket: print(\(dqfirst player sent\(dq, message) finally: del JOIN[join_key] async def handler(websocket): # Receive and parse the \(dqinit\(dq event from the UI. message = await websocket.recv() event = json.loads(message) assert event[\(dqtype\(dq] == \(dqinit\(dq # First player starts a new game. await start(websocket) .ft P .fi .UNINDENT .UNINDENT .sp In \fBindex.html\fP, add an \fB\fP element to display the link to share with the other player. .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C .ft P .fi .UNINDENT .UNINDENT .sp In \fBmain.js\fP, modify \fBreceiveMoves()\fP to handle the \fB\(dqinit\(dq\fP message and set the target of that link: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C switch (event.type) { case \(dqinit\(dq: // Create link for inviting the second player. document.querySelector(\(dq.join\(dq).href = \(dq?join=\(dq + event.join; break; // ... } .ft P .fi .UNINDENT .UNINDENT .sp Restart the WebSocket server and reload \fI\%http://localhost:8000/\fP in the browser. There\(aqs a link labeled JOIN below the board with a target that looks like \fI\%http://localhost:8000/?join=95ftAaU5DJVP1zvb\fP\&. .sp The server logs say \fBfirst player started game ...\fP\&. If you click the board, you see \fB\(dqplay\(dq\fP events. There is no feedback in the UI, though, because you haven\(aqt restored the game logic yet. .sp Before we get there, let\(aqs handle links with a \fBjoin\fP query parameter. .SS Join a game .sp We\(aqll now update the initialization sequence to account for the second player. .sp In \fBmain.js\fP, update \fBinitGame()\fP to send the join key in the \fB\(dqinit\(dq\fP message when it\(aqs in the URL: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C function initGame(websocket) { websocket.addEventListener(\(dqopen\(dq, () => { // Send an \(dqinit\(dq event according to who is connecting. const params = new URLSearchParams(window.location.search); let event = { type: \(dqinit\(dq }; if (params.has(\(dqjoin\(dq)) { // Second player joins an existing game. event.join = params.get(\(dqjoin\(dq); } else { // First player starts a new game. } websocket.send(JSON.stringify(event)); }); } .ft P .fi .UNINDENT .UNINDENT .sp In \fBapp.py\fP, update the \fBhandler\fP coroutine to look for the join key in the \fB\(dqinit\(dq\fP message, then load that game: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C async def error(websocket, message): event = { \(dqtype\(dq: \(dqerror\(dq, \(dqmessage\(dq: message, } await websocket.send(json.dumps(event)) async def join(websocket, join_key): # Find the Connect Four game. try: game, connected = JOIN[join_key] except KeyError: await error(websocket, \(dqGame not found.\(dq) return # Register to receive moves from this game. connected.add(websocket) try: # Temporary \- for testing. print(\(dqsecond player joined game\(dq, id(game)) async for message in websocket: print(\(dqsecond player sent\(dq, message) finally: connected.remove(websocket) async def handler(websocket): # Receive and parse the \(dqinit\(dq event from the UI. message = await websocket.recv() event = json.loads(message) assert event[\(dqtype\(dq] == \(dqinit\(dq if \(dqjoin\(dq in event: # Second player joins an existing game. await join(websocket, event[\(dqjoin\(dq]) else: # First player starts a new game. await start(websocket) .ft P .fi .UNINDENT .UNINDENT .sp Restart the WebSocket server and reload \fI\%http://localhost:8000/\fP in the browser. .sp Copy the link labeled JOIN and open it in another browser. You may also open it in another tab or another window of the same browser; however, that makes it a bit tricky to remember which one is the first or second player. .INDENT 0.0 .INDENT 3.5 .IP "You must start a new game when you restart the server." .sp Since games are stored in the memory of the Python process, they\(aqre lost when you stop the server. .sp Whenever you make changes to \fBapp.py\fP, you must restart the server, create a new game in a browser, and join it in another browser. .UNINDENT .UNINDENT .sp The server logs say \fBfirst player started game ...\fP and \fBsecond player joined game ...\fP\&. The numbers match, proving that the \fBgame\fP local variable in both connection handlers points to same object in the memory of the Python process. .sp Click the board in either browser. The server receives \fB\(dqplay\(dq\fP events from the corresponding player. .sp In the initialization sequence, you\(aqre routing connections to \fBstart()\fP or \fBjoin()\fP depending on the first message received by the server. This is a common pattern in servers that handle different clients. .INDENT 0.0 .INDENT 3.5 .IP "Why not use different URIs for \fBstart()\fP and \fBjoin()\fP?" .sp Instead of sending an initialization event, you could encode the join key in the WebSocket URI e.g. \fBws://localhost:8001/join/\fP\&. The WebSocket server would parse \fBwebsocket.path\fP and route the connection, similar to how HTTP servers route requests. .sp When you need to send sensitive data like authentication credentials to the server, sending it an event is considered more secure than encoding it in the URI because URIs end up in logs. .sp For the purposes of this tutorial, both approaches are equivalent because the join key comes from an HTTP URL. There isn\(aqt much at risk anyway! .UNINDENT .UNINDENT .sp Now you can restore the logic for playing moves and you\(aqll have a fully functional two\-player game. .SS Add the game logic .sp Once the initialization is done, the game is symmetrical, so you can write a single coroutine to process the moves of both players: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C async def play(websocket, game, player, connected): ... .ft P .fi .UNINDENT .UNINDENT .sp With such a coroutine, you can replace the temporary code for testing in \fBstart()\fP by: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C await play(websocket, game, PLAYER1, connected) .ft P .fi .UNINDENT .UNINDENT .sp and in \fBjoin()\fP by: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C await play(websocket, game, PLAYER2, connected) .ft P .fi .UNINDENT .UNINDENT .sp The \fBplay()\fP coroutine will reuse much of the code you wrote in the first part of the tutorial. .sp Try to implement this by yourself! .sp Keep in mind that you must restart the WebSocket server, reload the page to start a new game with the first player, copy the JOIN link, and join the game with the second player when you make changes. .sp When \fBplay()\fP works, you can play the game from two separate browsers, possibly running on separate computers on the same local network. .sp A complete solution is available at the bottom of this document. .SS Watch a game .sp Let\(aqs add one more feature: allow spectators to watch the game. .sp The process for inviting a spectator can be the same as for inviting the second player. You will have to duplicate all the initialization logic: .INDENT 0.0 .IP \(bu 2 declare a \fBWATCH\fP global variable similar to \fBJOIN\fP; .IP \(bu 2 generate a watch key when creating a game; it must be different from the join key, or else a spectator could hijack a game by tweaking the URL; .IP \(bu 2 include the watch key in the \fB\(dqinit\(dq\fP event sent to the first player; .IP \(bu 2 generate a WATCH link in the UI with a \fBwatch\fP query parameter; .IP \(bu 2 update the \fBinitGame()\fP function to handle such links; .IP \(bu 2 update the \fBhandler()\fP coroutine to invoke a \fBwatch()\fP coroutine for spectators; .IP \(bu 2 prevent \fBsendMoves()\fP from sending \fB\(dqplay\(dq\fP events for spectators. .UNINDENT .sp Once the initialization sequence is done, watching a game is as simple as registering the WebSocket connection in the \fBconnected\fP set in order to receive game events and doing nothing until the spectator disconnects. You can wait for a connection to terminate with \fI\%wait_closed()\fP: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C async def watch(websocket, watch_key): ... connected.add(websocket) try: await websocket.wait_closed() finally: connected.remove(websocket) .ft P .fi .UNINDENT .UNINDENT .sp The connection can terminate because the \fBreceiveMoves()\fP function closed it explicitly after receiving a \fB\(dqwin\(dq\fP event, because the spectator closed their browser, or because the network failed. .sp Again, try to implement this by yourself. .sp When \fBwatch()\fP works, you can invite spectators to watch the game from other browsers, as long as they\(aqre on the same local network. .sp As a further improvement, you may support adding spectators while a game is already in progress. This requires replaying moves that were played before the spectator was added to the \fBconnected\fP set. Past moves are available in the \fI\%moves\fP attribute of the game. .sp This feature is included in the solution proposed below. .SS Broadcast .sp When you need to send a message to the two players and to all spectators, you\(aqre using this pattern: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C async def handler(websocket): ... for connection in connected: await connection.send(json.dumps(event)) ... .ft P .fi .UNINDENT .UNINDENT .sp Since this is a very common pattern in WebSocket servers, websockets provides the \fI\%broadcast()\fP helper for this purpose: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C async def handler(websocket): ... websockets.broadcast(connected, json.dumps(event)) ... .ft P .fi .UNINDENT .UNINDENT .sp Calling \fI\%broadcast()\fP once is more efficient than calling \fI\%send()\fP in a loop. .sp However, there\(aqs a subtle difference in behavior. Did you notice that there\(aqs no \fBawait\fP in the second version? Indeed, \fI\%broadcast()\fP is a function, not a coroutine like \fI\%send()\fP or \fI\%recv()\fP\&. .sp It\(aqs quite obvious why \fI\%recv()\fP is a coroutine. When you want to receive the next message, you have to wait until the client sends it and the network transmits it. .sp It\(aqs less obvious why \fI\%send()\fP is a coroutine. If you send many messages or large messages, you could write data faster than the network can transmit it or the client can read it. Then, outgoing data will pile up in buffers, which will consume memory and may crash your application. .sp To avoid this problem, \fI\%send()\fP waits until the write buffer drains. By slowing down the application as necessary, this ensures that the server doesn\(aqt send data too quickly. This is called backpressure and it\(aqs useful for building robust systems. .sp That said, when you\(aqre sending the same messages to many clients in a loop, applying backpressure in this way can become counterproductive. When you\(aqre broadcasting, you don\(aqt want to slow down everyone to the pace of the slowest clients; you want to drop clients that cannot keep up with the data stream. That\(aqs why \fI\%broadcast()\fP doesn\(aqt wait until write buffers drain. .sp For our Connect Four game, there\(aqs no difference in practice: the total amount of data sent on a connection for a game of Connect Four is less than 64\ KB, so the write buffer never fills up and backpressure never kicks in anyway. .SS Summary .sp In this second part of the tutorial, you learned how to: .INDENT 0.0 .IP \(bu 2 configure a connection by exchanging initialization messages; .IP \(bu 2 keep track of connections within a single server process; .IP \(bu 2 wait until a client disconnects in a connection handler; .IP \(bu 2 broadcast a message to many connections efficiently. .UNINDENT .sp You can now play a Connect Four game from separate browser, communicating over WebSocket connections with a server that synchronizes the game logic! .sp However, the two players have to be on the same local network as the server, so the constraint of being in the same place still mostly applies. .sp Head over to the \fI\%third part\fP of the tutorial to deploy the game to the web and remove this constraint. .SS Solution .sp app.py .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C #!/usr/bin/env python import asyncio import json import secrets import websockets from connect4 import PLAYER1, PLAYER2, Connect4 JOIN = {} WATCH = {} async def error(websocket, message): \(dq\(dq\(dq Send an error message. \(dq\(dq\(dq event = { \(dqtype\(dq: \(dqerror\(dq, \(dqmessage\(dq: message, } await websocket.send(json.dumps(event)) async def replay(websocket, game): \(dq\(dq\(dq Send previous moves. \(dq\(dq\(dq # Make a copy to avoid an exception if game.moves changes while iteration # is in progress. If a move is played while replay is running, moves will # be sent out of order but each move will be sent once and eventually the # UI will be consistent. for player, column, row in game.moves.copy(): event = { \(dqtype\(dq: \(dqplay\(dq, \(dqplayer\(dq: player, \(dqcolumn\(dq: column, \(dqrow\(dq: row, } await websocket.send(json.dumps(event)) async def play(websocket, game, player, connected): \(dq\(dq\(dq Receive and process moves from a player. \(dq\(dq\(dq async for message in websocket: # Parse a \(dqplay\(dq event from the UI. event = json.loads(message) assert event[\(dqtype\(dq] == \(dqplay\(dq column = event[\(dqcolumn\(dq] try: # Play the move. row = game.play(player, column) except RuntimeError as exc: # Send an \(dqerror\(dq event if the move was illegal. await error(websocket, str(exc)) continue # Send a \(dqplay\(dq event to update the UI. event = { \(dqtype\(dq: \(dqplay\(dq, \(dqplayer\(dq: player, \(dqcolumn\(dq: column, \(dqrow\(dq: row, } websockets.broadcast(connected, json.dumps(event)) # If move is winning, send a \(dqwin\(dq event. if game.winner is not None: event = { \(dqtype\(dq: \(dqwin\(dq, \(dqplayer\(dq: game.winner, } websockets.broadcast(connected, json.dumps(event)) async def start(websocket): \(dq\(dq\(dq Handle a connection from the first player: start a new game. \(dq\(dq\(dq # Initialize a Connect Four game, the set of WebSocket connections # receiving moves from this game, and secret access tokens. game = Connect4() connected = {websocket} join_key = secrets.token_urlsafe(12) JOIN[join_key] = game, connected watch_key = secrets.token_urlsafe(12) WATCH[watch_key] = game, connected try: # Send the secret access tokens to the browser of the first player, # where they\(aqll be used for building \(dqjoin\(dq and \(dqwatch\(dq links. event = { \(dqtype\(dq: \(dqinit\(dq, \(dqjoin\(dq: join_key, \(dqwatch\(dq: watch_key, } await websocket.send(json.dumps(event)) # Receive and process moves from the first player. await play(websocket, game, PLAYER1, connected) finally: del JOIN[join_key] del WATCH[watch_key] async def join(websocket, join_key): \(dq\(dq\(dq Handle a connection from the second player: join an existing game. \(dq\(dq\(dq # Find the Connect Four game. try: game, connected = JOIN[join_key] except KeyError: await error(websocket, \(dqGame not found.\(dq) return # Register to receive moves from this game. connected.add(websocket) try: # Send the first move, in case the first player already played it. await replay(websocket, game) # Receive and process moves from the second player. await play(websocket, game, PLAYER2, connected) finally: connected.remove(websocket) async def watch(websocket, watch_key): \(dq\(dq\(dq Handle a connection from a spectator: watch an existing game. \(dq\(dq\(dq # Find the Connect Four game. try: game, connected = WATCH[watch_key] except KeyError: await error(websocket, \(dqGame not found.\(dq) return # Register to receive moves from this game. connected.add(websocket) try: # Send previous moves, in case the game already started. await replay(websocket, game) # Keep the connection open, but don\(aqt receive any messages. await websocket.wait_closed() finally: connected.remove(websocket) async def handler(websocket): \(dq\(dq\(dq Handle a connection and dispatch it according to who is connecting. \(dq\(dq\(dq # Receive and parse the \(dqinit\(dq event from the UI. message = await websocket.recv() event = json.loads(message) assert event[\(dqtype\(dq] == \(dqinit\(dq if \(dqjoin\(dq in event: # Second player joins an existing game. await join(websocket, event[\(dqjoin\(dq]) elif \(dqwatch\(dq in event: # Spectator watches an existing game. await watch(websocket, event[\(dqwatch\(dq]) else: # First player starts a new game. await start(websocket) async def main(): async with websockets.serve(handler, \(dq\(dq, 8001): await asyncio.Future() # run forever if __name__ == \(dq__main__\(dq: asyncio.run(main()) .ft P .fi .UNINDENT .UNINDENT .sp index.html .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C Connect Four
.ft P .fi .UNINDENT .UNINDENT .sp main.js .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C import { createBoard, playMove } from \(dq./connect4.js\(dq; function initGame(websocket) { websocket.addEventListener(\(dqopen\(dq, () => { // Send an \(dqinit\(dq event according to who is connecting. const params = new URLSearchParams(window.location.search); let event = { type: \(dqinit\(dq }; if (params.has(\(dqjoin\(dq)) { // Second player joins an existing game. event.join = params.get(\(dqjoin\(dq); } else if (params.has(\(dqwatch\(dq)) { // Spectator watches an existing game. event.watch = params.get(\(dqwatch\(dq); } else { // First player starts a new game. } websocket.send(JSON.stringify(event)); }); } function showMessage(message) { window.setTimeout(() => window.alert(message), 50); } function receiveMoves(board, websocket) { websocket.addEventListener(\(dqmessage\(dq, ({ data }) => { const event = JSON.parse(data); switch (event.type) { case \(dqinit\(dq: // Create links for inviting the second player and spectators. document.querySelector(\(dq.join\(dq).href = \(dq?join=\(dq + event.join; document.querySelector(\(dq.watch\(dq).href = \(dq?watch=\(dq + event.watch; break; case \(dqplay\(dq: // Update the UI with the move. playMove(board, event.player, event.column, event.row); break; case \(dqwin\(dq: showMessage(\(gaPlayer ${event.player} wins!\(ga); // No further messages are expected; close the WebSocket connection. websocket.close(1000); break; case \(dqerror\(dq: showMessage(event.message); break; default: throw new Error(\(gaUnsupported event type: ${event.type}.\(ga); } }); } function sendMoves(board, websocket) { // Don\(aqt send moves for a spectator watching a game. const params = new URLSearchParams(window.location.search); if (params.has(\(dqwatch\(dq)) { return; } // When clicking a column, send a \(dqplay\(dq event for a move in that column. board.addEventListener(\(dqclick\(dq, ({ target }) => { const column = target.dataset.column; // Ignore clicks outside a column. if (column === undefined) { return; } const event = { type: \(dqplay\(dq, column: parseInt(column, 10), }; websocket.send(JSON.stringify(event)); }); } window.addEventListener(\(dqDOMContentLoaded\(dq, () => { // Initialize the UI. const board = document.querySelector(\(dq.board\(dq); createBoard(board); // Open the WebSocket connection and register event handlers. const websocket = new WebSocket(\(dqws://localhost:8001/\(dq); initGame(websocket); receiveMoves(board, websocket); sendMoves(board, websocket); }); .ft P .fi .UNINDENT .UNINDENT .SS Part 3 \- Deploy to the web .INDENT 0.0 .INDENT 3.5 .IP "This is the third part of the tutorial." .INDENT 0.0 .IP \(bu 2 In the \fI\%first part\fP, you created a server and connected one browser; you could play if you shared the same browser. .IP \(bu 2 In this \fI\%second part\fP, you connected a second browser; you could play from different browsers on a local network. .IP \(bu 2 In this \fI\%third part\fP, you will deploy the game to the web; you can play from any browser connected to the Internet. .UNINDENT .UNINDENT .UNINDENT .sp In the first and second parts of the tutorial, for local development, you ran an HTTP server on \fBhttp://localhost:8000/\fP with: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ python \-m http.server .ft P .fi .UNINDENT .UNINDENT .sp and a WebSocket server on \fBws://localhost:8001/\fP with: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ python app.py .ft P .fi .UNINDENT .UNINDENT .sp Now you want to deploy these servers on the Internet. There\(aqs a vast range of hosting providers to choose from. For the sake of simplicity, we\(aqll rely on: .INDENT 0.0 .IP \(bu 2 GitHub Pages for the HTTP server; .IP \(bu 2 Heroku for the WebSocket server. .UNINDENT .SS Commit project to git .sp Perhaps you committed your work to git while you were progressing through the tutorial. If you didn\(aqt, now is a good time, because GitHub and Heroku offer git\-based deployment workflows. .sp Initialize a git repository: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ git init \-b main Initialized empty Git repository in websockets\-tutorial/.git/ $ git commit \-\-allow\-empty \-m \(dqInitial commit.\(dq [main (root\-commit) ...] Initial commit. .ft P .fi .UNINDENT .UNINDENT .sp Add all files and commit: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ git add . $ git commit \-m \(dqInitial implementation of Connect Four game.\(dq [main ...] Initial implementation of Connect Four game. 6 files changed, 500 insertions(+) create mode 100644 app.py create mode 100644 connect4.css create mode 100644 connect4.js create mode 100644 connect4.py create mode 100644 index.html create mode 100644 main.js .ft P .fi .UNINDENT .UNINDENT .SS Prepare the WebSocket server .sp Before you deploy the server, you must adapt it to meet requirements of Heroku\(aqs runtime. This involves two small changes: .INDENT 0.0 .IP 1. 3 Heroku expects the server to \fI\%listen on a specific port\fP, provided in the \fB$PORT\fP environment variable. .IP 2. 3 Heroku sends a \fBSIGTERM\fP signal when \fI\%shutting down a dyno\fP, which should trigger a clean exit. .UNINDENT .sp Adapt the \fBmain()\fP coroutine accordingly: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C import os import signal .ft P .fi .UNINDENT .UNINDENT .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C async def main(): # Set the stop condition when receiving SIGTERM. loop = asyncio.get_running_loop() stop = loop.create_future() loop.add_signal_handler(signal.SIGTERM, stop.set_result, None) port = int(os.environ.get(\(dqPORT\(dq, \(dq8001\(dq)) async with websockets.serve(handler, \(dq\(dq, port): await stop .ft P .fi .UNINDENT .UNINDENT .sp To catch the \fBSIGTERM\fP signal, \fBmain()\fP creates a \fI\%Future\fP called \fBstop\fP and registers a signal handler that sets the result of this future. The value of the future doesn\(aqt matter; it\(aqs only for waiting for \fBSIGTERM\fP\&. .sp Then, by using \fI\%serve()\fP as a context manager and exiting the context when \fBstop\fP has a result, \fBmain()\fP ensures that the server closes connections cleanly and exits on \fBSIGTERM\fP\&. .sp The app is now fully compatible with Heroku. .SS Deploy the WebSocket server .sp Create a \fBrequirements.txt\fP file with this content to install \fBwebsockets\fP when building the image: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C websockets .ft P .fi .UNINDENT .UNINDENT .INDENT 0.0 .INDENT 3.5 .IP "Heroku treats \fBrequirements.txt\fP as a signal to \fI\%detect a Python app\fP\&." .sp That\(aqs why you don\(aqt need to declare that you need a Python runtime. .UNINDENT .UNINDENT .sp Create a \fBProcfile\fP file with this content to configure the command for running the server: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C web: python app.py .ft P .fi .UNINDENT .UNINDENT .sp Commit your changes: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ git add . $ git commit \-m \(dqDeploy to Heroku.\(dq [main ...] Deploy to Heroku. 3 files changed, 12 insertions(+), 2 deletions(\-) create mode 100644 Procfile create mode 100644 requirements.txt .ft P .fi .UNINDENT .UNINDENT .sp Follow the \fI\%set\-up instructions\fP to install the Heroku CLI and to log in, if you haven\(aqt done that yet. .sp Create a Heroku app. You must choose a unique name and replace \fBwebsockets\-tutorial\fP by this name in the following command: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ heroku create websockets\-tutorial Creating ⬢ websockets\-tutorial... done https://websockets\-tutorial.herokuapp.com/ | https://git.heroku.com/websockets\-tutorial.git .ft P .fi .UNINDENT .UNINDENT .sp If you reuse a name that someone else already uses, you will receive this error; if this happens, try another name: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ heroku create websockets\-tutorial Creating ⬢ websockets\-tutorial... ! ▸ Name websockets\-tutorial is already taken .ft P .fi .UNINDENT .UNINDENT .sp Deploy by pushing the code to Heroku: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ git push heroku \&... lots of output... remote: Released v1 remote: https://websockets\-tutorial.herokuapp.com/ deployed to Heroku remote: remote: Verifying deploy... done. To https://git.heroku.com/websockets\-tutorial.git * [new branch] main \-> main .ft P .fi .UNINDENT .UNINDENT .sp You can test the WebSocket server with the interactive client exactly like you did in the first part of the tutorial. Replace \fBwebsockets\-tutorial\fP by the name of your app in the following command: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ python \-m websockets wss://websockets\-tutorial.herokuapp.com/ Connected to wss://websockets\-tutorial.herokuapp.com/. > {\(dqtype\(dq: \(dqinit\(dq} < {\(dqtype\(dq: \(dqinit\(dq, \(dqjoin\(dq: \(dq54ICxFae_Ip7TJE2\(dq, \(dqwatch\(dq: \(dq634w44TblL5Dbd9a\(dq} Connection closed: 1000 (OK). .ft P .fi .UNINDENT .UNINDENT .sp It works! .SS Prepare the web application .sp Before you deploy the web application, perhaps you\(aqre wondering how it will locate the WebSocket server? Indeed, at this point, its address is hard\-coded in \fBmain.js\fP: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C const websocket = new WebSocket(\(dqws://localhost:8001/\(dq); .ft P .fi .UNINDENT .UNINDENT .sp You can take this strategy one step further by checking the address of the HTTP server and determining the address of the WebSocket server accordingly. .sp Add this function to \fBmain.js\fP; replace \fBpython\-websockets\fP by your GitHub username and \fBwebsockets\-tutorial\fP by the name of your app on Heroku: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C function getWebSocketServer() { if (window.location.host === \(dqpython\-websockets.github.io\(dq) { return \(dqwss://websockets\-tutorial.herokuapp.com/\(dq; } else if (window.location.host === \(dqlocalhost:8000\(dq) { return \(dqws://localhost:8001/\(dq; } else { throw new Error(\(gaUnsupported host: ${window.location.host}\(ga); } } .ft P .fi .UNINDENT .UNINDENT .sp Then, update the initialization to connect to this address instead: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C const websocket = new WebSocket(getWebSocketServer()); .ft P .fi .UNINDENT .UNINDENT .sp Commit your changes: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ git add . $ git commit \-m \(dqConfigure WebSocket server address.\(dq [main ...] Configure WebSocket server address. 1 file changed, 11 insertions(+), 1 deletion(\-) .ft P .fi .UNINDENT .UNINDENT .SS Deploy the web application .sp Go to GitHub and create a new repository called \fBwebsockets\-tutorial\fP\&. .sp Push your code to this repository. You must replace \fBpython\-websockets\fP by your GitHub username in the following command: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ git remote add origin git@github.com:python\-websockets/websockets\-tutorial.git $ git push \-u origin main Enumerating objects: 11, done. Counting objects: 100% (11/11), done. Delta compression using up to 8 threads Compressing objects: 100% (10/10), done. Writing objects: 100% (11/11), 5.90 KiB | 2.95 MiB/s, done. Total 11 (delta 0), reused 0 (delta 0), pack\-reused 0 To github.com:/websockets\-tutorial.git * [new branch] main \-> main Branch \(aqmain\(aq set up to track remote branch \(aqmain\(aq from \(aqorigin\(aq. .ft P .fi .UNINDENT .UNINDENT .sp Go back to GitHub, open the Settings tab of the repository and select Pages in the menu. Select the main branch as source and click Save. GitHub tells you that your site is published. .sp Follow the link and start a game! .SS Summary .sp In this third part of the tutorial, you learned how to deploy a WebSocket application with Heroku. .sp You can start a Connect Four game, send the JOIN link to a friend, and play over the Internet! .sp Congratulations for completing the tutorial. Enjoy building real\-time web applications with websockets! .SS Solution .sp app.py .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C #!/usr/bin/env python import asyncio import json import os import secrets import signal import websockets from connect4 import PLAYER1, PLAYER2, Connect4 JOIN = {} WATCH = {} async def error(websocket, message): \(dq\(dq\(dq Send an error message. \(dq\(dq\(dq event = { \(dqtype\(dq: \(dqerror\(dq, \(dqmessage\(dq: message, } await websocket.send(json.dumps(event)) async def replay(websocket, game): \(dq\(dq\(dq Send previous moves. \(dq\(dq\(dq # Make a copy to avoid an exception if game.moves changes while iteration # is in progress. If a move is played while replay is running, moves will # be sent out of order but each move will be sent once and eventually the # UI will be consistent. for player, column, row in game.moves.copy(): event = { \(dqtype\(dq: \(dqplay\(dq, \(dqplayer\(dq: player, \(dqcolumn\(dq: column, \(dqrow\(dq: row, } await websocket.send(json.dumps(event)) async def play(websocket, game, player, connected): \(dq\(dq\(dq Receive and process moves from a player. \(dq\(dq\(dq async for message in websocket: # Parse a \(dqplay\(dq event from the UI. event = json.loads(message) assert event[\(dqtype\(dq] == \(dqplay\(dq column = event[\(dqcolumn\(dq] try: # Play the move. row = game.play(player, column) except RuntimeError as exc: # Send an \(dqerror\(dq event if the move was illegal. await error(websocket, str(exc)) continue # Send a \(dqplay\(dq event to update the UI. event = { \(dqtype\(dq: \(dqplay\(dq, \(dqplayer\(dq: player, \(dqcolumn\(dq: column, \(dqrow\(dq: row, } websockets.broadcast(connected, json.dumps(event)) # If move is winning, send a \(dqwin\(dq event. if game.winner is not None: event = { \(dqtype\(dq: \(dqwin\(dq, \(dqplayer\(dq: game.winner, } websockets.broadcast(connected, json.dumps(event)) async def start(websocket): \(dq\(dq\(dq Handle a connection from the first player: start a new game. \(dq\(dq\(dq # Initialize a Connect Four game, the set of WebSocket connections # receiving moves from this game, and secret access tokens. game = Connect4() connected = {websocket} join_key = secrets.token_urlsafe(12) JOIN[join_key] = game, connected watch_key = secrets.token_urlsafe(12) WATCH[watch_key] = game, connected try: # Send the secret access tokens to the browser of the first player, # where they\(aqll be used for building \(dqjoin\(dq and \(dqwatch\(dq links. event = { \(dqtype\(dq: \(dqinit\(dq, \(dqjoin\(dq: join_key, \(dqwatch\(dq: watch_key, } await websocket.send(json.dumps(event)) # Receive and process moves from the first player. await play(websocket, game, PLAYER1, connected) finally: del JOIN[join_key] del WATCH[watch_key] async def join(websocket, join_key): \(dq\(dq\(dq Handle a connection from the second player: join an existing game. \(dq\(dq\(dq # Find the Connect Four game. try: game, connected = JOIN[join_key] except KeyError: await error(websocket, \(dqGame not found.\(dq) return # Register to receive moves from this game. connected.add(websocket) try: # Send the first move, in case the first player already played it. await replay(websocket, game) # Receive and process moves from the second player. await play(websocket, game, PLAYER2, connected) finally: connected.remove(websocket) async def watch(websocket, watch_key): \(dq\(dq\(dq Handle a connection from a spectator: watch an existing game. \(dq\(dq\(dq # Find the Connect Four game. try: game, connected = WATCH[watch_key] except KeyError: await error(websocket, \(dqGame not found.\(dq) return # Register to receive moves from this game. connected.add(websocket) try: # Send previous moves, in case the game already started. await replay(websocket, game) # Keep the connection open, but don\(aqt receive any messages. await websocket.wait_closed() finally: connected.remove(websocket) async def handler(websocket): \(dq\(dq\(dq Handle a connection and dispatch it according to who is connecting. \(dq\(dq\(dq # Receive and parse the \(dqinit\(dq event from the UI. message = await websocket.recv() event = json.loads(message) assert event[\(dqtype\(dq] == \(dqinit\(dq if \(dqjoin\(dq in event: # Second player joins an existing game. await join(websocket, event[\(dqjoin\(dq]) elif \(dqwatch\(dq in event: # Spectator watches an existing game. await watch(websocket, event[\(dqwatch\(dq]) else: # First player starts a new game. await start(websocket) async def main(): # Set the stop condition when receiving SIGTERM. loop = asyncio.get_running_loop() stop = loop.create_future() loop.add_signal_handler(signal.SIGTERM, stop.set_result, None) port = int(os.environ.get(\(dqPORT\(dq, \(dq8001\(dq)) async with websockets.serve(handler, \(dq\(dq, port): await stop if __name__ == \(dq__main__\(dq: asyncio.run(main()) .ft P .fi .UNINDENT .UNINDENT .sp index.html .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C Connect Four
.ft P .fi .UNINDENT .UNINDENT .sp main.js .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C import { createBoard, playMove } from \(dq./connect4.js\(dq; function getWebSocketServer() { if (window.location.host === \(dqpython\-websockets.github.io\(dq) { return \(dqwss://websockets\-tutorial.herokuapp.com/\(dq; } else if (window.location.host === \(dqlocalhost:8000\(dq) { return \(dqws://localhost:8001/\(dq; } else { throw new Error(\(gaUnsupported host: ${window.location.host}\(ga); } } function initGame(websocket) { websocket.addEventListener(\(dqopen\(dq, () => { // Send an \(dqinit\(dq event according to who is connecting. const params = new URLSearchParams(window.location.search); let event = { type: \(dqinit\(dq }; if (params.has(\(dqjoin\(dq)) { // Second player joins an existing game. event.join = params.get(\(dqjoin\(dq); } else if (params.has(\(dqwatch\(dq)) { // Spectator watches an existing game. event.watch = params.get(\(dqwatch\(dq); } else { // First player starts a new game. } websocket.send(JSON.stringify(event)); }); } function showMessage(message) { window.setTimeout(() => window.alert(message), 50); } function receiveMoves(board, websocket) { websocket.addEventListener(\(dqmessage\(dq, ({ data }) => { const event = JSON.parse(data); switch (event.type) { case \(dqinit\(dq: // Create links for inviting the second player and spectators. document.querySelector(\(dq.join\(dq).href = \(dq?join=\(dq + event.join; document.querySelector(\(dq.watch\(dq).href = \(dq?watch=\(dq + event.watch; break; case \(dqplay\(dq: // Update the UI with the move. playMove(board, event.player, event.column, event.row); break; case \(dqwin\(dq: showMessage(\(gaPlayer ${event.player} wins!\(ga); // No further messages are expected; close the WebSocket connection. websocket.close(1000); break; case \(dqerror\(dq: showMessage(event.message); break; default: throw new Error(\(gaUnsupported event type: ${event.type}.\(ga); } }); } function sendMoves(board, websocket) { // Don\(aqt send moves for a spectator watching a game. const params = new URLSearchParams(window.location.search); if (params.has(\(dqwatch\(dq)) { return; } // When clicking a column, send a \(dqplay\(dq event for a move in that column. board.addEventListener(\(dqclick\(dq, ({ target }) => { const column = target.dataset.column; // Ignore clicks outside a column. if (column === undefined) { return; } const event = { type: \(dqplay\(dq, column: parseInt(column, 10), }; websocket.send(JSON.stringify(event)); }); } window.addEventListener(\(dqDOMContentLoaded\(dq, () => { // Initialize the UI. const board = document.querySelector(\(dq.board\(dq); createBoard(board); // Open the WebSocket connection and register event handlers. const websocket = new WebSocket(getWebSocketServer()); initGame(websocket); receiveMoves(board, websocket); sendMoves(board, websocket); }); .ft P .fi .UNINDENT .UNINDENT .sp Procfile .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C web: python app.py .ft P .fi .UNINDENT .UNINDENT .sp requirements.txt .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C websockets .ft P .fi .UNINDENT .UNINDENT .SS In a hurry? .sp Look at the \fI\%quick start guide\fP\&. .SH HOW-TO GUIDES .sp In a hurry? Check out these examples. .SS Quick start .sp Here are a few examples to get you started quickly with websockets. .SS Say \(dqHello world!\(dq .sp Here\(aqs a WebSocket server. .sp It receives a name from the client, sends a greeting, and closes the connection. .sp server.py .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C #!/usr/bin/env python import asyncio import websockets async def hello(websocket): name = await websocket.recv() print(f\(dq<<< {name}\(dq) greeting = f\(dqHello {name}!\(dq await websocket.send(greeting) print(f\(dq>>> {greeting}\(dq) async def main(): async with websockets.serve(hello, \(dqlocalhost\(dq, 8765): await asyncio.Future() # run forever if __name__ == \(dq__main__\(dq: asyncio.run(main()) .ft P .fi .UNINDENT .UNINDENT .sp \fI\%serve()\fP executes the connection handler coroutine \fBhello()\fP once for each WebSocket connection. It closes the WebSocket connection when the handler returns. .sp Here\(aqs a corresponding WebSocket client. .sp It sends a name to the server, receives a greeting, and closes the connection. .sp client.py .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C #!/usr/bin/env python import asyncio import websockets async def hello(): uri = \(dqws://localhost:8765\(dq async with websockets.connect(uri) as websocket: name = input(\(dqWhat\(aqs your name? \(dq) await websocket.send(name) print(f\(dq>>> {name}\(dq) greeting = await websocket.recv() print(f\(dq<<< {greeting}\(dq) if __name__ == \(dq__main__\(dq: asyncio.run(hello()) .ft P .fi .UNINDENT .UNINDENT .sp Using \fI\%connect()\fP as an asynchronous context manager ensures the WebSocket connection is closed. .SS Encrypt connections .sp Secure WebSocket connections improve confidentiality and also reliability because they reduce the risk of interference by bad proxies. .sp The \fBwss\fP protocol is to \fBws\fP what \fBhttps\fP is to \fBhttp\fP\&. The connection is encrypted with \fI\%TLS\fP (Transport Layer Security). \fBwss\fP requires certificates like \fBhttps\fP\&. .INDENT 0.0 .INDENT 3.5 .IP "TLS vs. SSL" .sp TLS is sometimes referred to as SSL (Secure Sockets Layer). SSL was an earlier encryption protocol; the name stuck. .UNINDENT .UNINDENT .sp Here\(aqs how to adapt the server to encrypt connections. You must download \fBlocalhost.pem\fP and save it in the same directory as \fBserver_secure.py\fP\&. .sp server_secure.py .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C #!/usr/bin/env python import asyncio import pathlib import ssl import websockets async def hello(websocket): name = await websocket.recv() print(f\(dq<<< {name}\(dq) greeting = f\(dqHello {name}!\(dq await websocket.send(greeting) print(f\(dq>>> {greeting}\(dq) ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) localhost_pem = pathlib.Path(__file__).with_name(\(dqlocalhost.pem\(dq) ssl_context.load_cert_chain(localhost_pem) async def main(): async with websockets.serve(hello, \(dqlocalhost\(dq, 8765, ssl=ssl_context): await asyncio.Future() # run forever if __name__ == \(dq__main__\(dq: asyncio.run(main()) .ft P .fi .UNINDENT .UNINDENT .sp Here\(aqs how to adapt the client similarly. .sp client_secure.py .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C #!/usr/bin/env python import asyncio import pathlib import ssl import websockets ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) localhost_pem = pathlib.Path(__file__).with_name(\(dqlocalhost.pem\(dq) ssl_context.load_verify_locations(localhost_pem) async def hello(): uri = \(dqwss://localhost:8765\(dq async with websockets.connect(uri, ssl=ssl_context) as websocket: name = input(\(dqWhat\(aqs your name? \(dq) await websocket.send(name) print(f\(dq>>> {name}\(dq) greeting = await websocket.recv() print(f\(dq<<< {greeting}\(dq) if __name__ == \(dq__main__\(dq: asyncio.run(hello()) .ft P .fi .UNINDENT .UNINDENT .sp In this example, the client needs a TLS context because the server uses a self\-signed certificate. .sp When connecting to a secure WebSocket server with a valid certificate — any certificate signed by a CA that your Python installation trusts — you can simply pass \fBssl=True\fP to \fI\%connect()\fP\&. .INDENT 0.0 .INDENT 3.5 .IP "Configure the TLS context securely" .sp This example demonstrates the \fBssl\fP argument with a TLS certificate shared between the client and the server. This is a simplistic setup. .sp Please review the advice and security considerations in the documentation of the \fI\%ssl\fP module to configure the TLS context securely. .UNINDENT .UNINDENT .SS Connect from a browser .sp The WebSocket protocol was invented for the web — as the name says! .sp Here\(aqs how to connect to a WebSocket server from a browser. .sp Run this script in a console: .sp show_time.py .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C #!/usr/bin/env python import asyncio import datetime import random import websockets async def show_time(websocket): while True: message = datetime.datetime.utcnow().isoformat() + \(dqZ\(dq await websocket.send(message) await asyncio.sleep(random.random() * 2 + 1) async def main(): async with websockets.serve(show_time, \(dqlocalhost\(dq, 5678): await asyncio.Future() # run forever if __name__ == \(dq__main__\(dq: asyncio.run(main()) .ft P .fi .UNINDENT .UNINDENT .sp Save this file as \fBshow_time.html\fP: .sp show_time.html .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C WebSocket demo .ft P .fi .UNINDENT .UNINDENT .sp Save this file as \fBshow_time.js\fP: .sp show_time.js .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C window.addEventListener(\(dqDOMContentLoaded\(dq, () => { const messages = document.createElement(\(dqul\(dq); document.body.appendChild(messages); const websocket = new WebSocket(\(dqws://localhost:5678/\(dq); websocket.onmessage = ({ data }) => { const message = document.createElement(\(dqli\(dq); const content = document.createTextNode(data); message.appendChild(content); messages.appendChild(message); }; }); .ft P .fi .UNINDENT .UNINDENT .sp Then, open \fBshow_time.html\fP in several browsers. Clocks tick irregularly. .SS Broadcast messages .sp Let\(aqs change the previous example to send the same timestamps to all browsers, instead of generating independent sequences for each client. .sp Stop the previous script if it\(aqs still running and run this script in a console: .sp show_time_2.py .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C #!/usr/bin/env python import asyncio import datetime import random import websockets CONNECTIONS = set() async def register(websocket): CONNECTIONS.add(websocket) try: await websocket.wait_closed() finally: CONNECTIONS.remove(websocket) async def show_time(): while True: message = datetime.datetime.utcnow().isoformat() + \(dqZ\(dq websockets.broadcast(CONNECTIONS, message) await asyncio.sleep(random.random() * 2 + 1) async def main(): async with websockets.serve(register, \(dqlocalhost\(dq, 5678): await show_time() if __name__ == \(dq__main__\(dq: asyncio.run(main()) .ft P .fi .UNINDENT .UNINDENT .sp Refresh \fBshow_time.html\fP in all browsers. Clocks tick in sync. .SS Manage application state .sp A WebSocket server can receive events from clients, process them to update the application state, and broadcast the updated state to all connected clients. .sp Here\(aqs an example where any client can increment or decrement a counter. The concurrency model of \fI\%asyncio\fP guarantees that updates are serialized. .sp Run this script in a console: .sp counter.py .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C #!/usr/bin/env python import asyncio import json import logging import websockets logging.basicConfig() USERS = set() VALUE = 0 def users_event(): return json.dumps({\(dqtype\(dq: \(dqusers\(dq, \(dqcount\(dq: len(USERS)}) def value_event(): return json.dumps({\(dqtype\(dq: \(dqvalue\(dq, \(dqvalue\(dq: VALUE}) async def counter(websocket): global USERS, VALUE try: # Register user USERS.add(websocket) websockets.broadcast(USERS, users_event()) # Send current state to user await websocket.send(value_event()) # Manage state changes async for message in websocket: event = json.loads(message) if event[\(dqaction\(dq] == \(dqminus\(dq: VALUE \-= 1 websockets.broadcast(USERS, value_event()) elif event[\(dqaction\(dq] == \(dqplus\(dq: VALUE += 1 websockets.broadcast(USERS, value_event()) else: logging.error(\(dqunsupported event: %s\(dq, event) finally: # Unregister user USERS.remove(websocket) websockets.broadcast(USERS, users_event()) async def main(): async with websockets.serve(counter, \(dqlocalhost\(dq, 6789): await asyncio.Future() # run forever if __name__ == \(dq__main__\(dq: asyncio.run(main()) .ft P .fi .UNINDENT .UNINDENT .sp Save this file as \fBcounter.html\fP: .sp counter.html .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C WebSocket demo
\-
?
+
? online
.ft P .fi .UNINDENT .UNINDENT .sp Save this file as \fBcounter.css\fP: .sp counter.css .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C body { font\-family: \(dqCourier New\(dq, sans\-serif; text\-align: center; } \&.buttons { font\-size: 4em; display: flex; justify\-content: center; } \&.button, .value { line\-height: 1; padding: 2rem; margin: 2rem; border: medium solid; min\-height: 1em; min\-width: 1em; } \&.button { cursor: pointer; user\-select: none; } \&.minus { color: red; } \&.plus { color: green; } \&.value { min\-width: 2em; } \&.state { font\-size: 2em; } .ft P .fi .UNINDENT .UNINDENT .sp Save this file as \fBcounter.js\fP: .sp counter.js .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C window.addEventListener(\(dqDOMContentLoaded\(dq, () => { const websocket = new WebSocket(\(dqws://localhost:6789/\(dq); document.querySelector(\(dq.minus\(dq).addEventListener(\(dqclick\(dq, () => { websocket.send(JSON.stringify({ action: \(dqminus\(dq })); }); document.querySelector(\(dq.plus\(dq).addEventListener(\(dqclick\(dq, () => { websocket.send(JSON.stringify({ action: \(dqplus\(dq })); }); websocket.onmessage = ({ data }) => { const event = JSON.parse(data); switch (event.type) { case \(dqvalue\(dq: document.querySelector(\(dq.value\(dq).textContent = event.value; break; case \(dqusers\(dq: const users = \(ga${event.count} user${event.count == 1 ? \(dq\(dq : \(dqs\(dq}\(ga; document.querySelector(\(dq.users\(dq).textContent = users; break; default: console.error(\(dqunsupported event\(dq, event); } }; }); .ft P .fi .UNINDENT .UNINDENT .sp Then open \fBcounter.html\fP file in several browsers and play with [+] and [\-]. .sp If you\(aqre stuck, perhaps you\(aqll find the answer here. .SS Cheat sheet .SS Server .INDENT 0.0 .IP \(bu 2 Write a coroutine that handles a single connection. It receives a WebSocket protocol instance and the URI path in argument. .INDENT 2.0 .IP \(bu 2 Call \fI\%recv()\fP and \fI\%send()\fP to receive and send messages at any time. .IP \(bu 2 When \fI\%recv()\fP or \fI\%send()\fP raises \fI\%ConnectionClosed\fP, clean up and exit. If you started other \fI\%asyncio.Task\fP, terminate them before exiting. .IP \(bu 2 If you aren\(aqt awaiting \fI\%recv()\fP, consider awaiting \fI\%wait_closed()\fP to detect quickly when the connection is closed. .IP \(bu 2 You may \fI\%ping()\fP or \fI\%pong()\fP if you wish but it isn\(aqt needed in general. .UNINDENT .IP \(bu 2 Create a server with \fI\%serve()\fP which is similar to asyncio\(aqs \fI\%create_server()\fP\&. You can also use it as an asynchronous context manager. .INDENT 2.0 .IP \(bu 2 The server takes care of establishing connections, then lets the handler execute the application logic, and finally closes the connection after the handler exits normally or with an exception. .IP \(bu 2 For advanced customization, you may subclass \fI\%WebSocketServerProtocol\fP and pass either this subclass or a factory function as the \fBcreate_protocol\fP argument. .UNINDENT .UNINDENT .SS Client .INDENT 0.0 .IP \(bu 2 Create a client with \fI\%connect()\fP which is similar to asyncio\(aqs \fI\%create_connection()\fP\&. You can also use it as an asynchronous context manager. .INDENT 2.0 .IP \(bu 2 For advanced customization, you may subclass \fI\%WebSocketClientProtocol\fP and pass either this subclass or a factory function as the \fBcreate_protocol\fP argument. .UNINDENT .IP \(bu 2 Call \fI\%recv()\fP and \fI\%send()\fP to receive and send messages at any time. .IP \(bu 2 You may \fI\%ping()\fP or \fI\%pong()\fP if you wish but it isn\(aqt needed in general. .IP \(bu 2 If you aren\(aqt using \fI\%connect()\fP as a context manager, call \fI\%close()\fP to terminate the connection. .UNINDENT .SS Debugging .sp If you don\(aqt understand what websockets is doing, enable logging: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C import logging logger = logging.getLogger(\(aqwebsockets\(aq) logger.setLevel(logging.DEBUG) logger.addHandler(logging.StreamHandler()) .ft P .fi .UNINDENT .UNINDENT .sp The logs contain: .INDENT 0.0 .IP \(bu 2 Exceptions in the connection handler at the \fBERROR\fP level .IP \(bu 2 Exceptions in the opening or closing handshake at the \fBINFO\fP level .IP \(bu 2 All frames at the \fBDEBUG\fP level — this can be very verbose .UNINDENT .sp If you\(aqre new to \fBasyncio\fP, you will certainly encounter issues that are related to asynchronous programming in general rather than to websockets in particular. Fortunately Python\(aqs official documentation provides advice to \fI\%develop with asyncio\fP\&. Check it out: it\(aqs invaluable! .SS Patterns .sp Here are typical patterns for processing messages in a WebSocket server or client. You will certainly implement some of them in your application. .sp This page gives examples of connection handlers for a server. However, they\(aqre also applicable to a client, simply by assuming that \fBwebsocket\fP is a connection created with \fI\%connect()\fP\&. .sp WebSocket connections are long\-lived. You will usually write a loop to process several messages during the lifetime of a connection. .SS Consumer .sp To receive messages from the WebSocket connection: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C async def consumer_handler(websocket): async for message in websocket: await consumer(message) .ft P .fi .UNINDENT .UNINDENT .sp In this example, \fBconsumer()\fP is a coroutine implementing your business logic for processing a message received on the WebSocket connection. Each message may be \fI\%str\fP or \fI\%bytes\fP\&. .sp Iteration terminates when the client disconnects. .SS Producer .sp To send messages to the WebSocket connection: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C async def producer_handler(websocket): while True: message = await producer() await websocket.send(message) .ft P .fi .UNINDENT .UNINDENT .sp In this example, \fBproducer()\fP is a coroutine implementing your business logic for generating the next message to send on the WebSocket connection. Each message must be \fI\%str\fP or \fI\%bytes\fP\&. .sp Iteration terminates when the client disconnects because \fI\%send()\fP raises a \fI\%ConnectionClosed\fP exception, which breaks out of the \fBwhile True\fP loop. .SS Consumer and producer .sp You can receive and send messages on the same WebSocket connection by combining the consumer and producer patterns. This requires running two tasks in parallel: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C async def handler(websocket): await asyncio.gather( consumer_handler(websocket), producer_handler(websocket), ) .ft P .fi .UNINDENT .UNINDENT .sp If a task terminates, \fI\%gather()\fP doesn\(aqt cancel the other task. This can result in a situation where the producer keeps running after the consumer finished, which may leak resources. .sp Here\(aqs a way to exit and close the WebSocket connection as soon as a task terminates, after canceling the other task: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C async def handler(websocket): consumer_task = asyncio.create_task(consumer_handler(websocket)) producer_task = asyncio.create_task(producer_handler(websocket)) done, pending = await asyncio.wait( [consumer_task, producer_task], return_when=asyncio.FIRST_COMPLETED, ) for task in pending: task.cancel() .ft P .fi .UNINDENT .UNINDENT .SS Registration .sp To keep track of currently connected clients, you can register them when they connect and unregister them when they disconnect: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C connected = set() async def handler(websocket): # Register. connected.add(websocket) try: # Broadcast a message to all connected clients. websockets.broadcast(connected, \(dqHello!\(dq) await asyncio.sleep(10) finally: # Unregister. connected.remove(websocket) .ft P .fi .UNINDENT .UNINDENT .sp This example maintains the set of connected clients in memory. This works as long as you run a single process. It doesn\(aqt scale to multiple processes. .SS Publish–subscribe .sp If you plan to run multiple processes and you want to communicate updates between processes, then you must deploy a messaging system. You may find publish\-subscribe functionality useful. .sp A complete implementation of this idea with Redis is described in the \fI\%Django integration guide\fP\&. .SS Reload on code changes .sp When developing a websockets server, you may run it locally to test changes. Unfortunately, whenever you want to try a new version of the code, you must stop the server and restart it, which slows down your development process. .sp Web frameworks such as Django or Flask provide a development server that reloads the application automatically when you make code changes. There is no such functionality in websockets because it\(aqs designed for production rather than development. .sp However, you can achieve the same result easily. .sp Install \fI\%watchdog\fP with the \fBwatchmedo\fP shell utility: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ pip install \(aqwatchdog[watchmedo]\(aq .ft P .fi .UNINDENT .UNINDENT .sp Run your server with \fBwatchmedo auto\-restart\fP: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ watchmedo auto\-restart \-\-pattern \(dq*.py\(dq \-\-recursive \-\-signal SIGTERM \e python app.py .ft P .fi .UNINDENT .UNINDENT .sp This example assumes that the server is defined in a script called \fBapp.py\fP\&. Adapt it as necessary. .sp This guide will help you integrate websockets into a broader system. .SS Integrate with Django .sp If you\(aqre looking at adding real\-time capabilities to a Django project with WebSocket, you have two main options. .INDENT 0.0 .IP 1. 3 Using Django \fI\%Channels\fP, a project adding WebSocket to Django, among other features. This approach is fully supported by Django. However, it requires switching to a new deployment architecture. .IP 2. 3 Deploying a separate WebSocket server next to your Django project. This technique is well suited when you need to add a small set of real\-time features — maybe a notification service — to an HTTP application. .UNINDENT .sp This guide shows how to implement the second technique with websockets. It assumes familiarity with Django. .SS Authenticate connections .sp Since the websockets server runs outside of Django, we need to integrate it with \fBdjango.contrib.auth\fP\&. .sp We will generate authentication tokens in the Django project. Then we will send them to the websockets server, where they will authenticate the user. .sp Generating a token for the current user and making it available in the browser is up to you. You could render the token in a template or fetch it with an API call. .sp Refer to the topic guide on \fI\%authentication\fP for details on this design. .SS Generate tokens .sp We want secure, short\-lived tokens containing the user ID. We\(aqll rely on \fI\%django\-sesame\fP, a small library designed exactly for this purpose. .sp Add django\-sesame to the dependencies of your Django project, install it, and configure it in the settings of the project: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C AUTHENTICATION_BACKENDS = [ \(dqdjango.contrib.auth.backends.ModelBackend\(dq, \(dqsesame.backends.ModelBackend\(dq, ] .ft P .fi .UNINDENT .UNINDENT .sp (If your project already uses another authentication backend than the default \fB\(dqdjango.contrib.auth.backends.ModelBackend\(dq\fP, adjust accordingly.) .sp You don\(aqt need \fB\(dqsesame.middleware.AuthenticationMiddleware\(dq\fP\&. It is for authenticating users in the Django server, while we\(aqre authenticating them in the websockets server. .sp We\(aqd like our tokens to be valid for 30 seconds. We expect web pages to load and to establish the WebSocket connection within this delay. Configure django\-sesame accordingly in the settings of your Django project: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C SESAME_MAX_AGE = 30 .ft P .fi .UNINDENT .UNINDENT .sp If you expect your web site to load faster for all clients, a shorter lifespan is possible. However, in the context of this document, it would make manual testing more difficult. .sp You could also enable single\-use tokens. However, this would update the last login date of the user every time a WebSocket connection is established. This doesn\(aqt seem like a good idea, both in terms of behavior and in terms of performance. .sp Now you can generate tokens in a \fBdjango\-admin shell\fP as follows: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C >>> from django.contrib.auth import get_user_model >>> User = get_user_model() >>> user = User.objects.get(username=\(dq\(dq) >>> from sesame.utils import get_token >>> get_token(user) \(aq\(aq .ft P .fi .UNINDENT .UNINDENT .sp Keep this console open: since tokens expire after 30 seconds, you\(aqll have to generate a new token every time you want to test connecting to the server. .SS Validate tokens .sp Let\(aqs move on to the websockets server. .sp Add websockets to the dependencies of your Django project and install it. Indeed, we\(aqre going to reuse the environment of the Django project, so we can call its APIs in the websockets server. .sp Now here\(aqs how to implement authentication. .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C #!/usr/bin/env python import asyncio import django import websockets django.setup() from sesame.utils import get_user from websockets.frames import CloseCode async def handler(websocket): sesame = await websocket.recv() user = await asyncio.to_thread(get_user, sesame) if user is None: await websocket.close(CloseCode.INTERNAL_ERROR, \(dqauthentication failed\(dq) return await websocket.send(f\(dqHello {user}!\(dq) async def main(): async with websockets.serve(handler, \(dqlocalhost\(dq, 8888): await asyncio.Future() # run forever if __name__ == \(dq__main__\(dq: asyncio.run(main()) .ft P .fi .UNINDENT .UNINDENT .sp Let\(aqs unpack this code. .sp We\(aqre calling \fBdjango.setup()\fP before doing anything with Django because we\(aqre using Django in a \fI\%standalone script\fP\&. This assumes that the \fBDJANGO_SETTINGS_MODULE\fP environment variable is set to the Python path to your settings module. .sp The connection handler reads the first message received from the client, which is expected to contain a django\-sesame token. Then it authenticates the user with \fBget_user()\fP, the API for \fI\%authentication outside a view\fP\&. If authentication fails, it closes the connection and exits. .sp When we call an API that makes a database query such as \fBget_user()\fP, we wrap the call in \fI\%to_thread()\fP\&. Indeed, the Django ORM doesn\(aqt support asynchronous I/O. It would block the event loop if it didn\(aqt run in a separate thread. \fI\%to_thread()\fP is available since Python 3.9. In earlier versions, use \fI\%run_in_executor()\fP instead. .sp Finally, we start a server with \fI\%serve()\fP\&. .sp We\(aqre ready to test! .sp Save this code to a file called \fBauthentication.py\fP, make sure the \fBDJANGO_SETTINGS_MODULE\fP environment variable is set properly, and start the websockets server: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ python authentication.py .ft P .fi .UNINDENT .UNINDENT .sp Generate a new token — remember, they\(aqre only valid for 30 seconds — and use it to connect to your server. Paste your token and press Enter when you get a prompt: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ python \-m websockets ws://localhost:8888/ Connected to ws://localhost:8888/ > < Hello ! Connection closed: 1000 (OK). .ft P .fi .UNINDENT .UNINDENT .sp It works! .sp If you enter an expired or invalid token, authentication fails and the server closes the connection: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ python \-m websockets ws://localhost:8888/ Connected to ws://localhost:8888. > not a token Connection closed: 1011 (internal error) authentication failed. .ft P .fi .UNINDENT .UNINDENT .sp You can also test from a browser by generating a new token and running the following code in the JavaScript console of the browser: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C websocket = new WebSocket(\(dqws://localhost:8888/\(dq); websocket.onopen = (event) => websocket.send(\(dq\(dq); websocket.onmessage = (event) => console.log(event.data); .ft P .fi .UNINDENT .UNINDENT .sp If you don\(aqt want to import your entire Django project into the websockets server, you can build a separate Django project with \fBdjango.contrib.auth\fP, \fBdjango\-sesame\fP, a suitable \fBUser\fP model, and a subset of the settings of the main project. .SS Stream events .sp We can connect and authenticate but our server doesn\(aqt do anything useful yet! .sp Let\(aqs send a message every time a user makes an action in the admin. This message will be broadcast to all users who can access the model on which the action was made. This may be used for showing notifications to other users. .sp Many use cases for WebSocket with Django follow a similar pattern. .SS Set up event bus .sp We need a event bus to enable communications between Django and websockets. Both sides connect permanently to the bus. Then Django writes events and websockets reads them. For the sake of simplicity, we\(aqll rely on \fI\%Redis Pub/Sub\fP\&. .sp The easiest way to add Redis to a Django project is by configuring a cache backend with \fI\%django\-redis\fP\&. This library manages connections to Redis efficiently, persisting them between requests, and provides an API to access the Redis connection directly. .sp Install Redis, add django\-redis to the dependencies of your Django project, install it, and configure it in the settings of the project: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C CACHES = { \(dqdefault\(dq: { \(dqBACKEND\(dq: \(dqdjango_redis.cache.RedisCache\(dq, \(dqLOCATION\(dq: \(dqredis://127.0.0.1:6379/1\(dq, }, } .ft P .fi .UNINDENT .UNINDENT .sp If you already have a default cache, add a new one with a different name and change \fBget_redis_connection(\(dqdefault\(dq)\fP in the code below to the same name. .SS Publish events .sp Now let\(aqs write events to the bus. .sp Add the following code to a module that is imported when your Django project starts. Typically, you would put it in a \fBsignals.py\fP module, which you would import in the \fBAppConfig.ready()\fP method of one of your apps: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C import json from django.contrib.admin.models import LogEntry from django.db.models.signals import post_save from django.dispatch import receiver from django_redis import get_redis_connection @receiver(post_save, sender=LogEntry) def publish_event(instance, **kwargs): event = { \(dqmodel\(dq: instance.content_type.name, \(dqobject\(dq: instance.object_repr, \(dqmessage\(dq: instance.get_change_message(), \(dqtimestamp\(dq: instance.action_time.isoformat(), \(dquser\(dq: str(instance.user), \(dqcontent_type_id\(dq: instance.content_type_id, \(dqobject_id\(dq: instance.object_id, } connection = get_redis_connection(\(dqdefault\(dq) payload = json.dumps(event) connection.publish(\(dqevents\(dq, payload) .ft P .fi .UNINDENT .UNINDENT .sp This code runs every time the admin saves a \fBLogEntry\fP object to keep track of a change. It extracts interesting data, serializes it to JSON, and writes an event to Redis. .sp Let\(aqs check that it works: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ redis\-cli 127.0.0.1:6379> SELECT 1 OK 127.0.0.1:6379[1]> SUBSCRIBE events Reading messages... (press Ctrl\-C to quit) 1) \(dqsubscribe\(dq 2) \(dqevents\(dq 3) (integer) 1 .ft P .fi .UNINDENT .UNINDENT .sp Leave this command running, start the Django development server and make changes in the admin: add, modify, or delete objects. You should see corresponding events published to the \fB\(dqevents\(dq\fP stream. .SS Broadcast events .sp Now let\(aqs turn to reading events and broadcasting them to connected clients. We need to add several features: .INDENT 0.0 .IP \(bu 2 Keep track of connected clients so we can broadcast messages. .IP \(bu 2 Tell which content types the user has permission to view or to change. .IP \(bu 2 Connect to the message bus and read events. .IP \(bu 2 Broadcast these events to users who have corresponding permissions. .UNINDENT .sp Here\(aqs a complete implementation. .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C #!/usr/bin/env python import asyncio import json import aioredis import django import websockets django.setup() from django.contrib.contenttypes.models import ContentType from sesame.utils import get_user from websockets.frames import CloseCode CONNECTIONS = {} def get_content_types(user): \(dq\(dq\(dqReturn the set of IDs of content types visible by user.\(dq\(dq\(dq # This does only three database queries because Django caches # all permissions on the first call to user.has_perm(...). return { ct.id for ct in ContentType.objects.all() if user.has_perm(f\(dq{ct.app_label}.view_{ct.model}\(dq) or user.has_perm(f\(dq{ct.app_label}.change_{ct.model}\(dq) } async def handler(websocket): \(dq\(dq\(dqAuthenticate user and register connection in CONNECTIONS.\(dq\(dq\(dq sesame = await websocket.recv() user = await asyncio.to_thread(get_user, sesame) if user is None: await websocket.close(CloseCode.INTERNAL_ERROR, \(dqauthentication failed\(dq) return ct_ids = await asyncio.to_thread(get_content_types, user) CONNECTIONS[websocket] = {\(dqcontent_type_ids\(dq: ct_ids} try: await websocket.wait_closed() finally: del CONNECTIONS[websocket] async def process_events(): \(dq\(dq\(dqListen to events in Redis and process them.\(dq\(dq\(dq redis = aioredis.from_url(\(dqredis://127.0.0.1:6379/1\(dq) pubsub = redis.pubsub() await pubsub.subscribe(\(dqevents\(dq) async for message in pubsub.listen(): if message[\(dqtype\(dq] != \(dqmessage\(dq: continue payload = message[\(dqdata\(dq].decode() # Broadcast event to all users who have permissions to see it. event = json.loads(payload) recipients = ( websocket for websocket, connection in CONNECTIONS.items() if event[\(dqcontent_type_id\(dq] in connection[\(dqcontent_type_ids\(dq] ) websockets.broadcast(recipients, payload) async def main(): async with websockets.serve(handler, \(dqlocalhost\(dq, 8888): await process_events() # runs forever if __name__ == \(dq__main__\(dq: asyncio.run(main()) .ft P .fi .UNINDENT .UNINDENT .sp Since the \fBget_content_types()\fP function makes a database query, it is wrapped inside \fI\%asyncio.to_thread()\fP\&. It runs once when each WebSocket connection is open; then its result is cached for the lifetime of the connection. Indeed, running it for each message would trigger database queries for all connected users at the same time, which would hurt the database. .sp The connection handler merely registers the connection in a global variable, associated to the list of content types for which events should be sent to that connection, and waits until the client disconnects. .sp The \fBprocess_events()\fP function reads events from Redis and broadcasts them to all connections that should receive them. We don\(aqt care much if a sending a notification fails — this happens when a connection drops between the moment we iterate on connections and the moment the corresponding message is sent — so we start a task with for each message and forget about it. Also, this means we\(aqre immediately ready to process the next event, even if it takes time to send a message to a slow client. .sp Since Redis can publish a message to multiple subscribers, multiple instances of this server can safely run in parallel. .SS Does it scale? .sp In theory, given enough servers, this design can scale to a hundred million clients, since Redis can handle ten thousand servers and each server can handle ten thousand clients. In practice, you would need a more scalable message bus before reaching that scale, due to the volume of messages. .sp The WebSocket protocol makes provisions for extending or specializing its features, which websockets supports fully. .SS Write an extension .sp During the opening handshake, WebSocket clients and servers negotiate which \fI\%extensions\fP will be used with which parameters. Then each frame is processed by extensions before being sent or after being received. .sp As a consequence, writing an extension requires implementing several classes: .INDENT 0.0 .IP \(bu 2 Extension Factory: it negotiates parameters and instantiates the extension. .sp Clients and servers require separate extension factories with distinct APIs. .sp Extension factories are the public API of an extension. .IP \(bu 2 Extension: it decodes incoming frames and encodes outgoing frames. .sp If the extension is symmetrical, clients and servers can use the same class. .sp Extensions are initialized by extension factories, so they don\(aqt need to be part of the public API of an extension. .UNINDENT .sp websockets provides base classes for extension factories and extensions. See \fI\%ClientExtensionFactory\fP, \fI\%ServerExtensionFactory\fP, and \fI\%Extension\fP for details. .sp Once your application is ready, learn how to deploy it on various platforms. .SS Deploy to Render .sp This guide describes how to deploy a websockets server to \fI\%Render\fP\&. .INDENT 0.0 .INDENT 3.5 .IP "The free plan of Render is sufficient for trying this guide." .sp However, on a \fI\%free plan\fP, connections are dropped after five minutes, which is quite short for WebSocket application. .UNINDENT .UNINDENT .sp We\(aqre going to deploy a very simple app. The process would be identical for a more realistic app. .SS Create repository .sp Deploying to Render requires a git repository. Let\(aqs initialize one: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ mkdir websockets\-echo $ cd websockets\-echo $ git init \-b main Initialized empty Git repository in websockets\-echo/.git/ $ git commit \-\-allow\-empty \-m \(dqInitial commit.\(dq [main (root\-commit) 816c3b1] Initial commit. .ft P .fi .UNINDENT .UNINDENT .sp Render requires the git repository to be hosted at GitHub or GitLab. .sp Sign up or log in to GitHub. Create a new repository named \fBwebsockets\-echo\fP\&. Don\(aqt enable any of the initialization options offered by GitHub. Then, follow instructions for pushing an existing repository from the command line. .sp After pushing, refresh your repository\(aqs homepage on GitHub. You should see an empty repository with an empty initial commit. .SS Create application .sp Here\(aqs the implementation of the app, an echo server. Save it in a file called \fBapp.py\fP: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C #!/usr/bin/env python import asyncio import http import signal import websockets async def echo(websocket): async for message in websocket: await websocket.send(message) async def health_check(path, request_headers): if path == \(dq/healthz\(dq: return http.HTTPStatus.OK, [], b\(dqOK\en\(dq async def main(): # Set the stop condition when receiving SIGTERM. loop = asyncio.get_running_loop() stop = loop.create_future() loop.add_signal_handler(signal.SIGTERM, stop.set_result, None) async with websockets.serve( echo, host=\(dq\(dq, port=8080, process_request=health_check, ): await stop if __name__ == \(dq__main__\(dq: asyncio.run(main()) .ft P .fi .UNINDENT .UNINDENT .sp This app implements requirements for \fI\%zero downtime deploys\fP: .INDENT 0.0 .IP \(bu 2 it provides a health check at \fB/healthz\fP; .IP \(bu 2 it closes connections and exits cleanly when it receives a \fBSIGTERM\fP signal. .UNINDENT .sp Create a \fBrequirements.txt\fP file containing this line to declare a dependency on websockets: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C websockets .ft P .fi .UNINDENT .UNINDENT .sp Confirm that you created the correct files and commit them to git: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ ls app.py requirements.txt $ git add . $ git commit \-m \(dqInitial implementation.\(dq [main f26bf7f] Initial implementation. 2 files changed, 37 insertions(+) create mode 100644 app.py create mode 100644 requirements.txt .ft P .fi .UNINDENT .UNINDENT .sp Push the changes to GitHub: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ git push \&... To github.com:/websockets\-echo.git 816c3b1..f26bf7f main \-> main .ft P .fi .UNINDENT .UNINDENT .sp The app is ready. Let\(aqs deploy it! .SS Deploy application .sp Sign up or log in to Render. .sp Create a new web service. Connect the git repository that you just created. .sp Then, finalize the configuration of your app as follows: .INDENT 0.0 .IP \(bu 2 \fBName\fP: websockets\-echo .IP \(bu 2 \fBStart Command\fP: \fBpython app.py\fP .UNINDENT .sp If you\(aqre just experimenting, select the free plan. Create the web service. .sp To configure the health check, go to Settings, scroll down to Health & Alerts, and set: .INDENT 0.0 .IP \(bu 2 \fBHealth Check Path\fP: /healthz .UNINDENT .sp This triggers a new deployment. .SS Validate deployment .sp Let\(aqs confirm that your application is running as expected. .sp Since it\(aqs a WebSocket server, you need a WebSocket client, such as the interactive client that comes with websockets. .sp If you\(aqre currently building a websockets server, perhaps you\(aqre already in a virtualenv where websockets is installed. If not, you can install it in a new virtualenv as follows: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ python \-m venv websockets\-client $ . websockets\-client/bin/activate $ pip install websockets .ft P .fi .UNINDENT .UNINDENT .sp Connect the interactive client — you must replace \fBwebsockets\-echo\fP with the name of your Render app in this command: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ python \-m websockets wss://websockets\-echo.onrender.com/ Connected to wss://websockets\-echo.onrender.com/. > .ft P .fi .UNINDENT .UNINDENT .sp Great! Your app is running! .sp Once you\(aqre connected, you can send any message and the server will echo it, or press Ctrl\-D to terminate the connection: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C > Hello! < Hello! Connection closed: 1000 (OK). .ft P .fi .UNINDENT .UNINDENT .sp You can also confirm that your application shuts down gracefully when you deploy a new version. Due to limitations of Render\(aqs free plan, you must upgrade to a paid plan before you perform this test. .sp Connect an interactive client again — remember to replace \fBwebsockets\-echo\fP with your app: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ python \-m websockets wss://websockets\-echo.onrender.com/ Connected to wss://websockets\-echo.onrender.com/. > .ft P .fi .UNINDENT .UNINDENT .sp Trigger a new deployment with Manual Deploy > Deploy latest commit. When the deployment completes, the connection is closed with code 1001 (going away). .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ python \-m websockets wss://websockets\-echo.onrender.com/ Connected to wss://websockets\-echo.onrender.com/. Connection closed: 1001 (going away). .ft P .fi .UNINDENT .UNINDENT .sp If graceful shutdown wasn\(aqt working, the server wouldn\(aqt perform a closing handshake and the connection would be closed with code 1006 (abnormal closure). .sp Remember to downgrade to a free plan if you upgraded just for testing this feature. .SS Deploy to Fly .sp This guide describes how to deploy a websockets server to \fI\%Fly\fP\&. .INDENT 0.0 .INDENT 3.5 .IP "The free tier of Fly is sufficient for trying this guide." .sp The \fI\%free tier\fP include up to three small VMs. This guide uses only one. .UNINDENT .UNINDENT .sp We\(aqre going to deploy a very simple app. The process would be identical for a more realistic app. .SS Create application .sp Here\(aqs the implementation of the app, an echo server. Save it in a file called \fBapp.py\fP: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C #!/usr/bin/env python import asyncio import http import signal import websockets async def echo(websocket): async for message in websocket: await websocket.send(message) async def health_check(path, request_headers): if path == \(dq/healthz\(dq: return http.HTTPStatus.OK, [], b\(dqOK\en\(dq async def main(): # Set the stop condition when receiving SIGTERM. loop = asyncio.get_running_loop() stop = loop.create_future() loop.add_signal_handler(signal.SIGTERM, stop.set_result, None) async with websockets.serve( echo, host=\(dq\(dq, port=8080, process_request=health_check, ): await stop if __name__ == \(dq__main__\(dq: asyncio.run(main()) .ft P .fi .UNINDENT .UNINDENT .sp This app implements typical requirements for running on a Platform as a Service: .INDENT 0.0 .IP \(bu 2 it provides a health check at \fB/healthz\fP; .IP \(bu 2 it closes connections and exits cleanly when it receives a \fBSIGTERM\fP signal. .UNINDENT .sp Create a \fBrequirements.txt\fP file containing this line to declare a dependency on websockets: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C websockets .ft P .fi .UNINDENT .UNINDENT .sp The app is ready. Let\(aqs deploy it! .SS Deploy application .sp Follow the \fI\%instructions\fP to install the Fly CLI, if you haven\(aqt done that yet. .sp Sign up or log in to Fly. .sp Launch the app — you\(aqll have to pick a different name because I\(aqm already using \fBwebsockets\-echo\fP: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ fly launch Creating app in ... Scanning source code Detected a Python app Using the following build configuration: Builder: paketobuildpacks/builder:base ? App Name (leave blank to use an auto\-generated name): websockets\-echo ? Select organization: ... ? Select region: ... Created app websockets\-echo in organization ... Wrote config file fly.toml ? Would you like to set up a Postgresql database now? No We have generated a simple Procfile for you. Modify it to fit your needs and run \(dqfly deploy\(dq to deploy your application. .ft P .fi .UNINDENT .UNINDENT .INDENT 0.0 .INDENT 3.5 .IP "This will build the image with a generic buildpack." .sp Fly can \fI\%build images\fP with a Dockerfile or a buildpack. Here, \fBfly launch\fP configures a generic Paketo buildpack. .sp If you\(aqd rather package the app with a Dockerfile, check out the guide to \fI\%containerize an application\fP\&. .UNINDENT .UNINDENT .sp Replace the auto\-generated \fBfly.toml\fP with: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C app = \(dqwebsockets\-echo\(dq kill_signal = \(dqSIGTERM\(dq [build] builder = \(dqpaketobuildpacks/builder:base\(dq [[services]] internal_port = 8080 protocol = \(dqtcp\(dq [[services.http_checks]] path = \(dq/healthz\(dq [[services.ports]] handlers = [\(dqtls\(dq, \(dqhttp\(dq] port = 443 .ft P .fi .UNINDENT .UNINDENT .sp This configuration: .INDENT 0.0 .IP \(bu 2 listens on port 443, terminates TLS, and forwards to the app on port 8080; .IP \(bu 2 declares a health check at \fB/healthz\fP; .IP \(bu 2 requests a \fBSIGTERM\fP for terminating the app. .UNINDENT .sp Replace the auto\-generated \fBProcfile\fP with: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C web: python app.py .ft P .fi .UNINDENT .UNINDENT .sp This tells Fly how to run the app. .sp Now you can deploy it: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ fly deploy \&... lots of output... ==> Monitoring deployment 1 desired, 1 placed, 1 healthy, 0 unhealthy [health checks: 1 total, 1 passing] \-\-> v0 deployed successfully .ft P .fi .UNINDENT .UNINDENT .SS Validate deployment .sp Let\(aqs confirm that your application is running as expected. .sp Since it\(aqs a WebSocket server, you need a WebSocket client, such as the interactive client that comes with websockets. .sp If you\(aqre currently building a websockets server, perhaps you\(aqre already in a virtualenv where websockets is installed. If not, you can install it in a new virtualenv as follows: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ python \-m venv websockets\-client $ . websockets\-client/bin/activate $ pip install websockets .ft P .fi .UNINDENT .UNINDENT .sp Connect the interactive client — you must replace \fBwebsockets\-echo\fP with the name of your Fly app in this command: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ python \-m websockets wss://websockets\-echo.fly.dev/ Connected to wss://websockets\-echo.fly.dev/. > .ft P .fi .UNINDENT .UNINDENT .sp Great! Your app is running! .sp Once you\(aqre connected, you can send any message and the server will echo it, or press Ctrl\-D to terminate the connection: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C > Hello! < Hello! Connection closed: 1000 (OK). .ft P .fi .UNINDENT .UNINDENT .sp You can also confirm that your application shuts down gracefully. .sp Connect an interactive client again — remember to replace \fBwebsockets\-echo\fP with your app: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ python \-m websockets wss://websockets\-echo.fly.dev/ Connected to wss://websockets\-echo.fly.dev/. > .ft P .fi .UNINDENT .UNINDENT .sp In another shell, restart the app — again, replace \fBwebsockets\-echo\fP with your app: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ fly restart websockets\-echo websockets\-echo is being restarted .ft P .fi .UNINDENT .UNINDENT .sp Go back to the first shell. The connection is closed with code 1001 (going away). .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ python \-m websockets wss://websockets\-echo.fly.dev/ Connected to wss://websockets\-echo.fly.dev/. Connection closed: 1001 (going away). .ft P .fi .UNINDENT .UNINDENT .sp If graceful shutdown wasn\(aqt working, the server wouldn\(aqt perform a closing handshake and the connection would be closed with code 1006 (abnormal closure). .SS Deploy to Heroku .sp This guide describes how to deploy a websockets server to \fI\%Heroku\fP\&. The same principles should apply to other Platform as a Service providers. .INDENT 0.0 .INDENT 3.5 .IP "Heroku no longer offers a free tier." .sp When this tutorial was written, in September 2021, Heroku offered a free tier where a websockets app could run at no cost. In November 2022, Heroku removed the free tier, making it impossible to maintain this document. As a consequence, it isn\(aqt updated anymore and may be removed in the future. .UNINDENT .UNINDENT .sp We\(aqre going to deploy a very simple app. The process would be identical for a more realistic app. .SS Create repository .sp Deploying to Heroku requires a git repository. Let\(aqs initialize one: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ mkdir websockets\-echo $ cd websockets\-echo $ git init \-b main Initialized empty Git repository in websockets\-echo/.git/ $ git commit \-\-allow\-empty \-m \(dqInitial commit.\(dq [main (root\-commit) 1e7947d] Initial commit. .ft P .fi .UNINDENT .UNINDENT .SS Create application .sp Here\(aqs the implementation of the app, an echo server. Save it in a file called \fBapp.py\fP: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C #!/usr/bin/env python import asyncio import signal import os import websockets async def echo(websocket): async for message in websocket: await websocket.send(message) async def main(): # Set the stop condition when receiving SIGTERM. loop = asyncio.get_running_loop() stop = loop.create_future() loop.add_signal_handler(signal.SIGTERM, stop.set_result, None) async with websockets.serve( echo, host=\(dq\(dq, port=int(os.environ[\(dqPORT\(dq]), ): await stop if __name__ == \(dq__main__\(dq: asyncio.run(main()) .ft P .fi .UNINDENT .UNINDENT .sp Heroku expects the server to \fI\%listen on a specific port\fP, which is provided in the \fB$PORT\fP environment variable. The app reads it and passes it to \fI\%serve()\fP\&. .sp Heroku sends a \fBSIGTERM\fP signal to all processes when \fI\%shutting down a dyno\fP\&. When the app receives this signal, it closes connections and exits cleanly. .sp Create a \fBrequirements.txt\fP file containing this line to declare a dependency on websockets: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C websockets .ft P .fi .UNINDENT .UNINDENT .sp Create a \fBProcfile\fP\&. .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C web: python app.py .ft P .fi .UNINDENT .UNINDENT .sp This tells Heroku how to run the app. .sp Confirm that you created the correct files and commit them to git: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ ls Procfile app.py requirements.txt $ git add . $ git commit \-m \(dqInitial implementation.\(dq [main 8418c62] Initial implementation. \ 3 files changed, 32 insertions(+) \ create mode 100644 Procfile \ create mode 100644 app.py \ create mode 100644 requirements.txt .ft P .fi .UNINDENT .UNINDENT .sp The app is ready. Let\(aqs deploy it! .SS Deploy application .sp Follow the \fI\%instructions\fP to install the Heroku CLI, if you haven\(aqt done that yet. .sp Sign up or log in to Heroku. .sp Create a Heroku app — you\(aqll have to pick a different name because I\(aqm already using \fBwebsockets\-echo\fP: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ heroku create websockets\-echo Creating ⬢ websockets\-echo... done https://websockets\-echo.herokuapp.com/ | https://git.heroku.com/websockets\-echo.git .ft P .fi .UNINDENT .UNINDENT .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ git push heroku \&... lots of output... remote: \-\-\-\-\-> Launching... remote: Released v1 remote: https://websockets\-echo.herokuapp.com/ deployed to Heroku remote: remote: Verifying deploy... done. To https://git.heroku.com/websockets\-echo.git \ * [new branch] main \-> main .ft P .fi .UNINDENT .UNINDENT .SS Validate deployment .sp Let\(aqs confirm that your application is running as expected. .sp Since it\(aqs a WebSocket server, you need a WebSocket client, such as the interactive client that comes with websockets. .sp If you\(aqre currently building a websockets server, perhaps you\(aqre already in a virtualenv where websockets is installed. If not, you can install it in a new virtualenv as follows: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ python \-m venv websockets\-client $ . websockets\-client/bin/activate $ pip install websockets .ft P .fi .UNINDENT .UNINDENT .sp Connect the interactive client — you must replace \fBwebsockets\-echo\fP with the name of your Heroku app in this command: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ python \-m websockets wss://websockets\-echo.herokuapp.com/ Connected to wss://websockets\-echo.herokuapp.com/. > .ft P .fi .UNINDENT .UNINDENT .sp Great! Your app is running! .sp Once you\(aqre connected, you can send any message and the server will echo it, or press Ctrl\-D to terminate the connection: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C > Hello! < Hello! Connection closed: 1000 (OK). .ft P .fi .UNINDENT .UNINDENT .sp You can also confirm that your application shuts down gracefully. .sp Connect an interactive client again — remember to replace \fBwebsockets\-echo\fP with your app: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ python \-m websockets wss://websockets\-echo.herokuapp.com/ Connected to wss://websockets\-echo.herokuapp.com/. > .ft P .fi .UNINDENT .UNINDENT .sp In another shell, restart the app — again, replace \fBwebsockets\-echo\fP with your app: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ heroku dyno:restart \-a websockets\-echo Restarting dynos on ⬢ websockets\-echo... done .ft P .fi .UNINDENT .UNINDENT .sp Go back to the first shell. The connection is closed with code 1001 (going away). .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ python \-m websockets wss://websockets\-echo.herokuapp.com/ Connected to wss://websockets\-echo.herokuapp.com/. Connection closed: 1001 (going away). .ft P .fi .UNINDENT .UNINDENT .sp If graceful shutdown wasn\(aqt working, the server wouldn\(aqt perform a closing handshake and the connection would be closed with code 1006 (abnormal closure). .SS Deploy to Kubernetes .sp This guide describes how to deploy a websockets server to \fI\%Kubernetes\fP\&. It assumes familiarity with Docker and Kubernetes. .sp We\(aqre going to deploy a simple app to a local Kubernetes cluster and to ensure that it scales as expected. .sp In a more realistic context, you would follow your organization\(aqs practices for deploying to Kubernetes, but you would apply the same principles as far as websockets is concerned. .SS Containerize application .sp Here\(aqs the app we\(aqre going to deploy. Save it in a file called \fBapp.py\fP: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C #!/usr/bin/env python import asyncio import http import signal import sys import time import websockets async def slow_echo(websocket): async for message in websocket: # Block the event loop! This allows saturating a single asyncio # process without opening an impractical number of connections. time.sleep(0.1) # 100ms await websocket.send(message) async def health_check(path, request_headers): if path == \(dq/healthz\(dq: return http.HTTPStatus.OK, [], b\(dqOK\en\(dq if path == \(dq/inemuri\(dq: loop = asyncio.get_running_loop() loop.call_later(1, time.sleep, 10) return http.HTTPStatus.OK, [], b\(dqSleeping for 10s\en\(dq if path == \(dq/seppuku\(dq: loop = asyncio.get_running_loop() loop.call_later(1, sys.exit, 69) return http.HTTPStatus.OK, [], b\(dqTerminating\en\(dq async def main(): # Set the stop condition when receiving SIGTERM. loop = asyncio.get_running_loop() stop = loop.create_future() loop.add_signal_handler(signal.SIGTERM, stop.set_result, None) async with websockets.serve( slow_echo, host=\(dq\(dq, port=80, process_request=health_check, ): await stop if __name__ == \(dq__main__\(dq: asyncio.run(main()) .ft P .fi .UNINDENT .UNINDENT .sp This is an echo server with one twist: every message blocks the server for 100ms, which creates artificial starvation of CPU time. This makes it easier to saturate the server for load testing. .sp The app exposes a health check on \fB/healthz\fP\&. It also provides two other endpoints for testing purposes: \fB/inemuri\fP will make the app unresponsive for 10 seconds and \fB/seppuku\fP will terminate it. .sp The quest for the perfect Python container image is out of scope of this guide, so we\(aqll go for the simplest possible configuration instead: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C FROM python:3.9\-alpine RUN pip install websockets COPY app.py . CMD [\(dqpython\(dq, \(dqapp.py\(dq] .ft P .fi .UNINDENT .UNINDENT .sp After saving this \fBDockerfile\fP, build the image: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ docker build \-t websockets\-test:1.0 . .ft P .fi .UNINDENT .UNINDENT .sp Test your image by running: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ docker run \-\-name run\-websockets\-test \-\-publish 32080:80 \-\-rm \e websockets\-test:1.0 .ft P .fi .UNINDENT .UNINDENT .sp Then, in another shell, in a virtualenv where websockets is installed, connect to the app and check that it echoes anything you send: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ python \-m websockets ws://localhost:32080/ Connected to ws://localhost:32080/. > Hey there! < Hey there! > .ft P .fi .UNINDENT .UNINDENT .sp Now, in yet another shell, stop the app with: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ docker kill \-s TERM run\-websockets\-test .ft P .fi .UNINDENT .UNINDENT .sp Going to the shell where you connected to the app, you can confirm that it shut down gracefully: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ python \-m websockets ws://localhost:32080/ Connected to ws://localhost:32080/. > Hey there! < Hey there! Connection closed: 1001 (going away). .ft P .fi .UNINDENT .UNINDENT .sp If it didn\(aqt, you\(aqd get code 1006 (abnormal closure). .SS Deploy application .sp Configuring Kubernetes is even further beyond the scope of this guide, so we\(aqll use a basic configuration for testing, with just one \fI\%Service\fP and one \fI\%Deployment\fP: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C apiVersion: v1 kind: Service metadata: name: websockets\-test spec: type: NodePort ports: \- port: 80 nodePort: 32080 selector: app: websockets\-test \-\-\- apiVersion: apps/v1 kind: Deployment metadata: name: websockets\-test spec: selector: matchLabels: app: websockets\-test template: metadata: labels: app: websockets\-test spec: containers: \- name: websockets\-test image: websockets\-test:1.0 livenessProbe: httpGet: path: /healthz port: 80 periodSeconds: 1 ports: \- containerPort: 80 .ft P .fi .UNINDENT .UNINDENT .sp For local testing, a service of type \fI\%NodePort\fP is good enough. For deploying to production, you would configure an \fI\%Ingress\fP\&. .sp After saving this to a file called \fBdeployment.yaml\fP, you can deploy: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ kubectl apply \-f deployment.yaml service/websockets\-test created deployment.apps/websockets\-test created .ft P .fi .UNINDENT .UNINDENT .sp Now you have a deployment with one pod running: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ kubectl get deployment websockets\-test NAME READY UP\-TO\-DATE AVAILABLE AGE websockets\-test 1/1 1 1 10s $ kubectl get pods \-l app=websockets\-test NAME READY STATUS RESTARTS AGE websockets\-test\-86b48f4bb7\-nltfh 1/1 Running 0 10s .ft P .fi .UNINDENT .UNINDENT .sp You can connect to the service — press Ctrl\-D to exit: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ python \-m websockets ws://localhost:32080/ Connected to ws://localhost:32080/. Connection closed: 1000 (OK). .ft P .fi .UNINDENT .UNINDENT .SS Validate deployment .sp First, let\(aqs ensure the liveness probe works by making the app unresponsive: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ curl http://localhost:32080/inemuri Sleeping for 10s .ft P .fi .UNINDENT .UNINDENT .sp Since we have only one pod, we know that this pod will go to sleep. .sp The liveness probe is configured to run every second. By default, liveness probes time out after one second and have a threshold of three failures. Therefore Kubernetes should restart the pod after at most 5 seconds. .sp Indeed, after a few seconds, the pod reports a restart: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ kubectl get pods \-l app=websockets\-test NAME READY STATUS RESTARTS AGE websockets\-test\-86b48f4bb7\-nltfh 1/1 Running 1 42s .ft P .fi .UNINDENT .UNINDENT .sp Next, let\(aqs take it one step further and crash the app: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ curl http://localhost:32080/seppuku Terminating .ft P .fi .UNINDENT .UNINDENT .sp The pod reports a second restart: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ kubectl get pods \-l app=websockets\-test NAME READY STATUS RESTARTS AGE websockets\-test\-86b48f4bb7\-nltfh 1/1 Running 2 72s .ft P .fi .UNINDENT .UNINDENT .sp All good — Kubernetes delivers on its promise to keep our app alive! .SS Scale deployment .sp Of course, Kubernetes is for scaling. Let\(aqs scale — modestly — to 10 pods: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ kubectl scale deployment.apps/websockets\-test \-\-replicas=10 deployment.apps/websockets\-test scaled .ft P .fi .UNINDENT .UNINDENT .sp After a few seconds, we have 10 pods running: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ kubectl get deployment websockets\-test NAME READY UP\-TO\-DATE AVAILABLE AGE websockets\-test 10/10 10 10 10m .ft P .fi .UNINDENT .UNINDENT .sp Now let\(aqs generate load. We\(aqll use this script: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C #!/usr/bin/env python import asyncio import sys import websockets URI = \(dqws://localhost:32080\(dq async def run(client_id, messages): async with websockets.connect(URI) as websocket: for message_id in range(messages): await websocket.send(f\(dq{client_id}:{message_id}\(dq) await websocket.recv() async def benchmark(clients, messages): await asyncio.wait([ asyncio.create_task(run(client_id, messages)) for client_id in range(clients) ]) if __name__ == \(dq__main__\(dq: clients, messages = int(sys.argv[1]), int(sys.argv[2]) asyncio.run(benchmark(clients, messages)) .ft P .fi .UNINDENT .UNINDENT .sp We\(aqll connect 500 clients in parallel, meaning 50 clients per pod, and have each client send 6 messages. Since the app blocks for 100ms before responding, if connections are perfectly distributed, we expect a total run time slightly over 50 * 6 * 0.1 = 30 seconds. .sp Let\(aqs try it: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ ulimit \-n 512 $ time python benchmark.py 500 6 python benchmark.py 500 6 2.40s user 0.51s system 7% cpu 36.471 total .ft P .fi .UNINDENT .UNINDENT .sp A total runtime of 36 seconds is in the right ballpark. Repeating this experiment with other parameters shows roughly consistent results, with the high variability you\(aqd expect from a quick benchmark without any effort to stabilize the test setup. .sp Finally, we can scale back to one pod. .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ kubectl scale deployment.apps/websockets\-test \-\-replicas=1 deployment.apps/websockets\-test scaled $ kubectl get deployment websockets\-test NAME READY UP\-TO\-DATE AVAILABLE AGE websockets\-test 1/1 1 1 15m .ft P .fi .UNINDENT .UNINDENT .SS Deploy with Supervisor .sp This guide proposes a simple way to deploy a websockets server directly on a Linux or BSD operating system. .sp We\(aqll configure \fI\%Supervisor\fP to run several server processes and to restart them if needed. .sp We\(aqll bind all servers to the same port. The OS will take care of balancing connections. .sp Create and activate a virtualenv: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ python \-m venv supervisor\-websockets $ . supervisor\-websockets/bin/activate .ft P .fi .UNINDENT .UNINDENT .sp Install websockets and Supervisor: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ pip install websockets $ pip install supervisor .ft P .fi .UNINDENT .UNINDENT .sp Save this app to a file called \fBapp.py\fP: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C #!/usr/bin/env python import asyncio import signal import websockets async def echo(websocket): async for message in websocket: await websocket.send(message) async def main(): # Set the stop condition when receiving SIGTERM. loop = asyncio.get_running_loop() stop = loop.create_future() loop.add_signal_handler(signal.SIGTERM, stop.set_result, None) async with websockets.serve( echo, host=\(dq\(dq, port=8080, reuse_port=True, ): await stop if __name__ == \(dq__main__\(dq: asyncio.run(main()) .ft P .fi .UNINDENT .UNINDENT .sp This is an echo server with two features added for the purpose of this guide: .INDENT 0.0 .IP \(bu 2 It shuts down gracefully when receiving a \fBSIGTERM\fP signal; .IP \(bu 2 It enables the \fBreuse_port\fP option of \fI\%create_server()\fP, which in turns sets \fBSO_REUSEPORT\fP on the accept socket. .UNINDENT .sp Save this Supervisor configuration to \fBsupervisord.conf\fP: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C [supervisord] [program:websockets\-test] command = python app.py process_name = %(program_name)s_%(process_num)02d numprocs = 4 autorestart = true .ft P .fi .UNINDENT .UNINDENT .sp This is the minimal configuration required to keep four instances of the app running, restarting them if they exit. .sp Now start Supervisor in the foreground: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ supervisord \-c supervisord.conf \-n INFO Increased RLIMIT_NOFILE limit to 1024 INFO supervisord started with pid 43596 INFO spawned: \(aqwebsockets\-test_00\(aq with pid 43597 INFO spawned: \(aqwebsockets\-test_01\(aq with pid 43598 INFO spawned: \(aqwebsockets\-test_02\(aq with pid 43599 INFO spawned: \(aqwebsockets\-test_03\(aq with pid 43600 INFO success: websockets\-test_00 entered RUNNING state, process has stayed up for > than 1 seconds (startsecs) INFO success: websockets\-test_01 entered RUNNING state, process has stayed up for > than 1 seconds (startsecs) INFO success: websockets\-test_02 entered RUNNING state, process has stayed up for > than 1 seconds (startsecs) INFO success: websockets\-test_03 entered RUNNING state, process has stayed up for > than 1 seconds (startsecs) .ft P .fi .UNINDENT .UNINDENT .sp In another shell, after activating the virtualenv, we can connect to the app — press Ctrl\-D to exit: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ python \-m websockets ws://localhost:8080/ Connected to ws://localhost:8080/. > Hello! < Hello! Connection closed: 1000 (OK). .ft P .fi .UNINDENT .UNINDENT .sp Look at the pid of an instance of the app in the logs and terminate it: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ kill \-TERM 43597 .ft P .fi .UNINDENT .UNINDENT .sp The logs show that Supervisor restarted this instance: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C INFO exited: websockets\-test_00 (exit status 0; expected) INFO spawned: \(aqwebsockets\-test_00\(aq with pid 43629 INFO success: websockets\-test_00 entered RUNNING state, process has stayed up for > than 1 seconds (startsecs) .ft P .fi .UNINDENT .UNINDENT .sp Now let\(aqs check what happens when we shut down Supervisor, but first let\(aqs establish a connection and leave it open: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ python \-m websockets ws://localhost:8080/ Connected to ws://localhost:8080/. > .ft P .fi .UNINDENT .UNINDENT .sp Look at the pid of supervisord itself in the logs and terminate it: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ kill \-TERM 43596 .ft P .fi .UNINDENT .UNINDENT .sp The logs show that Supervisor terminated all instances of the app before exiting: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C WARN received SIGTERM indicating exit request INFO waiting for websockets\-test_00, websockets\-test_01, websockets\-test_02, websockets\-test_03 to die INFO stopped: websockets\-test_02 (exit status 0) INFO stopped: websockets\-test_03 (exit status 0) INFO stopped: websockets\-test_01 (exit status 0) INFO stopped: websockets\-test_00 (exit status 0) .ft P .fi .UNINDENT .UNINDENT .sp And you can see that the connection to the app was closed gracefully: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ python \-m websockets ws://localhost:8080/ Connected to ws://localhost:8080/. Connection closed: 1001 (going away). .ft P .fi .UNINDENT .UNINDENT .sp In this example, we\(aqve been sharing the same virtualenv for supervisor and websockets. .sp In a real deployment, you would likely: .INDENT 0.0 .IP \(bu 2 Install Supervisor with the package manager of the OS. .IP \(bu 2 Create a virtualenv dedicated to your application. .IP \(bu 2 Add \fBenvironment=PATH=\(dqpath/to/your/virtualenv/bin\(dq\fP in the Supervisor configuration. Then \fBpython app.py\fP runs in that virtualenv. .UNINDENT .SS Deploy behind nginx .sp This guide demonstrates a way to load balance connections across multiple websockets server processes running on the same machine with \fI\%nginx\fP\&. .sp We\(aqll run server processes with Supervisor as described in \fI\%this guide\fP\&. .SS Run server processes .sp Save this app to \fBapp.py\fP: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C #!/usr/bin/env python import asyncio import os import signal import websockets async def echo(websocket): async for message in websocket: await websocket.send(message) async def main(): # Set the stop condition when receiving SIGTERM. loop = asyncio.get_running_loop() stop = loop.create_future() loop.add_signal_handler(signal.SIGTERM, stop.set_result, None) async with websockets.unix_serve( echo, path=f\(dq{os.environ[\(aqSUPERVISOR_PROCESS_NAME\(aq]}.sock\(dq, ): await stop if __name__ == \(dq__main__\(dq: asyncio.run(main()) .ft P .fi .UNINDENT .UNINDENT .sp We\(aqd like to nginx to connect to websockets servers via Unix sockets in order to avoid the overhead of TCP for communicating between processes running in the same OS. .sp We start the app with \fI\%unix_serve()\fP\&. Each server process listens on a different socket thanks to an environment variable set by Supervisor to a different value. .sp Save this configuration to \fBsupervisord.conf\fP: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C [supervisord] [program:websockets\-test] command = python app.py process_name = %(program_name)s_%(process_num)02d numprocs = 4 autorestart = true .ft P .fi .UNINDENT .UNINDENT .sp This configuration runs four instances of the app. .sp Install Supervisor and run it: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ supervisord \-c supervisord.conf \-n .ft P .fi .UNINDENT .UNINDENT .SS Configure and run nginx .sp Here\(aqs a simple nginx configuration to load balance connections across four processes: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C daemon off; events { } http { server { listen localhost:8080; location / { proxy_http_version 1.1; proxy_pass http://websocket; proxy_set_header Connection $http_connection; proxy_set_header Upgrade $http_upgrade; } } upstream websocket { least_conn; server unix:websockets\-test_00.sock; server unix:websockets\-test_01.sock; server unix:websockets\-test_02.sock; server unix:websockets\-test_03.sock; } } .ft P .fi .UNINDENT .UNINDENT .sp We set \fBdaemon off\fP so we can run nginx in the foreground for testing. .sp Then we combine the \fI\%WebSocket proxying\fP and \fI\%load balancing\fP guides: .INDENT 0.0 .IP \(bu 2 The WebSocket protocol requires HTTP/1.1. We must set the HTTP protocol version to 1.1, else nginx defaults to HTTP/1.0 for proxying. .IP \(bu 2 The WebSocket handshake involves the \fBConnection\fP and \fBUpgrade\fP HTTP headers. We must pass them to the upstream explicitly, else nginx drops them because they\(aqre hop\-by\-hop headers. .sp We deviate from the \fI\%WebSocket proxying\fP guide because its example adds a \fBConnection: Upgrade\fP header to every upstream request, even if the original request didn\(aqt contain that header. .IP \(bu 2 In the upstream configuration, we set the load balancing method to \fBleast_conn\fP in order to balance the number of active connections across servers. This is best for long running connections. .UNINDENT .sp Save the configuration to \fBnginx.conf\fP, install nginx, and run it: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ nginx \-c nginx.conf \-p . .ft P .fi .UNINDENT .UNINDENT .sp You can confirm that nginx proxies connections properly: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ PYTHONPATH=src python \-m websockets ws://localhost:8080/ Connected to ws://localhost:8080/. > Hello! < Hello! Connection closed: 1000 (OK). .ft P .fi .UNINDENT .UNINDENT .SS Deploy behind HAProxy .sp This guide demonstrates a way to load balance connections across multiple websockets server processes running on the same machine with \fI\%HAProxy\fP\&. .sp We\(aqll run server processes with Supervisor as described in \fI\%this guide\fP\&. .SS Run server processes .sp Save this app to \fBapp.py\fP: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C #!/usr/bin/env python import asyncio import os import signal import websockets async def echo(websocket): async for message in websocket: await websocket.send(message) async def main(): # Set the stop condition when receiving SIGTERM. loop = asyncio.get_running_loop() stop = loop.create_future() loop.add_signal_handler(signal.SIGTERM, stop.set_result, None) async with websockets.serve( echo, host=\(dqlocalhost\(dq, port=8000 + int(os.environ[\(dqSUPERVISOR_PROCESS_NAME\(dq][\-2:]), ): await stop if __name__ == \(dq__main__\(dq: asyncio.run(main()) .ft P .fi .UNINDENT .UNINDENT .sp Each server process listens on a different port by extracting an incremental index from an environment variable set by Supervisor. .sp Save this configuration to \fBsupervisord.conf\fP: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C [supervisord] [program:websockets\-test] command = python app.py process_name = %(program_name)s_%(process_num)02d numprocs = 4 autorestart = true .ft P .fi .UNINDENT .UNINDENT .sp This configuration runs four instances of the app. .sp Install Supervisor and run it: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ supervisord \-c supervisord.conf \-n .ft P .fi .UNINDENT .UNINDENT .SS Configure and run HAProxy .sp Here\(aqs a simple HAProxy configuration to load balance connections across four processes: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C defaults mode http timeout connect 10s timeout client 30s timeout server 30s frontend websocket bind localhost:8080 default_backend websocket backend websocket balance leastconn server websockets\-test_00 localhost:8000 server websockets\-test_01 localhost:8001 server websockets\-test_02 localhost:8002 server websockets\-test_03 localhost:8003 .ft P .fi .UNINDENT .UNINDENT .sp In the backend configuration, we set the load balancing method to \fBleastconn\fP in order to balance the number of active connections across servers. This is best for long running connections. .sp Save the configuration to \fBhaproxy.cfg\fP, install HAProxy, and run it: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ haproxy \-f haproxy.cfg .ft P .fi .UNINDENT .UNINDENT .sp You can confirm that HAProxy proxies connections properly: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ PYTHONPATH=src python \-m websockets ws://localhost:8080/ Connected to ws://localhost:8080/. > Hello! < Hello! Connection closed: 1000 (OK). .ft P .fi .UNINDENT .UNINDENT .sp If you\(aqre integrating the Sans\-I/O layer of websockets into a library, rather than building an application with websockets, follow this guide. .SS Integrate the Sans\-I/O layer .sp This guide explains how to integrate the \fI\%Sans\-I/O\fP layer of websockets to add support for WebSocket in another library. .sp As a prerequisite, you should decide how you will handle network I/O and asynchronous control flow. .sp Your integration layer will provide an API for the application on one side, will talk to the network on the other side, and will rely on websockets to implement the protocol in the middle. [image] .SS Opening a connection .SS Client\-side .sp If you\(aqre building a client, parse the URI you\(aqd like to connect to: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C from websockets.uri import parse_uri wsuri = parse_uri(\(dqws://example.com/\(dq) .ft P .fi .UNINDENT .UNINDENT .sp Open a TCP connection to \fB(wsuri.host, wsuri.port)\fP and perform a TLS handshake if \fBwsuri.secure\fP is \fI\%True\fP\&. .sp Initialize a \fI\%ClientProtocol\fP: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C from websockets.client import ClientProtocol protocol = ClientProtocol(wsuri) .ft P .fi .UNINDENT .UNINDENT .sp Create a WebSocket handshake request with \fI\%connect()\fP and send it with \fI\%send_request()\fP: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C request = protocol.connect() protocol.send_request(request) .ft P .fi .UNINDENT .UNINDENT .sp Then, call \fI\%data_to_send()\fP and send its output to the network, as described in \fI\%Send data\fP below. .sp Once you receive enough data, as explained in \fI\%Receive data\fP below, the first event returned by \fI\%events_received()\fP is the WebSocket handshake response. .sp When the handshake fails, the reason is available in \fI\%handshake_exc\fP: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C if protocol.handshake_exc is not None: raise protocol.handshake_exc .ft P .fi .UNINDENT .UNINDENT .sp Else, the WebSocket connection is open. .sp A WebSocket client API usually performs the handshake then returns a wrapper around the network socket and the \fI\%ClientProtocol\fP\&. .SS Server\-side .sp If you\(aqre building a server, accept network connections from clients and perform a TLS handshake if desired. .sp For each connection, initialize a \fI\%ServerProtocol\fP: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C from websockets.server import ServerProtocol protocol = ServerProtocol() .ft P .fi .UNINDENT .UNINDENT .sp Once you receive enough data, as explained in \fI\%Receive data\fP below, the first event returned by \fI\%events_received()\fP is the WebSocket handshake request. .sp Create a WebSocket handshake response with \fI\%accept()\fP and send it with \fI\%send_response()\fP: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C response = protocol.accept(request) protocol.send_response(response) .ft P .fi .UNINDENT .UNINDENT .sp Alternatively, you may reject the WebSocket handshake and return an HTTP response with \fI\%reject()\fP: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C response = protocol.reject(status, explanation) protocol.send_response(response) .ft P .fi .UNINDENT .UNINDENT .sp Then, call \fI\%data_to_send()\fP and send its output to the network, as described in \fI\%Send data\fP below. .sp Even when you call \fI\%accept()\fP, the WebSocket handshake may fail if the request is incorrect or unsupported. .sp When the handshake fails, the reason is available in \fI\%handshake_exc\fP: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C if protocol.handshake_exc is not None: raise protocol.handshake_exc .ft P .fi .UNINDENT .UNINDENT .sp Else, the WebSocket connection is open. .sp A WebSocket server API usually builds a wrapper around the network socket and the \fI\%ServerProtocol\fP\&. Then it invokes a connection handler that accepts the wrapper in argument. .sp It may also provide a way to close all connections and to shut down the server gracefully. .sp Going forwards, this guide focuses on handling an individual connection. .SS From the network to the application .sp Go through the five steps below until you reach the end of the data stream. .SS Receive data .sp When receiving data from the network, feed it to the protocol\(aqs \fI\%receive_data()\fP method. .sp When reaching the end of the data stream, call the protocol\(aqs \fI\%receive_eof()\fP method. .sp For example, if \fBsock\fP is a \fI\%socket\fP: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C try: data = sock.recv(65536) except OSError: # socket closed data = b\(dq\(dq if data: protocol.receive_data(data) else: protocol.receive_eof() .ft P .fi .UNINDENT .UNINDENT .sp These methods aren\(aqt expected to raise exceptions — unless you call them again after calling \fI\%receive_eof()\fP, which is an error. (If you get an exception, please file a bug!) .SS Send data .sp Then, call \fI\%data_to_send()\fP and send its output to the network: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C for data in protocol.data_to_send(): if data: sock.sendall(data) else: sock.shutdown(socket.SHUT_WR) .ft P .fi .UNINDENT .UNINDENT .sp The empty bytestring signals the end of the data stream. When you see it, you must half\-close the TCP connection. .sp Sending data right after receiving data is necessary because websockets responds to ping frames, close frames, and incorrect inputs automatically. .SS Expect TCP connection to close .sp Closing a WebSocket connection normally involves a two\-way WebSocket closing handshake. Then, regardless of whether the closure is normal or abnormal, the server starts the four\-way TCP closing handshake. If the network fails at the wrong point, you can end up waiting until the TCP timeout, which is very long. .sp To prevent dangling TCP connections when you expect the end of the data stream but you never reach it, call \fI\%close_expected()\fP and, if it returns \fI\%True\fP, schedule closing the TCP connection after a short timeout: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C # start a new execution thread to run this code sleep(10) sock.close() # does nothing if the socket is already closed .ft P .fi .UNINDENT .UNINDENT .sp If the connection is still open when the timeout elapses, closing the socket makes the execution thread that reads from the socket reach the end of the data stream, possibly with an exception. .SS Close TCP connection .sp If you called \fI\%receive_eof()\fP, close the TCP connection now. This is a clean closure because the receive buffer is empty. .sp After \fI\%receive_eof()\fP signals the end of the read stream, \fI\%data_to_send()\fP always signals the end of the write stream, unless it already ended. So, at this point, the TCP connection is already half\-closed. The only reason for closing it now is to release resources related to the socket. .sp Now you can exit the loop relaying data from the network to the application. .SS Receive events .sp Finally, call \fI\%events_received()\fP to obtain events parsed from the data provided to \fI\%receive_data()\fP: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C events = connection.events_received() .ft P .fi .UNINDENT .UNINDENT .sp The first event will be the WebSocket opening handshake request or response. See \fI\%Opening a connection\fP above for details. .sp All later events are WebSocket frames. There are two types of frames: .INDENT 0.0 .IP \(bu 2 Data frames contain messages transferred over the WebSocket connections. You should provide them to the application. See \fI\%Fragmentation\fP below for how to reassemble messages from frames. .IP \(bu 2 Control frames provide information about the connection\(aqs state. The main use case is to expose an abstraction over ping and pong to the application. Keep in mind that websockets responds to ping frames and close frames automatically. Don\(aqt duplicate this functionality! .UNINDENT .SS From the application to the network .sp The connection object provides one method for each type of WebSocket frame. .sp For sending a data frame: .INDENT 0.0 .IP \(bu 2 \fI\%send_continuation()\fP .IP \(bu 2 \fI\%send_text()\fP .IP \(bu 2 \fI\%send_binary()\fP .UNINDENT .sp These methods raise \fI\%ProtocolError\fP if you don\(aqt set the \fI\%FIN\fP bit correctly in fragmented messages. .sp For sending a control frame: .INDENT 0.0 .IP \(bu 2 \fI\%send_close()\fP .IP \(bu 2 \fI\%send_ping()\fP .IP \(bu 2 \fI\%send_pong()\fP .UNINDENT .sp \fI\%send_close()\fP initiates the closing handshake. See \fI\%Closing a connection\fP below for details. .sp If you encounter an unrecoverable error and you must fail the WebSocket connection, call \fI\%fail()\fP\&. .sp After any of the above, call \fI\%data_to_send()\fP and send its output to the network, as shown in \fI\%Send data\fP above. .sp If you called \fI\%send_close()\fP or \fI\%fail()\fP, you expect the end of the data stream. You should follow the process described in \fI\%Close TCP connection\fP above in order to prevent dangling TCP connections. .SS Closing a connection .sp Under normal circumstances, when a server wants to close the TCP connection: .INDENT 0.0 .IP \(bu 2 it closes the write side; .IP \(bu 2 it reads until the end of the stream, because it expects the client to close the read side; .IP \(bu 2 it closes the socket. .UNINDENT .sp When a client wants to close the TCP connection: .INDENT 0.0 .IP \(bu 2 it reads until the end of the stream, because it expects the server to close the read side; .IP \(bu 2 it closes the write side; .IP \(bu 2 it closes the socket. .UNINDENT .sp Applying the rules described earlier in this document gives the intended result. As a reminder, the rules are: .INDENT 0.0 .IP \(bu 2 When \fI\%data_to_send()\fP returns the empty bytestring, close the write side of the TCP connection. .IP \(bu 2 When you reach the end of the read stream, close the TCP connection. .IP \(bu 2 When \fI\%close_expected()\fP returns \fI\%True\fP, if you don\(aqt reach the end of the read stream quickly, close the TCP connection. .UNINDENT .SS Fragmentation .sp WebSocket messages may be fragmented. Since this is a protocol\-level concern, you may choose to reassemble fragmented messages before handing them over to the application. .sp To reassemble a message, read data frames until you get a frame where the \fI\%FIN\fP bit is set, then concatenate the payloads of all frames. .sp You will never receive an inconsistent sequence of frames because websockets raises a \fI\%ProtocolError\fP and fails the connection when this happens. However, you may receive an incomplete sequence if the connection drops in the middle of a fragmented message. .SS Tips .SS Serialize operations .sp The Sans\-I/O layer expects to run sequentially. If your interact with it from multiple threads or coroutines, you must ensure correct serialization. This should happen automatically in a cooperative multitasking environment. .sp However, you still have to make sure you don\(aqt break this property by accident. For example, serialize writes to the network when \fI\%data_to_send()\fP returns multiple values to prevent concurrent writes from interleaving incorrectly. .SS Avoid buffers .sp The Sans\-I/O layer doesn\(aqt do any buffering. It makes events available in \fI\%events_received()\fP as soon as they\(aqre received. .sp You should make incoming messages available to the application immediately and stop further processing until the application fetches them. This will usually result in the best performance. .SH FREQUENTLY ASKED QUESTIONS .INDENT 0.0 .INDENT 3.5 .IP "Many questions asked in websockets\(aq issue tracker are really about \fI\%asyncio\fP\&." .sp Python\(aqs documentation about \fI\%developing with asyncio\fP is a good complement. .UNINDENT .UNINDENT .SS Server .SS Why does the server close the connection prematurely? .sp Your connection handler exits prematurely. Wait for the work to be finished before returning. .sp For example, if your handler has a structure similar to: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C async def handler(websocket): asyncio.create_task(do_some_work()) .ft P .fi .UNINDENT .UNINDENT .sp change it to: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C async def handler(websocket): await do_some_work() .ft P .fi .UNINDENT .UNINDENT .SS Why does the server close the connection after one message? .sp Your connection handler exits after processing one message. Write a loop to process multiple messages. .sp For example, if your handler looks like this: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C async def handler(websocket): print(websocket.recv()) .ft P .fi .UNINDENT .UNINDENT .sp change it like this: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C async def handler(websocket): async for message in websocket: print(message) .ft P .fi .UNINDENT .UNINDENT .sp \fIDon\(aqt feel bad if this happens to you — it\(aqs the most common question in websockets\(aq issue tracker :\-)\fP .SS Why can only one client connect at a time? .sp Your connection handler blocks the event loop. Look for blocking calls. .sp Any call that may take some time must be asynchronous. .sp For example, this connection handler prevents the event loop from running during one second: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C async def handler(websocket): time.sleep(1) ... .ft P .fi .UNINDENT .UNINDENT .sp Change it to: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C async def handler(websocket): await asyncio.sleep(1) ... .ft P .fi .UNINDENT .UNINDENT .sp In addition, calling a coroutine doesn\(aqt guarantee that it will yield control to the event loop. .sp For example, this connection handler blocks the event loop by sending messages continuously: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C async def handler(websocket): while True: await websocket.send(\(dqfirehose!\(dq) .ft P .fi .UNINDENT .UNINDENT .sp \fI\%send()\fP completes synchronously as long as there\(aqs space in send buffers. The event loop never runs. (This pattern is uncommon in real\-world applications. It occurs mostly in toy programs.) .sp You can avoid the issue by yielding control to the event loop explicitly: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C async def handler(websocket): while True: await websocket.send(\(dqfirehose!\(dq) await asyncio.sleep(0) .ft P .fi .UNINDENT .UNINDENT .sp All this is part of learning asyncio. It isn\(aqt specific to websockets. .sp See also Python\(aqs documentation about \fI\%running blocking code\fP\&. .SS How do I send a message to all users? .sp Record all connections in a global variable: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C CONNECTIONS = set() async def handler(websocket): CONNECTIONS.add(websocket) try: await websocket.wait_closed() finally: CONNECTIONS.remove(websocket) .ft P .fi .UNINDENT .UNINDENT .sp Then, call \fI\%broadcast()\fP: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C import websockets def message_all(message): websockets.broadcast(CONNECTIONS, message) .ft P .fi .UNINDENT .UNINDENT .sp If you\(aqre running multiple server processes, make sure you call \fBmessage_all\fP in each process. .SS How do I send a message to a single user? .sp Record connections in a global variable, keyed by user identifier: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C CONNECTIONS = {} async def handler(websocket): user_id = ... # identify user in your app\(aqs context CONNECTIONS[user_id] = websocket try: await websocket.wait_closed() finally: del CONNECTIONS[user_id] .ft P .fi .UNINDENT .UNINDENT .sp Then, call \fI\%send()\fP: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C async def message_user(user_id, message): websocket = CONNECTIONS[user_id] # raises KeyError if user disconnected await websocket.send(message) # may raise websockets.ConnectionClosed .ft P .fi .UNINDENT .UNINDENT .sp Add error handling according to the behavior you want if the user disconnected before the message could be sent. .sp This example supports only one connection per user. To support concurrent connections by the same user, you can change \fBCONNECTIONS\fP to store a set of connections for each user. .sp If you\(aqre running multiple server processes, call \fBmessage_user\fP in each process. The process managing the user\(aqs connection sends the message; other processes do nothing. .sp When you reach a scale where server processes cannot keep up with the stream of all messages, you need a better architecture. For example, you could deploy an external publish / subscribe system such as \fI\%Redis\fP\&. Server processes would subscribe their clients. Then, they would receive messages only for the connections that they\(aqre managing. .SS How do I send a message to a channel, a topic, or some users? .sp websockets doesn\(aqt provide built\-in publish / subscribe functionality. .sp Record connections in a global variable, keyed by user identifier, as shown in \fI\%How do I send a message to a single user?\fP .sp Then, build the set of recipients and broadcast the message to them, as shown in \fI\%How do I send a message to all users?\fP .sp \fI\%Integrate with Django\fP contains a complete implementation of this pattern. .sp Again, as you scale, you may reach the performance limits of a basic in\-process implementation. You may need an external publish / subscribe system like \fI\%Redis\fP\&. .SS How do I pass arguments to the connection handler? .sp You can bind additional arguments to the connection handler with \fI\%functools.partial()\fP: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C import asyncio import functools import websockets async def handler(websocket, extra_argument): ... bound_handler = functools.partial(handler, extra_argument=42) start_server = websockets.serve(bound_handler, ...) .ft P .fi .UNINDENT .UNINDENT .sp Another way to achieve this result is to define the \fBhandler\fP coroutine in a scope where the \fBextra_argument\fP variable exists instead of injecting it through an argument. .SS How do I access the request path? .sp It is available in the \fI\%path\fP attribute. .sp You may route a connection to different handlers depending on the request path: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C async def handler(websocket): if websocket.path == \(dq/blue\(dq: await blue_handler(websocket) elif websocket.path == \(dq/green\(dq: await green_handler(websocket) else: # No handler for this path; close the connection. return .ft P .fi .UNINDENT .UNINDENT .sp You may also route the connection based on the first message received from the client, as shown in the \fI\%tutorial\fP\&. When you want to authenticate the connection before routing it, this is usually more convenient. .sp Generally speaking, there is far less emphasis on the request path in WebSocket servers than in HTTP servers. When a WebSocket server provides a single endpoint, it may ignore the request path entirely. .SS How do I access HTTP headers? .sp To access HTTP headers during the WebSocket handshake, you can override \fI\%process_request\fP: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C async def process_request(self, path, request_headers): authorization = request_headers[\(dqAuthorization\(dq] .ft P .fi .UNINDENT .UNINDENT .sp Once the connection is established, HTTP headers are available in \fI\%request_headers\fP and \fI\%response_headers\fP: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C async def handler(websocket): authorization = websocket.request_headers[\(dqAuthorization\(dq] .ft P .fi .UNINDENT .UNINDENT .SS How do I set HTTP headers? .sp To set the \fBSec\-WebSocket\-Extensions\fP or \fBSec\-WebSocket\-Protocol\fP headers in the WebSocket handshake response, use the \fBextensions\fP or \fBsubprotocols\fP arguments of \fI\%serve()\fP\&. .sp To override the \fBServer\fP header, use the \fBserver_header\fP argument. Set it to \fI\%None\fP to remove the header. .sp To set other HTTP headers, use the \fBextra_headers\fP argument. .SS How do I get the IP address of the client? .sp It\(aqs available in \fI\%remote_address\fP: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C async def handler(websocket): remote_ip = websocket.remote_address[0] .ft P .fi .UNINDENT .UNINDENT .SS How do I set the IP addresses that my server listens on? .sp Use the \fBhost\fP argument of \fI\%create_server()\fP: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C await websockets.serve(handler, host=\(dq192.168.0.1\(dq, port=8080) .ft P .fi .UNINDENT .UNINDENT .sp \fI\%serve()\fP accepts the same arguments as \fI\%create_server()\fP\&. .SS What does \fBOSError: [Errno 99] error while attempting to bind on address (\(aq::1\(aq, 80, 0, 0): address not available\fP mean? .sp You are calling \fI\%serve()\fP without a \fBhost\fP argument in a context where IPv6 isn\(aqt available. .sp To listen only on IPv4, specify \fBhost=\(dq0.0.0.0\(dq\fP or \fBfamily=socket.AF_INET\fP\&. .sp Refer to the documentation of \fI\%create_server()\fP for details. .SS How do I close a connection? .sp websockets takes care of closing the connection when the handler exits. .SS How do I stop a server? .sp Exit the \fI\%serve()\fP context manager. .sp Here\(aqs an example that terminates cleanly when it receives SIGTERM on Unix: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C #!/usr/bin/env python import asyncio import signal import websockets async def echo(websocket): async for message in websocket: await websocket.send(message) async def server(): # Set the stop condition when receiving SIGTERM. loop = asyncio.get_running_loop() stop = loop.create_future() loop.add_signal_handler(signal.SIGTERM, stop.set_result, None) async with websockets.serve(echo, \(dqlocalhost\(dq, 8765): await stop asyncio.run(server()) .ft P .fi .UNINDENT .UNINDENT .SS How do I stop a server while keeping existing connections open? .sp Call the server\(aqs \fI\%close()\fP method with \fBclose_connections=False\fP\&. .sp Here\(aqs how to adapt the example just above: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C async def server(): ... server = await websockets.serve(echo, \(dqlocalhost\(dq, 8765) await stop await server.close(close_connections=False) .ft P .fi .UNINDENT .UNINDENT .SS How do I implement a health check? .sp Intercept WebSocket handshake requests with the \fI\%process_request()\fP hook. .sp When a request is sent to the health check endpoint, treat is as an HTTP request and return a \fB(status, headers, body)\fP tuple, as in this example: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C #!/usr/bin/env python import asyncio import http import websockets async def health_check(path, request_headers): if path == \(dq/healthz\(dq: return http.HTTPStatus.OK, [], b\(dqOK\en\(dq async def echo(websocket): async for message in websocket: await websocket.send(message) async def main(): async with websockets.serve( echo, \(dqlocalhost\(dq, 8765, process_request=health_check, ): await asyncio.Future() # run forever asyncio.run(main()) .ft P .fi .UNINDENT .UNINDENT .SS How do I run HTTP and WebSocket servers on the same port? .sp You don\(aqt. .sp HTTP and WebSocket have widely different operational characteristics. Running them with the same server becomes inconvenient when you scale. .sp Providing an HTTP server is out of scope for websockets. It only aims at providing a WebSocket server. .sp There\(aqs limited support for returning HTTP responses with the \fI\%process_request\fP hook. .sp If you need more, pick an HTTP server and run it separately. .sp Alternatively, pick an HTTP framework that builds on top of \fBwebsockets\fP to support WebSocket connections, like \fI\%Sanic\fP\&. .SS Client .SS Why does the client close the connection prematurely? .sp You\(aqre exiting the context manager prematurely. Wait for the work to be finished before exiting. .sp For example, if your code has a structure similar to: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C async with connect(...) as websocket: asyncio.create_task(do_some_work()) .ft P .fi .UNINDENT .UNINDENT .sp change it to: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C async with connect(...) as websocket: await do_some_work() .ft P .fi .UNINDENT .UNINDENT .SS How do I access HTTP headers? .sp Once the connection is established, HTTP headers are available in \fI\%request_headers\fP and \fI\%response_headers\fP\&. .SS How do I set HTTP headers? .sp To set the \fBOrigin\fP, \fBSec\-WebSocket\-Extensions\fP, or \fBSec\-WebSocket\-Protocol\fP headers in the WebSocket handshake request, use the \fBorigin\fP, \fBextensions\fP, or \fBsubprotocols\fP arguments of \fI\%connect()\fP\&. .sp To override the \fBUser\-Agent\fP header, use the \fBuser_agent_header\fP argument. Set it to \fI\%None\fP to remove the header. .sp To set other HTTP headers, for example the \fBAuthorization\fP header, use the \fBextra_headers\fP argument: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C async with connect(..., extra_headers={\(dqAuthorization\(dq: ...}) as websocket: ... .ft P .fi .UNINDENT .UNINDENT .sp In the \fI\%threading\fP API, this argument is named \fBadditional_headers\fP: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C with connect(..., additional_headers={\(dqAuthorization\(dq: ...}) as websocket: ... .ft P .fi .UNINDENT .UNINDENT .SS How do I force the IP address that the client connects to? .sp Use the \fBhost\fP argument of \fI\%create_connection()\fP: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C await websockets.connect(\(dqws://example.com\(dq, host=\(dq192.168.0.1\(dq) .ft P .fi .UNINDENT .UNINDENT .sp \fI\%connect()\fP accepts the same arguments as \fI\%create_connection()\fP\&. .SS How do I close a connection? .sp The easiest is to use \fI\%connect()\fP as a context manager: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C async with connect(...) as websocket: ... .ft P .fi .UNINDENT .UNINDENT .sp The connection is closed when exiting the context manager. .SS How do I reconnect when the connection drops? .sp Use \fI\%connect()\fP as an asynchronous iterator: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C async for websocket in websockets.connect(...): try: ... except websockets.ConnectionClosed: continue .ft P .fi .UNINDENT .UNINDENT .sp Make sure you handle exceptions in the \fBasync for\fP loop. Uncaught exceptions will break out of the loop. .SS How do I stop a client that is processing messages in a loop? .sp You can close the connection. .sp Here\(aqs an example that terminates cleanly when it receives SIGTERM on Unix: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C #!/usr/bin/env python import asyncio import signal import websockets async def client(): uri = \(dqws://localhost:8765\(dq async with websockets.connect(uri) as websocket: # Close the connection when receiving SIGTERM. loop = asyncio.get_running_loop() loop.add_signal_handler( signal.SIGTERM, loop.create_task, websocket.close()) # Process messages received on the connection. async for message in websocket: ... asyncio.run(client()) .ft P .fi .UNINDENT .UNINDENT .SS How do I disable TLS/SSL certificate verification? .sp Look at the \fBssl\fP argument of \fI\%create_connection()\fP\&. .sp \fI\%connect()\fP accepts the same arguments as \fI\%create_connection()\fP\&. .SS Both sides .SS What does \fBConnectionClosedError: no close frame received or sent\fP mean? .sp If you\(aqre seeing this traceback in the logs of a server: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C connection handler failed Traceback (most recent call last): ... asyncio.exceptions.IncompleteReadError: 0 bytes read on a total of 2 expected bytes The above exception was the direct cause of the following exception: Traceback (most recent call last): ... websockets.exceptions.ConnectionClosedError: no close frame received or sent .ft P .fi .UNINDENT .UNINDENT .sp or if a client crashes with this traceback: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C Traceback (most recent call last): ... ConnectionResetError: [Errno 54] Connection reset by peer The above exception was the direct cause of the following exception: Traceback (most recent call last): ... websockets.exceptions.ConnectionClosedError: no close frame received or sent .ft P .fi .UNINDENT .UNINDENT .sp it means that the TCP connection was lost. As a consequence, the WebSocket connection was closed without receiving and sending a close frame, which is abnormal. .sp You can catch and handle \fI\%ConnectionClosed\fP to prevent it from being logged. .sp There are several reasons why long\-lived connections may be lost: .INDENT 0.0 .IP \(bu 2 End\-user devices tend to lose network connectivity often and unpredictably because they can move out of wireless network coverage, get unplugged from a wired network, enter airplane mode, be put to sleep, etc. .IP \(bu 2 HTTP load balancers or proxies that aren\(aqt configured for long\-lived connections may terminate connections after a short amount of time, usually 30 seconds, despite websockets\(aq keepalive mechanism. .UNINDENT .sp If you\(aqre facing a reproducible issue, \fI\%enable debug logs\fP to see when and how connections are closed. .SS What does \fBConnectionClosedError: sent 1011 (internal error) keepalive ping timeout; no close frame received\fP mean? .sp If you\(aqre seeing this traceback in the logs of a server: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C connection handler failed Traceback (most recent call last): ... asyncio.exceptions.CancelledError The above exception was the direct cause of the following exception: Traceback (most recent call last): ... websockets.exceptions.ConnectionClosedError: sent 1011 (internal error) keepalive ping timeout; no close frame received .ft P .fi .UNINDENT .UNINDENT .sp or if a client crashes with this traceback: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C Traceback (most recent call last): ... asyncio.exceptions.CancelledError The above exception was the direct cause of the following exception: Traceback (most recent call last): ... websockets.exceptions.ConnectionClosedError: sent 1011 (internal error) keepalive ping timeout; no close frame received .ft P .fi .UNINDENT .UNINDENT .sp it means that the WebSocket connection suffered from excessive latency and was closed after reaching the timeout of websockets\(aq keepalive mechanism. .sp You can catch and handle \fI\%ConnectionClosed\fP to prevent it from being logged. .sp There are two main reasons why latency may increase: .INDENT 0.0 .IP \(bu 2 Poor network connectivity. .IP \(bu 2 More traffic than the recipient can handle. .UNINDENT .sp See the discussion of \fI\%timeouts\fP for details. .sp If websockets\(aq default timeout of 20 seconds is too short for your use case, you can adjust it with the \fBping_timeout\fP argument. .SS How do I set a timeout on \fI\%recv()\fP? .sp On Python ≥ 3.11, use \fI\%asyncio.timeout()\fP: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C async with asyncio.timeout(timeout=10): message = await websocket.recv() .ft P .fi .UNINDENT .UNINDENT .sp On older versions of Python, use \fI\%asyncio.wait_for()\fP: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C message = await asyncio.wait_for(websocket.recv(), timeout=10) .ft P .fi .UNINDENT .UNINDENT .sp This technique works for most APIs. When it doesn\(aqt, for example with asynchronous context managers, websockets provides an \fBopen_timeout\fP argument. .SS How can I pass arguments to a custom protocol subclass? .sp You can bind additional arguments to the protocol factory with \fI\%functools.partial()\fP: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C import asyncio import functools import websockets class MyServerProtocol(websockets.WebSocketServerProtocol): def __init__(self, *args, extra_argument=None, **kwargs): super().__init__(*args, **kwargs) # do something with extra_argument create_protocol = functools.partial(MyServerProtocol, extra_argument=42) start_server = websockets.serve(..., create_protocol=create_protocol) .ft P .fi .UNINDENT .UNINDENT .sp This example was for a server. The same pattern applies on a client. .SS How do I keep idle connections open? .sp websockets sends pings at 20 seconds intervals to keep the connection open. .sp It closes the connection if it doesn\(aqt get a pong within 20 seconds. .sp You can adjust this behavior with \fBping_interval\fP and \fBping_timeout\fP\&. .sp See \fI\%Timeouts\fP for details. .SS How do I respond to pings? .sp If you are referring to \fI\%Ping\fP and \fI\%Pong\fP frames defined in the WebSocket protocol, don\(aqt bother, because websockets handles them for you. .sp If you are connecting to a server that defines its own heartbeat at the application level, then you need to build that logic into your application. .SS Using asyncio .SS How do I run two coroutines in parallel? .sp You must start two tasks, which the event loop will run concurrently. You can achieve this with \fI\%asyncio.gather()\fP or \fI\%asyncio.create_task()\fP\&. .sp Keep track of the tasks and make sure they terminate or you cancel them when the connection terminates. .SS Why does my program never receive any messages? .sp Your program runs a coroutine that never yields control to the event loop. The coroutine that receives messages never gets a chance to run. .sp Putting an \fBawait\fP statement in a \fBfor\fP or a \fBwhile\fP loop isn\(aqt enough to yield control. Awaiting a coroutine may yield control, but there\(aqs no guarantee that it will. .sp For example, \fI\%send()\fP only yields control when send buffers are full, which never happens in most practical cases. .sp If you run a loop that contains only synchronous operations and a \fI\%send()\fP call, you must yield control explicitly with \fI\%asyncio.sleep()\fP: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C async def producer(websocket): message = generate_next_message() await websocket.send(message) await asyncio.sleep(0) # yield control to the event loop .ft P .fi .UNINDENT .UNINDENT .sp \fI\%asyncio.sleep()\fP always suspends the current task, allowing other tasks to run. This behavior is documented precisely because it isn\(aqt expected from every coroutine. .sp See \fI\%issue 867\fP\&. .SS Why am I having problems with threads? .sp If you choose websockets\(aq default implementation based on \fI\%asyncio\fP, then you shouldn\(aqt use threads. Indeed, choosing \fI\%asyncio\fP to handle concurrency is mutually exclusive with \fI\%threading\fP\&. .sp If you believe that you need to run websockets in a thread and some logic in another thread, you should run that logic in a \fI\%Task\fP instead. If it blocks the event loop, \fI\%run_in_executor()\fP will help. .sp This question is really about \fI\%asyncio\fP\&. Please review the advice about \fI\%Concurrency and Multithreading\fP in the Python documentation. .SS Why does my simple program misbehave mysteriously? .sp You are using \fI\%time.sleep()\fP instead of \fI\%asyncio.sleep()\fP, which blocks the event loop and prevents asyncio from operating normally. .sp This may lead to messages getting send but not received, to connection timeouts, and to unexpected results of shotgun debugging e.g. adding an unnecessary call to \fI\%send()\fP makes the program functional. .SS Miscellaneous .SS Why do I get the error: \fBmodule \(aqwebsockets\(aq has no attribute \(aq...\(aq\fP? .sp Often, this is because you created a script called \fBwebsockets.py\fP in your current working directory. Then \fBimport websockets\fP imports this module instead of the websockets library. .SS Why is the default implementation located in \fBwebsockets.legacy\fP? .sp This is an artifact of websockets\(aq history. For its first eight years, only the \fI\%asyncio\fP implementation existed. Then, the Sans\-I/O implementation was added. Moving the code in a \fBlegacy\fP submodule eased this refactoring and optimized maintainability. .sp All public APIs were kept at their original locations. \fBwebsockets.legacy\fP isn\(aqt a public API. It\(aqs only visible in the source code and in stack traces. There is no intent to deprecate this implementation — at least until a superior alternative exists. .SS Why is websockets slower than another library in my benchmark? .sp Not all libraries are as feature\-complete as websockets. For a fair benchmark, you should disable features that the other library doesn\(aqt provide. Typically, you may need to disable: .INDENT 0.0 .IP \(bu 2 Compression: set \fBcompression=None\fP .IP \(bu 2 Keepalive: set \fBping_interval=None\fP .IP \(bu 2 UTF\-8 decoding: send \fBbytes\fP rather than \fBstr\fP .UNINDENT .sp If websockets is still slower than another Python library, please file a bug. .SS Are there \fBonopen\fP, \fBonmessage\fP, \fBonerror\fP, and \fBonclose\fP callbacks? .sp No, there aren\(aqt. .sp websockets provides high\-level, coroutine\-based APIs. Compared to callbacks, coroutines make it easier to manage control flow in concurrent code. .sp If you prefer callback\-based APIs, you should use another library. .SH API REFERENCE .SS Features .sp Check which implementations support which features and known limitations. .SS Features .sp Feature support matrices summarize which implementations support which features. .SS Both sides .TS center; |l|l|l|l|. _ T{ T} T{ \fI\%asyncio\fP T} T{ \fI\%threading\fP T} T{ \fI\%Sans\-I/O\fP T} _ T{ Perform the opening handshake T} T{ ✅ T} T{ ✅ T} T{ ✅ T} _ T{ Send a message T} T{ ✅ T} T{ ✅ T} T{ ✅ T} _ T{ Receive a message T} T{ ✅ T} T{ ✅ T} T{ ✅ T} _ T{ Iterate over received messages T} T{ ✅ T} T{ ✅ T} T{ ❌ T} _ T{ Send a fragmented message T} T{ ✅ T} T{ ✅ T} T{ ✅ T} _ T{ Receive a fragmented message after reassembly T} T{ ✅ T} T{ ✅ T} T{ ❌ T} _ T{ Receive a fragmented message frame by frame (\fI\%#479\fP) T} T{ ❌ T} T{ ✅ T} T{ ✅ T} _ T{ Send a ping T} T{ ✅ T} T{ ✅ T} T{ ✅ T} _ T{ Respond to pings automatically T} T{ ✅ T} T{ ✅ T} T{ ✅ T} _ T{ Send a pong T} T{ ✅ T} T{ ✅ T} T{ ✅ T} _ T{ Perform the closing handshake T} T{ ✅ T} T{ ✅ T} T{ ✅ T} _ T{ Report close codes and reasons from both sides T} T{ ❌ T} T{ ✅ T} T{ ✅ T} _ T{ Compress messages (\fI\%RFC 7692\fP) T} T{ ✅ T} T{ ✅ T} T{ ✅ T} _ T{ Tune memory usage for compression T} T{ ✅ T} T{ ✅ T} T{ ✅ T} _ T{ Negotiate extensions T} T{ ✅ T} T{ ✅ T} T{ ✅ T} _ T{ Implement custom extensions T} T{ ✅ T} T{ ✅ T} T{ ✅ T} _ T{ Negotiate a subprotocol T} T{ ✅ T} T{ ✅ T} T{ ✅ T} _ T{ Enforce security limits T} T{ ✅ T} T{ ✅ T} T{ ✅ T} _ T{ Log events T} T{ ✅ T} T{ ✅ T} T{ ✅ T} _ T{ Enforce opening timeout T} T{ ✅ T} T{ ✅ T} T{ — T} _ T{ Enforce closing timeout T} T{ ✅ T} T{ ✅ T} T{ — T} _ T{ Keepalive T} T{ ✅ T} T{ ❌ T} T{ — T} _ T{ Heartbeat T} T{ ✅ T} T{ ❌ T} T{ — T} _ .TE .SS Server .TS center; |l|l|l|l|. _ T{ T} T{ \fI\%asyncio\fP T} T{ \fI\%threading\fP T} T{ \fI\%Sans\-I/O\fP T} _ T{ Listen on a TCP socket T} T{ ✅ T} T{ ✅ T} T{ — T} _ T{ Listen on a Unix socket T} T{ ✅ T} T{ ✅ T} T{ — T} _ T{ Listen using a preexisting socket T} T{ ✅ T} T{ ✅ T} T{ — T} _ T{ Encrypt connection with TLS T} T{ ✅ T} T{ ✅ T} T{ — T} _ T{ Close server on context exit T} T{ ✅ T} T{ ✅ T} T{ — T} _ T{ Close connection on handler exit T} T{ ✅ T} T{ ✅ T} T{ — T} _ T{ Shut down server gracefully T} T{ ✅ T} T{ ✅ T} T{ — T} _ T{ Check \fBOrigin\fP header T} T{ ✅ T} T{ ✅ T} T{ ✅ T} _ T{ Customize subprotocol selection T} T{ ✅ T} T{ ✅ T} T{ ✅ T} _ T{ Configure \fBServer\fP header T} T{ ✅ T} T{ ✅ T} T{ ✅ T} _ T{ Alter opening handshake request T} T{ ❌ T} T{ ✅ T} T{ ✅ T} _ T{ Alter opening handshake response T} T{ ❌ T} T{ ✅ T} T{ ✅ T} _ T{ Perform HTTP Basic Authentication T} T{ ✅ T} T{ ❌ T} T{ ❌ T} _ T{ Perform HTTP Digest Authentication T} T{ ❌ T} T{ ❌ T} T{ ❌ T} _ T{ Force HTTP response T} T{ ✅ T} T{ ✅ T} T{ ✅ T} _ .TE .SS Client .TS center; |l|l|l|l|. _ T{ T} T{ \fI\%asyncio\fP T} T{ \fI\%threading\fP T} T{ \fI\%Sans\-I/O\fP T} _ T{ Connect to a TCP socket T} T{ ✅ T} T{ ✅ T} T{ — T} _ T{ Connect to a Unix socket T} T{ ✅ T} T{ ✅ T} T{ — T} _ T{ Connect using a preexisting socket T} T{ ✅ T} T{ ✅ T} T{ — T} _ T{ Encrypt connection with TLS T} T{ ✅ T} T{ ✅ T} T{ — T} _ T{ Close connection on context exit T} T{ ✅ T} T{ ✅ T} T{ — T} _ T{ Reconnect automatically T} T{ ✅ T} T{ ❌ T} T{ — T} _ T{ Configure \fBOrigin\fP header T} T{ ✅ T} T{ ✅ T} T{ ✅ T} _ T{ Configure \fBUser\-Agent\fP header T} T{ ✅ T} T{ ✅ T} T{ ✅ T} _ T{ Alter opening handshake request T} T{ ✅ T} T{ ✅ T} T{ ✅ T} _ T{ Connect to non\-ASCII IRIs T} T{ ✅ T} T{ ✅ T} T{ ✅ T} _ T{ Perform HTTP Basic Authentication T} T{ ✅ T} T{ ✅ T} T{ ✅ T} _ T{ Perform HTTP Digest Authentication (\fI\%#784\fP) T} T{ ❌ T} T{ ❌ T} T{ ❌ T} _ T{ Follow HTTP redirects T} T{ ✅ T} T{ ❌ T} T{ — T} _ T{ Connect via a HTTP proxy (\fI\%#364\fP) T} T{ ❌ T} T{ ❌ T} T{ — T} _ T{ Connect via a SOCKS5 proxy (\fI\%#475\fP) T} T{ ❌ T} T{ ❌ T} T{ — T} _ .TE .SS Known limitations .sp There is no way to control compression of outgoing frames on a per\-frame basis (\fI\%#538\fP). If compression is enabled, all frames are compressed. .sp The server doesn\(aqt check the Host header and respond with a HTTP 400 Bad Request if it is missing or invalid (\fI#1246\fP). .sp The client API doesn\(aqt attempt to guarantee that there is no more than one connection to a given IP address in a CONNECTING state. This behavior is \fI\%mandated by RFC 6455\fP\&. However, \fI\%connect()\fP isn\(aqt the right layer for enforcing this constraint. It\(aqs the caller\(aqs responsibility. .SS \fI\%asyncio\fP .sp This is the default implementation. It\(aqs ideal for servers that handle many clients concurrently. .SS Server (\fI\%asyncio\fP) .SS Starting a server .INDENT 0.0 .TP .B await websockets.server.serve(ws_handler, host=None, port=None, *, create_protocol=None, logger=None, compression=\(aqdeflate\(aq, origins=None, extensions=None, subprotocols=None, extra_headers=None, server_header=\(aqPython/x.y.z websockets/X.Y\(aq, process_request=None, select_subprotocol=None, ping_interval=20, ping_timeout=20, close_timeout=10, max_size=2**20, max_queue=2**5, read_limit=2**16, write_limit=2**16, **kwds) Start a WebSocket server listening on \fBhost\fP and \fBport\fP\&. .sp Whenever a client connects, the server creates a \fI\%WebSocketServerProtocol\fP, performs the opening handshake, and delegates to the connection handler, \fBws_handler\fP\&. .sp The handler receives the \fI\%WebSocketServerProtocol\fP and uses it to send and receive messages. .sp Once the handler completes, either normally or with an exception, the server performs the closing handshake and closes the connection. .sp Awaiting \fI\%serve()\fP yields a \fI\%WebSocketServer\fP\&. This object provides a \fI\%close()\fP method to shut down the server: .INDENT 7.0 .INDENT 3.5 .sp .nf .ft C stop = asyncio.Future() # set this future to exit the server server = await serve(...) await stop await server.close() .ft P .fi .UNINDENT .UNINDENT .sp \fI\%serve()\fP can be used as an asynchronous context manager. Then, the server is shut down automatically when exiting the context: .INDENT 7.0 .INDENT 3.5 .sp .nf .ft C stop = asyncio.Future() # set this future to exit the server async with serve(...): await stop .ft P .fi .UNINDENT .UNINDENT .INDENT 7.0 .TP .B Parameters .INDENT 7.0 .IP \(bu 2 \fBws_handler\fP (\fIUnion\fP\fI[\fP\fICallable\fP\fI[\fP\fI[\fP\fI\%WebSocketServerProtocol\fP\fI]\fP\fI, \fP\fIAwaitable\fP\fI[\fP\fIAny\fP\fI]\fP\fI]\fP\fI, \fP\fICallable\fP\fI[\fP\fI[\fP\fI\%WebSocketServerProtocol\fP\fI, \fP\fI\%str\fP\fI]\fP\fI, \fP\fIAwaitable\fP\fI[\fP\fIAny\fP\fI]\fP\fI]\fP\fI]\fP) \-\- Connection handler. It receives the WebSocket connection, which is a \fI\%WebSocketServerProtocol\fP, in argument. .IP \(bu 2 \fBhost\fP (\fIOptional\fP\fI[\fP\fIUnion\fP\fI[\fP\fI\%str\fP\fI, \fP\fISequence\fP\fI[\fP\fI\%str\fP\fI]\fP\fI]\fP\fI]\fP) \-\- Network interfaces the server binds to. See \fI\%create_server()\fP for details. .IP \(bu 2 \fBport\fP (\fIOptional\fP\fI[\fP\fI\%int\fP\fI]\fP) \-\- TCP port the server listens on. See \fI\%create_server()\fP for details. .IP \(bu 2 \fBcreate_protocol\fP (\fIOptional\fP\fI[\fP\fICallable\fP\fI[\fP\fI\&...\fP\fI, \fP\fI\%WebSocketServerProtocol\fP\fI]\fP\fI]\fP) \-\- Factory for the \fI\%asyncio.Protocol\fP managing the connection. It defaults to \fI\%WebSocketServerProtocol\fP\&. Set it to a wrapper or a subclass to customize connection handling. .IP \(bu 2 \fBlogger\fP (\fIOptional\fP\fI[\fP\fI\%LoggerLike\fP\fI]\fP) \-\- Logger for this server. It defaults to \fBlogging.getLogger(\(dqwebsockets.server\(dq)\fP\&. See the \fI\%logging guide\fP for details. .IP \(bu 2 \fBcompression\fP (\fIOptional\fP\fI[\fP\fI\%str\fP\fI]\fP) \-\- The \(dqpermessage\-deflate\(dq extension is enabled by default. Set \fBcompression\fP to \fI\%None\fP to disable it. See the \fI\%compression guide\fP for details. .IP \(bu 2 \fBorigins\fP (\fIOptional\fP\fI[\fP\fISequence\fP\fI[\fP\fIOptional\fP\fI[\fP\fI\%Origin\fP\fI]\fP\fI]\fP\fI]\fP) \-\- Acceptable values of the \fBOrigin\fP header, for defending against Cross\-Site WebSocket Hijacking attacks. Include \fI\%None\fP in the list if the lack of an origin is acceptable. .IP \(bu 2 \fBextensions\fP (\fIOptional\fP\fI[\fP\fISequence\fP\fI[\fP\fI\%ServerExtensionFactory\fP\fI]\fP\fI]\fP) \-\- List of supported extensions, in order in which they should be negotiated and run. .IP \(bu 2 \fBsubprotocols\fP (\fIOptional\fP\fI[\fP\fISequence\fP\fI[\fP\fI\%Subprotocol\fP\fI]\fP\fI]\fP) \-\- List of supported subprotocols, in order of decreasing preference. .IP \(bu 2 \fBextra_headers\fP (\fIUnion\fP\fI[\fP\fI\%HeadersLike\fP\fI, \fP\fICallable\fP\fI[\fP\fI[\fP\fI\%str\fP\fI, \fP\fI\%Headers\fP\fI]\fP\fI, \fP\fI\%HeadersLike\fP\fI]\fP\fI]\fP) \-\- Arbitrary HTTP headers to add to the response. This can be a \fI\%HeadersLike\fP or a callable taking the request path and headers in arguments and returning a \fI\%HeadersLike\fP\&. .IP \(bu 2 \fBserver_header\fP (\fIOptional\fP\fI[\fP\fI\%str\fP\fI]\fP) \-\- Value of the \fBServer\fP response header. It defaults to \fB\(dqPython/x.y.z websockets/X.Y\(dq\fP\&. Setting it to \fI\%None\fP removes the header. .IP \(bu 2 \fBprocess_request\fP (\fIOptional\fP\fI[\fP\fICallable\fP\fI[\fP\fI[\fP\fI\%str\fP\fI, \fP\fI\%Headers\fP\fI]\fP\fI, \fP\fIAwaitable\fP\fI[\fP\fIOptional\fP\fI[\fP\fITuple\fP\fI[\fP\fI\%StatusLike\fP\fI, \fP\fI\%HeadersLike\fP\fI, \fP\fI\%bytes\fP\fI]\fP\fI]\fP\fI]\fP\fI]\fP\fI]\fP) \-\- Intercept HTTP request before the opening handshake. See \fI\%process_request()\fP for details. .IP \(bu 2 \fBselect_subprotocol\fP (\fIOptional\fP\fI[\fP\fICallable\fP\fI[\fP\fI[\fP\fISequence\fP\fI[\fP\fI\%Subprotocol\fP\fI]\fP\fI, \fP\fISequence\fP\fI[\fP\fI\%Subprotocol\fP\fI]\fP\fI]\fP\fI, \fP\fI\%Subprotocol\fP\fI]\fP\fI]\fP) \-\- Select a subprotocol supported by the client. See \fI\%select_subprotocol()\fP for details. .IP \(bu 2 \fBopen_timeout\fP (\fIOptional\fP\fI[\fP\fI\%float\fP\fI]\fP) \-\- Timeout for opening connections in seconds. \fI\%None\fP disables the timeout. .UNINDENT .UNINDENT .sp See \fI\%WebSocketCommonProtocol\fP for the documentation of \fBping_interval\fP, \fBping_timeout\fP, \fBclose_timeout\fP, \fBmax_size\fP, \fBmax_queue\fP, \fBread_limit\fP, and \fBwrite_limit\fP\&. .sp Any other keyword arguments are passed the event loop\(aqs \fI\%create_server()\fP method. .sp For example: .INDENT 7.0 .IP \(bu 2 You can set \fBssl\fP to a \fI\%SSLContext\fP to enable TLS. .IP \(bu 2 You can set \fBsock\fP to a \fI\%socket\fP that you created outside of websockets. .UNINDENT .INDENT 7.0 .TP .B Returns WebSocket server. .TP .B Return type \fI\%WebSocketServer\fP .UNINDENT .UNINDENT .INDENT 0.0 .TP .B await websockets.server.unix_serve(ws_handler, path=None, *, create_protocol=None, logger=None, compression=\(aqdeflate\(aq, origins=None, extensions=None, subprotocols=None, extra_headers=None, server_header=\(aqPython/x.y.z websockets/X.Y\(aq, process_request=None, select_subprotocol=None, ping_interval=20, ping_timeout=20, close_timeout=10, max_size=2**20, max_queue=2**5, read_limit=2**16, write_limit=2**16, **kwds) Start a WebSocket server listening on a Unix socket. .sp This function is identical to \fI\%serve()\fP, except the \fBhost\fP and \fBport\fP arguments are replaced by \fBpath\fP\&. It is only available on Unix. .sp Unrecognized keyword arguments are passed the event loop\(aqs \fI\%create_unix_server()\fP method. .sp It\(aqs useful for deploying a server behind a reverse proxy such as nginx. .INDENT 7.0 .TP .B Parameters \fBpath\fP (\fI\%str\fP\fI | \fP\fI\%None\fP) \-\- File system path to the Unix socket. .UNINDENT .UNINDENT .SS Stopping a server .INDENT 0.0 .TP .B class websockets.server.WebSocketServer(logger=None) WebSocket server returned by \fI\%serve()\fP\&. .sp This class provides the same interface as \fI\%Server\fP, notably the \fI\%close()\fP and \fI\%wait_closed()\fP methods. .sp It keeps track of WebSocket connections in order to close them properly when shutting down. .INDENT 7.0 .TP .B Parameters \fBlogger\fP (\fIOptional\fP\fI[\fP\fI\%LoggerLike\fP\fI]\fP) \-\- Logger for this server. It defaults to \fBlogging.getLogger(\(dqwebsockets.server\(dq)\fP\&. See the \fI\%logging guide\fP for details. .UNINDENT .INDENT 7.0 .TP .B close(close_connections=True) Close the server. .INDENT 7.0 .IP \(bu 2 Close the underlying \fI\%Server\fP\&. .IP \(bu 2 When \fBclose_connections\fP is \fI\%True\fP, which is the default, close existing connections. Specifically: .INDENT 2.0 .IP \(bu 2 Reject opening WebSocket connections with an HTTP 503 (service unavailable) error. This happens when the server accepted the TCP connection but didn\(aqt complete the opening handshake before closing. .IP \(bu 2 Close open WebSocket connections with close code 1001 (going away). .UNINDENT .IP \(bu 2 Wait until all connection handlers terminate. .UNINDENT .sp \fI\%close()\fP is idempotent. .INDENT 7.0 .UNINDENT .UNINDENT .INDENT 7.0 .TP .B await wait_closed() Wait until the server is closed. .sp When \fI\%wait_closed()\fP returns, all TCP connections are closed and all connection handlers have returned. .sp To ensure a fast shutdown, a connection handler should always be awaiting at least one of: .INDENT 7.0 .IP \(bu 2 \fI\%recv()\fP: when the connection is closed, it raises \fI\%ConnectionClosedOK\fP; .IP \(bu 2 \fI\%wait_closed()\fP: when the connection is closed, it returns. .UNINDENT .sp Then the connection handler is immediately notified of the shutdown; it can clean up and exit. .INDENT 7.0 .UNINDENT .UNINDENT .INDENT 7.0 .TP .B get_loop() See \fI\%asyncio.Server.get_loop()\fP\&. .INDENT 7.0 .UNINDENT .UNINDENT .INDENT 7.0 .TP .B is_serving() See \fI\%asyncio.Server.is_serving()\fP\&. .INDENT 7.0 .UNINDENT .UNINDENT .INDENT 7.0 .TP .B await start_serving() See \fI\%asyncio.Server.start_serving()\fP\&. .sp Typical use: .INDENT 7.0 .INDENT 3.5 .sp .nf .ft C server = await serve(..., start_serving=False) # perform additional setup here... # ... then start the server await server.start_serving() .ft P .fi .UNINDENT .UNINDENT .INDENT 7.0 .UNINDENT .UNINDENT .INDENT 7.0 .TP .B await serve_forever() See \fI\%asyncio.Server.serve_forever()\fP\&. .sp Typical use: .INDENT 7.0 .INDENT 3.5 .sp .nf .ft C server = await serve(...) # this coroutine doesn\(aqt return # canceling it stops the server await server.serve_forever() .ft P .fi .UNINDENT .UNINDENT .sp This is an alternative to using \fI\%serve()\fP as an asynchronous context manager. Shutdown is triggered by canceling \fI\%serve_forever()\fP instead of exiting a \fI\%serve()\fP context. .INDENT 7.0 .UNINDENT .UNINDENT .INDENT 7.0 .TP .B sockets See \fI\%asyncio.Server.sockets\fP\&. .UNINDENT .UNINDENT .SS Using a connection .INDENT 0.0 .TP .B class websockets.server.WebSocketServerProtocol(ws_handler, ws_server, *, logger=None, origins=None, extensions=None, subprotocols=None, extra_headers=None, server_header=\(aqPython/x.y.z websockets/X.Y\(aq, process_request=None, select_subprotocol=None, ping_interval=20, ping_timeout=20, close_timeout=10, max_size=2**20, max_queue=2**5, read_limit=2**16, write_limit=2**16) WebSocket server connection. .sp \fI\%WebSocketServerProtocol\fP provides \fI\%recv()\fP and \fI\%send()\fP coroutines for receiving and sending messages. .sp It supports asynchronous iteration to receive messages: .INDENT 7.0 .INDENT 3.5 .sp .nf .ft C async for message in websocket: await process(message) .ft P .fi .UNINDENT .UNINDENT .sp The iterator exits normally when the connection is closed with close code 1000 (OK) or 1001 (going away) or without a close code. It raises a \fI\%ConnectionClosedError\fP when the connection is closed with any other code. .sp You may customize the opening handshake in a subclass by overriding \fI\%process_request()\fP or \fI\%select_subprotocol()\fP\&. .INDENT 7.0 .TP .B Parameters \fBws_server\fP (\fI\%WebSocketServer\fP) \-\- WebSocket server that created this connection. .UNINDENT .sp See \fI\%serve()\fP for the documentation of \fBws_handler\fP, \fBlogger\fP, \fBorigins\fP, \fBextensions\fP, \fBsubprotocols\fP, \fBextra_headers\fP, and \fBserver_header\fP\&. .sp See \fI\%WebSocketCommonProtocol\fP for the documentation of \fBping_interval\fP, \fBping_timeout\fP, \fBclose_timeout\fP, \fBmax_size\fP, \fBmax_queue\fP, \fBread_limit\fP, and \fBwrite_limit\fP\&. .INDENT 7.0 .TP .B await recv() Receive the next message. .sp When the connection is closed, \fI\%recv()\fP raises \fI\%ConnectionClosed\fP\&. Specifically, it raises \fI\%ConnectionClosedOK\fP after a normal connection closure and \fI\%ConnectionClosedError\fP after a protocol error or a network failure. This is how you detect the end of the message stream. .sp Canceling \fI\%recv()\fP is safe. There\(aqs no risk of losing the next message. The next invocation of \fI\%recv()\fP will return it. .sp This makes it possible to enforce a timeout by wrapping \fI\%recv()\fP in \fI\%timeout()\fP or \fI\%wait_for()\fP\&. .INDENT 7.0 .TP .B Returns A string (\fI\%str\fP) for a \fI\%Text\fP frame. A bytestring (\fI\%bytes\fP) for a \fI\%Binary\fP frame. .TP .B Return type \fI\%Data\fP .TP .B Raises .INDENT 7.0 .IP \(bu 2 \fI\%ConnectionClosed\fP \-\- When the connection is closed. .IP \(bu 2 \fI\%RuntimeError\fP \-\- If two coroutines call \fI\%recv()\fP concurrently. .UNINDENT .UNINDENT .UNINDENT .INDENT 7.0 .TP .B await send(message) Send a message. .sp A string (\fI\%str\fP) is sent as a \fI\%Text\fP frame. A bytestring or bytes\-like object (\fI\%bytes\fP, \fI\%bytearray\fP, or \fI\%memoryview\fP) is sent as a \fI\%Binary\fP frame. .sp \fI\%send()\fP also accepts an iterable or an asynchronous iterable of strings, bytestrings, or bytes\-like objects to enable \fI\%fragmentation\fP\&. Each item is treated as a message fragment and sent in its own frame. All items must be of the same type, or else \fI\%send()\fP will raise a \fI\%TypeError\fP and the connection will be closed. .sp \fI\%send()\fP rejects dict\-like objects because this is often an error. (If you want to send the keys of a dict\-like object as fragments, call its \fI\%keys()\fP method and pass the result to \fI\%send()\fP\&.) .sp Canceling \fI\%send()\fP is discouraged. Instead, you should close the connection with \fI\%close()\fP\&. Indeed, there are only two situations where \fI\%send()\fP may yield control to the event loop and then get canceled; in both cases, \fI\%close()\fP has the same effect and is more clear: .INDENT 7.0 .IP 1. 3 The write buffer is full. If you don\(aqt want to wait until enough data is sent, your only alternative is to close the connection. \fI\%close()\fP will likely time out then abort the TCP connection. .IP 2. 3 \fBmessage\fP is an asynchronous iterator that yields control. Stopping in the middle of a fragmented message will cause a protocol error and the connection will be closed. .UNINDENT .sp When the connection is closed, \fI\%send()\fP raises \fI\%ConnectionClosed\fP\&. Specifically, it raises \fI\%ConnectionClosedOK\fP after a normal connection closure and \fI\%ConnectionClosedError\fP after a protocol error or a network failure. .INDENT 7.0 .TP .B Parameters \fBmessage\fP (\fIUnion\fP\fI[\fP\fI\%Data\fP\fI, \fP\fIIterable\fP\fI[\fP\fI\%Data\fP\fI]\fP\fI, \fP\fIAsyncIterable\fP\fI[\fP\fI\%Data\fP\fI]\fP) \-\- message to send. .TP .B Raises .INDENT 7.0 .IP \(bu 2 \fI\%ConnectionClosed\fP \-\- When the connection is closed. .IP \(bu 2 \fI\%TypeError\fP \-\- If \fBmessage\fP doesn\(aqt have a supported type. .UNINDENT .UNINDENT .UNINDENT .INDENT 7.0 .TP .B await close(code=CloseCode.NORMAL_CLOSURE, reason=\(aq\(aq) Perform the closing handshake. .sp \fI\%close()\fP waits for the other end to complete the handshake and for the TCP connection to terminate. As a consequence, there\(aqs no need to await \fI\%wait_closed()\fP after \fI\%close()\fP\&. .sp \fI\%close()\fP is idempotent: it doesn\(aqt do anything once the connection is closed. .sp Wrapping \fI\%close()\fP in \fI\%create_task()\fP is safe, given that errors during connection termination aren\(aqt particularly useful. .sp Canceling \fI\%close()\fP is discouraged. If it takes too long, you can set a shorter \fBclose_timeout\fP\&. If you don\(aqt want to wait, let the Python process exit, then the OS will take care of closing the TCP connection. .INDENT 7.0 .TP .B Parameters .INDENT 7.0 .IP \(bu 2 \fBcode\fP (\fI\%int\fP) \-\- WebSocket close code. .IP \(bu 2 \fBreason\fP (\fI\%str\fP) \-\- WebSocket close reason. .UNINDENT .UNINDENT .UNINDENT .INDENT 7.0 .TP .B await wait_closed() Wait until the connection is closed. .sp This coroutine is identical to the \fI\%closed\fP attribute, except it can be awaited. .sp This can make it easier to detect connection termination, regardless of its cause, in tasks that interact with the WebSocket connection. .INDENT 7.0 .UNINDENT .UNINDENT .INDENT 7.0 .TP .B await ping(data=None) Send a \fI\%Ping\fP\&. .sp A ping may serve as a keepalive, as a check that the remote endpoint received all messages up to this point, or to measure \fI\%latency\fP\&. .sp Canceling \fI\%ping()\fP is discouraged. If \fI\%ping()\fP doesn\(aqt return immediately, it means the write buffer is full. If you don\(aqt want to wait, you should close the connection. .sp Canceling the \fI\%Future\fP returned by \fI\%ping()\fP has no effect. .INDENT 7.0 .TP .B Parameters \fBdata\fP (\fIOptional\fP\fI[\fP\fI\%Data\fP\fI]\fP) \-\- payload of the ping; a string will be encoded to UTF\-8; or \fI\%None\fP to generate a payload containing four random bytes. .TP .B Returns A future that will be completed when the corresponding pong is received. You can ignore it if you don\(aqt intend to wait. The result of the future is the latency of the connection in seconds. .INDENT 7.0 .INDENT 3.5 .sp .nf .ft C pong_waiter = await ws.ping() # only if you want to wait for the corresponding pong latency = await pong_waiter .ft P .fi .UNINDENT .UNINDENT .TP .B Return type \fI\%Future\fP[\fI\%float\fP] .TP .B Raises .INDENT 7.0 .IP \(bu 2 \fI\%ConnectionClosed\fP \-\- When the connection is closed. .IP \(bu 2 \fI\%RuntimeError\fP \-\- If another ping was sent with the same data and the corresponding pong wasn\(aqt received yet. .UNINDENT .UNINDENT .UNINDENT .INDENT 7.0 .TP .B await pong(data=b\(aq\(aq) Send a \fI\%Pong\fP\&. .sp An unsolicited pong may serve as a unidirectional heartbeat. .sp Canceling \fI\%pong()\fP is discouraged. If \fI\%pong()\fP doesn\(aqt return immediately, it means the write buffer is full. If you don\(aqt want to wait, you should close the connection. .INDENT 7.0 .TP .B Parameters \fBdata\fP (\fI\%Data\fP) \-\- Payload of the pong. A string will be encoded to UTF\-8. .TP .B Raises \fI\%ConnectionClosed\fP \-\- When the connection is closed. .UNINDENT .UNINDENT .sp You can customize the opening handshake in a subclass by overriding these methods: .INDENT 7.0 .TP .B await process_request(path, request_headers) Intercept the HTTP request and return an HTTP response if appropriate. .sp You may override this method in a \fI\%WebSocketServerProtocol\fP subclass, for example: .INDENT 7.0 .IP \(bu 2 to return an HTTP 200 OK response on a given path; then a load balancer can use this path for a health check; .IP \(bu 2 to authenticate the request and return an HTTP 401 Unauthorized or an HTTP 403 Forbidden when authentication fails. .UNINDENT .sp You may also override this method with the \fBprocess_request\fP argument of \fI\%serve()\fP and \fI\%WebSocketServerProtocol\fP\&. This is equivalent, except \fBprocess_request\fP won\(aqt have access to the protocol instance, so it can\(aqt store information for later use. .sp \fI\%process_request()\fP is expected to complete quickly. If it may run for a long time, then it should await \fI\%wait_closed()\fP and exit if \fI\%wait_closed()\fP completes, or else it could prevent the server from shutting down. .INDENT 7.0 .TP .B Parameters .INDENT 7.0 .IP \(bu 2 \fBpath\fP (\fI\%str\fP) \-\- request path, including optional query string. .IP \(bu 2 \fBrequest_headers\fP (\fI\%Headers\fP) \-\- request headers. .UNINDENT .TP .B Returns \fI\%None\fP to continue the WebSocket handshake normally. .sp An HTTP response, represented by a 3\-uple of the response status, headers, and body, to abort the WebSocket handshake and return that HTTP response instead. .TP .B Return type Optional[Tuple[\fI\%StatusLike\fP, \fI\%HeadersLike\fP, \fI\%bytes\fP]] .UNINDENT .UNINDENT .INDENT 7.0 .TP .B select_subprotocol(client_subprotocols, server_subprotocols) Pick a subprotocol among those supported by the client and the server. .sp If several subprotocols are available, select the preferred subprotocol by giving equal weight to the preferences of the client and the server. .sp If no subprotocol is available, proceed without a subprotocol. .sp You may provide a \fBselect_subprotocol\fP argument to \fI\%serve()\fP or \fI\%WebSocketServerProtocol\fP to override this logic. For example, you could reject the handshake if the client doesn\(aqt support a particular subprotocol, rather than accept the handshake without that subprotocol. .INDENT 7.0 .TP .B Parameters .INDENT 7.0 .IP \(bu 2 \fBclient_subprotocols\fP (\fI\%Sequence\fP\fI[\fP\fI\%Subprotocol\fP\fI]\fP) \-\- list of subprotocols offered by the client. .IP \(bu 2 \fBserver_subprotocols\fP (\fI\%Sequence\fP\fI[\fP\fI\%Subprotocol\fP\fI]\fP) \-\- list of subprotocols available on the server. .UNINDENT .TP .B Returns Selected subprotocol, if a common subprotocol was found. .sp \fI\%None\fP to continue without a subprotocol. .TP .B Return type Optional[\fI\%Subprotocol\fP] .UNINDENT .UNINDENT .sp WebSocket connection objects also provide these attributes: .INDENT 7.0 .TP .B id: \fI\%uuid.UUID\fP Unique identifier of the connection. Useful in logs. .UNINDENT .INDENT 7.0 .TP .B logger: \fI\%LoggerLike\fP Logger for this connection. .UNINDENT .INDENT 7.0 .TP .B property local_address: \fI\%Any\fP Local address of the connection. .sp For IPv4 connections, this is a \fB(host, port)\fP tuple. .sp The format of the address depends on the address family; see \fI\%getsockname()\fP\&. .sp \fI\%None\fP if the TCP connection isn\(aqt established yet. .UNINDENT .INDENT 7.0 .TP .B property remote_address: \fI\%Any\fP Remote address of the connection. .sp For IPv4 connections, this is a \fB(host, port)\fP tuple. .sp The format of the address depends on the address family; see \fI\%getpeername()\fP\&. .sp \fI\%None\fP if the TCP connection isn\(aqt established yet. .UNINDENT .INDENT 7.0 .TP .B property open: \fI\%bool\fP \fI\%True\fP when the connection is open; \fI\%False\fP otherwise. .sp This attribute may be used to detect disconnections. However, this approach is discouraged per the \fI\%EAFP\fP principle. Instead, you should handle \fI\%ConnectionClosed\fP exceptions. .UNINDENT .INDENT 7.0 .TP .B property closed: \fI\%bool\fP \fI\%True\fP when the connection is closed; \fI\%False\fP otherwise. .sp Be aware that both \fI\%open\fP and \fI\%closed\fP are \fI\%False\fP during the opening and closing sequences. .UNINDENT .INDENT 7.0 .TP .B latency: \fI\%float\fP Latency of the connection, in seconds. .sp This value is updated after sending a ping frame and receiving a matching pong frame. Before the first ping, \fI\%latency\fP is \fB0\fP\&. .sp By default, websockets enables a \fI\%keepalive\fP mechanism that sends ping frames automatically at regular intervals. You can also send ping frames and measure latency with \fI\%ping()\fP\&. .UNINDENT .sp The following attributes are available after the opening handshake, once the WebSocket connection is open: .INDENT 7.0 .TP .B path: \fI\%str\fP Path of the opening handshake request. .UNINDENT .INDENT 7.0 .TP .B request_headers: \fI\%Headers\fP Opening handshake request headers. .UNINDENT .INDENT 7.0 .TP .B response_headers: \fI\%Headers\fP Opening handshake response headers. .UNINDENT .INDENT 7.0 .TP .B subprotocol: \fI\%Subprotocol\fP | \fI\%None\fP Subprotocol, if one was negotiated. .UNINDENT .sp The following attributes are available after the closing handshake, once the WebSocket connection is closed: .INDENT 7.0 .TP .B property close_code: \fI\%int\fP | \fI\%None\fP WebSocket close code, defined in \fI\%section 7.1.5 of RFC 6455\fP\&. .sp \fI\%None\fP if the connection isn\(aqt closed yet. .UNINDENT .INDENT 7.0 .TP .B property close_reason: \fI\%str\fP | \fI\%None\fP WebSocket close reason, defined in \fI\%section 7.1.6 of RFC 6455\fP\&. .sp \fI\%None\fP if the connection isn\(aqt closed yet. .UNINDENT .UNINDENT .SS Basic authentication .sp websockets supports HTTP Basic Authentication according to \fI\%RFC 7235\fP and \fI\%RFC 7617\fP\&. .INDENT 0.0 .TP .B websockets.auth.basic_auth_protocol_factory(realm=None, credentials=None, check_credentials=None, create_protocol=None) Protocol factory that enforces HTTP Basic Auth. .sp \fI\%basic_auth_protocol_factory()\fP is designed to integrate with \fI\%serve()\fP like this: .INDENT 7.0 .INDENT 3.5 .sp .nf .ft C websockets.serve( ..., create_protocol=websockets.basic_auth_protocol_factory( realm=\(dqmy dev server\(dq, credentials=(\(dqhello\(dq, \(dqiloveyou\(dq), ) ) .ft P .fi .UNINDENT .UNINDENT .INDENT 7.0 .TP .B Parameters .INDENT 7.0 .IP \(bu 2 \fBrealm\fP (\fI\%str\fP\fI | \fP\fI\%None\fP) \-\- Scope of protection. It should contain only ASCII characters because the encoding of non\-ASCII characters is undefined. Refer to section 2.2 of \fI\%RFC 7235\fP for details. .IP \(bu 2 \fBcredentials\fP (\fI\%Tuple\fP\fI[\fP\fI\%str\fP\fI, \fP\fI\%str\fP\fI] \fP\fI| \fP\fI\%Iterable\fP\fI[\fP\fI\%Tuple\fP\fI[\fP\fI\%str\fP\fI, \fP\fI\%str\fP\fI]\fP\fI] \fP\fI| \fP\fI\%None\fP) \-\- Hard coded authorized credentials. It can be a \fB(username, password)\fP pair or a list of such pairs. .IP \(bu 2 \fBcheck_credentials\fP (\fI\%Callable\fP\fI[\fP\fI[\fP\fI\%str\fP\fI, \fP\fI\%str\fP\fI]\fP\fI, \fP\fI\%Awaitable\fP\fI[\fP\fI\%bool\fP\fI]\fP\fI] \fP\fI| \fP\fI\%None\fP) \-\- Coroutine that verifies credentials. It receives \fBusername\fP and \fBpassword\fP arguments and returns a \fI\%bool\fP\&. One of \fBcredentials\fP or \fBcheck_credentials\fP must be provided but not both. .IP \(bu 2 \fBcreate_protocol\fP (\fI\%Callable\fP\fI[\fP\fI[\fP\fI\&...\fP\fI]\fP\fI, \fP\fI\%BasicAuthWebSocketServerProtocol\fP\fI] \fP\fI| \fP\fI\%None\fP) \-\- Factory that creates the protocol. By default, this is \fI\%BasicAuthWebSocketServerProtocol\fP\&. It can be replaced by a subclass. .UNINDENT .TP .B Raises \fI\%TypeError\fP \-\- If the \fBcredentials\fP or \fBcheck_credentials\fP argument is wrong. .UNINDENT .UNINDENT .INDENT 0.0 .TP .B class websockets.auth.BasicAuthWebSocketServerProtocol(*args, realm=None, check_credentials=None, **kwargs) WebSocket server protocol that enforces HTTP Basic Auth. .INDENT 7.0 .UNINDENT .INDENT 7.0 .TP .B realm: \fI\%str\fP = \(aq\(aq Scope of protection. .sp If provided, it should contain only ASCII characters because the encoding of non\-ASCII characters is undefined. .UNINDENT .INDENT 7.0 .TP .B username: \fI\%str\fP | \fI\%None\fP = None Username of the authenticated user. .UNINDENT .INDENT 7.0 .TP .B await check_credentials(username, password) Check whether credentials are authorized. .sp This coroutine may be overridden in a subclass, for example to authenticate against a database or an external service. .INDENT 7.0 .TP .B Parameters .INDENT 7.0 .IP \(bu 2 \fBusername\fP (\fI\%str\fP) \-\- HTTP Basic Auth username. .IP \(bu 2 \fBpassword\fP (\fI\%str\fP) \-\- HTTP Basic Auth password. .UNINDENT .TP .B Returns \fI\%True\fP if the handshake should continue; \fI\%False\fP if it should fail with an HTTP 401 error. .TP .B Return type \fI\%bool\fP .UNINDENT .UNINDENT .UNINDENT .SS Broadcast .INDENT 0.0 .TP .B websockets.broadcast(websockets, message, raise_exceptions=False) Broadcast a message to several WebSocket connections. .sp A string (\fI\%str\fP) is sent as a \fI\%Text\fP frame. A bytestring or bytes\-like object (\fI\%bytes\fP, \fI\%bytearray\fP, or \fI\%memoryview\fP) is sent as a \fI\%Binary\fP frame. .sp \fI\%broadcast()\fP pushes the message synchronously to all connections even if their write buffers are overflowing. There\(aqs no backpressure. .sp If you broadcast messages faster than a connection can handle them, messages will pile up in its write buffer until the connection times out. Keep \fBping_interval\fP and \fBping_timeout\fP low to prevent excessive memory usage from slow connections. .sp Unlike \fI\%send()\fP, \fI\%broadcast()\fP doesn\(aqt support sending fragmented messages. Indeed, fragmentation is useful for sending large messages without buffering them in memory, while \fI\%broadcast()\fP buffers one copy per connection as fast as possible. .sp \fI\%broadcast()\fP skips connections that aren\(aqt open in order to avoid errors on connections where the closing handshake is in progress. .sp \fI\%broadcast()\fP ignores failures to write the message on some connections. It continues writing to other connections. On Python 3.11 and above, you may set \fBraise_exceptions\fP to \fI\%True\fP to record failures and raise all exceptions in a \fI\%PEP 654\fP \fI\%ExceptionGroup\fP\&. .INDENT 7.0 .TP .B Parameters .INDENT 7.0 .IP \(bu 2 \fBwebsockets\fP (\fI\%Iterable\fP\fI[\fP\fI\%WebSocketCommonProtocol\fP\fI]\fP) \-\- WebSocket connections to which the message will be sent. .IP \(bu 2 \fBmessage\fP (\fI\%str\fP\fI | \fP\fI\%bytes\fP) \-\- Message to send. .IP \(bu 2 \fBraise_exceptions\fP (\fI\%bool\fP) \-\- Whether to raise an exception in case of failures. .UNINDENT .TP .B Raises \fI\%TypeError\fP \-\- If \fBmessage\fP doesn\(aqt have a supported type. .UNINDENT .UNINDENT .SS Client (\fI\%asyncio\fP) .SS Opening a connection .INDENT 0.0 .TP .B await websockets.client.connect(uri, *, create_protocol=None, logger=None, compression=\(aqdeflate\(aq, origin=None, extensions=None, subprotocols=None, extra_headers=None, user_agent_header=\(aqPython/x.y.z websockets/X.Y\(aq, open_timeout=10, ping_interval=20, ping_timeout=20, close_timeout=10, max_size=2**20, max_queue=2**5, read_limit=2**16, write_limit=2**16, **kwds) Connect to the WebSocket server at \fBuri\fP\&. .sp Awaiting \fI\%connect()\fP yields a \fI\%WebSocketClientProtocol\fP which can then be used to send and receive messages. .sp \fI\%connect()\fP can be used as a asynchronous context manager: .INDENT 7.0 .INDENT 3.5 .sp .nf .ft C async with websockets.connect(...) as websocket: ... .ft P .fi .UNINDENT .UNINDENT .sp The connection is closed automatically when exiting the context. .sp \fI\%connect()\fP can be used as an infinite asynchronous iterator to reconnect automatically on errors: .INDENT 7.0 .INDENT 3.5 .sp .nf .ft C async for websocket in websockets.connect(...): try: ... except websockets.ConnectionClosed: continue .ft P .fi .UNINDENT .UNINDENT .sp The connection is closed automatically after each iteration of the loop. .sp If an error occurs while establishing the connection, \fI\%connect()\fP retries with exponential backoff. The backoff delay starts at three seconds and increases up to one minute. .sp If an error occurs in the body of the loop, you can handle the exception and \fI\%connect()\fP will reconnect with the next iteration; or you can let the exception bubble up and break out of the loop. This lets you decide which errors trigger a reconnection and which errors are fatal. .INDENT 7.0 .TP .B Parameters .INDENT 7.0 .IP \(bu 2 \fBuri\fP (\fI\%str\fP) \-\- URI of the WebSocket server. .IP \(bu 2 \fBcreate_protocol\fP (\fIOptional\fP\fI[\fP\fICallable\fP\fI[\fP\fI\&...\fP\fI, \fP\fI\%WebSocketClientProtocol\fP\fI]\fP\fI]\fP) \-\- Factory for the \fI\%asyncio.Protocol\fP managing the connection. It defaults to \fI\%WebSocketClientProtocol\fP\&. Set it to a wrapper or a subclass to customize connection handling. .IP \(bu 2 \fBlogger\fP (\fIOptional\fP\fI[\fP\fI\%LoggerLike\fP\fI]\fP) \-\- Logger for this client. It defaults to \fBlogging.getLogger(\(dqwebsockets.client\(dq)\fP\&. See the \fI\%logging guide\fP for details. .IP \(bu 2 \fBcompression\fP (\fIOptional\fP\fI[\fP\fI\%str\fP\fI]\fP) \-\- The \(dqpermessage\-deflate\(dq extension is enabled by default. Set \fBcompression\fP to \fI\%None\fP to disable it. See the \fI\%compression guide\fP for details. .IP \(bu 2 \fBorigin\fP (\fIOptional\fP\fI[\fP\fI\%Origin\fP\fI]\fP) \-\- Value of the \fBOrigin\fP header, for servers that require it. .IP \(bu 2 \fBextensions\fP (\fIOptional\fP\fI[\fP\fISequence\fP\fI[\fP\fI\%ClientExtensionFactory\fP\fI]\fP\fI]\fP) \-\- List of supported extensions, in order in which they should be negotiated and run. .IP \(bu 2 \fBsubprotocols\fP (\fIOptional\fP\fI[\fP\fISequence\fP\fI[\fP\fI\%Subprotocol\fP\fI]\fP\fI]\fP) \-\- List of supported subprotocols, in order of decreasing preference. .IP \(bu 2 \fBextra_headers\fP (\fIOptional\fP\fI[\fP\fI\%HeadersLike\fP\fI]\fP) \-\- Arbitrary HTTP headers to add to the handshake request. .IP \(bu 2 \fBuser_agent_header\fP (\fIOptional\fP\fI[\fP\fI\%str\fP\fI]\fP) \-\- Value of the \fBUser\-Agent\fP request header. It defaults to \fB\(dqPython/x.y.z websockets/X.Y\(dq\fP\&. Setting it to \fI\%None\fP removes the header. .IP \(bu 2 \fBopen_timeout\fP (\fIOptional\fP\fI[\fP\fI\%float\fP\fI]\fP) \-\- Timeout for opening the connection in seconds. \fI\%None\fP disables the timeout. .UNINDENT .UNINDENT .sp See \fI\%WebSocketCommonProtocol\fP for the documentation of \fBping_interval\fP, \fBping_timeout\fP, \fBclose_timeout\fP, \fBmax_size\fP, \fBmax_queue\fP, \fBread_limit\fP, and \fBwrite_limit\fP\&. .sp Any other keyword arguments are passed the event loop\(aqs \fI\%create_connection()\fP method. .sp For example: .INDENT 7.0 .IP \(bu 2 You can set \fBssl\fP to a \fI\%SSLContext\fP to enforce TLS settings. When connecting to a \fBwss://\fP URI, if \fBssl\fP isn\(aqt provided, a TLS context is created with \fI\%create_default_context()\fP\&. .IP \(bu 2 You can set \fBhost\fP and \fBport\fP to connect to a different host and port from those found in \fBuri\fP\&. This only changes the destination of the TCP connection. The host name from \fBuri\fP is still used in the TLS handshake for secure connections and in the \fBHost\fP header. .UNINDENT .INDENT 7.0 .TP .B Raises .INDENT 7.0 .IP \(bu 2 \fI\%InvalidURI\fP \-\- If \fBuri\fP isn\(aqt a valid WebSocket URI. .IP \(bu 2 \fI\%OSError\fP \-\- If the TCP connection fails. .IP \(bu 2 \fI\%InvalidHandshake\fP \-\- If the opening handshake fails. .IP \(bu 2 \fI\%TimeoutError\fP \-\- If the opening handshake times out. .UNINDENT .UNINDENT .UNINDENT .INDENT 0.0 .TP .B await websockets.client.unix_connect(path, uri=\(aqws://localhost/\(aq, *, create_protocol=None, logger=None, compression=\(aqdeflate\(aq, origin=None, extensions=None, subprotocols=None, extra_headers=None, user_agent_header=\(aqPython/x.y.z websockets/X.Y\(aq, open_timeout=10, ping_interval=20, ping_timeout=20, close_timeout=10, max_size=2**20, max_queue=2**5, read_limit=2**16, write_limit=2**16, **kwds) Similar to \fI\%connect()\fP, but for connecting to a Unix socket. .sp This function builds upon the event loop\(aqs \fI\%create_unix_connection()\fP method. .sp It is only available on Unix. .sp It\(aqs mainly useful for debugging servers listening on Unix sockets. .INDENT 7.0 .TP .B Parameters .INDENT 7.0 .IP \(bu 2 \fBpath\fP (\fI\%str\fP\fI | \fP\fI\%None\fP) \-\- File system path to the Unix socket. .IP \(bu 2 \fBuri\fP (\fI\%str\fP) \-\- URI of the WebSocket server; the host is used in the TLS handshake for secure connections and in the \fBHost\fP header. .UNINDENT .UNINDENT .UNINDENT .SS Using a connection .INDENT 0.0 .TP .B class websockets.client.WebSocketClientProtocol(*, logger=None, origin=None, extensions=None, subprotocols=None, extra_headers=None, user_agent_header=\(aqPython/x.y.z websockets/X.Y\(aq, ping_interval=20, ping_timeout=20, close_timeout=10, max_size=2**20, max_queue=2**5, read_limit=2**16, write_limit=2**16) WebSocket client connection. .sp \fI\%WebSocketClientProtocol\fP provides \fI\%recv()\fP and \fI\%send()\fP coroutines for receiving and sending messages. .sp It supports asynchronous iteration to receive incoming messages: .INDENT 7.0 .INDENT 3.5 .sp .nf .ft C async for message in websocket: await process(message) .ft P .fi .UNINDENT .UNINDENT .sp The iterator exits normally when the connection is closed with close code 1000 (OK) or 1001 (going away) or without a close code. It raises a \fI\%ConnectionClosedError\fP when the connection is closed with any other code. .sp See \fI\%connect()\fP for the documentation of \fBlogger\fP, \fBorigin\fP, \fBextensions\fP, \fBsubprotocols\fP, \fBextra_headers\fP, and \fBuser_agent_header\fP\&. .sp See \fI\%WebSocketCommonProtocol\fP for the documentation of \fBping_interval\fP, \fBping_timeout\fP, \fBclose_timeout\fP, \fBmax_size\fP, \fBmax_queue\fP, \fBread_limit\fP, and \fBwrite_limit\fP\&. .INDENT 7.0 .UNINDENT .INDENT 7.0 .TP .B await recv() Receive the next message. .sp When the connection is closed, \fI\%recv()\fP raises \fI\%ConnectionClosed\fP\&. Specifically, it raises \fI\%ConnectionClosedOK\fP after a normal connection closure and \fI\%ConnectionClosedError\fP after a protocol error or a network failure. This is how you detect the end of the message stream. .sp Canceling \fI\%recv()\fP is safe. There\(aqs no risk of losing the next message. The next invocation of \fI\%recv()\fP will return it. .sp This makes it possible to enforce a timeout by wrapping \fI\%recv()\fP in \fI\%timeout()\fP or \fI\%wait_for()\fP\&. .INDENT 7.0 .TP .B Returns A string (\fI\%str\fP) for a \fI\%Text\fP frame. A bytestring (\fI\%bytes\fP) for a \fI\%Binary\fP frame. .TP .B Return type \fI\%Data\fP .TP .B Raises .INDENT 7.0 .IP \(bu 2 \fI\%ConnectionClosed\fP \-\- When the connection is closed. .IP \(bu 2 \fI\%RuntimeError\fP \-\- If two coroutines call \fI\%recv()\fP concurrently. .UNINDENT .UNINDENT .UNINDENT .INDENT 7.0 .TP .B await send(message) Send a message. .sp A string (\fI\%str\fP) is sent as a \fI\%Text\fP frame. A bytestring or bytes\-like object (\fI\%bytes\fP, \fI\%bytearray\fP, or \fI\%memoryview\fP) is sent as a \fI\%Binary\fP frame. .sp \fI\%send()\fP also accepts an iterable or an asynchronous iterable of strings, bytestrings, or bytes\-like objects to enable \fI\%fragmentation\fP\&. Each item is treated as a message fragment and sent in its own frame. All items must be of the same type, or else \fI\%send()\fP will raise a \fI\%TypeError\fP and the connection will be closed. .sp \fI\%send()\fP rejects dict\-like objects because this is often an error. (If you want to send the keys of a dict\-like object as fragments, call its \fI\%keys()\fP method and pass the result to \fI\%send()\fP\&.) .sp Canceling \fI\%send()\fP is discouraged. Instead, you should close the connection with \fI\%close()\fP\&. Indeed, there are only two situations where \fI\%send()\fP may yield control to the event loop and then get canceled; in both cases, \fI\%close()\fP has the same effect and is more clear: .INDENT 7.0 .IP 1. 3 The write buffer is full. If you don\(aqt want to wait until enough data is sent, your only alternative is to close the connection. \fI\%close()\fP will likely time out then abort the TCP connection. .IP 2. 3 \fBmessage\fP is an asynchronous iterator that yields control. Stopping in the middle of a fragmented message will cause a protocol error and the connection will be closed. .UNINDENT .sp When the connection is closed, \fI\%send()\fP raises \fI\%ConnectionClosed\fP\&. Specifically, it raises \fI\%ConnectionClosedOK\fP after a normal connection closure and \fI\%ConnectionClosedError\fP after a protocol error or a network failure. .INDENT 7.0 .TP .B Parameters \fBmessage\fP (\fIUnion\fP\fI[\fP\fI\%Data\fP\fI, \fP\fIIterable\fP\fI[\fP\fI\%Data\fP\fI]\fP\fI, \fP\fIAsyncIterable\fP\fI[\fP\fI\%Data\fP\fI]\fP) \-\- message to send. .TP .B Raises .INDENT 7.0 .IP \(bu 2 \fI\%ConnectionClosed\fP \-\- When the connection is closed. .IP \(bu 2 \fI\%TypeError\fP \-\- If \fBmessage\fP doesn\(aqt have a supported type. .UNINDENT .UNINDENT .UNINDENT .INDENT 7.0 .TP .B await close(code=CloseCode.NORMAL_CLOSURE, reason=\(aq\(aq) Perform the closing handshake. .sp \fI\%close()\fP waits for the other end to complete the handshake and for the TCP connection to terminate. As a consequence, there\(aqs no need to await \fI\%wait_closed()\fP after \fI\%close()\fP\&. .sp \fI\%close()\fP is idempotent: it doesn\(aqt do anything once the connection is closed. .sp Wrapping \fI\%close()\fP in \fI\%create_task()\fP is safe, given that errors during connection termination aren\(aqt particularly useful. .sp Canceling \fI\%close()\fP is discouraged. If it takes too long, you can set a shorter \fBclose_timeout\fP\&. If you don\(aqt want to wait, let the Python process exit, then the OS will take care of closing the TCP connection. .INDENT 7.0 .TP .B Parameters .INDENT 7.0 .IP \(bu 2 \fBcode\fP (\fI\%int\fP) \-\- WebSocket close code. .IP \(bu 2 \fBreason\fP (\fI\%str\fP) \-\- WebSocket close reason. .UNINDENT .UNINDENT .UNINDENT .INDENT 7.0 .TP .B await wait_closed() Wait until the connection is closed. .sp This coroutine is identical to the \fI\%closed\fP attribute, except it can be awaited. .sp This can make it easier to detect connection termination, regardless of its cause, in tasks that interact with the WebSocket connection. .INDENT 7.0 .UNINDENT .UNINDENT .INDENT 7.0 .TP .B await ping(data=None) Send a \fI\%Ping\fP\&. .sp A ping may serve as a keepalive, as a check that the remote endpoint received all messages up to this point, or to measure \fI\%latency\fP\&. .sp Canceling \fI\%ping()\fP is discouraged. If \fI\%ping()\fP doesn\(aqt return immediately, it means the write buffer is full. If you don\(aqt want to wait, you should close the connection. .sp Canceling the \fI\%Future\fP returned by \fI\%ping()\fP has no effect. .INDENT 7.0 .TP .B Parameters \fBdata\fP (\fIOptional\fP\fI[\fP\fI\%Data\fP\fI]\fP) \-\- payload of the ping; a string will be encoded to UTF\-8; or \fI\%None\fP to generate a payload containing four random bytes. .TP .B Returns A future that will be completed when the corresponding pong is received. You can ignore it if you don\(aqt intend to wait. The result of the future is the latency of the connection in seconds. .INDENT 7.0 .INDENT 3.5 .sp .nf .ft C pong_waiter = await ws.ping() # only if you want to wait for the corresponding pong latency = await pong_waiter .ft P .fi .UNINDENT .UNINDENT .TP .B Return type \fI\%Future\fP[\fI\%float\fP] .TP .B Raises .INDENT 7.0 .IP \(bu 2 \fI\%ConnectionClosed\fP \-\- When the connection is closed. .IP \(bu 2 \fI\%RuntimeError\fP \-\- If another ping was sent with the same data and the corresponding pong wasn\(aqt received yet. .UNINDENT .UNINDENT .UNINDENT .INDENT 7.0 .TP .B await pong(data=b\(aq\(aq) Send a \fI\%Pong\fP\&. .sp An unsolicited pong may serve as a unidirectional heartbeat. .sp Canceling \fI\%pong()\fP is discouraged. If \fI\%pong()\fP doesn\(aqt return immediately, it means the write buffer is full. If you don\(aqt want to wait, you should close the connection. .INDENT 7.0 .TP .B Parameters \fBdata\fP (\fI\%Data\fP) \-\- Payload of the pong. A string will be encoded to UTF\-8. .TP .B Raises \fI\%ConnectionClosed\fP \-\- When the connection is closed. .UNINDENT .UNINDENT .sp WebSocket connection objects also provide these attributes: .INDENT 7.0 .TP .B id: \fI\%uuid.UUID\fP Unique identifier of the connection. Useful in logs. .UNINDENT .INDENT 7.0 .TP .B logger: \fI\%LoggerLike\fP Logger for this connection. .UNINDENT .INDENT 7.0 .TP .B property local_address: \fI\%Any\fP Local address of the connection. .sp For IPv4 connections, this is a \fB(host, port)\fP tuple. .sp The format of the address depends on the address family; see \fI\%getsockname()\fP\&. .sp \fI\%None\fP if the TCP connection isn\(aqt established yet. .UNINDENT .INDENT 7.0 .TP .B property remote_address: \fI\%Any\fP Remote address of the connection. .sp For IPv4 connections, this is a \fB(host, port)\fP tuple. .sp The format of the address depends on the address family; see \fI\%getpeername()\fP\&. .sp \fI\%None\fP if the TCP connection isn\(aqt established yet. .UNINDENT .INDENT 7.0 .TP .B property open: \fI\%bool\fP \fI\%True\fP when the connection is open; \fI\%False\fP otherwise. .sp This attribute may be used to detect disconnections. However, this approach is discouraged per the \fI\%EAFP\fP principle. Instead, you should handle \fI\%ConnectionClosed\fP exceptions. .UNINDENT .INDENT 7.0 .TP .B property closed: \fI\%bool\fP \fI\%True\fP when the connection is closed; \fI\%False\fP otherwise. .sp Be aware that both \fI\%open\fP and \fI\%closed\fP are \fI\%False\fP during the opening and closing sequences. .UNINDENT .INDENT 7.0 .TP .B latency: \fI\%float\fP Latency of the connection, in seconds. .sp This value is updated after sending a ping frame and receiving a matching pong frame. Before the first ping, \fI\%latency\fP is \fB0\fP\&. .sp By default, websockets enables a \fI\%keepalive\fP mechanism that sends ping frames automatically at regular intervals. You can also send ping frames and measure latency with \fI\%ping()\fP\&. .UNINDENT .sp The following attributes are available after the opening handshake, once the WebSocket connection is open: .INDENT 7.0 .TP .B path: \fI\%str\fP Path of the opening handshake request. .UNINDENT .INDENT 7.0 .TP .B request_headers: \fI\%Headers\fP Opening handshake request headers. .UNINDENT .INDENT 7.0 .TP .B response_headers: \fI\%Headers\fP Opening handshake response headers. .UNINDENT .INDENT 7.0 .TP .B subprotocol: \fI\%Subprotocol\fP | \fI\%None\fP Subprotocol, if one was negotiated. .UNINDENT .sp The following attributes are available after the closing handshake, once the WebSocket connection is closed: .INDENT 7.0 .TP .B property close_code: \fI\%int\fP | \fI\%None\fP WebSocket close code, defined in \fI\%section 7.1.5 of RFC 6455\fP\&. .sp \fI\%None\fP if the connection isn\(aqt closed yet. .UNINDENT .INDENT 7.0 .TP .B property close_reason: \fI\%str\fP | \fI\%None\fP WebSocket close reason, defined in \fI\%section 7.1.6 of RFC 6455\fP\&. .sp \fI\%None\fP if the connection isn\(aqt closed yet. .UNINDENT .UNINDENT .SS \fI\%threading\fP .sp This alternative implementation can be a good choice for clients. .SS Server (\fI\%threading\fP) .SS Creating a server .INDENT 0.0 .TP .B websockets.sync.server.serve(handler, host=None, port=None, *, sock=None, ssl_context=None, origins=None, extensions=None, subprotocols=None, select_subprotocol=None, process_request=None, process_response=None, server_header=\(aqPython/x.y.z websockets/X.Y\(aq, compression=\(aqdeflate\(aq, open_timeout=10, close_timeout=10, max_size=2**20, logger=None, create_connection=None) Create a WebSocket server listening on \fBhost\fP and \fBport\fP\&. .sp Whenever a client connects, the server creates a \fI\%ServerConnection\fP, performs the opening handshake, and delegates to the \fBhandler\fP\&. .sp The handler receives a \fI\%ServerConnection\fP instance, which you can use to send and receive messages. .sp Once the handler completes, either normally or with an exception, the server performs the closing handshake and closes the connection. .sp \fI\%WebSocketServer\fP mirrors the API of \fI\%BaseServer\fP\&. Treat it as a context manager to ensure that it will be closed and call the \fI\%serve_forever()\fP method to serve requests: .INDENT 7.0 .INDENT 3.5 .sp .nf .ft C def handler(websocket): ... with websockets.sync.server.serve(handler, ...) as server: server.serve_forever() .ft P .fi .UNINDENT .UNINDENT .INDENT 7.0 .TP .B Parameters .INDENT 7.0 .IP \(bu 2 \fBhandler\fP (\fI\%Callable\fP\fI[\fP\fI[\fP\fI\%ServerConnection\fP\fI]\fP\fI, \fP\fI\%None\fP\fI]\fP) \-\- Connection handler. It receives the WebSocket connection, which is a \fI\%ServerConnection\fP, in argument. .IP \(bu 2 \fBhost\fP (\fI\%str\fP\fI | \fP\fI\%None\fP) \-\- Network interfaces the server binds to. See \fI\%create_server()\fP for details. .IP \(bu 2 \fBport\fP (\fI\%int\fP\fI | \fP\fI\%None\fP) \-\- TCP port the server listens on. See \fI\%create_server()\fP for details. .IP \(bu 2 \fBsock\fP (\fI\%socket\fP\fI | \fP\fI\%None\fP) \-\- Preexisting TCP socket. \fBsock\fP replaces \fBhost\fP and \fBport\fP\&. You may call \fI\%socket.create_server()\fP to create a suitable TCP socket. .IP \(bu 2 \fBssl_context\fP (\fI\%SSLContext\fP\fI | \fP\fI\%None\fP) \-\- Configuration for enabling TLS on the connection. .IP \(bu 2 \fBorigins\fP (\fI\%Sequence\fP\fI[\fP\fI\%Origin\fP\fI | \fP\fI\%None\fP\fI] \fP\fI| \fP\fI\%None\fP) \-\- Acceptable values of the \fBOrigin\fP header, for defending against Cross\-Site WebSocket Hijacking attacks. Include \fI\%None\fP in the list if the lack of an origin is acceptable. .IP \(bu 2 \fBextensions\fP (\fI\%Sequence\fP\fI[\fP\fI\%ServerExtensionFactory\fP\fI] \fP\fI| \fP\fI\%None\fP) \-\- List of supported extensions, in order in which they should be negotiated and run. .IP \(bu 2 \fBsubprotocols\fP (\fI\%Sequence\fP\fI[\fP\fI\%Subprotocol\fP\fI] \fP\fI| \fP\fI\%None\fP) \-\- List of supported subprotocols, in order of decreasing preference. .IP \(bu 2 \fBselect_subprotocol\fP (\fI\%Callable\fP\fI[\fP\fI[\fP\fI\%ServerConnection\fP\fI, \fP\fI\%Sequence\fP\fI[\fP\fI\%Subprotocol\fP\fI]\fP\fI]\fP\fI, \fP\fI\%Subprotocol\fP\fI | \fP\fI\%None\fP\fI] \fP\fI| \fP\fI\%None\fP) \-\- Callback for selecting a subprotocol among those supported by the client and the server. It receives a \fI\%ServerConnection\fP (not a \fI\%ServerProtocol\fP!) instance and a list of subprotocols offered by the client. Other than the first argument, it has the same behavior as the \fI\%ServerProtocol.select_subprotocol\fP method. .IP \(bu 2 \fBprocess_request\fP (\fI\%Callable\fP\fI[\fP\fI[\fP\fI\%ServerConnection\fP\fI, \fP\fI\%Request\fP\fI]\fP\fI, \fP\fI\%Response\fP\fI | \fP\fI\%None\fP\fI] \fP\fI| \fP\fI\%None\fP) \-\- Intercept the request during the opening handshake. Return an HTTP response to force the response or \fI\%None\fP to continue normally. When you force an HTTP 101 Continue response, the handshake is successful. Else, the connection is aborted. .IP \(bu 2 \fBprocess_response\fP (\fI\%Callable\fP\fI[\fP\fI[\fP\fI\%ServerConnection\fP\fI, \fP\fI\%Request\fP\fI, \fP\fI\%Response\fP\fI]\fP\fI, \fP\fI\%Response\fP\fI | \fP\fI\%None\fP\fI] \fP\fI| \fP\fI\%None\fP) \-\- Intercept the response during the opening handshake. Return an HTTP response to force the response or \fI\%None\fP to continue normally. When you force an HTTP 101 Continue response, the handshake is successful. Else, the connection is aborted. .IP \(bu 2 \fBserver_header\fP (\fI\%str\fP\fI | \fP\fI\%None\fP) \-\- Value of the \fBServer\fP response header. It defaults to \fB\(dqPython/x.y.z websockets/X.Y\(dq\fP\&. Setting it to \fI\%None\fP removes the header. .IP \(bu 2 \fBcompression\fP (\fI\%str\fP\fI | \fP\fI\%None\fP) \-\- The \(dqpermessage\-deflate\(dq extension is enabled by default. Set \fBcompression\fP to \fI\%None\fP to disable it. See the \fI\%compression guide\fP for details. .IP \(bu 2 \fBopen_timeout\fP (\fI\%float\fP\fI | \fP\fI\%None\fP) \-\- Timeout for opening connections in seconds. \fI\%None\fP disables the timeout. .IP \(bu 2 \fBclose_timeout\fP (\fI\%float\fP\fI | \fP\fI\%None\fP) \-\- Timeout for closing connections in seconds. \fI\%None\fP disables the timeout. .IP \(bu 2 \fBmax_size\fP (\fI\%int\fP\fI | \fP\fI\%None\fP) \-\- Maximum size of incoming messages in bytes. \fI\%None\fP disables the limit. .IP \(bu 2 \fBlogger\fP (\fI\%Logger\fP\fI | \fP\fI\%LoggerAdapter\fP\fI | \fP\fI\%None\fP) \-\- Logger for this server. It defaults to \fBlogging.getLogger(\(dqwebsockets.server\(dq)\fP\&. See the \fI\%logging guide\fP for details. .IP \(bu 2 \fBcreate_connection\fP (\fI\%Type\fP\fI[\fP\fI\%ServerConnection\fP\fI] \fP\fI| \fP\fI\%None\fP) \-\- Factory for the \fI\%ServerConnection\fP managing the connection. Set it to a wrapper or a subclass to customize connection handling. .UNINDENT .UNINDENT .UNINDENT .INDENT 0.0 .TP .B websockets.sync.server.unix_serve(handler, path=None, *, sock=None, ssl_context=None, origins=None, extensions=None, subprotocols=None, select_subprotocol=None, process_request=None, process_response=None, server_header=\(aqPython/x.y.z websockets/X.Y\(aq, compression=\(aqdeflate\(aq, open_timeout=10, close_timeout=10, max_size=2**20, logger=None, create_connection=None) Create a WebSocket server listening on a Unix socket. .sp This function is identical to \fI\%serve()\fP, except the \fBhost\fP and \fBport\fP arguments are replaced by \fBpath\fP\&. It\(aqs only available on Unix. .sp It\(aqs useful for deploying a server behind a reverse proxy such as nginx. .INDENT 7.0 .TP .B Parameters .INDENT 7.0 .IP \(bu 2 \fBhandler\fP (\fI\%Callable\fP\fI[\fP\fI[\fP\fI\%ServerConnection\fP\fI]\fP\fI, \fP\fI\%Any\fP\fI]\fP) \-\- Connection handler. It receives the WebSocket connection, which is a \fI\%ServerConnection\fP, in argument. .IP \(bu 2 \fBpath\fP (\fI\%str\fP\fI | \fP\fI\%None\fP) \-\- File system path to the Unix socket. .UNINDENT .UNINDENT .UNINDENT .SS Running a server .INDENT 0.0 .TP .B class websockets.sync.server.WebSocketServer(socket, handler, logger=None) WebSocket server returned by \fI\%serve()\fP\&. .sp This class mirrors the API of \fI\%BaseServer\fP, notably the \fI\%serve_forever()\fP and \fI\%shutdown()\fP methods, as well as the context manager protocol. .INDENT 7.0 .TP .B Parameters .INDENT 7.0 .IP \(bu 2 \fBsocket\fP (\fI\%socket.socket\fP) \-\- Server socket listening for new connections. .IP \(bu 2 \fBhandler\fP (\fICallable\fP\fI[\fP\fI[\fP\fI\%socket.socket\fP\fI, \fP\fIAny\fP\fI]\fP\fI, \fP\fI\%None\fP\fI]\fP) \-\- Handler for one connection. Receives the socket and address returned by \fI\%accept()\fP\&. .IP \(bu 2 \fBlogger\fP (\fIOptional\fP\fI[\fP\fI\%LoggerLike\fP\fI]\fP) \-\- Logger for this server. .UNINDENT .UNINDENT .INDENT 7.0 .TP .B serve_forever() See \fI\%socketserver.BaseServer.serve_forever()\fP\&. .sp This method doesn\(aqt return. Calling \fI\%shutdown()\fP from another thread stops the server. .sp Typical use: .INDENT 7.0 .INDENT 3.5 .sp .nf .ft C with serve(...) as server: server.serve_forever() .ft P .fi .UNINDENT .UNINDENT .INDENT 7.0 .UNINDENT .UNINDENT .INDENT 7.0 .TP .B shutdown() See \fI\%socketserver.BaseServer.shutdown()\fP\&. .INDENT 7.0 .UNINDENT .UNINDENT .INDENT 7.0 .TP .B fileno() See \fI\%socketserver.BaseServer.fileno()\fP\&. .INDENT 7.0 .UNINDENT .UNINDENT .UNINDENT .SS Using a connection .INDENT 0.0 .TP .B class websockets.sync.server.ServerConnection(socket, protocol, *, close_timeout=10) Threaded implementation of a WebSocket server connection. .sp \fI\%ServerConnection\fP provides \fI\%recv()\fP and \fI\%send()\fP methods for receiving and sending messages. .sp It supports iteration to receive messages: .INDENT 7.0 .INDENT 3.5 .sp .nf .ft C for message in websocket: process(message) .ft P .fi .UNINDENT .UNINDENT .sp The iterator exits normally when the connection is closed with close code 1000 (OK) or 1001 (going away) or without a close code. It raises a \fI\%ConnectionClosedError\fP when the connection is closed with any other code. .INDENT 7.0 .TP .B Parameters .INDENT 7.0 .IP \(bu 2 \fBsocket\fP (\fI\%socket.socket\fP) \-\- Socket connected to a WebSocket client. .IP \(bu 2 \fBprotocol\fP (\fI\%ServerProtocol\fP) \-\- Sans\-I/O connection. .IP \(bu 2 \fBclose_timeout\fP (\fIOptional\fP\fI[\fP\fI\%float\fP\fI]\fP) \-\- Timeout for closing the connection in seconds. .UNINDENT .UNINDENT .INDENT 7.0 .TP .B for ... in __iter__() Iterate on incoming messages. .sp The iterator calls \fI\%recv()\fP and yields messages in an infinite loop. .sp It exits when the connection is closed normally. It raises a \fI\%ConnectionClosedError\fP exception after a protocol error or a network failure. .INDENT 7.0 .UNINDENT .UNINDENT .INDENT 7.0 .TP .B recv(timeout=None) Receive the next message. .sp When the connection is closed, \fI\%recv()\fP raises \fI\%ConnectionClosed\fP\&. Specifically, it raises \fI\%ConnectionClosedOK\fP after a normal closure and \fI\%ConnectionClosedError\fP after a protocol error or a network failure. This is how you detect the end of the message stream. .sp If \fBtimeout\fP is \fI\%None\fP, block until a message is received. If \fBtimeout\fP is set and no message is received within \fBtimeout\fP seconds, raise \fI\%TimeoutError\fP\&. Set \fBtimeout\fP to \fB0\fP to check if a message was already received. .sp If the message is fragmented, wait until all fragments are received, reassemble them, and return the whole message. .INDENT 7.0 .TP .B Returns A string (\fI\%str\fP) for a \fI\%Text\fP frame or a bytestring (\fI\%bytes\fP) for a \fI\%Binary\fP frame. .TP .B Raises .INDENT 7.0 .IP \(bu 2 \fI\%ConnectionClosed\fP \-\- When the connection is closed. .IP \(bu 2 \fI\%RuntimeError\fP \-\- If two threads call \fI\%recv()\fP or \fI\%recv_streaming()\fP concurrently. .UNINDENT .TP .B Return type \fI\%str\fP | \fI\%bytes\fP .UNINDENT .UNINDENT .INDENT 7.0 .TP .B for ... in recv_streaming() Receive the next message frame by frame. .sp If the message is fragmented, yield each fragment as it is received. The iterator must be fully consumed, or else the connection will become unusable. .sp \fI\%recv_streaming()\fP raises the same exceptions as \fI\%recv()\fP\&. .INDENT 7.0 .TP .B Returns An iterator of strings (\fI\%str\fP) for a \fI\%Text\fP frame or bytestrings (\fI\%bytes\fP) for a \fI\%Binary\fP frame. .TP .B Raises .INDENT 7.0 .IP \(bu 2 \fI\%ConnectionClosed\fP \-\- When the connection is closed. .IP \(bu 2 \fI\%RuntimeError\fP \-\- If two threads call \fI\%recv()\fP or \fI\%recv_streaming()\fP concurrently. .UNINDENT .TP .B Return type \fI\%Iterator\fP[\fI\%str\fP | \fI\%bytes\fP] .UNINDENT .UNINDENT .INDENT 7.0 .TP .B send(message) Send a message. .sp A string (\fI\%str\fP) is sent as a \fI\%Text\fP frame. A bytestring or bytes\-like object (\fI\%bytes\fP, \fI\%bytearray\fP, or \fI\%memoryview\fP) is sent as a \fI\%Binary\fP frame. .sp \fI\%send()\fP also accepts an iterable of strings, bytestrings, or bytes\-like objects to enable \fI\%fragmentation\fP\&. Each item is treated as a message fragment and sent in its own frame. All items must be of the same type, or else \fI\%send()\fP will raise a \fI\%TypeError\fP and the connection will be closed. .sp \fI\%send()\fP rejects dict\-like objects because this is often an error. (If you really want to send the keys of a dict\-like object as fragments, call its \fI\%keys()\fP method and pass the result to \fI\%send()\fP\&.) .sp When the connection is closed, \fI\%send()\fP raises \fI\%ConnectionClosed\fP\&. Specifically, it raises \fI\%ConnectionClosedOK\fP after a normal connection closure and \fI\%ConnectionClosedError\fP after a protocol error or a network failure. .INDENT 7.0 .TP .B Parameters \fBmessage\fP (\fI\%str\fP\fI | \fP\fI\%bytes\fP\fI | \fP\fI\%Iterable\fP\fI[\fP\fI\%str\fP\fI | \fP\fI\%bytes\fP\fI]\fP) \-\- Message to send. .TP .B Raises .INDENT 7.0 .IP \(bu 2 \fI\%ConnectionClosed\fP \-\- When the connection is closed. .IP \(bu 2 \fI\%RuntimeError\fP \-\- If a connection is busy sending a fragmented message. .IP \(bu 2 \fI\%TypeError\fP \-\- If \fBmessage\fP doesn\(aqt have a supported type. .UNINDENT .UNINDENT .UNINDENT .INDENT 7.0 .TP .B close(code=CloseCode.NORMAL_CLOSURE, reason=\(aq\(aq) Perform the closing handshake. .sp \fI\%close()\fP waits for the other end to complete the handshake, for the TCP connection to terminate, and for all incoming messages to be read with \fI\%recv()\fP\&. .sp \fI\%close()\fP is idempotent: it doesn\(aqt do anything once the connection is closed. .INDENT 7.0 .TP .B Parameters .INDENT 7.0 .IP \(bu 2 \fBcode\fP (\fI\%int\fP) \-\- WebSocket close code. .IP \(bu 2 \fBreason\fP (\fI\%str\fP) \-\- WebSocket close reason. .UNINDENT .UNINDENT .UNINDENT .INDENT 7.0 .TP .B ping(data=None) Send a \fI\%Ping\fP\&. .sp A ping may serve as a keepalive or as a check that the remote endpoint received all messages up to this point .INDENT 7.0 .TP .B Parameters \fBdata\fP (\fI\%str\fP\fI | \fP\fI\%bytes\fP\fI | \fP\fI\%None\fP) \-\- Payload of the ping. A \fI\%str\fP will be encoded to UTF\-8. If \fBdata\fP is \fI\%None\fP, the payload is four random bytes. .TP .B Returns An event that will be set when the corresponding pong is received. You can ignore it if you don\(aqt intend to wait. .INDENT 7.0 .INDENT 3.5 .sp .nf .ft C pong_event = ws.ping() pong_event.wait() # only if you want to wait for the pong .ft P .fi .UNINDENT .UNINDENT .TP .B Raises .INDENT 7.0 .IP \(bu 2 \fI\%ConnectionClosed\fP \-\- When the connection is closed. .IP \(bu 2 \fI\%RuntimeError\fP \-\- If another ping was sent with the same data and the corresponding pong wasn\(aqt received yet. .UNINDENT .TP .B Return type \fI\%Event\fP .UNINDENT .UNINDENT .INDENT 7.0 .TP .B pong(data=b\(aq\(aq) Send a \fI\%Pong\fP\&. .sp An unsolicited pong may serve as a unidirectional heartbeat. .INDENT 7.0 .TP .B Parameters \fBdata\fP (\fI\%str\fP\fI | \fP\fI\%bytes\fP) \-\- Payload of the pong. A \fI\%str\fP will be encoded to UTF\-8. .TP .B Raises \fI\%ConnectionClosed\fP \-\- When the connection is closed. .UNINDENT .UNINDENT .sp WebSocket connection objects also provide these attributes: .INDENT 7.0 .TP .B id: \fI\%uuid.UUID\fP Unique identifier of the connection. Useful in logs. .UNINDENT .INDENT 7.0 .TP .B logger: \fI\%LoggerLike\fP Logger for this connection. .UNINDENT .INDENT 7.0 .TP .B property local_address: \fI\%Any\fP Local address of the connection. .sp For IPv4 connections, this is a \fB(host, port)\fP tuple. .sp The format of the address depends on the address family. See \fI\%getsockname()\fP\&. .UNINDENT .INDENT 7.0 .TP .B property remote_address: \fI\%Any\fP Remote address of the connection. .sp For IPv4 connections, this is a \fB(host, port)\fP tuple. .sp The format of the address depends on the address family. See \fI\%getpeername()\fP\&. .UNINDENT .sp The following attributes are available after the opening handshake, once the WebSocket connection is open: .INDENT 7.0 .TP .B request: \fI\%Request\fP | \fI\%None\fP Opening handshake request. .UNINDENT .INDENT 7.0 .TP .B response: \fI\%Response\fP | \fI\%None\fP Opening handshake response. .UNINDENT .INDENT 7.0 .TP .B property subprotocol: \fI\%Subprotocol\fP | \fI\%None\fP Subprotocol negotiated during the opening handshake. .sp \fI\%None\fP if no subprotocol was negotiated. .UNINDENT .UNINDENT .SS Client (\fI\%threading\fP) .SS Opening a connection .INDENT 0.0 .TP .B websockets.sync.client.connect(uri, *, sock=None, ssl_context=None, server_hostname=None, origin=None, extensions=None, subprotocols=None, additional_headers=None, user_agent_header=\(aqPython/x.y.z websockets/X.Y\(aq, compression=\(aqdeflate\(aq, open_timeout=10, close_timeout=10, max_size=2**20, logger=None, create_connection=None) Connect to the WebSocket server at \fBuri\fP\&. .sp This function returns a \fI\%ClientConnection\fP instance, which you can use to send and receive messages. .sp \fI\%connect()\fP may be used as a context manager: .INDENT 7.0 .INDENT 3.5 .sp .nf .ft C async with websockets.sync.client.connect(...) as websocket: ... .ft P .fi .UNINDENT .UNINDENT .sp The connection is closed automatically when exiting the context. .INDENT 7.0 .TP .B Parameters .INDENT 7.0 .IP \(bu 2 \fBuri\fP (\fI\%str\fP) \-\- URI of the WebSocket server. .IP \(bu 2 \fBsock\fP (\fI\%socket\fP\fI | \fP\fI\%None\fP) \-\- Preexisting TCP socket. \fBsock\fP overrides the host and port from \fBuri\fP\&. You may call \fI\%socket.create_connection()\fP to create a suitable TCP socket. .IP \(bu 2 \fBssl_context\fP (\fI\%SSLContext\fP\fI | \fP\fI\%None\fP) \-\- Configuration for enabling TLS on the connection. .IP \(bu 2 \fBserver_hostname\fP (\fI\%str\fP\fI | \fP\fI\%None\fP) \-\- Host name for the TLS handshake. \fBserver_hostname\fP overrides the host name from \fBuri\fP\&. .IP \(bu 2 \fBorigin\fP (\fI\%Origin\fP\fI | \fP\fI\%None\fP) \-\- Value of the \fBOrigin\fP header, for servers that require it. .IP \(bu 2 \fBextensions\fP (\fI\%Sequence\fP\fI[\fP\fI\%ClientExtensionFactory\fP\fI] \fP\fI| \fP\fI\%None\fP) \-\- List of supported extensions, in order in which they should be negotiated and run. .IP \(bu 2 \fBsubprotocols\fP (\fI\%Sequence\fP\fI[\fP\fI\%Subprotocol\fP\fI] \fP\fI| \fP\fI\%None\fP) \-\- List of supported subprotocols, in order of decreasing preference. .IP \(bu 2 \fBadditional_headers\fP (\fI\%HeadersLike\fP\fI | \fP\fI\%None\fP) \-\- Arbitrary HTTP headers to add to the handshake request. .IP \(bu 2 \fBuser_agent_header\fP (\fI\%str\fP\fI | \fP\fI\%None\fP) \-\- Value of the \fBUser\-Agent\fP request header. It defaults to \fB\(dqPython/x.y.z websockets/X.Y\(dq\fP\&. Setting it to \fI\%None\fP removes the header. .IP \(bu 2 \fBcompression\fP (\fI\%str\fP\fI | \fP\fI\%None\fP) \-\- The \(dqpermessage\-deflate\(dq extension is enabled by default. Set \fBcompression\fP to \fI\%None\fP to disable it. See the \fI\%compression guide\fP for details. .IP \(bu 2 \fBopen_timeout\fP (\fI\%float\fP\fI | \fP\fI\%None\fP) \-\- Timeout for opening the connection in seconds. \fI\%None\fP disables the timeout. .IP \(bu 2 \fBclose_timeout\fP (\fI\%float\fP\fI | \fP\fI\%None\fP) \-\- Timeout for closing the connection in seconds. \fI\%None\fP disables the timeout. .IP \(bu 2 \fBmax_size\fP (\fI\%int\fP\fI | \fP\fI\%None\fP) \-\- Maximum size of incoming messages in bytes. \fI\%None\fP disables the limit. .IP \(bu 2 \fBlogger\fP (\fI\%Logger\fP\fI | \fP\fI\%LoggerAdapter\fP\fI | \fP\fI\%None\fP) \-\- Logger for this client. It defaults to \fBlogging.getLogger(\(dqwebsockets.client\(dq)\fP\&. See the \fI\%logging guide\fP for details. .IP \(bu 2 \fBcreate_connection\fP (\fI\%Type\fP\fI[\fP\fI\%ClientConnection\fP\fI] \fP\fI| \fP\fI\%None\fP) \-\- Factory for the \fI\%ClientConnection\fP managing the connection. Set it to a wrapper or a subclass to customize connection handling. .UNINDENT .TP .B Raises .INDENT 7.0 .IP \(bu 2 \fI\%InvalidURI\fP \-\- If \fBuri\fP isn\(aqt a valid WebSocket URI. .IP \(bu 2 \fI\%OSError\fP \-\- If the TCP connection fails. .IP \(bu 2 \fI\%InvalidHandshake\fP \-\- If the opening handshake fails. .IP \(bu 2 \fI\%TimeoutError\fP \-\- If the opening handshake times out. .UNINDENT .UNINDENT .UNINDENT .INDENT 0.0 .TP .B websockets.sync.client.unix_connect(path, uri=None, *, sock=None, ssl_context=None, server_hostname=None, origin=None, extensions=None, subprotocols=None, additional_headers=None, user_agent_header=\(aqPython/x.y.z websockets/X.Y\(aq, compression=\(aqdeflate\(aq, open_timeout=10, close_timeout=10, max_size=2**20, logger=None, create_connection=None) Connect to a WebSocket server listening on a Unix socket. .sp This function is identical to \fI\%connect()\fP, except for the additional \fBpath\fP argument. It\(aqs only available on Unix. .sp It\(aqs mainly useful for debugging servers listening on Unix sockets. .INDENT 7.0 .TP .B Parameters .INDENT 7.0 .IP \(bu 2 \fBpath\fP (\fI\%str\fP\fI | \fP\fI\%None\fP) \-\- File system path to the Unix socket. .IP \(bu 2 \fBuri\fP (\fI\%str\fP\fI | \fP\fI\%None\fP) \-\- URI of the WebSocket server. \fBuri\fP defaults to \fBws://localhost/\fP or, when a \fBssl_context\fP is provided, to \fBwss://localhost/\fP\&. .UNINDENT .UNINDENT .UNINDENT .SS Using a connection .INDENT 0.0 .TP .B class websockets.sync.client.ClientConnection(socket, protocol, *, close_timeout=10) Threaded implementation of a WebSocket client connection. .sp \fI\%ClientConnection\fP provides \fI\%recv()\fP and \fI\%send()\fP methods for receiving and sending messages. .sp It supports iteration to receive messages: .INDENT 7.0 .INDENT 3.5 .sp .nf .ft C for message in websocket: process(message) .ft P .fi .UNINDENT .UNINDENT .sp The iterator exits normally when the connection is closed with close code 1000 (OK) or 1001 (going away) or without a close code. It raises a \fI\%ConnectionClosedError\fP when the connection is closed with any other code. .INDENT 7.0 .TP .B Parameters .INDENT 7.0 .IP \(bu 2 \fBsocket\fP (\fI\%socket.socket\fP) \-\- Socket connected to a WebSocket server. .IP \(bu 2 \fBprotocol\fP (\fI\%ClientProtocol\fP) \-\- Sans\-I/O connection. .IP \(bu 2 \fBclose_timeout\fP (\fIOptional\fP\fI[\fP\fI\%float\fP\fI]\fP) \-\- Timeout for closing the connection in seconds. .UNINDENT .UNINDENT .INDENT 7.0 .TP .B for ... in __iter__() Iterate on incoming messages. .sp The iterator calls \fI\%recv()\fP and yields messages in an infinite loop. .sp It exits when the connection is closed normally. It raises a \fI\%ConnectionClosedError\fP exception after a protocol error or a network failure. .INDENT 7.0 .UNINDENT .UNINDENT .INDENT 7.0 .TP .B recv(timeout=None) Receive the next message. .sp When the connection is closed, \fI\%recv()\fP raises \fI\%ConnectionClosed\fP\&. Specifically, it raises \fI\%ConnectionClosedOK\fP after a normal closure and \fI\%ConnectionClosedError\fP after a protocol error or a network failure. This is how you detect the end of the message stream. .sp If \fBtimeout\fP is \fI\%None\fP, block until a message is received. If \fBtimeout\fP is set and no message is received within \fBtimeout\fP seconds, raise \fI\%TimeoutError\fP\&. Set \fBtimeout\fP to \fB0\fP to check if a message was already received. .sp If the message is fragmented, wait until all fragments are received, reassemble them, and return the whole message. .INDENT 7.0 .TP .B Returns A string (\fI\%str\fP) for a \fI\%Text\fP frame or a bytestring (\fI\%bytes\fP) for a \fI\%Binary\fP frame. .TP .B Raises .INDENT 7.0 .IP \(bu 2 \fI\%ConnectionClosed\fP \-\- When the connection is closed. .IP \(bu 2 \fI\%RuntimeError\fP \-\- If two threads call \fI\%recv()\fP or \fI\%recv_streaming()\fP concurrently. .UNINDENT .TP .B Return type \fI\%str\fP | \fI\%bytes\fP .UNINDENT .UNINDENT .INDENT 7.0 .TP .B for ... in recv_streaming() Receive the next message frame by frame. .sp If the message is fragmented, yield each fragment as it is received. The iterator must be fully consumed, or else the connection will become unusable. .sp \fI\%recv_streaming()\fP raises the same exceptions as \fI\%recv()\fP\&. .INDENT 7.0 .TP .B Returns An iterator of strings (\fI\%str\fP) for a \fI\%Text\fP frame or bytestrings (\fI\%bytes\fP) for a \fI\%Binary\fP frame. .TP .B Raises .INDENT 7.0 .IP \(bu 2 \fI\%ConnectionClosed\fP \-\- When the connection is closed. .IP \(bu 2 \fI\%RuntimeError\fP \-\- If two threads call \fI\%recv()\fP or \fI\%recv_streaming()\fP concurrently. .UNINDENT .TP .B Return type \fI\%Iterator\fP[\fI\%str\fP | \fI\%bytes\fP] .UNINDENT .UNINDENT .INDENT 7.0 .TP .B send(message) Send a message. .sp A string (\fI\%str\fP) is sent as a \fI\%Text\fP frame. A bytestring or bytes\-like object (\fI\%bytes\fP, \fI\%bytearray\fP, or \fI\%memoryview\fP) is sent as a \fI\%Binary\fP frame. .sp \fI\%send()\fP also accepts an iterable of strings, bytestrings, or bytes\-like objects to enable \fI\%fragmentation\fP\&. Each item is treated as a message fragment and sent in its own frame. All items must be of the same type, or else \fI\%send()\fP will raise a \fI\%TypeError\fP and the connection will be closed. .sp \fI\%send()\fP rejects dict\-like objects because this is often an error. (If you really want to send the keys of a dict\-like object as fragments, call its \fI\%keys()\fP method and pass the result to \fI\%send()\fP\&.) .sp When the connection is closed, \fI\%send()\fP raises \fI\%ConnectionClosed\fP\&. Specifically, it raises \fI\%ConnectionClosedOK\fP after a normal connection closure and \fI\%ConnectionClosedError\fP after a protocol error or a network failure. .INDENT 7.0 .TP .B Parameters \fBmessage\fP (\fI\%str\fP\fI | \fP\fI\%bytes\fP\fI | \fP\fI\%Iterable\fP\fI[\fP\fI\%str\fP\fI | \fP\fI\%bytes\fP\fI]\fP) \-\- Message to send. .TP .B Raises .INDENT 7.0 .IP \(bu 2 \fI\%ConnectionClosed\fP \-\- When the connection is closed. .IP \(bu 2 \fI\%RuntimeError\fP \-\- If a connection is busy sending a fragmented message. .IP \(bu 2 \fI\%TypeError\fP \-\- If \fBmessage\fP doesn\(aqt have a supported type. .UNINDENT .UNINDENT .UNINDENT .INDENT 7.0 .TP .B close(code=CloseCode.NORMAL_CLOSURE, reason=\(aq\(aq) Perform the closing handshake. .sp \fI\%close()\fP waits for the other end to complete the handshake, for the TCP connection to terminate, and for all incoming messages to be read with \fI\%recv()\fP\&. .sp \fI\%close()\fP is idempotent: it doesn\(aqt do anything once the connection is closed. .INDENT 7.0 .TP .B Parameters .INDENT 7.0 .IP \(bu 2 \fBcode\fP (\fI\%int\fP) \-\- WebSocket close code. .IP \(bu 2 \fBreason\fP (\fI\%str\fP) \-\- WebSocket close reason. .UNINDENT .UNINDENT .UNINDENT .INDENT 7.0 .TP .B ping(data=None) Send a \fI\%Ping\fP\&. .sp A ping may serve as a keepalive or as a check that the remote endpoint received all messages up to this point .INDENT 7.0 .TP .B Parameters \fBdata\fP (\fI\%str\fP\fI | \fP\fI\%bytes\fP\fI | \fP\fI\%None\fP) \-\- Payload of the ping. A \fI\%str\fP will be encoded to UTF\-8. If \fBdata\fP is \fI\%None\fP, the payload is four random bytes. .TP .B Returns An event that will be set when the corresponding pong is received. You can ignore it if you don\(aqt intend to wait. .INDENT 7.0 .INDENT 3.5 .sp .nf .ft C pong_event = ws.ping() pong_event.wait() # only if you want to wait for the pong .ft P .fi .UNINDENT .UNINDENT .TP .B Raises .INDENT 7.0 .IP \(bu 2 \fI\%ConnectionClosed\fP \-\- When the connection is closed. .IP \(bu 2 \fI\%RuntimeError\fP \-\- If another ping was sent with the same data and the corresponding pong wasn\(aqt received yet. .UNINDENT .TP .B Return type \fI\%Event\fP .UNINDENT .UNINDENT .INDENT 7.0 .TP .B pong(data=b\(aq\(aq) Send a \fI\%Pong\fP\&. .sp An unsolicited pong may serve as a unidirectional heartbeat. .INDENT 7.0 .TP .B Parameters \fBdata\fP (\fI\%str\fP\fI | \fP\fI\%bytes\fP) \-\- Payload of the pong. A \fI\%str\fP will be encoded to UTF\-8. .TP .B Raises \fI\%ConnectionClosed\fP \-\- When the connection is closed. .UNINDENT .UNINDENT .sp WebSocket connection objects also provide these attributes: .INDENT 7.0 .TP .B id: \fI\%uuid.UUID\fP Unique identifier of the connection. Useful in logs. .UNINDENT .INDENT 7.0 .TP .B logger: \fI\%LoggerLike\fP Logger for this connection. .UNINDENT .INDENT 7.0 .TP .B property local_address: \fI\%Any\fP Local address of the connection. .sp For IPv4 connections, this is a \fB(host, port)\fP tuple. .sp The format of the address depends on the address family. See \fI\%getsockname()\fP\&. .UNINDENT .INDENT 7.0 .TP .B property remote_address: \fI\%Any\fP Remote address of the connection. .sp For IPv4 connections, this is a \fB(host, port)\fP tuple. .sp The format of the address depends on the address family. See \fI\%getpeername()\fP\&. .UNINDENT .sp The following attributes are available after the opening handshake, once the WebSocket connection is open: .INDENT 7.0 .TP .B request: \fI\%Request\fP | \fI\%None\fP Opening handshake request. .UNINDENT .INDENT 7.0 .TP .B response: \fI\%Response\fP | \fI\%None\fP Opening handshake response. .UNINDENT .INDENT 7.0 .TP .B property subprotocol: \fI\%Subprotocol\fP | \fI\%None\fP Subprotocol negotiated during the opening handshake. .sp \fI\%None\fP if no subprotocol was negotiated. .UNINDENT .UNINDENT .SS \fI\%Sans\-I/O\fP .sp This layer is designed for integrating in third\-party libraries, typically application servers. .SS Server (\fI\%Sans\-I/O\fP) .INDENT 0.0 .TP .B class websockets.server.ServerProtocol(origins=None, extensions=None, subprotocols=None, state=State.CONNECTING, max_size=2**20, logger=None) Sans\-I/O implementation of a WebSocket server connection. .INDENT 7.0 .TP .B Parameters .INDENT 7.0 .IP \(bu 2 \fBorigins\fP (\fIOptional\fP\fI[\fP\fISequence\fP\fI[\fP\fIOptional\fP\fI[\fP\fI\%Origin\fP\fI]\fP\fI]\fP\fI]\fP) \-\- acceptable values of the \fBOrigin\fP header; include \fI\%None\fP in the list if the lack of an origin is acceptable. This is useful for defending against Cross\-Site WebSocket Hijacking attacks. .IP \(bu 2 \fBextensions\fP (\fI\%List\fP\fI[\fP\fI\%Extension\fP\fI]\fP) \-\- list of supported extensions, in order in which they should be tried. .IP \(bu 2 \fBsubprotocols\fP (\fIOptional\fP\fI[\fP\fISequence\fP\fI[\fP\fI\%Subprotocol\fP\fI]\fP\fI]\fP) \-\- list of supported subprotocols, in order of decreasing preference. .IP \(bu 2 \fBselect_subprotocol\fP (\fIOptional\fP\fI[\fP\fICallable\fP\fI[\fP\fI[\fP\fI\%ServerProtocol\fP\fI, \fP\fISequence\fP\fI[\fP\fI\%Subprotocol\fP\fI]\fP\fI]\fP\fI, \fP\fIOptional\fP\fI[\fP\fI\%Subprotocol\fP\fI]\fP\fI]\fP\fI]\fP) \-\- Callback for selecting a subprotocol among those supported by the client and the server. It has the same signature as the \fI\%select_subprotocol()\fP method, including a \fI\%ServerProtocol\fP instance as first argument. .IP \(bu 2 \fBstate\fP (\fI\%State\fP) \-\- initial state of the WebSocket connection. .IP \(bu 2 \fBmax_size\fP (\fIOptional\fP\fI[\fP\fI\%int\fP\fI]\fP) \-\- maximum size of incoming messages in bytes; \fI\%None\fP disables the limit. .IP \(bu 2 \fBlogger\fP (\fI\%Logger\fP\fI | \fP\fI\%LoggerAdapter\fP) \-\- logger for this connection; defaults to \fBlogging.getLogger(\(dqwebsockets.client\(dq)\fP; see the \fI\%logging guide\fP for details. .UNINDENT .UNINDENT .INDENT 7.0 .TP .B receive_data(data) Receive data from the network. .sp After calling this method: .INDENT 7.0 .IP \(bu 2 You must call \fI\%data_to_send()\fP and send this data to the network. .IP \(bu 2 You should call \fI\%events_received()\fP and process resulting events. .UNINDENT .INDENT 7.0 .TP .B Raises \fI\%EOFError\fP \-\- if \fI\%receive_eof()\fP was called earlier. .UNINDENT .UNINDENT .INDENT 7.0 .TP .B receive_eof() Receive the end of the data stream from the network. .sp After calling this method: .INDENT 7.0 .IP \(bu 2 You must call \fI\%data_to_send()\fP and send this data to the network; it will return \fB[b\(dq\(dq]\fP, signaling the end of the stream, or \fB[]\fP\&. .IP \(bu 2 You aren\(aqt expected to call \fI\%events_received()\fP; it won\(aqt return any new events. .UNINDENT .INDENT 7.0 .TP .B Raises \fI\%EOFError\fP \-\- if \fI\%receive_eof()\fP was called earlier. .UNINDENT .UNINDENT .INDENT 7.0 .TP .B accept(request) Create a handshake response to accept the connection. .sp If the connection cannot be established, the handshake response actually rejects the handshake. .sp You must send the handshake response with \fI\%send_response()\fP\&. .sp You may modify it before sending it, for example to add HTTP headers. .INDENT 7.0 .TP .B Parameters \fBrequest\fP (\fI\%Request\fP) \-\- WebSocket handshake request event received from the client. .TP .B Returns WebSocket handshake response event to send to the client. .TP .B Return type \fI\%Response\fP .UNINDENT .UNINDENT .INDENT 7.0 .TP .B select_subprotocol(subprotocols) Pick a subprotocol among those offered by the client. .sp If several subprotocols are supported by both the client and the server, pick the first one in the list declared the server. .sp If the server doesn\(aqt support any subprotocols, continue without a subprotocol, regardless of what the client offers. .sp If the server supports at least one subprotocol and the client doesn\(aqt offer any, abort the handshake with an HTTP 400 error. .sp You provide a \fBselect_subprotocol\fP argument to \fI\%ServerProtocol\fP to override this logic. For example, you could accept the connection even if client doesn\(aqt offer a subprotocol, rather than reject it. .sp Here\(aqs how to negotiate the \fBchat\fP subprotocol if the client supports it and continue without a subprotocol otherwise: .INDENT 7.0 .INDENT 3.5 .sp .nf .ft C def select_subprotocol(protocol, subprotocols): if \(dqchat\(dq in subprotocols: return \(dqchat\(dq .ft P .fi .UNINDENT .UNINDENT .INDENT 7.0 .TP .B Parameters \fBsubprotocols\fP (\fI\%Sequence\fP\fI[\fP\fI\%Subprotocol\fP\fI]\fP) \-\- list of subprotocols offered by the client. .TP .B Returns Selected subprotocol, if a common subprotocol was found. .sp \fI\%None\fP to continue without a subprotocol. .TP .B Return type Optional[\fI\%Subprotocol\fP] .TP .B Raises \fI\%NegotiationError\fP \-\- custom implementations may raise this exception to abort the handshake with an HTTP 400 error. .UNINDENT .UNINDENT .INDENT 7.0 .TP .B reject(status, text) Create a handshake response to reject the connection. .sp A short plain text response is the best fallback when failing to establish a WebSocket connection. .sp You must send the handshake response with \fI\%send_response()\fP\&. .sp You can modify it before sending it, for example to alter HTTP headers. .INDENT 7.0 .TP .B Parameters .INDENT 7.0 .IP \(bu 2 \fBstatus\fP (\fI\%HTTPStatus\fP\fI | \fP\fI\%int\fP) \-\- HTTP status code. .IP \(bu 2 \fBtext\fP (\fI\%str\fP) \-\- HTTP response body; will be encoded to UTF\-8. .UNINDENT .TP .B Returns WebSocket handshake response event to send to the client. .TP .B Return type \fI\%Response\fP .UNINDENT .UNINDENT .INDENT 7.0 .TP .B send_response(response) Send a handshake response to the client. .INDENT 7.0 .TP .B Parameters \fBresponse\fP (\fI\%Response\fP) \-\- WebSocket handshake response event to send. .UNINDENT .UNINDENT .INDENT 7.0 .TP .B send_continuation(data, fin) Send a \fI\%Continuation frame\fP\&. .INDENT 7.0 .TP .B Parameters .INDENT 7.0 .IP \(bu 2 \fBdata\fP (\fI\%bytes\fP) \-\- payload containing the same kind of data as the initial frame. .IP \(bu 2 \fBfin\fP (\fI\%bool\fP) \-\- FIN bit; set it to \fI\%True\fP if this is the last frame of a fragmented message and to \fI\%False\fP otherwise. .UNINDENT .TP .B Raises \fI\%ProtocolError\fP \-\- if a fragmented message isn\(aqt in progress. .UNINDENT .UNINDENT .INDENT 7.0 .TP .B send_text(data, fin=True) Send a \fI\%Text frame\fP\&. .INDENT 7.0 .TP .B Parameters .INDENT 7.0 .IP \(bu 2 \fBdata\fP (\fI\%bytes\fP) \-\- payload containing text encoded with UTF\-8. .IP \(bu 2 \fBfin\fP (\fI\%bool\fP) \-\- FIN bit; set it to \fI\%False\fP if this is the first frame of a fragmented message. .UNINDENT .TP .B Raises \fI\%ProtocolError\fP \-\- if a fragmented message is in progress. .UNINDENT .UNINDENT .INDENT 7.0 .TP .B send_binary(data, fin=True) Send a \fI\%Binary frame\fP\&. .INDENT 7.0 .TP .B Parameters .INDENT 7.0 .IP \(bu 2 \fBdata\fP (\fI\%bytes\fP) \-\- payload containing arbitrary binary data. .IP \(bu 2 \fBfin\fP (\fI\%bool\fP) \-\- FIN bit; set it to \fI\%False\fP if this is the first frame of a fragmented message. .UNINDENT .TP .B Raises \fI\%ProtocolError\fP \-\- if a fragmented message is in progress. .UNINDENT .UNINDENT .INDENT 7.0 .TP .B send_close(code=None, reason=\(aq\(aq) Send a \fI\%Close frame\fP\&. .INDENT 7.0 .TP .B Parameters .INDENT 7.0 .IP \(bu 2 \fBcode\fP (\fI\%int\fP\fI | \fP\fI\%None\fP) \-\- close code. .IP \(bu 2 \fBreason\fP (\fI\%str\fP) \-\- close reason. .UNINDENT .TP .B Raises \fI\%ProtocolError\fP \-\- if a fragmented message is being sent, if the code isn\(aqt valid, or if a reason is provided without a code .UNINDENT .UNINDENT .INDENT 7.0 .TP .B send_ping(data) Send a \fI\%Ping frame\fP\&. .INDENT 7.0 .TP .B Parameters \fBdata\fP (\fI\%bytes\fP) \-\- payload containing arbitrary binary data. .UNINDENT .UNINDENT .INDENT 7.0 .TP .B send_pong(data) Send a \fI\%Pong frame\fP\&. .INDENT 7.0 .TP .B Parameters \fBdata\fP (\fI\%bytes\fP) \-\- payload containing arbitrary binary data. .UNINDENT .UNINDENT .INDENT 7.0 .TP .B fail(code, reason=\(aq\(aq) \fI\%Fail the WebSocket connection\fP\&. .INDENT 7.0 .TP .B Parameters .INDENT 7.0 .IP \(bu 2 \fBcode\fP (\fI\%int\fP) \-\- close code .IP \(bu 2 \fBreason\fP (\fI\%str\fP) \-\- close reason .UNINDENT .TP .B Raises \fI\%ProtocolError\fP \-\- if the code isn\(aqt valid. .UNINDENT .UNINDENT .INDENT 7.0 .TP .B events_received() Fetch events generated from data received from the network. .sp Call this method immediately after any of the \fBreceive_*()\fP methods. .sp Process resulting events, likely by passing them to the application. .INDENT 7.0 .TP .B Returns Events read from the connection. .TP .B Return type List[\fI\%Event\fP] .UNINDENT .UNINDENT .INDENT 7.0 .TP .B data_to_send() Obtain data to send to the network. .sp Call this method immediately after any of the \fBreceive_*()\fP, \fBsend_*()\fP, or \fI\%fail()\fP methods. .sp Write resulting data to the connection. .sp The empty bytestring \fI\%SEND_EOF\fP signals the end of the data stream. When you receive it, half\-close the TCP connection. .INDENT 7.0 .TP .B Returns Data to write to the connection. .TP .B Return type List[\fI\%bytes\fP] .UNINDENT .UNINDENT .INDENT 7.0 .TP .B close_expected() Tell if the TCP connection is expected to close soon. .sp Call this method immediately after any of the \fBreceive_*()\fP, \fBsend_close()\fP, or \fI\%fail()\fP methods. .sp If it returns \fI\%True\fP, schedule closing the TCP connection after a short timeout if the other side hasn\(aqt already closed it. .INDENT 7.0 .TP .B Returns Whether the TCP connection is expected to close soon. .TP .B Return type \fI\%bool\fP .UNINDENT .UNINDENT .sp WebSocket protocol objects also provide these attributes: .INDENT 7.0 .TP .B id: \fI\%uuid.UUID\fP Unique identifier of the connection. Useful in logs. .UNINDENT .INDENT 7.0 .TP .B logger: \fI\%LoggerLike\fP Logger for this connection. .UNINDENT .INDENT 7.0 .TP .B property state: \fI\%State\fP WebSocket connection state. .sp Defined in 4.1, 4.2, 7.1.3, and 7.1.4 of \fI\%RFC 6455\fP\&. .UNINDENT .sp The following attributes are available after the opening handshake, once the WebSocket connection is open: .INDENT 7.0 .TP .B handshake_exc: \fI\%Exception\fP | \fI\%None\fP Exception to raise if the opening handshake failed. .sp \fI\%None\fP if the opening handshake succeeded. .UNINDENT .sp The following attributes are available after the closing handshake, once the WebSocket connection is closed: .INDENT 7.0 .TP .B property close_code: \fI\%int\fP | \fI\%None\fP \fI\%WebSocket close code\fP\&. .sp \fI\%None\fP if the connection isn\(aqt closed yet. .UNINDENT .INDENT 7.0 .TP .B property close_reason: \fI\%str\fP | \fI\%None\fP \fI\%WebSocket close reason\fP\&. .sp \fI\%None\fP if the connection isn\(aqt closed yet. .UNINDENT .INDENT 7.0 .TP .B property close_exc: \fI\%ConnectionClosed\fP Exception to raise when trying to interact with a closed connection. .sp Don\(aqt raise this exception while the connection \fI\%state\fP is \fI\%CLOSING\fP; wait until it\(aqs \fI\%CLOSED\fP\&. .sp Indeed, the exception includes the close code and reason, which are known only once the connection is closed. .INDENT 7.0 .TP .B Raises \fI\%AssertionError\fP \-\- if the connection isn\(aqt closed yet. .UNINDENT .UNINDENT .UNINDENT .SS Client (\fI\%Sans\-I/O\fP) .INDENT 0.0 .TP .B class websockets.client.ClientProtocol(wsuri, origin=None, extensions=None, subprotocols=None, state=State.CONNECTING, max_size=2**20, logger=None) Sans\-I/O implementation of a WebSocket client connection. .INDENT 7.0 .TP .B Parameters .INDENT 7.0 .IP \(bu 2 \fBwsuri\fP (\fI\%WebSocketURI\fP) \-\- URI of the WebSocket server, parsed with \fI\%parse_uri()\fP\&. .IP \(bu 2 \fBorigin\fP (\fIOptional\fP\fI[\fP\fI\%Origin\fP\fI]\fP) \-\- value of the \fBOrigin\fP header. This is useful when connecting to a server that validates the \fBOrigin\fP header to defend against Cross\-Site WebSocket Hijacking attacks. .IP \(bu 2 \fBextensions\fP (\fIOptional\fP\fI[\fP\fISequence\fP\fI[\fP\fI\%ClientExtensionFactory\fP\fI]\fP\fI]\fP) \-\- list of supported extensions, in order in which they should be tried. .IP \(bu 2 \fBsubprotocols\fP (\fIOptional\fP\fI[\fP\fISequence\fP\fI[\fP\fI\%Subprotocol\fP\fI]\fP\fI]\fP) \-\- list of supported subprotocols, in order of decreasing preference. .IP \(bu 2 \fBstate\fP (\fI\%State\fP) \-\- initial state of the WebSocket connection. .IP \(bu 2 \fBmax_size\fP (\fIOptional\fP\fI[\fP\fI\%int\fP\fI]\fP) \-\- maximum size of incoming messages in bytes; \fI\%None\fP disables the limit. .IP \(bu 2 \fBlogger\fP (\fIOptional\fP\fI[\fP\fI\%LoggerLike\fP\fI]\fP) \-\- logger for this connection; defaults to \fBlogging.getLogger(\(dqwebsockets.client\(dq)\fP; see the \fI\%logging guide\fP for details. .UNINDENT .UNINDENT .INDENT 7.0 .TP .B receive_data(data) Receive data from the network. .sp After calling this method: .INDENT 7.0 .IP \(bu 2 You must call \fI\%data_to_send()\fP and send this data to the network. .IP \(bu 2 You should call \fI\%events_received()\fP and process resulting events. .UNINDENT .INDENT 7.0 .TP .B Raises \fI\%EOFError\fP \-\- if \fI\%receive_eof()\fP was called earlier. .UNINDENT .UNINDENT .INDENT 7.0 .TP .B receive_eof() Receive the end of the data stream from the network. .sp After calling this method: .INDENT 7.0 .IP \(bu 2 You must call \fI\%data_to_send()\fP and send this data to the network; it will return \fB[b\(dq\(dq]\fP, signaling the end of the stream, or \fB[]\fP\&. .IP \(bu 2 You aren\(aqt expected to call \fI\%events_received()\fP; it won\(aqt return any new events. .UNINDENT .INDENT 7.0 .TP .B Raises \fI\%EOFError\fP \-\- if \fI\%receive_eof()\fP was called earlier. .UNINDENT .UNINDENT .INDENT 7.0 .TP .B connect() Create a handshake request to open a connection. .sp You must send the handshake request with \fI\%send_request()\fP\&. .sp You can modify it before sending it, for example to add HTTP headers. .INDENT 7.0 .TP .B Returns WebSocket handshake request event to send to the server. .TP .B Return type \fI\%Request\fP .UNINDENT .UNINDENT .INDENT 7.0 .TP .B send_request(request) Send a handshake request to the server. .INDENT 7.0 .TP .B Parameters \fBrequest\fP (\fI\%Request\fP) \-\- WebSocket handshake request event. .UNINDENT .UNINDENT .INDENT 7.0 .TP .B send_continuation(data, fin) Send a \fI\%Continuation frame\fP\&. .INDENT 7.0 .TP .B Parameters .INDENT 7.0 .IP \(bu 2 \fBdata\fP (\fI\%bytes\fP) \-\- payload containing the same kind of data as the initial frame. .IP \(bu 2 \fBfin\fP (\fI\%bool\fP) \-\- FIN bit; set it to \fI\%True\fP if this is the last frame of a fragmented message and to \fI\%False\fP otherwise. .UNINDENT .TP .B Raises \fI\%ProtocolError\fP \-\- if a fragmented message isn\(aqt in progress. .UNINDENT .UNINDENT .INDENT 7.0 .TP .B send_text(data, fin=True) Send a \fI\%Text frame\fP\&. .INDENT 7.0 .TP .B Parameters .INDENT 7.0 .IP \(bu 2 \fBdata\fP (\fI\%bytes\fP) \-\- payload containing text encoded with UTF\-8. .IP \(bu 2 \fBfin\fP (\fI\%bool\fP) \-\- FIN bit; set it to \fI\%False\fP if this is the first frame of a fragmented message. .UNINDENT .TP .B Raises \fI\%ProtocolError\fP \-\- if a fragmented message is in progress. .UNINDENT .UNINDENT .INDENT 7.0 .TP .B send_binary(data, fin=True) Send a \fI\%Binary frame\fP\&. .INDENT 7.0 .TP .B Parameters .INDENT 7.0 .IP \(bu 2 \fBdata\fP (\fI\%bytes\fP) \-\- payload containing arbitrary binary data. .IP \(bu 2 \fBfin\fP (\fI\%bool\fP) \-\- FIN bit; set it to \fI\%False\fP if this is the first frame of a fragmented message. .UNINDENT .TP .B Raises \fI\%ProtocolError\fP \-\- if a fragmented message is in progress. .UNINDENT .UNINDENT .INDENT 7.0 .TP .B send_close(code=None, reason=\(aq\(aq) Send a \fI\%Close frame\fP\&. .INDENT 7.0 .TP .B Parameters .INDENT 7.0 .IP \(bu 2 \fBcode\fP (\fI\%int\fP\fI | \fP\fI\%None\fP) \-\- close code. .IP \(bu 2 \fBreason\fP (\fI\%str\fP) \-\- close reason. .UNINDENT .TP .B Raises \fI\%ProtocolError\fP \-\- if a fragmented message is being sent, if the code isn\(aqt valid, or if a reason is provided without a code .UNINDENT .UNINDENT .INDENT 7.0 .TP .B send_ping(data) Send a \fI\%Ping frame\fP\&. .INDENT 7.0 .TP .B Parameters \fBdata\fP (\fI\%bytes\fP) \-\- payload containing arbitrary binary data. .UNINDENT .UNINDENT .INDENT 7.0 .TP .B send_pong(data) Send a \fI\%Pong frame\fP\&. .INDENT 7.0 .TP .B Parameters \fBdata\fP (\fI\%bytes\fP) \-\- payload containing arbitrary binary data. .UNINDENT .UNINDENT .INDENT 7.0 .TP .B fail(code, reason=\(aq\(aq) \fI\%Fail the WebSocket connection\fP\&. .INDENT 7.0 .TP .B Parameters .INDENT 7.0 .IP \(bu 2 \fBcode\fP (\fI\%int\fP) \-\- close code .IP \(bu 2 \fBreason\fP (\fI\%str\fP) \-\- close reason .UNINDENT .TP .B Raises \fI\%ProtocolError\fP \-\- if the code isn\(aqt valid. .UNINDENT .UNINDENT .INDENT 7.0 .TP .B events_received() Fetch events generated from data received from the network. .sp Call this method immediately after any of the \fBreceive_*()\fP methods. .sp Process resulting events, likely by passing them to the application. .INDENT 7.0 .TP .B Returns Events read from the connection. .TP .B Return type List[\fI\%Event\fP] .UNINDENT .UNINDENT .INDENT 7.0 .TP .B data_to_send() Obtain data to send to the network. .sp Call this method immediately after any of the \fBreceive_*()\fP, \fBsend_*()\fP, or \fI\%fail()\fP methods. .sp Write resulting data to the connection. .sp The empty bytestring \fI\%SEND_EOF\fP signals the end of the data stream. When you receive it, half\-close the TCP connection. .INDENT 7.0 .TP .B Returns Data to write to the connection. .TP .B Return type List[\fI\%bytes\fP] .UNINDENT .UNINDENT .INDENT 7.0 .TP .B close_expected() Tell if the TCP connection is expected to close soon. .sp Call this method immediately after any of the \fBreceive_*()\fP, \fBsend_close()\fP, or \fI\%fail()\fP methods. .sp If it returns \fI\%True\fP, schedule closing the TCP connection after a short timeout if the other side hasn\(aqt already closed it. .INDENT 7.0 .TP .B Returns Whether the TCP connection is expected to close soon. .TP .B Return type \fI\%bool\fP .UNINDENT .UNINDENT .sp WebSocket protocol objects also provide these attributes: .INDENT 7.0 .TP .B id: \fI\%uuid.UUID\fP Unique identifier of the connection. Useful in logs. .UNINDENT .INDENT 7.0 .TP .B logger: \fI\%LoggerLike\fP Logger for this connection. .UNINDENT .INDENT 7.0 .TP .B property state: \fI\%State\fP WebSocket connection state. .sp Defined in 4.1, 4.2, 7.1.3, and 7.1.4 of \fI\%RFC 6455\fP\&. .UNINDENT .sp The following attributes are available after the opening handshake, once the WebSocket connection is open: .INDENT 7.0 .TP .B handshake_exc: \fI\%Exception\fP | \fI\%None\fP Exception to raise if the opening handshake failed. .sp \fI\%None\fP if the opening handshake succeeded. .UNINDENT .sp The following attributes are available after the closing handshake, once the WebSocket connection is closed: .INDENT 7.0 .TP .B property close_code: \fI\%int\fP | \fI\%None\fP \fI\%WebSocket close code\fP\&. .sp \fI\%None\fP if the connection isn\(aqt closed yet. .UNINDENT .INDENT 7.0 .TP .B property close_reason: \fI\%str\fP | \fI\%None\fP \fI\%WebSocket close reason\fP\&. .sp \fI\%None\fP if the connection isn\(aqt closed yet. .UNINDENT .INDENT 7.0 .TP .B property close_exc: \fI\%ConnectionClosed\fP Exception to raise when trying to interact with a closed connection. .sp Don\(aqt raise this exception while the connection \fI\%state\fP is \fI\%CLOSING\fP; wait until it\(aqs \fI\%CLOSED\fP\&. .sp Indeed, the exception includes the close code and reason, which are known only once the connection is closed. .INDENT 7.0 .TP .B Raises \fI\%AssertionError\fP \-\- if the connection isn\(aqt closed yet. .UNINDENT .UNINDENT .UNINDENT .SS Extensions .sp The Per\-Message Deflate extension is built in. You may also define custom extensions. .SS Extensions .sp The WebSocket protocol supports \fI\%extensions\fP\&. .sp At the time of writing, there\(aqs only one \fI\%registered extension\fP with a public specification, WebSocket Per\-Message Deflate. .SS Per\-Message Deflate .sp \fI\%websockets.extensions.permessage_deflate\fP implements WebSocket Per\-Message Deflate. .sp This extension is specified in \fI\%RFC 7692\fP\&. .sp Refer to the \fI\%topic guide on compression\fP to learn more about tuning compression settings. .INDENT 0.0 .TP .B class websockets.extensions.permessage_deflate.ClientPerMessageDeflateFactory(server_no_context_takeover=False, client_no_context_takeover=False, server_max_window_bits=None, client_max_window_bits=True, compress_settings=None) Client\-side extension factory for the Per\-Message Deflate extension. .sp Parameters behave as described in \fI\%section 7.1 of RFC 7692\fP\&. .sp Set them to \fI\%True\fP to include them in the negotiation offer without a value or to an integer value to include them with this value. .INDENT 7.0 .TP .B Parameters .INDENT 7.0 .IP \(bu 2 \fBserver_no_context_takeover\fP (\fI\%bool\fP) \-\- prevent server from using context takeover. .IP \(bu 2 \fBclient_no_context_takeover\fP (\fI\%bool\fP) \-\- prevent client from using context takeover. .IP \(bu 2 \fBserver_max_window_bits\fP (\fIOptional\fP\fI[\fP\fI\%int\fP\fI]\fP) \-\- maximum size of the server\(aqs LZ77 sliding window in bits, between 8 and 15. .IP \(bu 2 \fBclient_max_window_bits\fP (\fIOptional\fP\fI[\fP\fIUnion\fP\fI[\fP\fI\%int\fP\fI, \fP\fI\%bool\fP\fI]\fP\fI]\fP) \-\- maximum size of the client\(aqs LZ77 sliding window in bits, between 8 and 15, or \fI\%True\fP to indicate support without setting a limit. .IP \(bu 2 \fBcompress_settings\fP (\fIOptional\fP\fI[\fP\fIDict\fP\fI[\fP\fI\%str\fP\fI, \fP\fIAny\fP\fI]\fP\fI]\fP) \-\- additional keyword arguments for \fI\%zlib.compressobj()\fP, excluding \fBwbits\fP\&. .UNINDENT .UNINDENT .UNINDENT .INDENT 0.0 .TP .B class websockets.extensions.permessage_deflate.ServerPerMessageDeflateFactory(server_no_context_takeover=False, client_no_context_takeover=False, server_max_window_bits=None, client_max_window_bits=None, compress_settings=None, require_client_max_window_bits=False) Server\-side extension factory for the Per\-Message Deflate extension. .sp Parameters behave as described in \fI\%section 7.1 of RFC 7692\fP\&. .sp Set them to \fI\%True\fP to include them in the negotiation offer without a value or to an integer value to include them with this value. .INDENT 7.0 .TP .B Parameters .INDENT 7.0 .IP \(bu 2 \fBserver_no_context_takeover\fP (\fI\%bool\fP) \-\- prevent server from using context takeover. .IP \(bu 2 \fBclient_no_context_takeover\fP (\fI\%bool\fP) \-\- prevent client from using context takeover. .IP \(bu 2 \fBserver_max_window_bits\fP (\fIOptional\fP\fI[\fP\fI\%int\fP\fI]\fP) \-\- maximum size of the server\(aqs LZ77 sliding window in bits, between 8 and 15. .IP \(bu 2 \fBclient_max_window_bits\fP (\fIOptional\fP\fI[\fP\fI\%int\fP\fI]\fP) \-\- maximum size of the client\(aqs LZ77 sliding window in bits, between 8 and 15. .IP \(bu 2 \fBcompress_settings\fP (\fIOptional\fP\fI[\fP\fIDict\fP\fI[\fP\fI\%str\fP\fI, \fP\fIAny\fP\fI]\fP\fI]\fP) \-\- additional keyword arguments for \fI\%zlib.compressobj()\fP, excluding \fBwbits\fP\&. .IP \(bu 2 \fBrequire_client_max_window_bits\fP (\fI\%bool\fP) \-\- do not enable compression at all if client doesn\(aqt advertise support for \fBclient_max_window_bits\fP; the default behavior is to enable compression without enforcing \fBclient_max_window_bits\fP\&. .UNINDENT .UNINDENT .UNINDENT .SS Base classes .INDENT 0.0 .TP .B \fI\%websockets.extensions\fP defines base classes for implementing extensions. .UNINDENT .sp Refer to the \fI\%how\-to guide on extensions\fP to learn more about writing an extension. .INDENT 0.0 .TP .B class websockets.extensions.Extension Base class for extensions. .INDENT 7.0 .TP .B name: \fI\%ExtensionName\fP Extension identifier. .UNINDENT .INDENT 7.0 .TP .B decode(frame, *, max_size=None) Decode an incoming frame. .INDENT 7.0 .TP .B Parameters .INDENT 7.0 .IP \(bu 2 \fBframe\fP (\fI\%Frame\fP) \-\- incoming frame. .IP \(bu 2 \fBmax_size\fP (\fI\%int\fP\fI | \fP\fI\%None\fP) \-\- maximum payload size in bytes. .UNINDENT .TP .B Returns Decoded frame. .TP .B Return type \fI\%Frame\fP .TP .B Raises \fI\%PayloadTooBig\fP \-\- if decoding the payload exceeds \fBmax_size\fP\&. .UNINDENT .UNINDENT .INDENT 7.0 .TP .B encode(frame) Encode an outgoing frame. .INDENT 7.0 .TP .B Parameters \fBframe\fP (\fI\%Frame\fP) \-\- outgoing frame. .TP .B Returns Encoded frame. .TP .B Return type \fI\%Frame\fP .UNINDENT .UNINDENT .UNINDENT .INDENT 0.0 .TP .B class websockets.extensions.ClientExtensionFactory Base class for client\-side extension factories. .INDENT 7.0 .TP .B name: \fI\%ExtensionName\fP Extension identifier. .UNINDENT .INDENT 7.0 .TP .B get_request_params() Build parameters to send to the server for this extension. .INDENT 7.0 .TP .B Returns Parameters to send to the server. .TP .B Return type List[\fI\%ExtensionParameter\fP] .UNINDENT .UNINDENT .INDENT 7.0 .TP .B process_response_params(params, accepted_extensions) Process parameters received from the server. .INDENT 7.0 .TP .B Parameters .INDENT 7.0 .IP \(bu 2 \fBparams\fP (\fISequence\fP\fI[\fP\fI\%ExtensionParameter\fP\fI]\fP) \-\- parameters received from the server for this extension. .IP \(bu 2 \fBaccepted_extensions\fP (\fISequence\fP\fI[\fP\fI\%Extension\fP\fI]\fP) \-\- list of previously accepted extensions. .UNINDENT .TP .B Returns An extension instance. .TP .B Return type \fI\%Extension\fP .TP .B Raises \fI\%NegotiationError\fP \-\- if parameters aren\(aqt acceptable. .UNINDENT .UNINDENT .UNINDENT .INDENT 0.0 .TP .B class websockets.extensions.ServerExtensionFactory Base class for server\-side extension factories. .INDENT 7.0 .TP .B process_request_params(params, accepted_extensions) Process parameters received from the client. .INDENT 7.0 .TP .B Parameters .INDENT 7.0 .IP \(bu 2 \fBparams\fP (\fISequence\fP\fI[\fP\fI\%ExtensionParameter\fP\fI]\fP) \-\- parameters received from the client for this extension. .IP \(bu 2 \fBaccepted_extensions\fP (\fISequence\fP\fI[\fP\fI\%Extension\fP\fI]\fP) \-\- list of previously accepted extensions. .UNINDENT .TP .B Returns To accept the offer, parameters to send to the client for this extension and an extension instance. .TP .B Return type Tuple[List[\fI\%ExtensionParameter\fP], \fI\%Extension\fP] .TP .B Raises \fI\%NegotiationError\fP \-\- to reject the offer, if parameters received from the client aren\(aqt acceptable. .UNINDENT .UNINDENT .UNINDENT .SS Shared .sp These low\-level APIs are shared by all implementations. .SS Data structures .SS WebSocket events .INDENT 0.0 .TP .B class websockets.frames.Frame(opcode, data, fin=True, rsv1=False, rsv2=False, rsv3=False) WebSocket frame. .INDENT 7.0 .UNINDENT .INDENT 7.0 .TP .B opcode Opcode. .INDENT 7.0 .TP .B Type \fI\%websockets.frames.Opcode\fP .UNINDENT .UNINDENT .INDENT 7.0 .TP .B data Payload data. .INDENT 7.0 .TP .B Type \fI\%bytes\fP .UNINDENT .UNINDENT .INDENT 7.0 .TP .B fin FIN bit. .INDENT 7.0 .TP .B Type \fI\%bool\fP .UNINDENT .UNINDENT .INDENT 7.0 .TP .B rsv1 RSV1 bit. .INDENT 7.0 .TP .B Type \fI\%bool\fP .UNINDENT .UNINDENT .INDENT 7.0 .TP .B rsv2 RSV2 bit. .INDENT 7.0 .TP .B Type \fI\%bool\fP .UNINDENT .UNINDENT .INDENT 7.0 .TP .B rsv3 RSV3 bit. .INDENT 7.0 .TP .B Type \fI\%bool\fP .UNINDENT .UNINDENT .sp Only these fields are needed. The MASK bit, payload length and masking\-key are handled on the fly when parsing and serializing frames. .UNINDENT .INDENT 0.0 .TP .B class websockets.frames.Opcode(value, names=None, *values, module=None, qualname=None, type=None, start=1, boundary=None) Opcode values for WebSocket frames. .INDENT 7.0 .TP .B CONT = 0 .UNINDENT .INDENT 7.0 .TP .B TEXT = 1 .UNINDENT .INDENT 7.0 .TP .B BINARY = 2 .UNINDENT .INDENT 7.0 .TP .B CLOSE = 8 .UNINDENT .INDENT 7.0 .TP .B PING = 9 .UNINDENT .INDENT 7.0 .TP .B PONG = 10 .UNINDENT .UNINDENT .INDENT 0.0 .TP .B class websockets.frames.Close(code, reason) Code and reason for WebSocket close frames. .INDENT 7.0 .UNINDENT .INDENT 7.0 .TP .B code Close code. .INDENT 7.0 .TP .B Type \fI\%int\fP .UNINDENT .UNINDENT .INDENT 7.0 .TP .B reason Close reason. .INDENT 7.0 .TP .B Type \fI\%str\fP .UNINDENT .UNINDENT .UNINDENT .INDENT 0.0 .TP .B class websockets.frames.CloseCode(value, names=None, *values, module=None, qualname=None, type=None, start=1, boundary=None) Close code values for WebSocket close frames. .INDENT 7.0 .TP .B NORMAL_CLOSURE = 1000 .UNINDENT .INDENT 7.0 .TP .B GOING_AWAY = 1001 .UNINDENT .INDENT 7.0 .TP .B PROTOCOL_ERROR = 1002 .UNINDENT .INDENT 7.0 .TP .B UNSUPPORTED_DATA = 1003 .UNINDENT .INDENT 7.0 .TP .B NO_STATUS_RCVD = 1005 .UNINDENT .INDENT 7.0 .TP .B ABNORMAL_CLOSURE = 1006 .UNINDENT .INDENT 7.0 .TP .B INVALID_DATA = 1007 .UNINDENT .INDENT 7.0 .TP .B POLICY_VIOLATION = 1008 .UNINDENT .INDENT 7.0 .TP .B MESSAGE_TOO_BIG = 1009 .UNINDENT .INDENT 7.0 .TP .B MANDATORY_EXTENSION = 1010 .UNINDENT .INDENT 7.0 .TP .B INTERNAL_ERROR = 1011 .UNINDENT .INDENT 7.0 .TP .B SERVICE_RESTART = 1012 .UNINDENT .INDENT 7.0 .TP .B TRY_AGAIN_LATER = 1013 .UNINDENT .INDENT 7.0 .TP .B BAD_GATEWAY = 1014 .UNINDENT .INDENT 7.0 .TP .B TLS_HANDSHAKE = 1015 .UNINDENT .UNINDENT .SS HTTP events .INDENT 0.0 .TP .B class websockets.http11.Request(path, headers, _exception=None) WebSocket handshake request. .INDENT 7.0 .UNINDENT .INDENT 7.0 .TP .B path Request path, including optional query. .INDENT 7.0 .TP .B Type \fI\%str\fP .UNINDENT .UNINDENT .INDENT 7.0 .TP .B headers Request headers. .INDENT 7.0 .TP .B Type \fI\%websockets.datastructures.Headers\fP .UNINDENT .UNINDENT .UNINDENT .INDENT 0.0 .TP .B class websockets.http11.Response(status_code, reason_phrase, headers, body=None, _exception=None) WebSocket handshake response. .INDENT 7.0 .UNINDENT .INDENT 7.0 .TP .B status_code Response code. .INDENT 7.0 .TP .B Type \fI\%int\fP .UNINDENT .UNINDENT .INDENT 7.0 .TP .B reason_phrase Response reason. .INDENT 7.0 .TP .B Type \fI\%str\fP .UNINDENT .UNINDENT .INDENT 7.0 .TP .B headers Response headers. .INDENT 7.0 .TP .B Type \fI\%websockets.datastructures.Headers\fP .UNINDENT .UNINDENT .INDENT 7.0 .TP .B body Response body, if any. .INDENT 7.0 .TP .B Type \fI\%bytes\fP | \fI\%None\fP .UNINDENT .UNINDENT .UNINDENT .INDENT 0.0 .TP .B class websockets.datastructures.Headers(*args, **kwargs) Efficient data structure for manipulating HTTP headers. .sp A \fI\%list\fP of \fB(name, values)\fP is inefficient for lookups. .sp A \fI\%dict\fP doesn\(aqt suffice because header names are case\-insensitive and multiple occurrences of headers with the same name are possible. .sp \fI\%Headers\fP stores HTTP headers in a hybrid data structure to provide efficient insertions and lookups while preserving the original data. .sp In order to account for multiple values with minimal hassle, \fI\%Headers\fP follows this logic: .INDENT 7.0 .IP \(bu 2 .INDENT 2.0 .TP .B When getting a header with \fBheaders[name]\fP: .INDENT 7.0 .IP \(bu 2 if there\(aqs no value, \fI\%KeyError\fP is raised; .IP \(bu 2 if there\(aqs exactly one value, it\(aqs returned; .IP \(bu 2 if there\(aqs more than one value, \fI\%MultipleValuesError\fP is raised. .UNINDENT .UNINDENT .IP \(bu 2 When setting a header with \fBheaders[name] = value\fP, the value is appended to the list of values for that header. .IP \(bu 2 When deleting a header with \fBdel headers[name]\fP, all values for that header are removed (this is slow). .UNINDENT .sp Other methods for manipulating headers are consistent with this logic. .sp As long as no header occurs multiple times, \fI\%Headers\fP behaves like \fI\%dict\fP, except keys are lower\-cased to provide case\-insensitivity. .sp Two methods support manipulating multiple values explicitly: .INDENT 7.0 .IP \(bu 2 \fI\%get_all()\fP returns a list of all values for a header; .IP \(bu 2 \fI\%raw_items()\fP returns an iterator of \fB(name, values)\fP pairs. .UNINDENT .INDENT 7.0 .UNINDENT .INDENT 7.0 .TP .B get_all(key) Return the (possibly empty) list of all values for a header. .INDENT 7.0 .TP .B Parameters \fBkey\fP (\fI\%str\fP) \-\- header name. .UNINDENT .UNINDENT .INDENT 7.0 .TP .B raw_items() Return an iterator of all values as \fB(name, value)\fP pairs. .INDENT 7.0 .UNINDENT .UNINDENT .UNINDENT .INDENT 0.0 .TP .B exception websockets.datastructures.MultipleValuesError Exception raised when \fI\%Headers\fP has more than one value for a key. .UNINDENT .SS URIs .INDENT 0.0 .TP .B websockets.uri.parse_uri(uri) Parse and validate a WebSocket URI. .INDENT 7.0 .TP .B Parameters \fBuri\fP (\fI\%str\fP) \-\- WebSocket URI. .TP .B Returns Parsed WebSocket URI. .TP .B Return type \fI\%WebSocketURI\fP .TP .B Raises \fI\%InvalidURI\fP \-\- if \fBuri\fP isn\(aqt a valid WebSocket URI. .UNINDENT .UNINDENT .INDENT 0.0 .TP .B class websockets.uri.WebSocketURI(secure, host, port, path, query, username=None, password=None) WebSocket URI. .INDENT 7.0 .UNINDENT .INDENT 7.0 .TP .B secure \fI\%True\fP for a \fBwss\fP URI, \fI\%False\fP for a \fBws\fP URI. .INDENT 7.0 .TP .B Type \fI\%bool\fP .UNINDENT .UNINDENT .INDENT 7.0 .TP .B host Normalized to lower case. .INDENT 7.0 .TP .B Type \fI\%str\fP .UNINDENT .UNINDENT .INDENT 7.0 .TP .B port Always set even if it\(aqs the default. .INDENT 7.0 .TP .B Type \fI\%int\fP .UNINDENT .UNINDENT .INDENT 7.0 .TP .B path May be empty. .INDENT 7.0 .TP .B Type \fI\%str\fP .UNINDENT .UNINDENT .INDENT 7.0 .TP .B query May be empty if the URI doesn\(aqt include a query component. .INDENT 7.0 .TP .B Type \fI\%str\fP .UNINDENT .UNINDENT .INDENT 7.0 .TP .B username Available when the URI contains \fI\%User Information\fP\&. .INDENT 7.0 .TP .B Type \fI\%str\fP | \fI\%None\fP .UNINDENT .UNINDENT .INDENT 7.0 .TP .B password Available when the URI contains \fI\%User Information\fP\&. .INDENT 7.0 .TP .B Type \fI\%str\fP | \fI\%None\fP .UNINDENT .UNINDENT .UNINDENT .SS Exceptions .sp \fI\%websockets.exceptions\fP defines the following exception hierarchy: .INDENT 0.0 .IP \(bu 2 .INDENT 2.0 .TP .B \fI\%WebSocketException\fP .INDENT 7.0 .IP \(bu 2 .INDENT 2.0 .TP .B \fI\%ConnectionClosed\fP .INDENT 7.0 .IP \(bu 2 \fI\%ConnectionClosedError\fP .IP \(bu 2 \fI\%ConnectionClosedOK\fP .UNINDENT .UNINDENT .IP \(bu 2 .INDENT 2.0 .TP .B \fI\%InvalidHandshake\fP .INDENT 7.0 .IP \(bu 2 \fI\%SecurityError\fP .IP \(bu 2 \fI\%InvalidMessage\fP .IP \(bu 2 .INDENT 2.0 .TP .B \fI\%InvalidHeader\fP .INDENT 7.0 .IP \(bu 2 \fI\%InvalidHeaderFormat\fP .IP \(bu 2 \fI\%InvalidHeaderValue\fP .IP \(bu 2 \fI\%InvalidOrigin\fP .IP \(bu 2 \fI\%InvalidUpgrade\fP .UNINDENT .UNINDENT .IP \(bu 2 \fI\%InvalidStatus\fP .IP \(bu 2 \fI\%InvalidStatusCode\fP (legacy) .IP \(bu 2 .INDENT 2.0 .TP .B \fI\%NegotiationError\fP .INDENT 7.0 .IP \(bu 2 \fI\%DuplicateParameter\fP .IP \(bu 2 \fI\%InvalidParameterName\fP .IP \(bu 2 \fI\%InvalidParameterValue\fP .UNINDENT .UNINDENT .IP \(bu 2 \fI\%AbortHandshake\fP .IP \(bu 2 \fI\%RedirectHandshake\fP .UNINDENT .UNINDENT .IP \(bu 2 \fI\%InvalidState\fP .IP \(bu 2 \fI\%InvalidURI\fP .IP \(bu 2 \fI\%PayloadTooBig\fP .IP \(bu 2 \fI\%ProtocolError\fP .UNINDENT .UNINDENT .UNINDENT .INDENT 0.0 .TP .B exception websockets.exceptions.AbortHandshake(status, headers, body=b\(aq\(aq) Raised to abort the handshake on purpose and return an HTTP response. .sp This exception is an implementation detail. .sp The public API is \fI\%process_request()\fP\&. .INDENT 7.0 .UNINDENT .INDENT 7.0 .TP .B status HTTP status code. .INDENT 7.0 .TP .B Type \fI\%HTTPStatus\fP .UNINDENT .UNINDENT .INDENT 7.0 .TP .B headers HTTP response headers. .INDENT 7.0 .TP .B Type \fI\%Headers\fP .UNINDENT .UNINDENT .INDENT 7.0 .TP .B body HTTP response body. .INDENT 7.0 .TP .B Type \fI\%bytes\fP .UNINDENT .UNINDENT .UNINDENT .INDENT 0.0 .TP .B exception websockets.exceptions.ConnectionClosed(rcvd, sent, rcvd_then_sent=None) Raised when trying to interact with a closed connection. .INDENT 7.0 .UNINDENT .INDENT 7.0 .TP .B rcvd if a close frame was received, its code and reason are available in \fBrcvd.code\fP and \fBrcvd.reason\fP\&. .INDENT 7.0 .TP .B Type Optional[\fI\%Close\fP] .UNINDENT .UNINDENT .INDENT 7.0 .TP .B sent if a close frame was sent, its code and reason are available in \fBsent.code\fP and \fBsent.reason\fP\&. .INDENT 7.0 .TP .B Type Optional[\fI\%Close\fP] .UNINDENT .UNINDENT .INDENT 7.0 .TP .B rcvd_then_sent if close frames were received and sent, this attribute tells in which order this happened, from the perspective of this side of the connection. .INDENT 7.0 .TP .B Type Optional[\fI\%bool\fP] .UNINDENT .UNINDENT .UNINDENT .INDENT 0.0 .TP .B exception websockets.exceptions.ConnectionClosedError(rcvd, sent, rcvd_then_sent=None) Like \fI\%ConnectionClosed\fP, when the connection terminated with an error. .sp A close frame with a code other than 1000 (OK) or 1001 (going away) was received or sent, or the closing handshake didn\(aqt complete properly. .INDENT 7.0 .UNINDENT .UNINDENT .INDENT 0.0 .TP .B exception websockets.exceptions.ConnectionClosedOK(rcvd, sent, rcvd_then_sent=None) Like \fI\%ConnectionClosed\fP, when the connection terminated properly. .sp A close code with code 1000 (OK) or 1001 (going away) or without a code was received and sent. .INDENT 7.0 .UNINDENT .UNINDENT .INDENT 0.0 .TP .B exception websockets.exceptions.DuplicateParameter(name) Raised when a parameter name is repeated in an extension header. .INDENT 7.0 .UNINDENT .UNINDENT .INDENT 0.0 .TP .B exception websockets.exceptions.InvalidHandshake Raised during the handshake when the WebSocket connection fails. .UNINDENT .INDENT 0.0 .TP .B exception websockets.exceptions.InvalidHeader(name, value=None) Raised when an HTTP header doesn\(aqt have a valid format or value. .INDENT 7.0 .UNINDENT .UNINDENT .INDENT 0.0 .TP .B exception websockets.exceptions.InvalidHeaderFormat(name, error, header, pos) Raised when an HTTP header cannot be parsed. .sp The format of the header doesn\(aqt match the grammar for that header. .INDENT 7.0 .UNINDENT .UNINDENT .INDENT 0.0 .TP .B exception websockets.exceptions.InvalidHeaderValue(name, value=None) Raised when an HTTP header has a wrong value. .sp The format of the header is correct but a value isn\(aqt acceptable. .INDENT 7.0 .UNINDENT .UNINDENT .INDENT 0.0 .TP .B exception websockets.exceptions.InvalidMessage Raised when a handshake request or response is malformed. .UNINDENT .INDENT 0.0 .TP .B exception websockets.exceptions.InvalidOrigin(origin) Raised when the Origin header in a request isn\(aqt allowed. .INDENT 7.0 .UNINDENT .UNINDENT .INDENT 0.0 .TP .B exception websockets.exceptions.InvalidParameterName(name) Raised when a parameter name in an extension header is invalid. .INDENT 7.0 .UNINDENT .UNINDENT .INDENT 0.0 .TP .B exception websockets.exceptions.InvalidParameterValue(name, value) Raised when a parameter value in an extension header is invalid. .INDENT 7.0 .UNINDENT .UNINDENT .INDENT 0.0 .TP .B exception websockets.exceptions.InvalidState Raised when an operation is forbidden in the current state. .sp This exception is an implementation detail. .sp It should never be raised in normal circumstances. .UNINDENT .INDENT 0.0 .TP .B exception websockets.exceptions.InvalidStatus(response) Raised when a handshake response rejects the WebSocket upgrade. .INDENT 7.0 .UNINDENT .UNINDENT .INDENT 0.0 .TP .B exception websockets.exceptions.InvalidStatusCode(status_code, headers) Raised when a handshake response status code is invalid. .INDENT 7.0 .UNINDENT .UNINDENT .INDENT 0.0 .TP .B exception websockets.exceptions.InvalidURI(uri, msg) Raised when connecting to a URI that isn\(aqt a valid WebSocket URI. .INDENT 7.0 .UNINDENT .UNINDENT .INDENT 0.0 .TP .B exception websockets.exceptions.InvalidUpgrade(name, value=None) Raised when the Upgrade or Connection header isn\(aqt correct. .INDENT 7.0 .UNINDENT .UNINDENT .INDENT 0.0 .TP .B exception websockets.exceptions.NegotiationError Raised when negotiating an extension fails. .UNINDENT .INDENT 0.0 .TP .B exception websockets.exceptions.PayloadTooBig Raised when receiving a frame with a payload exceeding the maximum size. .UNINDENT .INDENT 0.0 .TP .B exception websockets.exceptions.ProtocolError Raised when a frame breaks the protocol. .UNINDENT .INDENT 0.0 .TP .B exception websockets.exceptions.RedirectHandshake(uri) Raised when a handshake gets redirected. .sp This exception is an implementation detail. .INDENT 7.0 .UNINDENT .UNINDENT .INDENT 0.0 .TP .B exception websockets.exceptions.SecurityError Raised when a handshake request or response breaks a security rule. .sp Security limits are hard coded. .UNINDENT .INDENT 0.0 .TP .B exception websockets.exceptions.WebSocketException Base class for all exceptions defined by websockets. .UNINDENT .INDENT 0.0 .TP .B websockets.exceptions.WebSocketProtocolError alias of \fI\%ProtocolError\fP .UNINDENT .SS Types .INDENT 0.0 .TP .B websockets.typing.Data Types supported in a WebSocket message: \fI\%str\fP for a \fI\%Text\fP frame, \fI\%bytes\fP for a \fI\%Binary\fP\&. .sp alias of \fI\%Union\fP[\fI\%str\fP, \fI\%bytes\fP] .UNINDENT .INDENT 0.0 .TP .B websockets.typing.LoggerLike Types accepted where a \fI\%Logger\fP is expected. .sp alias of \fI\%Union\fP[\fI\%Logger\fP, \fI\%LoggerAdapter\fP] .UNINDENT .INDENT 0.0 .TP .B websockets.typing.StatusLike Types accepted where an \fI\%HTTPStatus\fP is expected. .sp alias of \fI\%Union\fP[\fI\%HTTPStatus\fP, \fI\%int\fP] .UNINDENT .INDENT 0.0 .TP .B websockets.typing.Origin = websockets.typing.Origin Value of a \fBOrigin\fP header. .UNINDENT .INDENT 0.0 .TP .B websockets.typing.Subprotocol = websockets.typing.Subprotocol Subprotocol in a \fBSec\-WebSocket\-Protocol\fP header. .UNINDENT .INDENT 0.0 .TP .B websockets.typing.ExtensionName = websockets.typing.ExtensionName Name of a WebSocket extension. .UNINDENT .INDENT 0.0 .TP .B websockets.typing.ExtensionParameter Parameter of a WebSocket extension. .sp alias of \fI\%Tuple\fP[\fI\%str\fP, \fI\%Optional\fP[\fI\%str\fP]] .UNINDENT .INDENT 0.0 .TP .B websockets.protocol.Event Events that \fI\%events_received()\fP may return. .sp alias of \fI\%Union\fP[\fI\%Request\fP, \fI\%Response\fP, \fI\%Frame\fP] .UNINDENT .INDENT 0.0 .TP .B websockets.datastructures.HeadersLike Types accepted where \fI\%Headers\fP is expected. .sp In addition to \fI\%Headers\fP itself, this includes dict\-like types where both keys and values are \fI\%str\fP\&. .sp alias of \fI\%Union\fP[\fI\%Headers\fP, \fI\%Mapping\fP[\fI\%str\fP, \fI\%str\fP], \fI\%Iterable\fP[\fI\%Tuple\fP[\fI\%str\fP, \fI\%str\fP]], \fI\%SupportsKeysAndGetItem\fP] .UNINDENT .INDENT 0.0 .TP .B websockets.datastructures.SupportsKeysAndGetItem = Dict\-like types with \fBkeys() \-> str\fP and \fB__getitem__(key: str) \-> str\fP methods. .UNINDENT .SS API stability .sp Public APIs documented in this API reference are subject to the \fI\%backwards\-compatibility policy\fP\&. .sp Anything that isn\(aqt listed in the API reference is a private API. There\(aqs no guarantees of behavior or backwards\-compatibility for private APIs. .SS Convenience imports .sp For convenience, many public APIs can be imported directly from the \fBwebsockets\fP package. .SH TOPIC GUIDES .sp Get a deeper understanding of how websockets is built and why. .SS Deployment .sp When you deploy your websockets server to production, at a high level, your architecture will almost certainly look like the following diagram: [image] .sp The basic unit for scaling a websockets server is \(dqone server process\(dq. Each blue box in the diagram represents one server process. .sp There\(aqs more variation in routing. While the routing layer is shown as one big box, it is likely to involve several subsystems. .sp When you design a deployment, your should consider two questions: .INDENT 0.0 .IP 1. 3 How will I run the appropriate number of server processes? .IP 2. 3 How will I route incoming connections to these processes? .UNINDENT .sp These questions are strongly related. There\(aqs a wide range of acceptable answers, depending on your goals and your constraints. .sp You can find a few concrete examples in the \fI\%deployment how\-to guides\fP\&. .SS Running server processes .SS How many processes do I need? .sp 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. .sp CPU and memory usage increase with the number of connections to the server. .sp 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. .sp 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. .sp You can also automate this calculation by configuring an autoscaler to keep CPU usage or connection count within acceptable limits. .sp Don\(aqt scale with threads. Threads doesn\(aqt make sense for a server built with \fI\%asyncio\fP\&. .SS How do I run processes? .sp Most solutions for running multiple instances of a server process fall into one of these three buckets: .INDENT 0.0 .IP 1. 3 Running N processes on a platform: .INDENT 3.0 .IP \(bu 2 a Kubernetes Deployment .IP \(bu 2 its equivalent on a Platform as a Service provider .UNINDENT .IP 2. 3 Running N servers: .INDENT 3.0 .IP \(bu 2 an AWS Auto Scaling group, a GCP Managed instance group, etc. .IP \(bu 2 a fixed set of long\-lived servers .UNINDENT .IP 3. 3 Running N processes on a server: .INDENT 3.0 .IP \(bu 2 preferably via a process manager or supervisor .UNINDENT .UNINDENT .sp Option 1 is easiest of you have access to such a platform. .sp Option 2 almost always combines with option 3. .SS How do I start a process? .sp Run a Python program that invokes \fI\%serve()\fP\&. That\(aqs it. .sp Don\(aqt run an ASGI server such as Uvicorn, Hypercorn, or Daphne. They\(aqre alternatives to websockets, not complements. .sp Don\(aqt run a WSGI server such as Gunicorn, Waitress, or mod_wsgi. They aren\(aqt designed to run WebSocket applications. .sp Applications servers handle network connections and expose a Python API. You don\(aqt need one because websockets handles network connections directly. .SS How do I stop a process? .sp Process managers send the SIGTERM signal to terminate processes. Catch this signal and exit the server to ensure a graceful shutdown. .sp Here\(aqs an example: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C #!/usr/bin/env python import asyncio import signal import websockets async def echo(websocket): async for message in websocket: await websocket.send(message) async def server(): # Set the stop condition when receiving SIGTERM. loop = asyncio.get_running_loop() stop = loop.create_future() loop.add_signal_handler(signal.SIGTERM, stop.set_result, None) async with websockets.serve(echo, \(dqlocalhost\(dq, 8765): await stop asyncio.run(server()) .ft P .fi .UNINDENT .UNINDENT .sp When exiting the context manager, \fI\%serve()\fP closes all connections with code 1001 (going away). As a consequence: .INDENT 0.0 .IP \(bu 2 If the connection handler is awaiting \fI\%recv()\fP, it receives a \fI\%ConnectionClosedOK\fP exception. It can catch the exception and clean up before exiting. .IP \(bu 2 Otherwise, it should be waiting on \fI\%wait_closed()\fP, so it can receive the \fI\%ConnectionClosedOK\fP exception and exit. .UNINDENT .sp This example is easily adapted to handle other signals. .sp If you override the default signal handler for SIGINT, which raises \fI\%KeyboardInterrupt\fP, be aware that you won\(aqt be able to interrupt a program with Ctrl\-C anymore when it\(aqs stuck in a loop. .SS Routing connections .SS What does routing involve? .sp 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. .sp You should always secure WebSocket connections with TLS. Since the routing layer carries the public domain name, it should terminate TLS connections. .sp Finally, it must route connections to the server processes, balancing new connections across them. .SS How do I route connections? .sp Here are typical solutions for load balancing, matched to ways of running processes: .INDENT 0.0 .IP 1. 3 If you\(aqre running on a platform, it comes with a routing layer: .INDENT 3.0 .IP \(bu 2 a Kubernetes Ingress and Service .IP \(bu 2 a service mesh: Istio, Consul, Linkerd, etc. .IP \(bu 2 the routing mesh of a Platform as a Service .UNINDENT .IP 2. 3 If you\(aqre running N servers, you may load balance with: .INDENT 3.0 .IP \(bu 2 a cloud load balancer: AWS Elastic Load Balancing, GCP Cloud Load Balancing, etc. .IP \(bu 2 A software load balancer: HAProxy, NGINX, etc. .UNINDENT .IP 3. 3 If you\(aqre running N processes on a server, you may load balance with: .INDENT 3.0 .IP \(bu 2 A software load balancer: HAProxy, NGINX, etc. .IP \(bu 2 The operating system — all processes listen on the same port .UNINDENT .UNINDENT .sp 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. .sp There are many possibilities. Don\(aqt add layers that you don\(aqt need, though. .SS How do I implement a health check? .sp Load balancers need a way to check whether server processes are up and running to avoid routing connections to a non\-functional backend. .sp websockets provide minimal support for responding to HTTP requests with the \fI\%process_request()\fP hook. .sp Here\(aqs an example: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C #!/usr/bin/env python import asyncio import http import websockets async def health_check(path, request_headers): if path == \(dq/healthz\(dq: return http.HTTPStatus.OK, [], b\(dqOK\en\(dq async def echo(websocket): async for message in websocket: await websocket.send(message) async def main(): async with websockets.serve( echo, \(dqlocalhost\(dq, 8765, process_request=health_check, ): await asyncio.Future() # run forever asyncio.run(main()) .ft P .fi .UNINDENT .UNINDENT .SS Logging .SS Logs contents .sp When you run a WebSocket client, your code calls coroutines provided by websockets. .sp If an error occurs, websockets tells you by raising an exception. For example, it raises a \fI\%ConnectionClosed\fP exception if the other side closes the connection. .sp 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. .sp Given this \fI\%inversion of control\fP, 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. .sp Logs tell you about these errors. .sp Besides errors, you may want to record the activity of the server. .sp In a request/response protocol such as HTTP, there\(aqs an obvious way to record activity: log one event per request/response. Unfortunately, this solution doesn\(aqt work well for a bidirectional protocol such as WebSocket. .sp Instead, when running as a server, websockets logs one event when a \fI\%connection is established\fP and another event when a \fI\%connection is closed\fP\&. .sp By default, websockets doesn\(aqt 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. .sp 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. .sp See \fI\%log levels\fP below for a list of events logged by websockets logs at each log level. .SS Configure logging .sp websockets relies on the \fI\%logging\fP module from the standard library in order to maximize compatibility and integrate nicely with other libraries: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C import logging .ft P .fi .UNINDENT .UNINDENT .sp websockets logs to the \fB\(dqwebsockets.client\(dq\fP and \fB\(dqwebsockets.server\(dq\fP loggers. .sp websockets doesn\(aqt provide a default logging configuration because requirements vary a lot depending on the environment. .sp Here\(aqs a basic configuration for a server in production: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C logging.basicConfig( format=\(dq%(asctime)s %(message)s\(dq, level=logging.INFO, ) .ft P .fi .UNINDENT .UNINDENT .sp Here\(aqs how to enable debug logs for development: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C logging.basicConfig( format=\(dq%(message)s\(dq, level=logging.DEBUG, ) .ft P .fi .UNINDENT .UNINDENT .sp Furthermore, websockets adds a \fBwebsocket\fP attribute to log records, so you can include additional information about the current connection in logs. .sp You could attempt to add information with a formatter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C # this doesn\(aqt work! logging.basicConfig( format=\(dq{asctime} {websocket.id} {websocket.remote_address[0]} {message}\(dq, level=logging.INFO, style=\(dq{\(dq, ) .ft P .fi .UNINDENT .UNINDENT .sp However, this technique runs into two problems: .INDENT 0.0 .IP \(bu 2 The formatter applies to all records. It will crash if it receives a record without a \fBwebsocket\fP attribute. For example, this happens when logging that the server starts because there is no current connection. .IP \(bu 2 Even with \fI\%str.format()\fP style, you\(aqre restricted to attribute and index lookups, which isn\(aqt enough to implement some fairly simple requirements. .UNINDENT .sp There\(aqs a better way. \fI\%connect()\fP and \fI\%serve()\fP accept a \fBlogger\fP argument to override the default \fI\%Logger\fP\&. You can set \fBlogger\fP to a \fI\%LoggerAdapter\fP that enriches logs. .sp For example, if the server is behind a reverse proxy, \fI\%remote_address\fP gives the IP address of the proxy, which isn\(aqt useful. IP addresses of clients are provided in an HTTP header set by the proxy. .sp Here\(aqs how to include them in logs, assuming they\(aqre in the \fBX\-Forwarded\-For\fP header: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C logging.basicConfig( format=\(dq%(asctime)s %(message)s\(dq, level=logging.INFO, ) class LoggerAdapter(logging.LoggerAdapter): \(dq\(dq\(dqAdd connection ID and client IP address to websockets logs.\(dq\(dq\(dq def process(self, msg, kwargs): try: websocket = kwargs[\(dqextra\(dq][\(dqwebsocket\(dq] except KeyError: return msg, kwargs xff = websocket.request_headers.get(\(dqX\-Forwarded\-For\(dq) return f\(dq{websocket.id} {xff} {msg}\(dq, kwargs async with websockets.serve( ..., # Python < 3.10 requires passing None as the second argument. logger=LoggerAdapter(logging.getLogger(\(dqwebsockets.server\(dq), None), ): ... .ft P .fi .UNINDENT .UNINDENT .SS Logging to JSON .sp Even though \fI\%logging\fP predates structured logging, it\(aqs still possible to output logs as JSON with a bit of effort. .sp First, we need a \fI\%Formatter\fP that renders JSON: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C import json import logging import datetime class JSONFormatter(logging.Formatter): \(dq\(dq\(dq Render logs as JSON. To add details to a log record, store them in a \(ga\(gaevent_data\(ga\(ga custom attribute. This dict is merged into the event. \(dq\(dq\(dq def __init__(self): pass # override logging.Formatter constructor def format(self, record): event = { \(dqtimestamp\(dq: self.getTimestamp(record.created), \(dqmessage\(dq: record.getMessage(), \(dqlevel\(dq: record.levelname, \(dqlogger\(dq: record.name, } event_data = getattr(record, \(dqevent_data\(dq, None) if event_data: event.update(event_data) if record.exc_info: event[\(dqexc_info\(dq] = self.formatException(record.exc_info) if record.stack_info: event[\(dqstack_info\(dq] = self.formatStack(record.stack_info) return json.dumps(event) def getTimestamp(self, created): return datetime.datetime.utcfromtimestamp(created).isoformat() .ft P .fi .UNINDENT .UNINDENT .sp Then, we configure logging to apply this formatter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C handler = logging.StreamHandler() handler.setFormatter(formatter) logger = logging.getLogger() logger.addHandler(handler) logger.setLevel(logging.INFO) .ft P .fi .UNINDENT .UNINDENT .sp Finally, we populate the \fBevent_data\fP custom attribute in log records with a \fI\%LoggerAdapter\fP: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C class LoggerAdapter(logging.LoggerAdapter): \(dq\(dq\(dqAdd connection ID and client IP address to websockets logs.\(dq\(dq\(dq def process(self, msg, kwargs): try: websocket = kwargs[\(dqextra\(dq][\(dqwebsocket\(dq] except KeyError: return msg, kwargs kwargs[\(dqextra\(dq][\(dqevent_data\(dq] = { \(dqconnection_id\(dq: str(websocket.id), \(dqremote_addr\(dq: websocket.request_headers.get(\(dqX\-Forwarded\-For\(dq), } return msg, kwargs async with websockets.serve( ..., # Python < 3.10 requires passing None as the second argument. logger=LoggerAdapter(logging.getLogger(\(dqwebsockets.server\(dq), None), ): ... .ft P .fi .UNINDENT .UNINDENT .SS Disable logging .sp If your application doesn\(aqt configure \fI\%logging\fP, Python outputs messages of severity \fBWARNING\fP and higher to \fI\%stderr\fP\&. 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. .sp If you want to disable this behavior for websockets, you can add a \fI\%NullHandler\fP: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C logging.getLogger(\(dqwebsockets\(dq).addHandler(logging.NullHandler()) .ft P .fi .UNINDENT .UNINDENT .sp Additionally, if your application configures \fI\%logging\fP, you must disable propagation to the root logger, or else its handlers could output logs: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C logging.getLogger(\(dqwebsockets\(dq).propagate = False .ft P .fi .UNINDENT .UNINDENT .sp Alternatively, you could set the log level to \fBCRITICAL\fP for the \fB\(dqwebsockets\(dq\fP logger, as the highest level currently used is \fBERROR\fP: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C logging.getLogger(\(dqwebsockets\(dq).setLevel(logging.CRITICAL) .ft P .fi .UNINDENT .UNINDENT .sp Or you could configure a filter to drop all messages: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C logging.getLogger(\(dqwebsockets\(dq).addFilter(lambda record: None) .ft P .fi .UNINDENT .UNINDENT .SS Log levels .sp Here\(aqs what websockets logs at each level. .SS \fBERROR\fP .INDENT 0.0 .IP \(bu 2 Exceptions raised by connection handler coroutines in servers .IP \(bu 2 Exceptions resulting from bugs in websockets .UNINDENT .SS \fBWARNING\fP .INDENT 0.0 .IP \(bu 2 Failures in \fI\%broadcast()\fP .UNINDENT .SS \fBINFO\fP .INDENT 0.0 .IP \(bu 2 Server starting and stopping .IP \(bu 2 Server establishing and closing connections .IP \(bu 2 Client reconnecting automatically .UNINDENT .SS \fBDEBUG\fP .INDENT 0.0 .IP \(bu 2 Changes to the state of connections .IP \(bu 2 Handshake requests and responses .IP \(bu 2 All frames sent and received .IP \(bu 2 Steps to close a connection .IP \(bu 2 Keepalive pings and pongs .IP \(bu 2 Errors handled transparently .UNINDENT .sp Debug messages have cute prefixes that make logs easier to scan: .INDENT 0.0 .IP \(bu 2 \fB>\fP \- send something .IP \(bu 2 \fB<\fP \- receive something .IP \(bu 2 \fB=\fP \- set connection state .IP \(bu 2 \fBx\fP \- shut down connection .IP \(bu 2 \fB%\fP \- manage pings and pongs .IP \(bu 2 \fB!\fP \- handle errors and timeouts .UNINDENT .SS Authentication .sp The WebSocket protocol was designed for creating web applications that need bidirectional communication between clients running in browsers and servers. .sp In most practical use cases, WebSocket servers need to authenticate clients in order to route communications appropriately and securely. .sp \fI\%RFC 6455\fP stays elusive when it comes to authentication: .INDENT 0.0 .INDENT 3.5 This protocol doesn\(aqt 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. .UNINDENT .UNINDENT .sp None of these three mechanisms works well in practice. Using cookies is cumbersome, HTTP authentication isn\(aqt supported by all mainstream browsers, and TLS authentication in a browser is an esoteric user experience. .sp Fortunately, there are better alternatives! Let\(aqs discuss them. .SS System design .sp Consider a setup where the WebSocket server is separate from the HTTP server. .sp Most servers built with websockets to complement a web application adopt this design because websockets doesn\(aqt aim at supporting HTTP. .sp The following diagram illustrates the authentication flow. [image] .sp 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). .sp Usernames and passwords aren\(aqt a good choice of credentials here, if only because passwords aren\(aqt available in clear text in the database. .sp 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. .SS Sending credentials .sp Assume the web application obtained authentication credentials, likely a token, from the HTTP server. There\(aqs four options for passing them to the WebSocket server. .INDENT 0.0 .IP 1. 3 \fBSending credentials as the first message in the WebSocket connection.\fP .sp This is fully reliable and the most secure mechanism in this discussion. It has two minor downsides: .INDENT 3.0 .IP \(bu 2 Authentication is performed at the application layer. Ideally, it would be managed at the protocol layer. .IP \(bu 2 Authentication is performed after the WebSocket handshake, making it impossible to monitor authentication failures with HTTP response codes. .UNINDENT .IP 2. 3 \fBAdding credentials to the WebSocket URI in a query parameter.\fP .sp This is also fully reliable but less secure. Indeed, it has a major downside: .INDENT 3.0 .IP \(bu 2 URIs end up in logs, which leaks credentials. Even if that risk could be lowered with single\-use tokens, it is usually considered unacceptable. .UNINDENT .sp 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. .IP 3. 3 \fBSetting a cookie on the domain of the WebSocket URI.\fP .sp 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. .sp Unfortunately, when the WebSocket server runs on a different domain from the web application, this idea bumps into the \fI\%Same\-Origin Policy\fP\&. For security reasons, setting a cookie on a different origin is impossible. .sp The proper workaround consists in: .INDENT 3.0 .IP \(bu 2 creating a hidden \fI\%iframe\fP served from the domain of the WebSocket server .IP \(bu 2 sending the token to the iframe with \fI\%postMessage\fP .IP \(bu 2 setting the cookie in the iframe .UNINDENT .sp before opening the WebSocket connection. .sp 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. .sp However, the cookie would be shared with all subdomains of the parent domain. For a cookie containing credentials, this is unacceptable. .UNINDENT .INDENT 0.0 .IP 4. 3 \fBAdding credentials to the WebSocket URI in user information.\fP .sp Letting the browser perform HTTP Basic Auth is a nice idea in theory. .sp In practice it doesn\(aqt work due to poor support in browsers. .sp As of May 2021: .INDENT 3.0 .IP \(bu 2 Chrome 90 behaves as expected. .IP \(bu 2 Firefox 88 caches credentials too aggressively. .sp 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. .sp When tokens are short\-lived or single\-use, this bug produces an interesting effect: every other WebSocket connection fails. .IP \(bu 2 Safari 14 ignores credentials entirely. .UNINDENT .UNINDENT .sp Two other options are off the table: .INDENT 0.0 .IP 1. 3 \fBSetting a custom HTTP header\fP .sp This would be the most elegant mechanism, solving all issues with the options discussed above. .sp Unfortunately, it doesn\(aqt work because the \fI\%WebSocket API\fP doesn\(aqt support \fI\%setting custom headers\fP\&. .UNINDENT .INDENT 0.0 .IP 2. 3 \fBAuthenticating with a TLS certificate\fP .sp 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\(aqt take security advice from the documentation of random open source projects. .UNINDENT .SS Let\(aqs experiment! .sp The \fI\%experiments/authentication\fP directory demonstrates these techniques. .sp Run the experiment in an environment where websockets is installed: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ python experiments/authentication/app.py Running on http://localhost:8000/ .ft P .fi .UNINDENT .UNINDENT .sp When you browse to the HTTP server at \fI\%http://localhost:8000/\fP and you submit a username, the server creates a token and returns a testing web page. .sp 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. .SS First message .sp As soon as the connection is open, the client sends a message containing the token: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C const websocket = new WebSocket(\(dqws://.../\(dq); websocket.onopen = () => websocket.send(token); // ... .ft P .fi .UNINDENT .UNINDENT .sp At the beginning of the connection handler, the server receives this message and authenticates the user. If authentication fails, the server closes the connection: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C async def first_message_handler(websocket): token = await websocket.recv() user = get_user(token) if user is None: await websocket.close(CloseCode.INTERNAL_ERROR, \(dqauthentication failed\(dq) return ... .ft P .fi .UNINDENT .UNINDENT .SS Query parameter .sp The client adds the token to the WebSocket URI in a query parameter before opening the connection: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C const uri = \(gaws://.../?token=${token}\(ga; const websocket = new WebSocket(uri); // ... .ft P .fi .UNINDENT .UNINDENT .sp The server intercepts the HTTP request, extracts the token and authenticates the user. If authentication fails, it returns an HTTP 401: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C class QueryParamProtocol(websockets.WebSocketServerProtocol): async def process_request(self, path, headers): token = get_query_parameter(path, \(dqtoken\(dq) if token is None: return http.HTTPStatus.UNAUTHORIZED, [], b\(dqMissing token\en\(dq user = get_user(token) if user is None: return http.HTTPStatus.UNAUTHORIZED, [], b\(dqInvalid token\en\(dq self.user = user async def query_param_handler(websocket): user = websocket.user ... .ft P .fi .UNINDENT .UNINDENT .SS Cookie .sp The client sets a cookie containing the token before opening the connection. .sp 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. .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C // in main window iframe.contentWindow.postMessage(token, \(dqhttp://...\(dq); // in iframe document.cookie = \(gatoken=${data}; SameSite=Strict\(ga; // in main window const websocket = new WebSocket(\(dqws://.../\(dq); // ... .ft P .fi .UNINDENT .UNINDENT .sp This sequence must be synchronized between the main window and the iframe. This involves several events. Look at the full implementation for details. .sp The server intercepts the HTTP request, extracts the token and authenticates the user. If authentication fails, it returns an HTTP 401: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C class CookieProtocol(websockets.WebSocketServerProtocol): async def process_request(self, path, headers): # Serve iframe on non\-WebSocket requests ... token = get_cookie(headers.get(\(dqCookie\(dq, \(dq\(dq), \(dqtoken\(dq) if token is None: return http.HTTPStatus.UNAUTHORIZED, [], b\(dqMissing token\en\(dq user = get_user(token) if user is None: return http.HTTPStatus.UNAUTHORIZED, [], b\(dqInvalid token\en\(dq self.user = user async def cookie_handler(websocket): user = websocket.user ... .ft P .fi .UNINDENT .UNINDENT .SS User information .sp The client adds the token to the WebSocket URI in user information before opening the connection: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C const uri = \(gaws://token:${token}@.../\(ga; const websocket = new WebSocket(uri); // ... .ft P .fi .UNINDENT .UNINDENT .sp Since HTTP Basic Auth is designed to accept a username and a password rather than a token, we send \fBtoken\fP as username and the token as password. .sp The server intercepts the HTTP request, extracts the token and authenticates the user. If authentication fails, it returns an HTTP 401: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C class UserInfoProtocol(websockets.BasicAuthWebSocketServerProtocol): async def check_credentials(self, username, password): if username != \(dqtoken\(dq: 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 ... .ft P .fi .UNINDENT .UNINDENT .SS Machine\-to\-machine authentication .sp 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. .sp To authenticate a websockets client with HTTP Basic Authentication (\fI\%RFC 7617\fP), include the credentials in the URI: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C async with websockets.connect( f\(dqwss://{username}:{password}@example.com\(dq, ) as websocket: ... .ft P .fi .UNINDENT .UNINDENT .sp (You must \fI\%quote()\fP \fBusername\fP and \fBpassword\fP if they contain unsafe characters.) .sp To authenticate a websockets client with HTTP Bearer Authentication (\fI\%RFC 6750\fP), add a suitable \fBAuthorization\fP header: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C async with websockets.connect( \(dqwss://example.com\(dq, extra_headers={\(dqAuthorization\(dq: f\(dqBearer {token}\(dq} ) as websocket: ... .ft P .fi .UNINDENT .UNINDENT .SS Broadcasting messages .INDENT 0.0 .INDENT 3.5 .IP "If you just want to send a message to all connected clients, use \fI\%broadcast()\fP\&." .sp If you want to learn about its design in depth, continue reading this document. .UNINDENT .UNINDENT .sp WebSocket servers often send the same message to all connected clients or to a subset of clients for which the message is relevant. .sp Let\(aqs explore options for broadcasting a message, explain the design of \fI\%broadcast()\fP, and discuss alternatives. .sp For each option, we\(aqll provide a connection handler called \fBhandler()\fP and a function or coroutine called \fBbroadcast()\fP that sends a message to all connected clients. .sp Integrating them is left as an exercise for the reader. You could start with: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C 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, \(dqlocalhost\(dq, 8765): await broadcast_messages() # runs forever if __name__ == \(dq__main__\(dq: asyncio.run(main()) .ft P .fi .UNINDENT .UNINDENT .sp \fBbroadcast_messages()\fP must yield control to the event loop between each message, or else it will never let the server run. That\(aqs why it includes \fBawait asyncio.sleep(1)\fP\&. .sp A complete example is available in the \fI\%experiments/broadcast\fP directory. .SS The naive way .sp 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. .sp Here\(aqs a connection handler that registers clients in a global variable: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C CLIENTS = set() async def handler(websocket): CLIENTS.add(websocket) try: await websocket.wait_closed() finally: CLIENTS.remove(websocket) .ft P .fi .UNINDENT .UNINDENT .sp This implementation assumes that the client will never send any messages. If you\(aqd rather not make this assumption, you can change: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C await websocket.wait_closed() .ft P .fi .UNINDENT .UNINDENT .sp to: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C async for _ in websocket: pass .ft P .fi .UNINDENT .UNINDENT .sp Here\(aqs a coroutine that broadcasts a message to all clients: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C async def broadcast(message): for websocket in CLIENTS.copy(): try: await websocket.send(message) except websockets.ConnectionClosed: pass .ft P .fi .UNINDENT .UNINDENT .sp There are two tricks in this version of \fBbroadcast()\fP\&. .sp First, it makes a copy of \fBCLIENTS\fP before iterating it. Else, if a client connects or disconnects while \fBbroadcast()\fP is running, the loop would fail with: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C RuntimeError: Set changed size during iteration .ft P .fi .UNINDENT .UNINDENT .sp Second, it ignores \fI\%ConnectionClosed\fP exceptions because a client could disconnect between the moment \fBbroadcast()\fP makes a copy of \fBCLIENTS\fP and the moment it sends a message to this client. This is fine: a client that disconnected doesn\(aqt belongs to \(dqall connected clients\(dq anymore. .sp The naive way can be very fast. Indeed, if all connections have enough free space in their write buffers, \fBawait websocket.send(message)\fP writes the message and returns immediately, as it doesn\(aqt need to wait for the buffer to drain. In this case, \fBbroadcast()\fP doesn\(aqt yield control to the event loop, which minimizes overhead. .sp The naive way can also fail badly. If the write buffer of a connection reaches \fBwrite_limit\fP, \fBbroadcast()\fP waits for the buffer to drain before sending the message to other clients. This can cause a massive drop in performance. .sp As a consequence, this pattern works only when write buffers never fill up, which is usually outside of the control of the server. .sp If you know for sure that you will never write more than \fBwrite_limit\fP bytes within \fBping_interval + ping_timeout\fP, then websockets will terminate slow connections before the write buffer has time to fill up. .sp Don\(aqt set extreme \fBwrite_limit\fP, \fBping_interval\fP, and \fBping_timeout\fP values to ensure that this condition holds. Set reasonable values and use the built\-in \fI\%broadcast()\fP function instead. .SS The concurrent way .sp The naive way didn\(aqt work well because it serialized writes, while the whole point of asynchronous I/O is to perform I/O concurrently. .sp Let\(aqs modify \fBbroadcast()\fP to send messages concurrently: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C 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)) .ft P .fi .UNINDENT .UNINDENT .sp We move the error handling logic in a new coroutine and we schedule a \fI\%Task\fP to run it instead of executing it immediately. .sp Since \fBbroadcast()\fP no longer awaits coroutines, we can make it a function rather than a coroutine and do away with the copy of \fBCLIENTS\fP\&. .sp This version of \fBbroadcast()\fP makes clients independent from one another: a slow client won\(aqt block others. As a side effect, it makes messages independent from one another. .sp 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. .sp 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. .sp 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: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C async def broadcast(message): if CLIENTS: # asyncio.wait doesn\(aqt accept an empty list await asyncio.wait([ asyncio.create_task(send(websocket, message)) for websocket in CLIENTS ]) .ft P .fi .UNINDENT .UNINDENT .sp However, this doesn\(aqt really work in practice. Quite often, it will block until the slowest client times out. .SS Backpressure meets broadcast .sp At this point, it becomes apparent that backpressure, usually a good practice, doesn\(aqt work well when broadcasting a message to thousands of clients. .sp When you\(aqre sending messages to a single client, you don\(aqt want to send them faster than the network can transfer them and the client accept them. This is why \fI\%send()\fP 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. .sp 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. .sp The \fI\%send()\fP API is designed to enforce backpressure by default. This helps users of websockets write robust programs even if they never heard about backpressure. .sp For comparison, \fI\%asyncio.StreamWriter\fP requires users to understand backpressure and to await \fI\%drain()\fP explicitly after each \fI\%write()\fP\&. .sp 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\(aqt going to receive the message anyway. So it doesn\(aqt make sense to synchronize with the slowest client. .sp How do we avoid running out of memory when slow clients can\(aqt keep up with the broadcast rate, then? The most straightforward option is to disconnect them. .sp If a client gets too far behind, eventually it reaches the limit defined by \fBping_timeout\fP and websockets terminates the connection. You can read the discussion of \fI\%keepalive and timeouts\fP for details. .SS How \fI\%broadcast()\fP works .sp The built\-in \fI\%broadcast()\fP function is similar to the naive way. The main difference is that it doesn\(aqt apply backpressure. .sp This provides the best performance by avoiding the overhead of scheduling and running one task per client. .sp Also, when sending text messages, encoding to UTF\-8 happens only once rather than once per client, providing a small performance gain. .SS Per\-client queues .sp At this point, we deal with slow clients rather brutally: we disconnect then. .sp Can we do better? For example, we could decide to skip or to batch messages, depending on how far behind a client is. .sp 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: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C 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() .ft P .fi .UNINDENT .UNINDENT .sp Then we can broadcast a message by pushing it to all queues: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C def broadcast(message): for queue in CLIENTS: queue.put_nowait(message) .ft P .fi .UNINDENT .UNINDENT .sp The queues provide an additional buffer between the \fBbroadcast()\fP function and clients. This makes it easier to support slow clients without excessive memory usage because queued messages aren\(aqt duplicated to write buffers until \fBrelay()\fP processes them. .SS Publish–subscribe .sp Can we avoid centralizing the list of connected clients in a global variable? .sp If each client subscribes to a stream a messages, then broadcasting becomes as simple as publishing a message to the stream. .sp Here\(aqs a message stream that supports multiple consumers: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C 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() .ft P .fi .UNINDENT .UNINDENT .sp The stream is implemented as a linked list of futures. It isn\(aqt 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. .sp The connection handler subscribes to the stream and sends messages: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C async def handler(websocket): async for message in PUBSUB: await websocket.send(message) .ft P .fi .UNINDENT .UNINDENT .sp The broadcast function publishes to the stream: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C def broadcast(message): PUBSUB.publish(message) .ft P .fi .UNINDENT .UNINDENT .sp 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 \fBPubSub\fP class could be extended or refactored to provide this information. .sp The \fBfor\fP loop is gone from this version of the \fBbroadcast()\fP function. However, there\(aqs still a \fBfor\fP loop iterating on all clients hidden deep inside \fI\%asyncio\fP\&. When \fBpublish()\fP sets the result of the \fBwaiter\fP future, \fI\%asyncio\fP 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 \fBsubscribe()\fP\&. .SS Performance considerations .sp The built\-in \fI\%broadcast()\fP 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. .sp 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. .sp It would be tempting to prepare a frame and reuse it for all connections. However, this isn\(aqt possible in general for two reasons: .INDENT 0.0 .IP \(bu 2 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. .IP \(bu 2 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. .UNINDENT .sp 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 \fI\%broadcast()\fP function. .sp There is no major difference between the performance of per\-client queues and publish–subscribe. .SS Compression .sp Most WebSocket servers exchange JSON messages because they\(aqre convenient to parse and serialize in a browser. These messages contain text data and tend to be repetitive. .sp This makes the stream of messages highly compressible. Enabling compression can reduce network traffic by more than 80%. .sp There\(aqs a standard for compressing messages. \fI\%RFC 7692\fP defines WebSocket Per\-Message Deflate, a compression extension based on the \fI\%Deflate\fP algorithm. .SS Configuring compression .sp \fI\%connect()\fP and \fI\%serve()\fP enable compression by default because the reduction in network bandwidth is usually worth the additional memory and CPU cost. .sp If you want to disable compression, set \fBcompression=None\fP: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C import websockets websockets.connect(..., compression=None) websockets.serve(..., compression=None) .ft P .fi .UNINDENT .UNINDENT .sp If you want to customize compression settings, you can enable the Per\-Message Deflate extension explicitly with \fI\%ClientPerMessageDeflateFactory\fP or \fI\%ServerPerMessageDeflateFactory\fP: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C 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={\(dqmemLevel\(dq: 4}, ), ], ) websockets.serve( ..., extensions=[ permessage_deflate.ServerPerMessageDeflateFactory( server_max_window_bits=11, client_max_window_bits=11, compress_settings={\(dqmemLevel\(dq: 4}, ), ], ) .ft P .fi .UNINDENT .UNINDENT .sp The Window Bits and Memory Level values in these examples reduce memory usage at the expense of compression rate. .SS Compression settings .sp 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. .INDENT 0.0 .IP \(bu 2 \fBContext Takeover\fP 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. .sp Context takeover should remain enabled to get good performance on applications that send a stream of messages with similar structure, that is, most applications. .sp This requires retaining the compression context and state between messages, which increases the memory footprint of a connection. .IP \(bu 2 \fBWindow Bits\fP 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. .sp 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. .sp On the client side, websockets lets the server pick a suitable value, which has the same effect as defaulting to 15. .UNINDENT .sp \fI\%zlib\fP offers additional parameters for tuning compression. They control the trade\-off between compression rate, memory usage, and CPU usage only for compressing. They\(aqre transparent for decompressing. Unless mentioned otherwise, websockets inherits defaults of \fI\%compressobj()\fP\&. .INDENT 0.0 .IP \(bu 2 \fBMemory Level\fP controls the size of the compression state. It must be an integer between 1 (lowest memory usage) and 9 (best compression). .sp websockets defaults to 5. This is lower than zlib\(aqs default of 8. Not only does a lower memory level reduce memory usage, but it can also increase speed thanks to memory locality. .IP \(bu 2 \fBCompression Level\fP controls the effort to optimize compression. It must be an integer between 1 (lowest CPU usage) and 9 (best compression). .IP \(bu 2 \fBStrategy\fP selects the compression strategy. The best choice depends on the type of data being compressed. .UNINDENT .SS Tuning compression .SS For servers .sp 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. .sp Here\(aqs 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. .TS center; |l|l|l|l|l|. _ T{ Window Bits T} T{ Memory Level T} T{ Memory usage T} T{ Size vs. default T} T{ Time vs. default T} _ T{ 15 T} T{ 8 T} T{ 322\ KiB T} T{ \-4.0% T} T{ +15% T} _ T{ 14 T} T{ 7 T} T{ 178\ KiB T} T{ \-2.6% T} T{ +10% T} _ T{ 13 T} T{ 6 T} T{ 106\ KiB T} T{ \-1.4% T} T{ +5% T} _ T{ \fB12\fP T} T{ \fB5\fP T} T{ \fB70\ KiB\fP T} T{ \fB=\fP T} T{ \fB=\fP T} _ T{ 11 T} T{ 4 T} T{ 52\ KiB T} T{ +3.7% T} T{ \-5% T} _ T{ 10 T} T{ 3 T} T{ 43\ KiB T} T{ +90% T} T{ +50% T} _ T{ 9 T} T{ 2 T} T{ 39\ KiB T} T{ +160% T} T{ +100% T} _ T{ — T} T{ — T} T{ 19\ KiB T} T{ +452% T} T{ — T} _ .TE .sp Window Bits and Memory Level don\(aqt have to move in lockstep. However, other combinations don\(aqt yield significantly better results than those shown above. .sp 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. .sp You can adapt \fI\%compression/benchmark.py\fP by creating a list of typical messages and passing it to the \fB_run\fP function. .sp Window Bits = 11 and Memory Level = 4 looks like the sweet spot in this table. .sp 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. .sp Defaults must be safe for all applications, hence a more conservative choice. .sp The benchmark focuses on compression because it\(aqs more expensive than decompression. Indeed, leaving aside small allocations, theoretical memory usage is: .INDENT 0.0 .IP \(bu 2 \fB(1 << (windowBits + 2)) + (1 << (memLevel + 9))\fP for compression; .IP \(bu 2 \fB1 << windowBits\fP for decompression. .UNINDENT .sp CPU usage is also higher for compression than decompression. .sp While it\(aqs 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. .sp When a client doesn\(aqt 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. .sp If you are very sensitive to memory usage, you can reverse this behavior by setting the \fBrequire_client_max_window_bits\fP parameter of \fI\%ServerPerMessageDeflateFactory\fP to \fBTrue\fP\&. .SS For clients .sp By default, websockets enables compression with Memory Level = 5 but leaves the Window Bits setting up to the server. .sp There\(aqs two good reasons and one bad reason for not optimizing the client side like the server side: .INDENT 0.0 .IP 1. 3 If the maintainers of a server configured some optimized settings, we don\(aqt want to override them with more restrictive settings. .IP 2. 3 Optimizing memory usage doesn\(aqt matter very much for clients because it\(aqs uncommon to open thousands of client connections in a program. .IP 3. 3 On a more pragmatic note, some servers misbehave badly when a client configures compression settings. \fI\%AWS API Gateway\fP is the worst offender. .sp Unfortunately, even though websockets is right and AWS is wrong, many users jump to the conclusion that websockets doesn\(aqt work. .sp Until the ecosystem levels up, interoperability with buggy servers seems more valuable than optimizing memory usage. .UNINDENT .SS Further reading .sp This \fI\%blog post by Ilya Grigorik\fP provides more details about how compression settings affect memory usage and how to optimize them. .sp This \fI\%experiment by Peter Thorson\fP recommends Window Bits = 11 and Memory Level = 4 for optimizing memory usage. .SS Timeouts .SS Long\-lived connections .sp Since the WebSocket protocol is intended for real\-time communications over long\-lived connections, it is desirable to ensure that connections don\(aqt break, and if they do, to report the problem quickly. .sp Connections can drop as a consequence of temporary network connectivity issues, which are very common, even within data centers. .sp Furthermore, WebSocket builds on top of HTTP/1.1 where connections are short\-lived, even with \fBConnection: keep\-alive\fP\&. Typically, HTTP/1.1 infrastructure closes idle connections after 30 to 120 seconds. .sp As a consequence, proxies may terminate WebSocket connections prematurely when no message was exchanged in 30 seconds. .SS Keepalive in websockets .sp To avoid these problems, websockets runs a keepalive and heartbeat mechanism based on WebSocket \fI\%Ping\fP and \fI\%Pong\fP frames, which are designed for this purpose. .sp It loops through these steps: .INDENT 0.0 .IP 1. 3 Wait 20 seconds. .IP 2. 3 Send a Ping frame. .IP 3. 3 Receive a corresponding Pong frame within 20 seconds. .UNINDENT .sp If the Pong frame isn\(aqt received, websockets considers the connection broken and closes it. .sp This mechanism serves two purposes: .INDENT 0.0 .IP 1. 3 It creates a trickle of traffic so that the TCP connection isn\(aqt idle and network infrastructure along the path keeps it open (\(dqkeepalive\(dq). .IP 2. 3 It detects if the connection drops or becomes so slow that it\(aqs unusable in practice (\(dqheartbeat\(dq). In that case, it terminates the connection and your application gets a \fI\%ConnectionClosed\fP exception. .UNINDENT .sp Timings are configurable with the \fBping_interval\fP and \fBping_timeout\fP arguments of \fI\%connect()\fP and \fI\%serve()\fP\&. Shorter values will detect connection drops faster but they will increase network traffic and they will be more sensitive to latency. .sp Setting \fBping_interval\fP to \fI\%None\fP disables the whole keepalive and heartbeat mechanism. .sp Setting \fBping_timeout\fP to \fI\%None\fP disables only timeouts. This enables keepalive, to keep idle connections open, and disables heartbeat, to support large latency spikes. .INDENT 0.0 .INDENT 3.5 .IP "Why doesn\(aqt websockets rely on TCP keepalive?" .sp 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. .UNINDENT .UNINDENT .SS Keepalive in browsers .sp Browsers don\(aqt 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. .sp In this scenario, the \fBWebSocket\fP object in the browser doesn\(aqt fire a \fBclose\fP event. If you have a reconnection mechanism, it doesn\(aqt kick in because it believes that the connection is still working. .sp 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. .sp Unfortunately, the WebSocket API in browsers doesn\(aqt expose the native Ping and Pong functionality in the WebSocket protocol. You have to roll your own in the application layer. .SS Latency issues .sp Latency between a client and a server may increase for two reasons: .INDENT 0.0 .IP \(bu 2 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. .IP \(bu 2 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. .sp If the server is more than 20 seconds behind, it doesn\(aqt see the Pong before the default timeout elapses. As a consequence, it closes the connection. This is a reasonable choice to prevent overload. .sp If traffic spikes cause unwanted timeouts and you\(aqre confident that the server will catch up eventually, you can increase \fBping_timeout\fP or you can set it to \fI\%None\fP to disable heartbeat entirely. .sp The same reasoning applies to situations where the server sends more traffic than the client can accept. .UNINDENT .sp The latency measured during the last exchange of Ping and Pong frames is available in the \fI\%latency\fP attribute. Alternatively, you can measure the latency at any time with the \fI\%ping\fP method. .SS Design .sp This document describes the design of websockets. It assumes familiarity with the specification of the WebSocket protocol in \fI\%RFC 6455\fP\&. .sp It\(aqs primarily intended at maintainers. It may also be useful for users who wish to understand what happens under the hood. .sp \fBWARNING:\fP .INDENT 0.0 .INDENT 3.5 Internals described in this document may change at any time. .sp Backwards compatibility is only guaranteed for \fI\%public APIs\fP\&. .UNINDENT .UNINDENT .SS Lifecycle .SS State .sp WebSocket connections go through a trivial state machine: .INDENT 0.0 .IP \(bu 2 \fBCONNECTING\fP: initial state, .IP \(bu 2 \fBOPEN\fP: when the opening handshake is complete, .IP \(bu 2 \fBCLOSING\fP: when the closing handshake is started, .IP \(bu 2 \fBCLOSED\fP: when the TCP connection is closed. .UNINDENT .sp Transitions happen in the following places: .INDENT 0.0 .IP \(bu 2 \fBCONNECTING \-> OPEN\fP: in \fBconnection_open()\fP which runs when the \fI\%opening handshake\fP completes and the WebSocket connection is established — not to be confused with \fI\%connection_made()\fP which runs when the TCP connection is established; .IP \(bu 2 \fBOPEN \-> CLOSING\fP: in \fBwrite_frame()\fP 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 \fI\%closing handshake\fP; also in \fBfail_connection()\fP which duplicates a few lines of code from \fBwrite_close_frame()\fP and \fBwrite_frame()\fP; .IP \(bu 2 \fB* \-> CLOSED\fP: in \fBconnection_lost()\fP which is always called exactly once when the TCP connection is closed. .UNINDENT .SS Coroutines .sp The following diagram shows which coroutines are running at each stage of the connection lifecycle on the client side. \fI\%\fP .sp The lifecycle is identical on the server side, except inversion of control makes the equivalent of \fI\%connect()\fP implicit. .sp Coroutines shown in green are called by the application. Multiple coroutines may interact with the WebSocket connection concurrently. .sp Coroutines shown in gray manage the connection. When the opening handshake succeeds, \fBconnection_open()\fP starts two tasks: .INDENT 0.0 .IP \(bu 2 \fBtransfer_data_task\fP runs \fBtransfer_data()\fP which handles incoming data and lets \fI\%recv()\fP consume it. It may be canceled to terminate the connection. It never exits with an exception other than \fI\%CancelledError\fP\&. See \fI\%data transfer\fP below. .IP \(bu 2 \fBkeepalive_ping_task\fP runs \fBkeepalive_ping()\fP 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 \fI\%CancelledError\fP\&. .IP \(bu 2 \fBclose_connection_task\fP runs \fBclose_connection()\fP 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 \fI\%connection termination\fP below. .UNINDENT .sp Besides, \fBfail_connection()\fP starts the same \fBclose_connection_task\fP when the opening handshake fails, in order to close the TCP connection. .sp Splitting the responsibilities between two tasks makes it easier to guarantee that websockets can terminate connections: .INDENT 0.0 .IP \(bu 2 within a fixed timeout, .IP \(bu 2 without leaking pending tasks, .IP \(bu 2 without leaking open TCP connections, .UNINDENT .sp regardless of whether the connection terminates normally or abnormally. .sp \fBtransfer_data_task\fP completes when no more data will be received on the connection. Under normal circumstances, it exits after exchanging close frames. .sp \fBclose_connection_task\fP completes when the TCP connection is closed. .SS Opening handshake .sp websockets performs the opening handshake when establishing a WebSocket connection. On the client side, \fI\%connect()\fP executes it before returning the protocol to the caller. On the server side, it\(aqs executed before passing the protocol to the \fBws_handler\fP coroutine handling the connection. .sp 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. .sp On the client side, \fBhandshake()\fP: .INDENT 0.0 .IP \(bu 2 builds an HTTP request based on the \fBuri\fP and parameters passed to \fI\%connect()\fP; .IP \(bu 2 writes the HTTP request to the network; .IP \(bu 2 reads an HTTP response from the network; .IP \(bu 2 checks the HTTP response, validates \fBextensions\fP and \fBsubprotocol\fP, and configures the protocol accordingly; .IP \(bu 2 moves to the \fBOPEN\fP state. .UNINDENT .sp On the server side, \fBhandshake()\fP: .INDENT 0.0 .IP \(bu 2 reads an HTTP request from the network; .IP \(bu 2 calls \fI\%process_request()\fP which may abort the WebSocket handshake and return an HTTP response instead; this hook only makes sense on the server side; .IP \(bu 2 checks the HTTP request, negotiates \fBextensions\fP and \fBsubprotocol\fP, and configures the protocol accordingly; .IP \(bu 2 builds an HTTP response based on the above and parameters passed to \fI\%serve()\fP; .IP \(bu 2 writes the HTTP response to the network; .IP \(bu 2 moves to the \fBOPEN\fP state; .IP \(bu 2 returns the \fBpath\fP part of the \fBuri\fP\&. .UNINDENT .sp 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. .sp If anything goes wrong during the opening handshake, websockets \fI\%fails the connection\fP\&. .SS Data transfer .SS Symmetry .sp 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: .INDENT 0.0 .IP \(bu 2 \fI\%client\-to\-server masking\fP: the client masks outgoing frames; the server unmasks incoming frames; .IP \(bu 2 \fI\%closing the TCP connection\fP: the server closes the connection immediately; the client waits for the server to do it. .UNINDENT .sp These differences are so minor that all the logic for \fI\%data framing\fP, for \fI\%sending and receiving data\fP and for \fI\%closing the connection\fP is implemented in the same class, \fI\%WebSocketCommonProtocol\fP\&. .sp The \fBis_client\fP attribute tells which side a protocol instance is managing. This attribute is defined on the \fI\%WebSocketServerProtocol\fP and \fI\%WebSocketClientProtocol\fP classes. .SS Data flow .sp 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. \fI\%\fP .sp 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. .SS Receiving data .sp The left side of the diagram shows how websockets receives data. .sp Incoming data is written to a \fI\%StreamReader\fP in order to implement flow control and provide backpressure on the TCP connection. .sp \fBtransfer_data_task\fP, which is started when the WebSocket connection is established, processes this data. .sp When it receives data frames, it reassembles fragments and puts the resulting messages in the \fBmessages\fP queue. .sp When it encounters a control frame: .INDENT 0.0 .IP \(bu 2 if it\(aqs a close frame, it starts the closing handshake; .IP \(bu 2 if it\(aqs a ping frame, it answers with a pong frame; .IP \(bu 2 if it\(aqs a pong frame, it acknowledges the corresponding ping (unless it\(aqs an unsolicited pong). .UNINDENT .sp 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 \fI\%recv()\fP at any time. While this happens naturally in many use cases, it cannot be relied upon. .sp Then \fI\%recv()\fP fetches the next message from the \fBmessages\fP queue, with some complexity added for handling backpressure and termination correctly. .SS Sending data .sp The right side of the diagram shows how websockets sends data. .sp \fI\%send()\fP writes one or several data frames containing the message. While sending a fragmented message, concurrent calls to \fI\%send()\fP are put on hold until all fragments are sent. This makes concurrent calls safe. .sp \fI\%ping()\fP writes a ping frame and yields a \fI\%Future\fP which will be completed when a matching pong frame is received. .sp \fI\%pong()\fP writes a pong frame. .sp \fI\%close()\fP writes a close frame and waits for the TCP connection to terminate. .sp Outgoing data is written to a \fI\%StreamWriter\fP in order to implement flow control and provide backpressure from the TCP connection. .SS Closing handshake .sp When the other side of the connection initiates the closing handshake, \fBread_message()\fP receives a close frame while in the \fBOPEN\fP state. It moves to the \fBCLOSING\fP state, sends a close frame, and returns \fI\%None\fP, causing \fBtransfer_data_task\fP to terminate. .sp When this side of the connection initiates the closing handshake with \fI\%close()\fP, it moves to the \fBCLOSING\fP state and sends a close frame. When the other side sends a close frame, \fBread_message()\fP receives it in the \fBCLOSING\fP state and returns \fI\%None\fP, also causing \fBtransfer_data_task\fP to terminate. .sp If the other side doesn\(aqt send a close frame within the connection\(aqs close timeout, websockets \fI\%fails the connection\fP\&. .sp The closing handshake can take up to \fB2 * close_timeout\fP: one \fBclose_timeout\fP to write a close frame and one \fBclose_timeout\fP to receive a close frame. .sp Then websockets terminates the TCP connection. .SS Connection termination .sp \fBclose_connection_task\fP, which is started when the WebSocket connection is established, is responsible for eventually closing the TCP connection. .sp First \fBclose_connection_task\fP waits for \fBtransfer_data_task\fP to terminate, which may happen as a result of: .INDENT 0.0 .IP \(bu 2 a successful closing handshake: as explained above, this exits the infinite loop in \fBtransfer_data_task\fP; .IP \(bu 2 a timeout while waiting for the closing handshake to complete: this cancels \fBtransfer_data_task\fP; .IP \(bu 2 a protocol error, including connection errors: depending on the exception, \fBtransfer_data_task\fP \fI\%fails the connection\fP with a suitable code and exits. .UNINDENT .sp \fBclose_connection_task\fP is separate from \fBtransfer_data_task\fP to make it easier to implement the timeout on the closing handshake. Canceling \fBtransfer_data_task\fP creates no risk of canceling \fBclose_connection_task\fP and failing to close the TCP connection, thus leaking resources. .sp Then \fBclose_connection_task\fP cancels \fBkeepalive_ping()\fP\&. This task has no protocol compliance responsibilities. Terminating it to avoid leaking it is the only concern. .sp Terminating the TCP connection can take up to \fB2 * close_timeout\fP on the server side and \fB3 * close_timeout\fP on the client side. Clients start by waiting for the server to close the connection, hence the extra \fBclose_timeout\fP\&. 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. .SS Connection failure .sp If the opening handshake doesn\(aqt complete successfully, websockets fails the connection by closing the TCP connection. .sp Once the opening handshake has completed, websockets fails the connection by canceling \fBtransfer_data_task\fP and sending a close frame if appropriate. .sp \fBtransfer_data_task\fP exits, unblocking \fBclose_connection_task\fP, which closes the TCP connection. .SS Server shutdown .sp \fI\%WebSocketServer\fP closes asynchronously like \fI\%asyncio.Server\fP\&. The shutdown happen in two steps: .INDENT 0.0 .IP 1. 3 Stop listening and accepting new connections; .IP 2. 3 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). .UNINDENT .sp The first call to \fI\%close\fP starts a task that performs this sequence. Further calls are ignored. This is the easiest way to make \fI\%close\fP and \fI\%wait_closed\fP idempotent. .SS Cancellation .SS User code .sp websockets provides a WebSocket application server. It manages connections and passes them to user\-provided connection handlers. This is an \fIinversion of control\fP scenario: library code calls user code. .sp 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. .sp 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 \fI\%CancelledError\fP, terminate or cancel these tasks, and then re\-raise the exception. .sp Cancellation is tricky in \fI\%asyncio\fP applications, especially when it interacts with finalization logic. In the example above, what if a handler gets interrupted with \fI\%CancelledError\fP while it\(aqs finalizing the tasks it started, after detecting that the connection dropped? .sp websockets considers that cancellation may only be triggered by the caller of a coroutine when it doesn\(aqt care about the results of that coroutine anymore. (Source: \fI\%Guido van Rossum\fP). Since connection handlers run arbitrary user code, websockets has no way of deciding whether that code is still doing something worth caring about. .sp 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. .sp Conversely, cancellation isn\(aqt a concern for WebSocket clients because they don\(aqt involve inversion of control. .SS Library .sp Most \fI\%public APIs\fP 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. .sp 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. .sp Once the WebSocket connection is established, internal tasks \fBtransfer_data_task\fP and \fBclose_connection_task\fP mustn\(aqt get accidentally canceled if a coroutine that awaits them is canceled. In other words, they must be shielded from cancellation. .sp \fI\%recv()\fP waits for the next message in the queue or for \fBtransfer_data_task\fP to terminate, whichever comes first. It relies on \fI\%wait()\fP for waiting on two futures in parallel. As a consequence, even though it\(aqs waiting on a \fI\%Future\fP signaling the next message and on \fBtransfer_data_task\fP, it doesn\(aqt propagate cancellation to them. .sp \fBensure_open()\fP is called by \fI\%send()\fP, \fI\%ping()\fP, and \fI\%pong()\fP\&. When the connection state is \fBCLOSING\fP, it waits for \fBtransfer_data_task\fP but shields it to prevent cancellation. .sp \fI\%close()\fP waits for the data transfer task to terminate with \fI\%timeout()\fP\&. If it\(aqs canceled or if the timeout elapses, \fBtransfer_data_task\fP is canceled, which is correct at this point. \fI\%close()\fP then waits for \fBclose_connection_task\fP but shields it to prevent cancellation. .sp \fI\%close()\fP and \fBfail_connection()\fP are the only places where \fBtransfer_data_task\fP may be canceled. .sp \fBclose_connection_task\fP starts by waiting for \fBtransfer_data_task\fP\&. It catches \fI\%CancelledError\fP to prevent a cancellation of \fBtransfer_data_task\fP from propagating to \fBclose_connection_task\fP\&. .SS Backpressure .sp \fBNOTE:\fP .INDENT 0.0 .INDENT 3.5 This section discusses backpressure from the perspective of a server but the concept applies to clients symmetrically. .UNINDENT .UNINDENT .sp 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. .sp 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. .sp websockets is designed to make it easy to get backpressure right. .sp For incoming data, websockets builds upon \fI\%StreamReader\fP 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. .sp For outgoing data, websockets builds upon \fI\%StreamWriter\fP which implements flow control. If the output buffers grow too large, it waits until they\(aqre drained. That\(aqs why all APIs that write frames are asynchronous. .sp Of course, it\(aqs still possible for an application to create its own unbounded buffers and break the backpressure. Be careful with queues. .SS Buffers .sp \fBNOTE:\fP .INDENT 0.0 .INDENT 3.5 This section discusses buffers from the perspective of a server but it applies to clients as well. .UNINDENT .UNINDENT .sp An asynchronous systems works best when its buffers are almost always empty. .sp 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. .sp 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. .sp By default websockets has rather high limits. You can decrease them according to your application\(aqs characteristics. .sp Bufferbloat can happen at every level in the stack where there is a buffer. For each connection, the receiving side contains these buffers: .INDENT 0.0 .IP \(bu 2 OS buffers: tuning them is an advanced optimization. .IP \(bu 2 \fI\%StreamReader\fP bytes buffer: the default limit is 64\ KiB. You can set another limit by passing a \fBread_limit\fP keyword argument to \fI\%connect()\fP or \fI\%serve()\fP\&. .IP \(bu 2 Incoming messages \fI\%deque\fP: 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 \fBmax_size\fP and \fBmax_queue\fP keyword arguments of \fI\%connect()\fP or \fI\%serve()\fP according to your application\(aqs requirements. .UNINDENT .sp For each connection, the sending side contains these buffers: .INDENT 0.0 .IP \(bu 2 \fI\%StreamWriter\fP bytes buffer: the default size is 64\ KiB. You can set another limit by passing a \fBwrite_limit\fP keyword argument to \fI\%connect()\fP or \fI\%serve()\fP\&. .IP \(bu 2 OS buffers: tuning them is an advanced optimization. .UNINDENT .SS Concurrency .sp Awaiting any combination of \fI\%recv()\fP, \fI\%send()\fP, \fI\%close()\fP \fI\%ping()\fP, or \fI\%pong()\fP concurrently is safe, including multiple calls to the same method, with one exception and one limitation. .INDENT 0.0 .IP \(bu 2 \fBOnly one coroutine can receive messages at a time.\fP This constraint avoids non\-deterministic behavior (and simplifies the implementation). If a coroutine is awaiting \fI\%recv()\fP, awaiting it again in another coroutine raises \fI\%RuntimeError\fP\&. .IP \(bu 2 \fBSending a fragmented message forces serialization.\fP Indeed, the WebSocket protocol doesn\(aqt support multiplexing messages. If a coroutine is awaiting \fI\%send()\fP 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. .UNINDENT .sp Receiving frames is independent from sending frames. This isolates \fI\%recv()\fP, which receives frames, from the other methods, which send frames. .sp While the connection is open, each frame is sent with a single write. Combined with the concurrency model of \fI\%asyncio\fP, this enforces serialization. The only other requirement is to prevent interleaving other data frames in the middle of a fragmented message. .sp After the connection is closed, sending a frame raises \fI\%ConnectionClosed\fP, which is safe. .SS Memory usage .sp 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. .sp Memory usage of a single connection is the sum of: .INDENT 0.0 .IP 1. 3 the baseline amount of memory websockets requires for each connection, .IP 2. 3 the amount of data held in buffers before the application processes it, .IP 3. 3 any additional memory allocated by the application itself. .UNINDENT .SS Baseline .sp Compression settings are the main factor affecting the baseline amount of memory used by each connection. .sp With websockets\(aq defaults, on the server side, a single connections uses 70\ KiB of memory. .sp Refer to the \fI\%topic guide on compression\fP to learn more about tuning compression settings. .SS Buffers .sp Under normal circumstances, buffers are almost always empty. .sp Under high load, if a server receives more messages than it can process, bufferbloat can result in excessive memory usage. .sp By default websockets has generous limits. It is strongly recommended to adapt them to your application. When you call \fI\%serve()\fP: .INDENT 0.0 .IP \(bu 2 Set \fBmax_size\fP (default: 1\ MiB, UTF\-8 encoded) to the maximum size of messages your application generates. .IP \(bu 2 Set \fBmax_queue\fP (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. .UNINDENT .sp Furthermore, you can lower \fBread_limit\fP and \fBwrite_limit\fP (default: 64\ KiB) to reduce the size of buffers for incoming and outgoing data. .sp The design document provides \fI\%more details about buffers\fP\&. .SS Security .SS Encryption .sp For production use, a server should require encrypted connections. .sp See this example of \fI\%encrypting connections with TLS\fP\&. .SS Memory usage .sp \fBWARNING:\fP .INDENT 0.0 .INDENT 3.5 An attacker who can open an arbitrary number of connections will be able to perform a denial of service by memory exhaustion. If you\(aqre concerned by denial of service attacks, you must reject suspicious connections before they reach websockets, typically in a reverse proxy. .UNINDENT .UNINDENT .sp With the default settings, opening a connection uses 70\ KiB of memory. .sp 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. .sp Configuring a server to \fI\%optimize memory usage\fP will improve security in addition to improving performance. .SS Other limits .sp websockets implements additional limits on the amount of data it accepts in order to minimize exposure to security vulnerabilities. .sp 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\(aqs expected in standard use cases. They\(aqre hard\-coded. .sp If you need to change these limits, you can monkey\-patch the constants in \fBwebsockets.http11\fP\&. .SS Performance .sp Here are tips to optimize performance. .SS uvloop .sp You can make a websockets application faster by running it with \fI\%uvloop\fP\&. .sp (This advice isn\(aqt specific to websockets. It applies to any \fI\%asyncio\fP application.) .SS broadcast .sp \fI\%broadcast()\fP is the most efficient way to send a message to many clients. .SH ABOUT WEBSOCKETS .sp This is about websockets\-the\-project rather than websockets\-the\-software. .SS Changelog .SS Backwards\-compatibility policy .sp websockets is intended for production use. Therefore, stability is a goal. .sp websockets also aims at providing the best API for WebSocket in Python. .sp While we value stability, we value progress more. When an improvement requires changing a public API, we make the change and document it in this changelog. .sp When possible with reasonable effort, we preserve backwards\-compatibility for five years after the release that introduced the change. .sp When a release contains backwards\-incompatible API changes, the major version is increased, else the minor version is increased. Patch versions are only for fixing regressions shortly after a release. .sp Only documented APIs are public. Undocumented, private APIs may change without notice. .SS 12.0 .sp \fIOctober 21, 2023\fP .SS Backwards\-incompatible changes .INDENT 0.0 .INDENT 3.5 .IP "websockets 12.0 requires Python ≥ 3.8." .sp websockets 11.0 is the last version supporting Python 3.7. .UNINDENT .UNINDENT .SS Improvements .INDENT 0.0 .IP \(bu 2 Made convenience imports from \fBwebsockets\fP compatible with static code analysis tools such as auto\-completion in an IDE or type checking with \fI\%mypy\fP\&. .IP \(bu 2 Accepted a plain \fI\%int\fP where an \fI\%HTTPStatus\fP is expected. .IP \(bu 2 Added \fI\%CloseCode\fP\&. .UNINDENT .SS 11.0.3 .sp \fIMay 7, 2023\fP .SS Bug fixes .INDENT 0.0 .IP \(bu 2 Fixed the \fI\%threading\fP implementation of servers on Windows. .UNINDENT .SS 11.0.2 .sp \fIApril 18, 2023\fP .SS Bug fixes .INDENT 0.0 .IP \(bu 2 Fixed a deadlock in the \fI\%threading\fP implementation when closing a connection without reading all messages. .UNINDENT .SS 11.0.1 .sp \fIApril 6, 2023\fP .SS Bug fixes .INDENT 0.0 .IP \(bu 2 Restored the C extension in the source distribution. .UNINDENT .SS 11.0 .sp \fIApril 2, 2023\fP .SS Backwards\-incompatible changes .INDENT 0.0 .INDENT 3.5 .IP "The Sans\-I/O implementation was moved." .sp Aliases provide compatibility for all previously public APIs according to the \fI\%backwards\-compatibility policy\fP\&. .INDENT 0.0 .IP \(bu 2 The \fBconnection\fP module was renamed to \fBprotocol\fP\&. .IP \(bu 2 The \fBconnection.Connection\fP, \fBserver.ServerConnection\fP, and \fBclient.ClientConnection\fP classes were renamed to \fBprotocol.Protocol\fP, \fBserver.ServerProtocol\fP, and \fBclient.ClientProtocol\fP\&. .UNINDENT .UNINDENT .UNINDENT .INDENT 0.0 .INDENT 3.5 .IP "Sans\-I/O protocol constructors now use keyword\-only arguments." .sp If you instantiate \fI\%ServerProtocol\fP or \fI\%ClientProtocol\fP directly, make sure you are using keyword arguments. .UNINDENT .UNINDENT .INDENT 0.0 .INDENT 3.5 .IP "Closing a connection without an empty close frame is OK." .sp Receiving an empty close frame now results in \fI\%ConnectionClosedOK\fP instead of \fI\%ConnectionClosedError\fP\&. .sp As a consequence, calling \fBWebSocket.close()\fP without arguments in a browser isn\(aqt reported as an error anymore. .UNINDENT .UNINDENT .INDENT 0.0 .INDENT 3.5 .IP "\fI\%serve()\fP times out on the opening handshake after 10 seconds by default." .sp You can adjust the timeout with the \fBopen_timeout\fP parameter. Set it to \fI\%None\fP to disable the timeout entirely. .UNINDENT .UNINDENT .SS New features .INDENT 0.0 .INDENT 3.5 .IP "websockets 11.0 introduces a implementation on top of \fI\%threading\fP\&." .sp It may be more convenient if you don\(aqt need to manage many connections and you\(aqre more comfortable with \fI\%threading\fP than \fI\%asyncio\fP\&. .sp It is particularly suited to client applications that establish only one connection. It may be used for servers handling few connections. .sp See \fI\%connect()\fP and \fI\%serve()\fP for details. .UNINDENT .UNINDENT .INDENT 0.0 .IP \(bu 2 Added \fBopen_timeout\fP to \fI\%serve()\fP\&. .IP \(bu 2 Made it possible to close a server without closing existing connections. .IP \(bu 2 Added \fI\%select_subprotocol\fP to customize negotiation of subprotocols in the Sans\-I/O layer. .UNINDENT .SS Improvements .INDENT 0.0 .IP \(bu 2 Added platform\-independent wheels. .IP \(bu 2 Improved error handling in \fI\%broadcast()\fP\&. .IP \(bu 2 Set \fBserver_hostname\fP automatically on TLS connections when providing a \fBsock\fP argument to \fI\%connect()\fP\&. .UNINDENT .SS 10.4 .sp \fIOctober 25, 2022\fP .SS New features .INDENT 0.0 .IP \(bu 2 Validated compatibility with Python 3.11. .IP \(bu 2 Added the \fI\%latency\fP property to protocols. .IP \(bu 2 Changed \fI\%ping\fP to return the latency of the connection. .IP \(bu 2 Supported overriding or removing the \fBUser\-Agent\fP header in clients and the \fBServer\fP header in servers. .IP \(bu 2 Added deployment guides for more Platform as a Service providers. .UNINDENT .SS Improvements .INDENT 0.0 .IP \(bu 2 Improved FAQ. .UNINDENT .SS 10.3 .sp \fIApril 17, 2022\fP .SS Backwards\-incompatible changes .INDENT 0.0 .INDENT 3.5 .IP "The \fBexception\fP attribute of \fI\%Request\fP and \fI\%Response\fP is deprecated." .sp Use the \fBhandshake_exc\fP attribute of \fI\%ServerProtocol\fP and \fI\%ClientProtocol\fP instead. .sp See \fI\%Integrate the Sans\-I/O layer\fP for details. .UNINDENT .UNINDENT .SS Improvements .INDENT 0.0 .IP \(bu 2 Reduced noise in logs when \fI\%ssl\fP or \fI\%zlib\fP raise exceptions. .UNINDENT .SS 10.2 .sp \fIFebruary 21, 2022\fP .SS Improvements .INDENT 0.0 .IP \(bu 2 Made compression negotiation more lax for compatibility with Firefox. .IP \(bu 2 Improved FAQ and quick start guide. .UNINDENT .SS Bug fixes .INDENT 0.0 .IP \(bu 2 Fixed backwards\-incompatibility in 10.1 for connection handlers created with \fI\%functools.partial()\fP\&. .IP \(bu 2 Avoided leaking open sockets when \fI\%connect()\fP is canceled. .UNINDENT .SS 10.1 .sp \fINovember 14, 2021\fP .SS New features .INDENT 0.0 .IP \(bu 2 Added a tutorial. .IP \(bu 2 Made the second parameter of connection handlers optional. It will be deprecated in the next major release. The request path is available in the \fI\%path\fP attribute of the first argument. .sp If you implemented the connection handler of a server as: .INDENT 2.0 .INDENT 3.5 .sp .nf .ft C async def handler(request, path): ... .ft P .fi .UNINDENT .UNINDENT .sp You should replace it by: .INDENT 2.0 .INDENT 3.5 .sp .nf .ft C async def handler(request): path = request.path # if handler() uses the path argument ... .ft P .fi .UNINDENT .UNINDENT .IP \(bu 2 Added \fBpython \-m websockets \-\-version\fP\&. .UNINDENT .SS Improvements .INDENT 0.0 .IP \(bu 2 Added wheels for Python 3.10, PyPy 3.7, and for more platforms. .IP \(bu 2 Reverted optimization of default compression settings for clients, mainly to avoid triggering bugs in poorly implemented servers like \fI\%AWS API Gateway\fP\&. .IP \(bu 2 Mirrored the entire \fI\%Server\fP API in \fI\%WebSocketServer\fP\&. .IP \(bu 2 Improved performance for large messages on ARM processors. .IP \(bu 2 Documented how to auto\-reload on code changes in development. .UNINDENT .SS Bug fixes .INDENT 0.0 .IP \(bu 2 Avoided half\-closing TCP connections that are already closed. .UNINDENT .SS 10.0 .sp \fISeptember 9, 2021\fP .SS Backwards\-incompatible changes .INDENT 0.0 .INDENT 3.5 .IP "websockets 10.0 requires Python ≥ 3.7." .sp websockets 9.1 is the last version supporting Python 3.6. .UNINDENT .UNINDENT .INDENT 0.0 .INDENT 3.5 .IP "The \fBloop\fP parameter is deprecated from all APIs." .sp This reflects a decision made in Python 3.8. See the release notes of Python 3.10 for details. .sp The \fBloop\fP parameter is also removed from \fI\%WebSocketServer\fP\&. This should be transparent. .UNINDENT .UNINDENT .INDENT 0.0 .INDENT 3.5 .IP "\fI\%connect()\fP times out after 10 seconds by default." .sp You can adjust the timeout with the \fBopen_timeout\fP parameter. Set it to \fI\%None\fP to disable the timeout entirely. .UNINDENT .UNINDENT .INDENT 0.0 .INDENT 3.5 .IP "The \fBlegacy_recv\fP option is deprecated." .sp See the release notes of websockets 3.0 for details. .UNINDENT .UNINDENT .INDENT 0.0 .INDENT 3.5 .IP "The signature of \fI\%ConnectionClosed\fP changed." .sp If you raise \fI\%ConnectionClosed\fP or a subclass, rather than catch them when websockets raises them, you must change your code. .UNINDENT .UNINDENT .INDENT 0.0 .INDENT 3.5 .IP "A \fBmsg\fP parameter was added to \fI\%InvalidURI\fP\&." .sp If you raise \fI\%InvalidURI\fP, rather than catch it when websockets raises it, you must change your code. .UNINDENT .UNINDENT .SS New features .INDENT 0.0 .INDENT 3.5 .IP "websockets 10.0 introduces a \fI\%Sans\-I/O API\fP for easier integration in third\-party libraries." .sp If you\(aqre integrating websockets in a library, rather than just using it, look at the \fI\%Sans\-I/O integration guide\fP\&. .UNINDENT .UNINDENT .INDENT 0.0 .IP \(bu 2 Added compatibility with Python 3.10. .IP \(bu 2 Added \fI\%broadcast()\fP to send a message to many clients. .IP \(bu 2 Added support for reconnecting automatically by using \fI\%connect()\fP as an asynchronous iterator. .IP \(bu 2 Added \fBopen_timeout\fP to \fI\%connect()\fP\&. .IP \(bu 2 Documented how to integrate with \fI\%Django\fP\&. .IP \(bu 2 Documented how to deploy websockets in production, with several options. .IP \(bu 2 Documented how to authenticate connections. .IP \(bu 2 Documented how to broadcast messages to many connections. .UNINDENT .SS Improvements .INDENT 0.0 .IP \(bu 2 Improved logging. See the \fI\%logging guide\fP\&. .IP \(bu 2 Optimized default compression settings to reduce memory usage. .IP \(bu 2 Optimized processing of client\-to\-server messages when the C extension isn\(aqt available. .IP \(bu 2 Supported relative redirects in \fI\%connect()\fP\&. .IP \(bu 2 Handled TCP connection drops during the opening handshake. .IP \(bu 2 Made it easier to customize authentication with \fI\%check_credentials()\fP\&. .IP \(bu 2 Provided additional information in \fI\%ConnectionClosed\fP exceptions. .IP \(bu 2 Clarified several exceptions or log messages. .IP \(bu 2 Restructured documentation. .IP \(bu 2 Improved API documentation. .IP \(bu 2 Extended FAQ. .UNINDENT .SS Bug fixes .INDENT 0.0 .IP \(bu 2 Avoided a crash when receiving a ping while the connection is closing. .UNINDENT .SS 9.1 .sp \fIMay 27, 2021\fP .SS Security fix .INDENT 0.0 .INDENT 3.5 .IP "websockets 9.1 fixes a security issue introduced in 8.0." .sp Version 8.0 was vulnerable to timing attacks on HTTP Basic Auth passwords (\fI\%CVE\-2021\-33880\fP). .UNINDENT .UNINDENT .SS 9.0.2 .sp \fIMay 15, 2021\fP .SS Bug fixes .INDENT 0.0 .IP \(bu 2 Restored compatibility of \fBpython \-m websockets\fP with Python < 3.9. .IP \(bu 2 Restored compatibility with mypy. .UNINDENT .SS 9.0.1 .sp \fIMay 2, 2021\fP .SS Bug fixes .INDENT 0.0 .IP \(bu 2 Fixed issues with the packaging of the 9.0 release. .UNINDENT .SS 9.0 .sp \fIMay 1, 2021\fP .SS Backwards\-incompatible changes .INDENT 0.0 .INDENT 3.5 .IP "Several modules are moved or deprecated." .sp Aliases provide compatibility for all previously public APIs according to the \fI\%backwards\-compatibility policy\fP .INDENT 0.0 .IP \(bu 2 \fI\%Headers\fP and \fI\%MultipleValuesError\fP are moved from \fBwebsockets.http\fP to \fI\%websockets.datastructures\fP\&. If you\(aqre using them, you should adjust the import path. .IP \(bu 2 The \fBclient\fP, \fBserver\fP, \fBprotocol\fP, and \fBauth\fP modules were moved from the \fBwebsockets\fP package to a \fBwebsockets.legacy\fP sub\-package. Despite the name, they\(aqre still fully supported. .IP \(bu 2 The \fBframing\fP, \fBhandshake\fP, \fBheaders\fP, \fBhttp\fP, and \fBuri\fP modules in the \fBwebsockets\fP package are deprecated. These modules provided low\-level APIs for reuse by other projects, but they didn\(aqt reach that goal. Keeping these APIs public makes it more difficult to improve websockets. .UNINDENT .sp These changes pave the path for a refactoring that should be a transparent upgrade for most uses and facilitate integration by other projects. .UNINDENT .UNINDENT .INDENT 0.0 .INDENT 3.5 .IP "Convenience imports from \fBwebsockets\fP are performed lazily." .sp While Python supports this, tools relying on static code analysis don\(aqt. This breaks auto\-completion in an IDE or type checking with \fI\%mypy\fP\&. .sp If you depend on such tools, use the real import paths, which can be found in the API documentation, for example: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C from websockets.client import connect from websockets.server import serve .ft P .fi .UNINDENT .UNINDENT .UNINDENT .UNINDENT .SS New features .INDENT 0.0 .IP \(bu 2 Added compatibility with Python 3.9. .UNINDENT .SS Improvements .INDENT 0.0 .IP \(bu 2 Added support for IRIs in addition to URIs. .IP \(bu 2 Added close codes 1012, 1013, and 1014. .IP \(bu 2 Raised an error when passing a \fI\%dict\fP to \fI\%send()\fP\&. .IP \(bu 2 Improved error reporting. .UNINDENT .SS Bug fixes .INDENT 0.0 .IP \(bu 2 Fixed sending fragmented, compressed messages. .IP \(bu 2 Fixed \fBHost\fP header sent when connecting to an IPv6 address. .IP \(bu 2 Fixed creating a client or a server with an existing Unix socket. .IP \(bu 2 Aligned maximum cookie size with popular web browsers. .IP \(bu 2 Ensured cancellation always propagates, even on Python versions where \fI\%CancelledError\fP inherits \fI\%Exception\fP\&. .UNINDENT .SS 8.1 .sp \fINovember 1, 2019\fP .SS New features .INDENT 0.0 .IP \(bu 2 Added compatibility with Python 3.8. .UNINDENT .SS 8.0.2 .sp \fIJuly 31, 2019\fP .SS Bug fixes .INDENT 0.0 .IP \(bu 2 Restored the ability to pass a socket with the \fBsock\fP parameter of \fI\%serve()\fP\&. .IP \(bu 2 Removed an incorrect assertion when a connection drops. .UNINDENT .SS 8.0.1 .sp \fIJuly 21, 2019\fP .SS Bug fixes .INDENT 0.0 .IP \(bu 2 Restored the ability to import \fBWebSocketProtocolError\fP from \fBwebsockets\fP\&. .UNINDENT .SS 8.0 .sp \fIJuly 7, 2019\fP .SS Backwards\-incompatible changes .INDENT 0.0 .INDENT 3.5 .IP "websockets 8.0 requires Python ≥ 3.6." .sp websockets 7.0 is the last version supporting Python 3.4 and 3.5. .UNINDENT .UNINDENT .INDENT 0.0 .INDENT 3.5 .IP "\fBprocess_request\fP is now expected to be a coroutine." .sp If you\(aqre passing a \fBprocess_request\fP argument to \fI\%serve()\fP or \fI\%WebSocketServerProtocol\fP, or if you\(aqre overriding \fI\%process_request()\fP in a subclass, define it with \fBasync def\fP instead of \fBdef\fP\&. Previously, both were supported. .sp For backwards compatibility, functions are still accepted, but mixing functions and coroutines won\(aqt work in some inheritance scenarios. .UNINDENT .UNINDENT .INDENT 0.0 .INDENT 3.5 .IP "\fBmax_queue\fP must be \fI\%None\fP to disable the limit." .sp If you were setting \fBmax_queue=0\fP to make the queue of incoming messages unbounded, change it to \fBmax_queue=None\fP\&. .UNINDENT .UNINDENT .INDENT 0.0 .INDENT 3.5 .IP "The \fBhost\fP, \fBport\fP, and \fBsecure\fP attributes of \fI\%WebSocketCommonProtocol\fP are deprecated." .sp Use \fI\%local_address\fP in servers and \fI\%remote_address\fP in clients instead of \fBhost\fP and \fBport\fP\&. .UNINDENT .UNINDENT .INDENT 0.0 .INDENT 3.5 .IP "\fBWebSocketProtocolError\fP is renamed to \fI\%ProtocolError\fP\&." .sp An alias provides backwards compatibility. .UNINDENT .UNINDENT .INDENT 0.0 .INDENT 3.5 .IP "\fBread_response()\fP now returns the reason phrase." .sp If you\(aqre using this low\-level API, you must change your code. .UNINDENT .UNINDENT .SS New features .INDENT 0.0 .IP \(bu 2 Added \fI\%basic_auth_protocol_factory()\fP to enforce HTTP Basic Auth on the server side. .IP \(bu 2 \fI\%connect()\fP handles redirects from the server during the handshake. .IP \(bu 2 \fI\%connect()\fP supports overriding \fBhost\fP and \fBport\fP\&. .IP \(bu 2 Added \fI\%unix_connect()\fP for connecting to Unix sockets. .IP \(bu 2 Added support for asynchronous generators in \fI\%send()\fP to generate fragmented messages incrementally. .IP \(bu 2 Enabled readline in the interactive client. .IP \(bu 2 Added type hints (\fI\%PEP 484\fP). .IP \(bu 2 Added a FAQ to the documentation. .IP \(bu 2 Added documentation for extensions. .IP \(bu 2 Documented how to optimize memory usage. .UNINDENT .SS Improvements .INDENT 0.0 .IP \(bu 2 \fI\%send()\fP, \fI\%ping()\fP, and \fI\%pong()\fP support bytes\-like types \fI\%bytearray\fP and \fI\%memoryview\fP in addition to \fI\%bytes\fP\&. .IP \(bu 2 Added \fI\%ConnectionClosedOK\fP and \fI\%ConnectionClosedError\fP subclasses of \fI\%ConnectionClosed\fP to tell apart normal connection termination from errors. .IP \(bu 2 Changed \fI\%WebSocketServer.close()\fP to perform a proper closing handshake instead of failing the connection. .IP \(bu 2 Improved error messages when HTTP parsing fails. .IP \(bu 2 Improved API documentation. .UNINDENT .SS Bug fixes .INDENT 0.0 .IP \(bu 2 Prevented spurious log messages about \fI\%ConnectionClosed\fP exceptions in keepalive ping task. If you were using \fBping_timeout=None\fP as a workaround, you can remove it. .IP \(bu 2 Avoided a crash when a \fBextra_headers\fP callable returns \fI\%None\fP\&. .UNINDENT .SS 7.0 .sp \fINovember 1, 2018\fP .SS Backwards\-incompatible changes .INDENT 0.0 .INDENT 3.5 .IP "Keepalive is enabled by default." .sp websockets now sends Ping frames at regular intervals and closes the connection if it doesn\(aqt receive a matching Pong frame. See \fI\%WebSocketCommonProtocol\fP for details. .UNINDENT .UNINDENT .INDENT 0.0 .INDENT 3.5 .IP "Termination of connections by \fI\%WebSocketServer.close()\fP changes." .sp Previously, connections handlers were canceled. Now, connections are closed with close code 1001 (going away). .sp From the perspective of the connection handler, this is the same as if the remote endpoint was disconnecting. This removes the need to prepare for \fI\%CancelledError\fP in connection handlers. .sp You can restore the previous behavior by adding the following line at the beginning of connection handlers: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C def handler(websocket, path): closed = asyncio.ensure_future(websocket.wait_closed()) closed.add_done_callback(lambda task: task.cancel()) .ft P .fi .UNINDENT .UNINDENT .UNINDENT .UNINDENT .INDENT 0.0 .INDENT 3.5 .IP "Calling \fI\%recv()\fP concurrently raises a \fI\%RuntimeError\fP\&." .sp Concurrent calls lead to non\-deterministic behavior because there are no guarantees about which coroutine will receive which message. .UNINDENT .UNINDENT .INDENT 0.0 .INDENT 3.5 .IP "The \fBtimeout\fP argument of \fI\%serve()\fP and \fI\%connect()\fP is renamed to \fBclose_timeout\fP ." .sp This prevents confusion with \fBping_timeout\fP\&. .sp For backwards compatibility, \fBtimeout\fP is still supported. .UNINDENT .UNINDENT .INDENT 0.0 .INDENT 3.5 .IP "The \fBorigins\fP argument of \fI\%serve()\fP changes." .sp Include \fI\%None\fP in the list rather than \fB\(aq\(aq\fP to allow requests that don\(aqt contain an Origin header. .UNINDENT .UNINDENT .INDENT 0.0 .INDENT 3.5 .IP "Pending pings aren\(aqt canceled when the connection is closed." .sp A ping — as in \fBping = await websocket.ping()\fP — for which no pong was received yet used to be canceled when the connection is closed, so that \fBawait ping\fP raised \fI\%CancelledError\fP\&. .sp Now \fBawait ping\fP raises \fI\%ConnectionClosed\fP like other public APIs. .UNINDENT .UNINDENT .SS New features .INDENT 0.0 .IP \(bu 2 Added \fBprocess_request\fP and \fBselect_subprotocol\fP arguments to \fI\%serve()\fP and \fI\%WebSocketServerProtocol\fP to facilitate customization of \fI\%process_request()\fP and \fI\%select_subprotocol()\fP\&. .IP \(bu 2 Added support for sending fragmented messages. .IP \(bu 2 Added the \fI\%wait_closed()\fP method to protocols. .IP \(bu 2 Added an interactive client: \fBpython \-m websockets \fP\&. .UNINDENT .SS Improvements .INDENT 0.0 .IP \(bu 2 Improved handling of multiple HTTP headers with the same name. .IP \(bu 2 Improved error messages when a required HTTP header is missing. .UNINDENT .SS Bug fixes .INDENT 0.0 .IP \(bu 2 Fixed a data loss bug in \fI\%recv()\fP: canceling it at the wrong time could result in messages being dropped. .UNINDENT .SS 6.0 .sp \fIJuly 16, 2018\fP .SS Backwards\-incompatible changes .INDENT 0.0 .INDENT 3.5 .IP "The \fI\%Headers\fP class is introduced and several APIs are updated to use it." .INDENT 0.0 .IP \(bu 2 The \fBrequest_headers\fP argument of \fI\%process_request()\fP is now a \fI\%Headers\fP instead of an \fBhttp.client.HTTPMessage\fP\&. .IP \(bu 2 The \fBrequest_headers\fP and \fBresponse_headers\fP attributes of \fI\%WebSocketCommonProtocol\fP are now \fI\%Headers\fP instead of \fBhttp.client.HTTPMessage\fP\&. .IP \(bu 2 The \fBraw_request_headers\fP and \fBraw_response_headers\fP attributes of \fI\%WebSocketCommonProtocol\fP are removed. Use \fI\%raw_items()\fP instead. .IP \(bu 2 Functions defined in the \fBhandshake\fP module now receive \fI\%Headers\fP in argument instead of \fBget_header\fP or \fBset_header\fP functions. This affects libraries that rely on low\-level APIs. .IP \(bu 2 Functions defined in the \fBhttp\fP module now return HTTP headers as \fI\%Headers\fP instead of lists of \fB(name, value)\fP pairs. .UNINDENT .sp Since \fI\%Headers\fP and \fBhttp.client.HTTPMessage\fP provide similar APIs, much of the code dealing with HTTP headers won\(aqt require changes. .UNINDENT .UNINDENT .SS New features .INDENT 0.0 .IP \(bu 2 Added compatibility with Python 3.7. .UNINDENT .SS 5.0.1 .sp \fIMay 24, 2018\fP .SS Bug fixes .INDENT 0.0 .IP \(bu 2 Fixed a regression in 5.0 that broke some invocations of \fI\%serve()\fP and \fI\%connect()\fP\&. .UNINDENT .SS 5.0 .sp \fIMay 22, 2018\fP .SS Security fix .INDENT 0.0 .INDENT 3.5 .IP "websockets 5.0 fixes a security issue introduced in 4.0." .sp Version 4.0 was vulnerable to denial of service by memory exhaustion because it didn\(aqt enforce \fBmax_size\fP when decompressing compressed messages (\fI\%CVE\-2018\-1000518\fP). .UNINDENT .UNINDENT .SS Backwards\-incompatible changes .INDENT 0.0 .INDENT 3.5 .IP "A \fBuser_info\fP field is added to the return value of \fBparse_uri\fP and \fBWebSocketURI\fP\&." .sp If you\(aqre unpacking \fBWebSocketURI\fP into four variables, adjust your code to account for that fifth field. .UNINDENT .UNINDENT .SS New features .INDENT 0.0 .IP \(bu 2 \fI\%connect()\fP performs HTTP Basic Auth when the URI contains credentials. .IP \(bu 2 \fI\%unix_serve()\fP can be used as an asynchronous context manager on Python ≥ 3.5.1. .IP \(bu 2 Added the \fI\%closed\fP property to protocols. .IP \(bu 2 Added new examples in the documentation. .UNINDENT .SS Improvements .INDENT 0.0 .IP \(bu 2 Iterating on incoming messages no longer raises an exception when the connection terminates with close code 1001 (going away). .IP \(bu 2 A plain HTTP request now receives a 426 Upgrade Required response and doesn\(aqt log a stack trace. .IP \(bu 2 If a \fI\%ping()\fP doesn\(aqt receive a pong, it\(aqs canceled when the connection is closed. .IP \(bu 2 Reported the cause of \fI\%ConnectionClosed\fP exceptions. .IP \(bu 2 Stopped logging stack traces when the TCP connection dies prematurely. .IP \(bu 2 Prevented writing to a closing TCP connection during unclean shutdowns. .IP \(bu 2 Made connection termination more robust to network congestion. .IP \(bu 2 Prevented processing of incoming frames after failing the connection. .IP \(bu 2 Updated documentation with new features from Python 3.6. .IP \(bu 2 Improved several sections of the documentation. .UNINDENT .SS Bug fixes .INDENT 0.0 .IP \(bu 2 Prevented \fI\%TypeError\fP due to missing close code on connection close. .IP \(bu 2 Fixed a race condition in the closing handshake that raised \fI\%InvalidState\fP\&. .UNINDENT .SS 4.0.1 .sp \fINovember 2, 2017\fP .SS Bug fixes .INDENT 0.0 .IP \(bu 2 Fixed issues with the packaging of the 4.0 release. .UNINDENT .SS 4.0 .sp \fINovember 2, 2017\fP .SS Backwards\-incompatible changes .INDENT 0.0 .INDENT 3.5 .IP "websockets 4.0 requires Python ≥ 3.4." .sp websockets 3.4 is the last version supporting Python 3.3. .UNINDENT .UNINDENT .INDENT 0.0 .INDENT 3.5 .IP "Compression is enabled by default." .sp In August 2017, Firefox and Chrome support the permessage\-deflate extension, but not Safari and IE. .sp Compression should improve performance but it increases RAM and CPU use. .sp If you want to disable compression, add \fBcompression=None\fP when calling \fI\%serve()\fP or \fI\%connect()\fP\&. .UNINDENT .UNINDENT .INDENT 0.0 .INDENT 3.5 .IP "The \fBstate_name\fP attribute of protocols is deprecated." .sp Use \fBprotocol.state.name\fP instead of \fBprotocol.state_name\fP\&. .UNINDENT .UNINDENT .SS New features .INDENT 0.0 .IP \(bu 2 \fI\%WebSocketCommonProtocol\fP instances can be used as asynchronous iterators on Python ≥ 3.6. They yield incoming messages. .IP \(bu 2 Added \fI\%unix_serve()\fP for listening on Unix sockets. .IP \(bu 2 Added the \fI\%sockets\fP attribute to the return value of \fI\%serve()\fP\&. .IP \(bu 2 Allowed \fBextra_headers\fP to override \fBServer\fP and \fBUser\-Agent\fP headers. .UNINDENT .SS Improvements .INDENT 0.0 .IP \(bu 2 Reorganized and extended documentation. .IP \(bu 2 Rewrote connection termination to increase robustness in edge cases. .IP \(bu 2 Reduced verbosity of \(dqFailing the WebSocket connection\(dq logs. .UNINDENT .SS Bug fixes .INDENT 0.0 .IP \(bu 2 Aborted connections if they don\(aqt close within the configured \fBtimeout\fP\&. .IP \(bu 2 Stopped leaking pending tasks when \fI\%cancel()\fP is called on a connection while it\(aqs being closed. .UNINDENT .SS 3.4 .sp \fIAugust 20, 2017\fP .SS Backwards\-incompatible changes .INDENT 0.0 .INDENT 3.5 .IP "\fBInvalidStatus\fP is replaced by \fI\%InvalidStatusCode\fP\&." .sp This exception is raised when \fI\%connect()\fP receives an invalid response status code from the server. .UNINDENT .UNINDENT .SS New features .INDENT 0.0 .IP \(bu 2 \fI\%serve()\fP can be used as an asynchronous context manager on Python ≥ 3.5.1. .IP \(bu 2 Added support for customizing handling of incoming connections with \fI\%process_request()\fP\&. .IP \(bu 2 Made read and write buffer sizes configurable. .UNINDENT .SS Improvements .INDENT 0.0 .IP \(bu 2 Renamed \fI\%serve()\fP and \fI\%connect()\fP\(aqs \fBklass\fP argument to \fBcreate_protocol\fP to reflect that it can also be a callable. For backwards compatibility, \fBklass\fP is still supported. .IP \(bu 2 Rewrote HTTP handling for simplicity and performance. .IP \(bu 2 Added an optional C extension to speed up low\-level operations. .UNINDENT .SS Bug fixes .INDENT 0.0 .IP \(bu 2 Providing a \fBsock\fP argument to \fI\%connect()\fP no longer crashes. .UNINDENT .SS 3.3 .sp \fIMarch 29, 2017\fP .SS New features .INDENT 0.0 .IP \(bu 2 Ensured compatibility with Python 3.6. .UNINDENT .SS Improvements .INDENT 0.0 .IP \(bu 2 Reduced noise in logs caused by connection resets. .UNINDENT .SS Bug fixes .INDENT 0.0 .IP \(bu 2 Avoided crashing on concurrent writes on slow connections. .UNINDENT .SS 3.2 .sp \fIAugust 17, 2016\fP .SS New features .INDENT 0.0 .IP \(bu 2 Added \fBtimeout\fP, \fBmax_size\fP, and \fBmax_queue\fP arguments to \fI\%connect()\fP and \fI\%serve()\fP\&. .UNINDENT .SS Improvements .INDENT 0.0 .IP \(bu 2 Made server shutdown more robust. .UNINDENT .SS 3.1 .sp \fIApril 21, 2016\fP .SS New features .INDENT 0.0 .IP \(bu 2 Added flow control for incoming data. .UNINDENT .SS Bug fixes .INDENT 0.0 .IP \(bu 2 Avoided a warning when closing a connection before the opening handshake. .UNINDENT .SS 3.0 .sp \fIDecember 25, 2015\fP .SS Backwards\-incompatible changes .INDENT 0.0 .INDENT 3.5 .IP "\fI\%recv()\fP now raises an exception when the connection is closed." .sp \fI\%recv()\fP used to return \fI\%None\fP when the connection was closed. This required checking the return value of every call: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C message = await websocket.recv() if message is None: return .ft P .fi .UNINDENT .UNINDENT .sp Now it raises a \fI\%ConnectionClosed\fP exception instead. This is more Pythonic. The previous code can be simplified to: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C message = await websocket.recv() .ft P .fi .UNINDENT .UNINDENT .sp When implementing a server, there\(aqs no strong reason to handle such exceptions. Let them bubble up, terminate the handler coroutine, and the server will simply ignore them. .sp In order to avoid stranding projects built upon an earlier version, the previous behavior can be restored by passing \fBlegacy_recv=True\fP to \fI\%serve()\fP, \fI\%connect()\fP, \fI\%WebSocketServerProtocol\fP, or \fI\%WebSocketClientProtocol\fP\&. .UNINDENT .UNINDENT .SS New features .INDENT 0.0 .IP \(bu 2 \fI\%connect()\fP can be used as an asynchronous context manager on Python ≥ 3.5.1. .IP \(bu 2 \fI\%ping()\fP and \fI\%pong()\fP support data passed as \fI\%str\fP in addition to \fI\%bytes\fP\&. .IP \(bu 2 Made \fBstate_name\fP attribute on protocols a public API. .UNINDENT .SS Improvements .INDENT 0.0 .IP \(bu 2 Updated documentation with \fBawait\fP and \fBasync\fP syntax from Python 3.5. .IP \(bu 2 Worked around an \fI\%asyncio\fP bug affecting connection termination under load. .IP \(bu 2 Improved documentation. .UNINDENT .SS 2.7 .sp \fINovember 18, 2015\fP .SS New features .INDENT 0.0 .IP \(bu 2 Added compatibility with Python 3.5. .UNINDENT .SS Improvements .INDENT 0.0 .IP \(bu 2 Refreshed documentation. .UNINDENT .SS 2.6 .sp \fIAugust 18, 2015\fP .SS New features .INDENT 0.0 .IP \(bu 2 Added \fBlocal_address\fP and \fBremote_address\fP attributes on protocols. .IP \(bu 2 Closed open connections with code 1001 when a server shuts down. .UNINDENT .SS Bug fixes .INDENT 0.0 .IP \(bu 2 Avoided TCP fragmentation of small frames. .UNINDENT .SS 2.5 .sp \fIJuly 28, 2015\fP .SS New features .INDENT 0.0 .IP \(bu 2 Provided access to handshake request and response HTTP headers. .IP \(bu 2 Allowed customizing handshake request and response HTTP headers. .IP \(bu 2 Added support for running on a non\-default event loop. .UNINDENT .SS Improvements .INDENT 0.0 .IP \(bu 2 Improved documentation. .IP \(bu 2 Sent a 403 status code instead of 400 when request Origin isn\(aqt allowed. .IP \(bu 2 Clarified that the closing handshake can be initiated by the client. .IP \(bu 2 Set the close code and reason more consistently. .IP \(bu 2 Strengthened connection termination. .UNINDENT .SS Bug fixes .INDENT 0.0 .IP \(bu 2 Canceling \fI\%recv()\fP no longer drops the next message. .UNINDENT .SS 2.4 .sp \fIJanuary 31, 2015\fP .SS New features .INDENT 0.0 .IP \(bu 2 Added support for subprotocols. .IP \(bu 2 Added \fBloop\fP argument to \fI\%connect()\fP and \fI\%serve()\fP\&. .UNINDENT .SS 2.3 .sp \fINovember 3, 2014\fP .SS Improvements .INDENT 0.0 .IP \(bu 2 Improved compliance of close codes. .UNINDENT .SS 2.2 .sp \fIJuly 28, 2014\fP .SS New features .INDENT 0.0 .IP \(bu 2 Added support for limiting message size. .UNINDENT .SS 2.1 .sp \fIApril 26, 2014\fP .SS New features .INDENT 0.0 .IP \(bu 2 Added \fBhost\fP, \fBport\fP and \fBsecure\fP attributes on protocols. .IP \(bu 2 Added support for providing and checking \fI\%Origin\fP\&. .UNINDENT .SS 2.0 .sp \fIFebruary 16, 2014\fP .SS Backwards\-incompatible changes .INDENT 0.0 .INDENT 3.5 .IP "\fI\%send()\fP, \fI\%ping()\fP, and \fI\%pong()\fP are now coroutines." .sp They used to be functions. .sp Instead of: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C websocket.send(message) .ft P .fi .UNINDENT .UNINDENT .sp you must write: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C await websocket.send(message) .ft P .fi .UNINDENT .UNINDENT .UNINDENT .UNINDENT .SS New features .INDENT 0.0 .IP \(bu 2 Added flow control for outgoing data. .UNINDENT .SS 1.0 .sp \fINovember 14, 2013\fP .SS New features .INDENT 0.0 .IP \(bu 2 Initial public release. .UNINDENT .SS Contributing .sp Thanks for taking the time to contribute to websockets! .SS Code of Conduct .sp This project and everyone participating in it is governed by the \fI\%Code of Conduct\fP\&. By participating, you are expected to uphold this code. Please report inappropriate behavior to aymeric DOT augustin AT fractalideas DOT com. .sp \fI(If I\(aqm the person with the inappropriate behavior, please accept my apologies. I know I can mess up. I can\(aqt expect you to tell me, but if you choose to do so, I\(aqll do my best to handle criticism constructively. \-\- Aymeric)\fP .SS Contributions .sp Bug reports, patches and suggestions are welcome! .sp Please open an \fI\%issue\fP or send a \fI\%pull request\fP\&. .sp Feedback about the documentation is especially valuable, as the primary author feels more confident about writing code than writing docs :\-) .sp If you\(aqre wondering why things are done in a certain way, the \fI\%design document\fP provides lots of details about the internals of websockets. .SS Questions .sp GitHub issues aren\(aqt a good medium for handling questions. There are better places to ask questions, for example Stack Overflow. .sp If you want to ask a question anyway, please make sure that: .INDENT 0.0 .IP \(bu 2 it\(aqs a question about websockets and not about \fI\%asyncio\fP; .IP \(bu 2 it isn\(aqt answered in the documentation; .IP \(bu 2 it wasn\(aqt asked already. .UNINDENT .sp A good question can be written as a suggestion to improve the documentation. .SS Cryptocurrency users .sp websockets appears to be quite popular for interfacing with Bitcoin or other cryptocurrency trackers. I\(aqm strongly opposed to Bitcoin\(aqs carbon footprint. .sp I\(aqm aware of efforts to build proof\-of\-stake models. I\(aqll care once the total energy consumption of all cryptocurrencies drops to a non\-bullshit level. .sp You already negated all of humanity\(aqs efforts to develop renewable energy. Please stop heating the planet where my children will have to live. .sp Since websockets is released under an open\-source license, you can use it for any purpose you like. However, I won\(aqt spend any of my time to help you. .sp I will summarily close issues related to Bitcoin or cryptocurrency in any way. .SS License .sp Copyright (c) Aymeric Augustin and contributors .sp Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: .INDENT 0.0 .INDENT 3.5 .INDENT 0.0 .IP \(bu 2 Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. .IP \(bu 2 Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. .IP \(bu 2 Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. .UNINDENT .UNINDENT .UNINDENT .sp THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \(dqAS IS\(dq AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .SS websockets for enterprise .SS Available as part of the Tidelift Subscription [image] .sp Tidelift is working with the maintainers of websockets and thousands of other open source projects to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. .SS Enterprise\-ready open source software—managed for you .sp The Tidelift Subscription is a managed open source subscription for application dependencies covering millions of open source projects across JavaScript, Python, Java, PHP, Ruby, .NET, and more. .sp Your subscription includes: .INDENT 0.0 .IP \(bu 2 \fBSecurity updates\fP .INDENT 2.0 .IP \(bu 2 Tidelift’s security response team coordinates patches for new breaking security vulnerabilities and alerts immediately through a private channel, so your software supply chain is always secure. .UNINDENT .IP \(bu 2 \fBLicensing verification and indemnification\fP .INDENT 2.0 .IP \(bu 2 Tidelift verifies license information to enable easy policy enforcement and adds intellectual property indemnification to cover creators and users in case something goes wrong. You always have a 100% up\-to\-date bill of materials for your dependencies to share with your legal team, customers, or partners. .UNINDENT .IP \(bu 2 \fBMaintenance and code improvement\fP .INDENT 2.0 .IP \(bu 2 Tidelift ensures the software you rely on keeps working as long as you need it to work. Your managed dependencies are actively maintained and we recruit additional maintainers where required. .UNINDENT .IP \(bu 2 \fBPackage selection and version guidance\fP .INDENT 2.0 .IP \(bu 2 We help you choose the best open source packages from the start—and then guide you through updates to stay on the best releases as new issues arise. .UNINDENT .IP \(bu 2 \fBRoadmap input\fP .INDENT 2.0 .IP \(bu 2 Take a seat at the table with the creators behind the software you use. Tidelift’s participating maintainers earn more income as their software is used by more subscribers, so they’re interested in knowing what you need. .UNINDENT .IP \(bu 2 \fBTooling and cloud integration\fP .INDENT 2.0 .IP \(bu 2 Tidelift works with GitHub, GitLab, BitBucket, and more. We support every cloud platform (and other deployment targets, too). .UNINDENT .UNINDENT .sp The end result? All of the capabilities you expect from commercial\-grade software, for the full breadth of open source you use. That means less time grappling with esoteric open source trivia, and more time building your own applications—and your business. .SH AUTHOR Aymeric Augustin .SH COPYRIGHT 2013-2024, Aymeric Augustin and contributors .\" Generated by docutils manpage writer. .