Upgrade to the new asyncio implementation¶
The new asyncio implementation is a rewrite of the original
implementation of websockets.
It provides a very similar API. However, there are a few differences.
The recommended upgrade process is:
Make sure that your application doesn’t use any deprecated APIs. If it doesn’t raise any warnings, you can skip this step.
Check if your application depends on missing features. If it does, you should stick to the original implementation until they’re added.
Update import paths. For straightforward usage of websockets, this could be the only step you need to take. Upgrading could be transparent.
Review API changes and adapt your application to preserve its current functionality or take advantage of improvements in the new implementation.
In the interest of brevity, only connect() and
serve() are discussed below but everything also applies
to unix_connect() and unix_serve()
respectively.
What will happen to the original implementation?
The original implementation is now considered legacy.
The next steps are:
Deprecating it once the new implementation reaches feature parity.
Maintaining it for five years per the backwards-compatibility policy.
Removing it. This is expected to happen around 2030.
Deprecated APIs¶
Here’s the list of deprecated behaviors that the original implementation still supports and that the new implementation doesn’t reproduce.
If you’re seeing a DeprecationWarning, follow upgrade instructions from
the release notes of the version in which the feature was deprecated.
The
pathargument of connection handlers — unnecessary since 10.1 and deprecated in 13.0.The
loopandlegacy_recvarguments ofconnect()andserve(), which were removed — deprecated in 10.0.The
timeoutandklassarguments ofconnect()andserve(), which were renamed toclose_timeoutandcreate_protocol— deprecated in 7.0 and 3.4 respectively.An empty string in the
originsargument ofserve()— deprecated in 7.0.The
host,port, andsecureattributes of connections — deprecated in 8.0.
Missing features¶
All features listed below will be provided in a future release.
If your application relies on one of them, you should stick to the original implementation until the new implementation supports it in a future release.
HTTP Basic Authentication¶
On the server side, serve() doesn’t provide HTTP Basic
Authentication yet.
For the avoidance of doubt, on the client side, connect()
performs HTTP Basic Authentication.
Following redirects¶
The new implementation of connect() doesn’t follow HTTP
redirects yet.
Automatic reconnection¶
The new implementation of connect() doesn’t provide
automatic reconnection yet.
In other words, the following pattern isn’t supported:
from websockets.asyncio.client import connect
async for websocket in connect(...): # this doesn't work yet
...
Import paths¶
For context, the websockets package is structured as follows:
The new implementation is found in the
websockets.asynciopackage.The original implementation was moved to the
websockets.legacypackage.The
websocketspackage provides aliases for convenience.The
websockets.clientandwebsockets.serverpackages provide aliases for backwards-compatibility with earlier versions of websockets.Currently, all aliases point to the original implementation. In the future, they will point to the new implementation or they will be deprecated.
To upgrade to the new asyncio implementation, change import paths as
shown in the tables below.
Client APIs¶
Legacy |
New |
|---|---|
|
|
|
|
|
Server APIs¶
Legacy |
New |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
not available yet |
|
not available yet |
API changes¶
Controlling UTF-8 decoding¶
The new implementation of the recv() method
provides the decode argument to control UTF-8 decoding of messages. This
didn’t exist in the original implementation.
If you’re calling encode() on a str object returned by
recv(), using decode=False and removing
encode() saves a round-trip of UTF-8 decoding and encoding for text
messages.
You can also force UTF-8 decoding of binary messages with decode=True. This
is rarely useful and has no performance benefits over decoding a bytes
object returned by recv().
Receiving fragmented messages¶
The new implementation provides the
recv_streaming() method for receiving a
fragmented message frame by frame. There was no way to do this in the original
implementation.
Depending on your use case, adopting this method may improve performance when streaming large messages. Specifically, it could reduce memory usage.
Customizing the opening handshake¶
On the client side, if you’re adding headers to the handshake request sent by
connect() with the extra_headers argument, you must
rename it to additional_headers.
On the server side, if you’re customizing how serve()
processes the opening handshake with the process_request, extra_headers,
or select_subprotocol, you must update your code. process_response and
select_subprotocol have new signatures; process_response replaces
extra_headers and provides more flexibility.
process_request¶
The signature of process_request changed. This is easiest to illustrate with
an example:
import http
# Original implementation
def process_request(path, request_headers):
return http.HTTPStatus.OK, [], b"OK\n"
serve(..., process_request=process_request, ...)
# New implementation
def process_request(connection, request):
return connection.respond(http.HTTPStatus.OK, "OK\n")
serve(..., process_request=process_request, ...)
connection is always available in process_request. In the original
implementation, you had to write a subclass of
WebSocketServerProtocol and pass it in the
create_protocol argument to make the connection object available in a
process_request method. This pattern isn’t useful anymore; you can replace
it with a process_request function or coroutine.
path and headers are available as attributes of the request object.
process_response¶
process_request replaces extra_headers and provides more flexibility.
In the most basic case, you would adapt your code as follows:
# Original implementation
serve(..., extra_headers=HEADERS, ...)
# New implementation
def process_response(connection, request, response):
response.headers.update(HEADERS)
return response
serve(..., process_response=process_response, ...)
connection is always available in process_response, similar to
process_request. In the original implementation, there was no way to make
the connection object available.
In addition, the request and response objects are available, which
enables a broader range of use cases (e.g., logging) and makes
process_response more useful than extra_headers.
select_subprotocol¶
The signature of select_subprotocol changed. Here’s an example:
# Original implementation
def select_subprotocol(client_subprotocols, server_subprotocols):
if "chat" in client_subprotocols:
return "chat"
# New implementation
def select_subprotocol(connection, subprotocols):
if "chat" in subprotocols
return "chat"
serve(..., select_subprotocol=select_subprotocol, ...)
connection is always available in select_subprotocol. This brings the
same benefits as in process_request. It may remove the need to subclass of
WebSocketServerProtocol.
The subprotocols argument contains the list of subprotocols offered by the
client. The list of subprotocols supported by the server was removed because
select_subprotocols already knows which subprotocols it may select and under
which conditions.
Arguments of connect() and serve()¶
ws_handler → handler¶
The first argument of serve() is now called handler
instead of ws_handler. It’s usually passed as a positional argument, making
this change transparent. If you’re passing it as a keyword argument, you must
update its name.
create_protocol → create_connection¶
The keyword argument of serve() for customizing the
creation of the connection object is now called create_connection instead of
create_protocol. It must return a ServerConnection
instead of a WebSocketServerProtocol.
If you were customizing connection objects, you should check the new
implementation and possibly redo your customization. Keep in mind that the
changes to process_request and select_subprotocol remove most use cases
for create_connection.
max_queue¶
The max_queue argument of connect() and
serve() has a new meaning but achieves a similar effect.
It is now the high-water mark of a buffer of incoming frames. It defaults to 16 frames. It used to be the size of a buffer of incoming messages that refilled as soon as a message was read. It used to default to 32 messages.
This can make a difference when messages are fragmented in several frames. In
that case, you may want to increase max_queue. If you’re writing a high
performance server and you know that you’re receiving fragmented messages,
probably you should adopt recv_streaming()
and optimize the performance of reads again. In all other cases, given how
uncommon fragmentation is, you shouldn’t worry about this change.
read_limit¶
The read_limit argument doesn’t exist in the new implementation because it
doesn’t buffer data received from the network in a
StreamReader. With a better design, this buffer could be
removed.
The buffer of incoming frames configured by max_queue is the only read
buffer now.
write_limit¶
The write_limit argument of connect() and
serve() defaults to 32 KiB instead of 64 KiB.
Attributes of connections¶
path, request_headers and response_headers¶
The path,
request_headers and
response_headers properties are
replaced by request and
response, which provide a headers
attribute.
If your code relies on them, you can replace:
connection.path
connection.request_headers
connection.response_headers
with:
connection.request.path
connection.request.headers
connection.response.headers
open and closed¶
The open and
closed properties are removed.
Using them was discouraged.
Instead, you should call recv() or
send() and handle
ConnectionClosed exceptions.
If your code relies on them, you can replace:
connection.open
connection.closed
with:
from websockets.protocol import State
connection.state is State.OPEN
connection.state is State.CLOSED