.\" 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.
.