pax_global_header 0000666 0000000 0000000 00000000064 14715046371 0014522 g ustar 00root root 0000000 0000000 52 comment=0403823185b272ae389da1c9182773932b4df950
websockets-14.1/ 0000775 0000000 0000000 00000000000 14715046371 0013620 5 ustar 00root root 0000000 0000000 websockets-14.1/.github/ 0000775 0000000 0000000 00000000000 14715046371 0015160 5 ustar 00root root 0000000 0000000 websockets-14.1/.github/FUNDING.yml 0000664 0000000 0000000 00000000120 14715046371 0016766 0 ustar 00root root 0000000 0000000 github: python-websockets
open_collective: websockets
tidelift: pypi/websockets
websockets-14.1/.github/ISSUE_TEMPLATE/ 0000775 0000000 0000000 00000000000 14715046371 0017343 5 ustar 00root root 0000000 0000000 websockets-14.1/.github/ISSUE_TEMPLATE/config.yml 0000664 0000000 0000000 00000000034 14715046371 0021330 0 ustar 00root root 0000000 0000000 blank_issues_enabled: false
websockets-14.1/.github/ISSUE_TEMPLATE/issue.md 0000664 0000000 0000000 00000001572 14715046371 0021022 0 ustar 00root root 0000000 0000000 ---
name: Report an issue
about: Let us know about a problem with websockets
title: ''
labels: ''
assignees: ''
---
websockets-14.1/.github/dependabot.yml 0000664 0000000 0000000 00000000277 14715046371 0020016 0 ustar 00root root 0000000 0000000 version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
day: "saturday"
time: "07:00"
timezone: "Europe/Paris"
websockets-14.1/.github/workflows/ 0000775 0000000 0000000 00000000000 14715046371 0017215 5 ustar 00root root 0000000 0000000 websockets-14.1/.github/workflows/release.yml 0000664 0000000 0000000 00000004675 14715046371 0021374 0 ustar 00root root 0000000 0000000 name: Make release
on:
push:
tags:
- '*'
workflow_dispatch:
jobs:
sdist:
name: Build source distribution and architecture-independent wheel
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Install Python 3.x
uses: actions/setup-python@v5
with:
python-version: 3.x
- name: Install build
run: pip install build
- name: Build sdist & wheel
run: python -m build
env:
BUILD_EXTENSION: no
- name: Save sdist & wheel
uses: actions/upload-artifact@v4
with:
name: dist-architecture-independent
path: |
dist/*.tar.gz
dist/*.whl
wheels:
name: Build architecture-specific wheels on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
os:
- ubuntu-latest
- windows-latest
- macOS-latest
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Install Python 3.x
uses: actions/setup-python@v5
with:
python-version: 3.x
- name: Set up QEMU
if: runner.os == 'Linux'
uses: docker/setup-qemu-action@v3
with:
platforms: all
- name: Build wheels
uses: pypa/cibuildwheel@v2.21.3
env:
BUILD_EXTENSION: yes
- name: Save wheels
uses: actions/upload-artifact@v4
with:
name: dist-${{ matrix.os }}
path: wheelhouse/*.whl
upload:
name: Upload
needs:
- sdist
- wheels
runs-on: ubuntu-latest
# Don't release when running the workflow manually from GitHub's UI.
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
permissions:
id-token: write
attestations: write
contents: write
steps:
- name: Download artifacts
uses: actions/download-artifact@v4
with:
pattern: dist-*
merge-multiple: true
path: dist
- name: Attest provenance
uses: actions/attest-build-provenance@v1
with:
subject-path: dist/*
- name: Upload to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
- name: Create GitHub release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh release -R python-websockets/websockets create ${{ github.ref_name }} --notes "See https://websockets.readthedocs.io/en/stable/project/changelog.html for details."
websockets-14.1/.github/workflows/tests.yml 0000664 0000000 0000000 00000003462 14715046371 0021107 0 ustar 00root root 0000000 0000000 name: Run tests
on:
push:
branches:
- main
pull_request:
branches:
- main
env:
WEBSOCKETS_TESTS_TIMEOUT_FACTOR: 10
jobs:
coverage:
name: Run test coverage checks
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Install Python 3.x
uses: actions/setup-python@v5
with:
python-version: "3.x"
- name: Install tox
run: pip install tox
- name: Run tests with coverage
run: tox -e coverage
- name: Run tests with per-module coverage
run: tox -e maxi_cov
quality:
name: Run code quality checks
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Install Python 3.x
uses: actions/setup-python@v5
with:
python-version: "3.x"
- name: Install tox
run: pip install tox
- name: Check code formatting & style
run: tox -e ruff
- name: Check types statically
run: tox -e mypy
matrix:
name: Run tests on Python ${{ matrix.python }}
needs:
- coverage
- quality
runs-on: ubuntu-latest
strategy:
matrix:
python:
- "3.9"
- "3.10"
- "3.11"
- "3.12"
- "3.13"
- "pypy-3.10"
is_main:
- ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
exclude:
- python: "pypy-3.10"
is_main: false
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Install Python ${{ matrix.python }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python }}
allow-prereleases: true
- name: Install tox
run: pip install tox
- name: Run tests
run: tox -e py
websockets-14.1/.gitignore 0000664 0000000 0000000 00000000270 14715046371 0015607 0 ustar 00root root 0000000 0000000 *.pyc
*.so
.coverage
.direnv/
.envrc
.idea/
.mypy_cache/
.tox/
.vscode/
build/
compliance/reports/
dist/
docs/_build/
experiments/compression/corpus/
htmlcov/
src/websockets.egg-info/
websockets-14.1/.readthedocs.yml 0000664 0000000 0000000 00000000340 14715046371 0016703 0 ustar 00root root 0000000 0000000 version: 2
build:
os: ubuntu-20.04
tools:
python: "3.10"
jobs:
post_checkout:
- git fetch --unshallow
sphinx:
configuration: docs/conf.py
python:
install:
- requirements: docs/requirements.txt
websockets-14.1/CODE_OF_CONDUCT.md 0000664 0000000 0000000 00000006251 14715046371 0016423 0 ustar 00root root 0000000 0000000 # Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at aymeric DOT augustin AT fractalideas DOT com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/
websockets-14.1/LICENSE 0000664 0000000 0000000 00000002752 14715046371 0014633 0 ustar 00root root 0000000 0000000 Copyright (c) Aymeric Augustin and contributors
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* 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.
* 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.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 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.
websockets-14.1/MANIFEST.in 0000664 0000000 0000000 00000000166 14715046371 0015361 0 ustar 00root root 0000000 0000000 include LICENSE
include src/websockets/py.typed
include src/websockets/speedups.c # required when BUILD_EXTENSION=no
websockets-14.1/Makefile 0000664 0000000 0000000 00000001406 14715046371 0015261 0 ustar 00root root 0000000 0000000 .PHONY: default style types tests coverage maxi_cov build clean
export PYTHONASYNCIODEBUG=1
export PYTHONPATH=src
export PYTHONWARNINGS=default
build:
python setup.py build_ext --inplace
style:
ruff format compliance src tests
ruff check --fix compliance src tests
types:
mypy --strict src
tests:
python -m unittest
coverage:
coverage run --source src/websockets,tests -m unittest
coverage html
coverage report --show-missing --fail-under=100
maxi_cov:
python tests/maxi_cov.py
coverage html
coverage report --show-missing --fail-under=100
clean:
find src -name '*.so' -delete
find . -name '*.pyc' -delete
find . -name __pycache__ -delete
rm -rf .coverage .mypy_cache build compliance/reports dist docs/_build htmlcov MANIFEST src/websockets.egg-info
websockets-14.1/README.rst 0000664 0000000 0000000 00000014336 14715046371 0015316 0 ustar 00root root 0000000 0000000 .. image:: logo/horizontal.svg
:width: 480px
:alt: websockets
|licence| |version| |pyversions| |tests| |docs| |openssf|
.. |licence| image:: https://img.shields.io/pypi/l/websockets.svg
:target: https://pypi.python.org/pypi/websockets
.. |version| image:: https://img.shields.io/pypi/v/websockets.svg
:target: https://pypi.python.org/pypi/websockets
.. |pyversions| image:: https://img.shields.io/pypi/pyversions/websockets.svg
:target: https://pypi.python.org/pypi/websockets
.. |tests| image:: https://img.shields.io/github/checks-status/python-websockets/websockets/main?label=tests
:target: https://github.com/python-websockets/websockets/actions/workflows/tests.yml
.. |docs| image:: https://img.shields.io/readthedocs/websockets.svg
:target: https://websockets.readthedocs.io/
.. |openssf| image:: https://bestpractices.coreinfrastructure.org/projects/6475/badge
:target: https://bestpractices.coreinfrastructure.org/projects/6475
What is ``websockets``?
-----------------------
websockets is a library for building WebSocket_ servers and clients in Python
with a focus on correctness, simplicity, robustness, and performance.
.. _WebSocket: https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API
Built on top of ``asyncio``, Python's standard asynchronous I/O framework, the
default implementation provides an elegant coroutine-based API.
An implementation on top of ``threading`` and a Sans-I/O implementation are also
available.
`Documentation is available on Read the Docs. `_
.. copy-pasted because GitHub doesn't support the include directive
Here's an echo server with the ``asyncio`` API:
.. code:: python
#!/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, "localhost", 8765):
await asyncio.get_running_loop().create_future() # run forever
asyncio.run(main())
Here's how a client sends and receives messages with the ``threading`` API:
.. code:: python
#!/usr/bin/env python
from websockets.sync.client import connect
def hello():
with connect("ws://localhost:8765") as websocket:
websocket.send("Hello world!")
message = websocket.recv()
print(f"Received: {message}")
hello()
Does that look good?
`Get started with the tutorial! `_
.. raw:: html
websockets for enterprise
Available as part of the Tidelift Subscription
The maintainers of websockets and thousands of other packages are working with Tidelift 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. Learn more.
(If you contribute to websockets and would like to become an official support provider, let me know.)
Why should I use ``websockets``?
--------------------------------
The development of ``websockets`` is shaped by four principles:
1. **Correctness**: ``websockets`` is heavily tested for compliance with
:rfc:`6455`. Continuous integration fails under 100% branch coverage.
2. **Simplicity**: all you need to understand is ``msg = await ws.recv()`` and
``await ws.send(msg)``. ``websockets`` takes care of managing connections
so you can focus on your application.
3. **Robustness**: ``websockets`` is built for production. For example, it was
the only library to `handle backpressure correctly`_ before the issue
became widely known in the Python community.
4. **Performance**: memory usage is optimized and configurable. A C extension
accelerates expensive operations. It's pre-compiled for Linux, macOS and
Windows and packaged in the wheel format for each system and Python version.
Documentation is a first class concern in the project. Head over to `Read the
Docs`_ and see for yourself.
.. _Read the Docs: https://websockets.readthedocs.io/
.. _handle backpressure correctly: https://vorpus.org/blog/some-thoughts-on-asynchronous-api-design-in-a-post-asyncawait-world/#websocket-servers
Why shouldn't I use ``websockets``?
-----------------------------------
* If you prefer callbacks over coroutines: ``websockets`` was created to
provide the best coroutine-based API to manage WebSocket connections in
Python. Pick another library for a callback-based API.
* If you're looking for a mixed HTTP / WebSocket library: ``websockets`` aims
at being an excellent implementation of :rfc:`6455`: The WebSocket Protocol
and :rfc:`7692`: Compression Extensions for WebSocket. Its support for HTTP
is minimal — just enough for an HTTP health check.
If you want to do both in the same server, look at HTTP frameworks that
build on top of ``websockets`` to support WebSocket connections, like
Sanic_.
.. _Sanic: https://sanicframework.org/en/
What else?
----------
Bug reports, patches and suggestions are welcome!
To report a security vulnerability, please use the `Tidelift security
contact`_. Tidelift will coordinate the fix and disclosure.
.. _Tidelift security contact: https://tidelift.com/security
For anything else, please open an issue_ or send a `pull request`_.
.. _issue: https://github.com/python-websockets/websockets/issues/new
.. _pull request: https://github.com/python-websockets/websockets/compare/
Participants must uphold the `Contributor Covenant code of conduct`_.
.. _Contributor Covenant code of conduct: https://github.com/python-websockets/websockets/blob/main/CODE_OF_CONDUCT.md
``websockets`` is released under the `BSD license`_.
.. _BSD license: https://github.com/python-websockets/websockets/blob/main/LICENSE
websockets-14.1/SECURITY.md 0000664 0000000 0000000 00000000374 14715046371 0015415 0 ustar 00root root 0000000 0000000 # Security
## Policy
Only the latest version receives security updates.
## Contact information
Please report security vulnerabilities to the
[Tidelift security team](https://tidelift.com/security).
Tidelift will coordinate the fix and disclosure.
websockets-14.1/compliance/ 0000775 0000000 0000000 00000000000 14715046371 0015732 5 ustar 00root root 0000000 0000000 websockets-14.1/compliance/README.rst 0000664 0000000 0000000 00000005524 14715046371 0017427 0 ustar 00root root 0000000 0000000 Autobahn Testsuite
==================
General information and installation instructions are available at
https://github.com/crossbario/autobahn-testsuite.
Running the test suite
----------------------
All commands below must be run from the root directory of the repository.
To get acceptable performance, compile the C extension first:
.. code-block:: console
$ python setup.py build_ext --inplace
Run each command in a different shell. Testing takes several minutes to complete
— wstest is the bottleneck. When clients finish, stop servers with Ctrl-C.
You can exclude slow tests by modifying the configuration files as follows::
"exclude-cases": ["9.*", "12.*", "13.*"]
The test server and client applications shouldn't display any exceptions.
To test the servers:
.. code-block:: console
$ PYTHONPATH=src python compliance/asyncio/server.py
$ PYTHONPATH=src python compliance/sync/server.py
$ docker run --interactive --tty --rm \
--volume "${PWD}/compliance/config:/config" \
--volume "${PWD}/compliance/reports:/reports" \
--name fuzzingclient \
crossbario/autobahn-testsuite \
wstest --mode fuzzingclient --spec /config/fuzzingclient.json
$ open compliance/reports/servers/index.html
To test the clients:
.. code-block:: console
$ docker run --interactive --tty --rm \
--volume "${PWD}/compliance/config:/config" \
--volume "${PWD}/compliance/reports:/reports" \
--publish 9001:9001 \
--name fuzzingserver \
crossbario/autobahn-testsuite \
wstest --mode fuzzingserver --spec /config/fuzzingserver.json
$ PYTHONPATH=src python compliance/asyncio/client.py
$ PYTHONPATH=src python compliance/sync/client.py
$ open compliance/reports/clients/index.html
Conformance notes
-----------------
Some test cases are more strict than the RFC. Given the implementation of the
library and the test client and server applications, websockets passes with a
"Non-Strict" result in these cases.
In 3.2, 3.3, 4.1.3, 4.1.4, 4.2.3, 4.2.4, and 5.15 websockets notices the
protocol error and closes the connection at the library level before the
application gets a chance to echo the previous frame.
In 6.4.1, 6.4.2, 6.4.3, and 6.4.4, even though it uses an incremental decoder,
websockets doesn't notice the invalid utf-8 fast enough to get a "Strict" pass.
These tests are more strict than the RFC.
Test case 7.1.5 fails because websockets treats closing the connection in the
middle of a fragmented message as a protocol error. As a consequence, it sends
a close frame with code 1002. The test suite expects a close frame with code
1000, echoing the close code that it sent. This isn't required. RFC 6455 states
that "the endpoint typically echos the status code it received", which leaves
the possibility to send a close frame with a different status code.
websockets-14.1/compliance/asyncio/ 0000775 0000000 0000000 00000000000 14715046371 0017377 5 ustar 00root root 0000000 0000000 websockets-14.1/compliance/asyncio/client.py 0000664 0000000 0000000 00000002550 14715046371 0021231 0 ustar 00root root 0000000 0000000 import asyncio
import json
import logging
from websockets.asyncio.client import connect
from websockets.exceptions import WebSocketException
logging.basicConfig(level=logging.WARNING)
SERVER = "ws://localhost:9001"
AGENT = "websockets.asyncio"
async def get_case_count():
async with connect(f"{SERVER}/getCaseCount") as ws:
return json.loads(await ws.recv())
async def run_case(case):
async with connect(
f"{SERVER}/runCase?case={case}&agent={AGENT}",
max_size=2**25,
) as ws:
try:
async for msg in ws:
await ws.send(msg)
except WebSocketException:
pass
async def update_reports():
async with connect(
f"{SERVER}/updateReports?agent={AGENT}",
open_timeout=60,
):
pass
async def main():
cases = await get_case_count()
for case in range(1, cases + 1):
print(f"Running test case {case:03d} / {cases}... ", end="\t")
try:
await run_case(case)
except WebSocketException as exc:
print(f"ERROR: {type(exc).__name__}: {exc}")
except Exception as exc:
print(f"FAIL: {type(exc).__name__}: {exc}")
else:
print("OK")
print(f"Ran {cases} test cases")
await update_reports()
print("Updated reports")
if __name__ == "__main__":
asyncio.run(main())
websockets-14.1/compliance/asyncio/server.py 0000664 0000000 0000000 00000001226 14715046371 0021260 0 ustar 00root root 0000000 0000000 import asyncio
import logging
from websockets.asyncio.server import serve
from websockets.exceptions import WebSocketException
logging.basicConfig(level=logging.WARNING)
HOST, PORT = "0.0.0.0", 9002
async def echo(ws):
try:
async for msg in ws:
await ws.send(msg)
except WebSocketException:
pass
async def main():
async with serve(
echo,
HOST,
PORT,
server_header="websockets.sync",
max_size=2**25,
) as server:
try:
await server.serve_forever()
except KeyboardInterrupt:
pass
if __name__ == "__main__":
asyncio.run(main())
websockets-14.1/compliance/config/ 0000775 0000000 0000000 00000000000 14715046371 0017177 5 ustar 00root root 0000000 0000000 websockets-14.1/compliance/config/fuzzingclient.json 0000664 0000000 0000000 00000000314 14715046371 0022763 0 ustar 00root root 0000000 0000000
{
"servers": [{
"url": "ws://host.docker.internal:9002"
}, {
"url": "ws://host.docker.internal:9003"
}],
"outdir": "/reports/servers",
"cases": ["*"],
"exclude-cases": []
}
websockets-14.1/compliance/config/fuzzingserver.json 0000664 0000000 0000000 00000000161 14715046371 0023013 0 ustar 00root root 0000000 0000000
{
"url": "ws://localhost:9001",
"outdir": "/reports/clients",
"cases": ["*"],
"exclude-cases": []
}
websockets-14.1/compliance/sync/ 0000775 0000000 0000000 00000000000 14715046371 0016706 5 ustar 00root root 0000000 0000000 websockets-14.1/compliance/sync/client.py 0000664 0000000 0000000 00000002370 14715046371 0020540 0 ustar 00root root 0000000 0000000 import json
import logging
from websockets.exceptions import WebSocketException
from websockets.sync.client import connect
logging.basicConfig(level=logging.WARNING)
SERVER = "ws://localhost:9001"
AGENT = "websockets.sync"
def get_case_count():
with connect(f"{SERVER}/getCaseCount") as ws:
return json.loads(ws.recv())
def run_case(case):
with connect(
f"{SERVER}/runCase?case={case}&agent={AGENT}",
max_size=2**25,
) as ws:
try:
for msg in ws:
ws.send(msg)
except WebSocketException:
pass
def update_reports():
with connect(
f"{SERVER}/updateReports?agent={AGENT}",
open_timeout=60,
):
pass
def main():
cases = get_case_count()
for case in range(1, cases + 1):
print(f"Running test case {case:03d} / {cases}... ", end="\t")
try:
run_case(case)
except WebSocketException as exc:
print(f"ERROR: {type(exc).__name__}: {exc}")
except Exception as exc:
print(f"FAIL: {type(exc).__name__}: {exc}")
else:
print("OK")
print(f"Ran {cases} test cases")
update_reports()
print("Updated reports")
if __name__ == "__main__":
main()
websockets-14.1/compliance/sync/server.py 0000664 0000000 0000000 00000001126 14715046371 0020566 0 ustar 00root root 0000000 0000000 import logging
from websockets.exceptions import WebSocketException
from websockets.sync.server import serve
logging.basicConfig(level=logging.WARNING)
HOST, PORT = "0.0.0.0", 9003
def echo(ws):
try:
for msg in ws:
ws.send(msg)
except WebSocketException:
pass
def main():
with serve(
echo,
HOST,
PORT,
server_header="websockets.asyncio",
max_size=2**25,
) as server:
try:
server.serve_forever()
except KeyboardInterrupt:
pass
if __name__ == "__main__":
main()
websockets-14.1/docs/ 0000775 0000000 0000000 00000000000 14715046371 0014550 5 ustar 00root root 0000000 0000000 websockets-14.1/docs/Makefile 0000664 0000000 0000000 00000001345 14715046371 0016213 0 ustar 00root root 0000000 0000000 # Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line, and also
# from the environment for the first two.
SPHINXOPTS ?=
SPHINXBUILD ?= sphinx-build
SOURCEDIR = .
BUILDDIR = _build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
livehtml:
sphinx-autobuild --watch "$(SOURCEDIR)/../src" "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
websockets-14.1/docs/_static/ 0000775 0000000 0000000 00000000000 14715046371 0016176 5 ustar 00root root 0000000 0000000 websockets-14.1/docs/_static/favicon.ico 0000777 0000000 0000000 00000000000 14715046371 0024021 2../../logo/favicon.ico ustar 00root root 0000000 0000000 websockets-14.1/docs/_static/tidelift.png 0000777 0000000 0000000 00000000000 14715046371 0024403 2../../logo/tidelift.png ustar 00root root 0000000 0000000 websockets-14.1/docs/_static/websockets.svg 0000777 0000000 0000000 00000000000 14715046371 0025003 2../../logo/vertical.svg ustar 00root root 0000000 0000000 websockets-14.1/docs/conf.py 0000664 0000000 0000000 00000013654 14715046371 0016060 0 ustar 00root root 0000000 0000000 # Configuration file for the Sphinx documentation builder.
#
# This file only contains a selection of the most common options. For a full
# list see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html
import datetime
import importlib
import inspect
import os
import subprocess
import sys
# -- Path setup --------------------------------------------------------------
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
sys.path.insert(0, os.path.join(os.path.abspath(".."), "src"))
# -- Project information -----------------------------------------------------
project = "websockets"
copyright = f"2013-{datetime.date.today().year}, Aymeric Augustin and contributors"
author = "Aymeric Augustin"
from websockets.version import tag as version, version as release
# -- General configuration ---------------------------------------------------
nitpicky = True
nitpick_ignore = [
# topics/design.rst discusses undocumented APIs
("py:meth", "client.WebSocketClientProtocol.handshake"),
("py:meth", "server.WebSocketServerProtocol.handshake"),
("py:attr", "protocol.WebSocketCommonProtocol.is_client"),
("py:attr", "protocol.WebSocketCommonProtocol.messages"),
("py:meth", "protocol.WebSocketCommonProtocol.close_connection"),
("py:attr", "protocol.WebSocketCommonProtocol.close_connection_task"),
("py:meth", "protocol.WebSocketCommonProtocol.keepalive_ping"),
("py:attr", "protocol.WebSocketCommonProtocol.keepalive_ping_task"),
("py:meth", "protocol.WebSocketCommonProtocol.transfer_data"),
("py:attr", "protocol.WebSocketCommonProtocol.transfer_data_task"),
("py:meth", "protocol.WebSocketCommonProtocol.connection_open"),
("py:meth", "protocol.WebSocketCommonProtocol.ensure_open"),
("py:meth", "protocol.WebSocketCommonProtocol.fail_connection"),
("py:meth", "protocol.WebSocketCommonProtocol.connection_lost"),
("py:meth", "protocol.WebSocketCommonProtocol.read_message"),
("py:meth", "protocol.WebSocketCommonProtocol.write_frame"),
]
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
"sphinx.ext.autodoc",
"sphinx.ext.intersphinx",
"sphinx.ext.linkcode",
"sphinx.ext.napoleon",
"sphinx_copybutton",
"sphinx_inline_tabs",
"sphinxcontrib.spelling",
"sphinxcontrib_trio",
"sphinxext.opengraph",
]
# It is currently inconvenient to install PyEnchant on Apple Silicon.
try:
import sphinxcontrib.spelling
except ImportError:
extensions.remove("sphinxcontrib.spelling")
autodoc_typehints = "description"
autodoc_typehints_description_target = "documented"
# Workaround for https://github.com/sphinx-doc/sphinx/issues/9560
from sphinx.domains.python import PythonDomain
assert PythonDomain.object_types["data"].roles == ("data", "obj")
PythonDomain.object_types["data"].roles = ("data", "class", "obj")
intersphinx_mapping = {"python": ("https://docs.python.org/3", None)}
spelling_show_suggestions = True
# Add any paths that contain templates here, relative to this directory.
templates_path = ["_templates"]
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
# Configure viewcode extension.
from websockets.version import commit
code_url = f"https://github.com/python-websockets/websockets/blob/{commit}"
def linkcode_resolve(domain, info):
# Non-linkable objects from the starter kit in the tutorial.
if domain == "js" or info["module"] == "connect4":
return
assert domain == "py", "expected only Python objects"
mod = importlib.import_module(info["module"])
if "." in info["fullname"]:
objname, attrname = info["fullname"].split(".")
obj = getattr(mod, objname)
try:
# object is a method of a class
obj = getattr(obj, attrname)
except AttributeError:
# object is an attribute of a class
return None
else:
obj = getattr(mod, info["fullname"])
try:
file = inspect.getsourcefile(obj)
lines = inspect.getsourcelines(obj)
except TypeError:
# e.g. object is a typing.Union
return None
file = os.path.relpath(file, os.path.abspath(".."))
if not file.startswith("src/websockets"):
# e.g. object is a typing.NewType
return None
start, end = lines[1], lines[1] + len(lines[0]) - 1
return f"{code_url}/{file}#L{start}-L{end}"
# Configure opengraph extension
# Social cards don't support the SVG logo. Also, the text preview looks bad.
ogp_social_cards = {"enable": False}
# -- Options for HTML output -------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
html_theme = "furo"
html_theme_options = {
"light_css_variables": {
"color-brand-primary": "#306998", # blue from logo
"color-brand-content": "#0b487a", # blue more saturated and less dark
},
"dark_css_variables": {
"color-brand-primary": "#ffd43bcc", # yellow from logo, more muted than content
"color-brand-content": "#ffd43bd9", # yellow from logo, transparent like text
},
"sidebar_hide_name": True,
}
html_logo = "_static/websockets.svg"
html_favicon = "_static/favicon.ico"
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ["_static"]
html_copy_source = False
html_show_sphinx = False
websockets-14.1/docs/faq/ 0000775 0000000 0000000 00000000000 14715046371 0015317 5 ustar 00root root 0000000 0000000 websockets-14.1/docs/faq/asyncio.rst 0000664 0000000 0000000 00000005501 14715046371 0017517 0 ustar 00root root 0000000 0000000 Using asyncio
=============
.. currentmodule:: websockets.asyncio.connection
.. admonition:: This FAQ is written for the new :mod:`asyncio` implementation.
:class: hint
Answers are also valid for the legacy :mod:`asyncio` implementation.
How do I run two coroutines in parallel?
----------------------------------------
You must start two tasks, which the event loop will run concurrently. You can
achieve this with :func:`asyncio.gather` or :func:`asyncio.create_task`.
Keep track of the tasks and make sure that they terminate or that you cancel
them when the connection terminates.
Why does my program never receive any messages?
-----------------------------------------------
Your program runs a coroutine that never yields control to the event loop. The
coroutine that receives messages never gets a chance to run.
Putting an ``await`` statement in a ``for`` or a ``while`` loop isn't enough
to yield control. Awaiting a coroutine may yield control, but there's no
guarantee that it will.
For example, :meth:`~Connection.send` only yields control when send buffers are
full, which never happens in most practical cases.
If you run a loop that contains only synchronous operations and a
:meth:`~Connection.send` call, you must yield control explicitly with
:func:`asyncio.sleep`::
async def producer(websocket):
message = generate_next_message()
await websocket.send(message)
await asyncio.sleep(0) # yield control to the event loop
:func:`asyncio.sleep` always suspends the current task, allowing other tasks
to run. This behavior is documented precisely because it isn't expected from
every coroutine.
See `issue 867`_.
.. _issue 867: https://github.com/python-websockets/websockets/issues/867
Why am I having problems with threads?
--------------------------------------
If you choose websockets' :mod:`asyncio` implementation, then you shouldn't use
threads. Indeed, choosing :mod:`asyncio` to handle concurrency is mutually
exclusive with :mod:`threading`.
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 :class:`~asyncio.Task` instead.
If it has to run in another thread because it would block the event loop,
:func:`~asyncio.to_thread` or :meth:`~asyncio.loop.run_in_executor` is the way
to go.
Please review the advice about :ref:`asyncio-multithreading` in the Python
documentation.
Why does my simple program misbehave mysteriously?
--------------------------------------------------
You are using :func:`time.sleep` instead of :func:`asyncio.sleep`, which
blocks the event loop and prevents asyncio from operating normally.
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 a coroutine makes the program functional.
websockets-14.1/docs/faq/client.rst 0000664 0000000 0000000 00000007022 14715046371 0017330 0 ustar 00root root 0000000 0000000 Client
======
.. currentmodule:: websockets.asyncio.client
.. admonition:: This FAQ is written for the new :mod:`asyncio` implementation.
:class: hint
Answers are also valid for the legacy :mod:`asyncio` implementation.
They translate to the :mod:`threading` implementation by removing ``await``
and ``async`` keywords and by using a :class:`~threading.Thread` instead of
a :class:`~asyncio.Task` for concurrent execution.
Why does the client close the connection prematurely?
-----------------------------------------------------
You're exiting the context manager prematurely. Wait for the work to be
finished before exiting.
For example, if your code has a structure similar to::
async with connect(...) as websocket:
asyncio.create_task(do_some_work())
change it to::
async with connect(...) as websocket:
await do_some_work()
How do I access HTTP headers?
-----------------------------
Once the connection is established, HTTP headers are available in the
:attr:`~ClientConnection.request` and :attr:`~ClientConnection.response`
objects::
async with connect(...) as websocket:
websocket.request.headers
websocket.response.headers
How do I set HTTP headers?
--------------------------
To set the ``Origin``, ``Sec-WebSocket-Extensions``, or
``Sec-WebSocket-Protocol`` headers in the WebSocket handshake request, use the
``origin``, ``extensions``, or ``subprotocols`` arguments of :func:`~connect`.
To override the ``User-Agent`` header, use the ``user_agent_header`` argument.
Set it to :obj:`None` to remove the header.
To set other HTTP headers, for example the ``Authorization`` header, use the
``additional_headers`` argument::
async with connect(..., additional_headers={"Authorization": ...}) as websocket:
...
In the legacy :mod:`asyncio` API, this argument is named ``extra_headers``.
How do I force the IP address that the client connects to?
----------------------------------------------------------
Use the ``host`` argument :func:`~connect`::
async with connect(..., host="192.168.0.1") as websocket:
...
:func:`~connect` accepts the same arguments as
:meth:`~asyncio.loop.create_connection` and passes them through.
How do I close a connection?
----------------------------
The easiest is to use :func:`~connect` as a context manager::
async with connect(...) as websocket:
...
The connection is closed when exiting the context manager.
How do I reconnect when the connection drops?
---------------------------------------------
Use :func:`~websockets.asyncio.client.connect` as an asynchronous iterator::
from websockets.asyncio.client import connect
from websockets.exceptions import ConnectionClosed
async for websocket in connect(...):
try:
...
except ConnectionClosed:
continue
Make sure you handle exceptions in the ``async for`` loop. Uncaught exceptions
will break out of the loop.
How do I stop a client that is processing messages in a loop?
-------------------------------------------------------------
You can close the connection.
Here's an example that terminates cleanly when it receives SIGTERM on Unix:
.. literalinclude:: ../../example/faq/shutdown_client.py
:emphasize-lines: 11-13
How do I disable TLS/SSL certificate verification?
--------------------------------------------------
Look at the ``ssl`` argument of :meth:`~asyncio.loop.create_connection`.
:func:`~connect` accepts the same arguments as
:meth:`~asyncio.loop.create_connection` and passes them through.
websockets-14.1/docs/faq/common.rst 0000664 0000000 0000000 00000011614 14715046371 0017344 0 ustar 00root root 0000000 0000000 Both sides
==========
.. currentmodule:: websockets.asyncio.connection
What does ``ConnectionClosedError: no close frame received or sent`` mean?
--------------------------------------------------------------------------
If you're seeing this traceback in the logs of a server:
.. code-block:: pytb
connection handler failed
Traceback (most recent call last):
...
websockets.exceptions.ConnectionClosedError: no close frame received or sent
or if a client crashes with this traceback:
.. code-block:: pytb
Traceback (most recent call last):
...
websockets.exceptions.ConnectionClosedError: no close frame received or sent
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.
You can catch and handle :exc:`~websockets.exceptions.ConnectionClosed` to
prevent it from being logged.
There are several reasons why long-lived connections may be lost:
* 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.
* HTTP load balancers or proxies that aren't configured for long-lived
connections may terminate connections after a short amount of time, usually
30 seconds, despite websockets' keepalive mechanism.
If you're facing a reproducible issue, :ref:`enable debug logs ` to
see when and how connections are closed.
What does ``ConnectionClosedError: sent 1011 (internal error) keepalive ping timeout; no close frame received`` mean?
---------------------------------------------------------------------------------------------------------------------
If you're seeing this traceback in the logs of a server:
.. code-block:: pytb
connection handler failed
Traceback (most recent call last):
...
websockets.exceptions.ConnectionClosedError: sent 1011 (internal error) keepalive ping timeout; no close frame received
or if a client crashes with this traceback:
.. code-block:: pytb
Traceback (most recent call last):
...
websockets.exceptions.ConnectionClosedError: sent 1011 (internal error) keepalive ping timeout; no close frame received
it means that the WebSocket connection suffered from excessive latency and was
closed after reaching the timeout of websockets' keepalive mechanism.
You can catch and handle :exc:`~websockets.exceptions.ConnectionClosed` to
prevent it from being logged.
There are two main reasons why latency may increase:
* Poor network connectivity.
* More traffic than the recipient can handle.
See the discussion of :doc:`keepalive <../topics/keepalive>` for details.
If websockets' default timeout of 20 seconds is too short for your use case,
you can adjust it with the ``ping_timeout`` argument.
How do I set a timeout on :meth:`~Connection.recv`?
---------------------------------------------------
On Python ≥ 3.11, use :func:`asyncio.timeout`::
async with asyncio.timeout(timeout=10):
message = await websocket.recv()
On older versions of Python, use :func:`asyncio.wait_for`::
message = await asyncio.wait_for(websocket.recv(), timeout=10)
This technique works for most APIs. When it doesn't, for example with
asynchronous context managers, websockets provides an ``open_timeout`` argument.
How can I pass arguments to a custom connection subclass?
---------------------------------------------------------
You can bind additional arguments to the connection factory with
:func:`functools.partial`::
import asyncio
import functools
from websockets.asyncio.server import ServerConnection, serve
class MyServerConnection(ServerConnection):
def __init__(self, *args, extra_argument=None, **kwargs):
super().__init__(*args, **kwargs)
# do something with extra_argument
create_connection = functools.partial(ServerConnection, extra_argument=42)
async with serve(..., create_connection=create_connection):
...
This example was for a server. The same pattern applies on a client.
How do I keep idle connections open?
------------------------------------
websockets sends pings at 20 seconds intervals to keep the connection open.
It closes the connection if it doesn't get a pong within 20 seconds.
You can adjust this behavior with ``ping_interval`` and ``ping_timeout``.
See :doc:`../topics/keepalive` for details.
How do I respond to pings?
--------------------------
If you are referring to Ping_ and Pong_ frames defined in the WebSocket
protocol, don't bother, because websockets handles them for you.
.. _Ping: https://datatracker.ietf.org/doc/html/rfc6455.html#section-5.5.2
.. _Pong: https://datatracker.ietf.org/doc/html/rfc6455.html#section-5.5.3
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.
websockets-14.1/docs/faq/index.rst 0000664 0000000 0000000 00000000702 14715046371 0017157 0 ustar 00root root 0000000 0000000 Frequently asked questions
==========================
.. currentmodule:: websockets
.. admonition:: Many questions asked in websockets' issue tracker are really
about :mod:`asyncio`.
:class: seealso
Python's documentation about `developing with asyncio`_ is a good
complement.
.. _developing with asyncio: https://docs.python.org/3/library/asyncio-dev.html
.. toctree::
server
client
common
asyncio
misc
websockets-14.1/docs/faq/misc.rst 0000664 0000000 0000000 00000002605 14715046371 0017007 0 ustar 00root root 0000000 0000000 Miscellaneous
=============
.. currentmodule:: websockets
.. Remove this question when dropping Python < 3.13, which provides natively
.. a good error message in this case.
Why do I get the error: ``module 'websockets' has no attribute '...'``?
.......................................................................
Often, this is because you created a script called ``websockets.py`` in your
current working directory. Then ``import websockets`` imports this module
instead of the websockets library.
Why is websockets slower than another library in my benchmark?
..............................................................
Not all libraries are as feature-complete as websockets. For a fair benchmark,
you should disable features that the other library doesn't provide. Typically,
you must disable:
* Compression: set ``compression=None``
* Keepalive: set ``ping_interval=None``
* UTF-8 decoding: send ``bytes`` rather than ``str``
If websockets is still slower than another Python library, please file a bug.
Are there ``onopen``, ``onmessage``, ``onerror``, and ``onclose`` callbacks?
............................................................................
No, there aren't.
websockets provides high-level, coroutine-based APIs. Compared to callbacks,
coroutines make it easier to manage control flow in concurrent code.
If you prefer callback-based APIs, you should use another library.
websockets-14.1/docs/faq/server.rst 0000664 0000000 0000000 00000027332 14715046371 0017366 0 ustar 00root root 0000000 0000000 Server
======
.. currentmodule:: websockets.asyncio.server
.. admonition:: This FAQ is written for the new :mod:`asyncio` implementation.
:class: hint
Answers are also valid for the legacy :mod:`asyncio` implementation.
They translate to the :mod:`threading` implementation by removing ``await``
and ``async`` keywords and by using a :class:`~threading.Thread` instead of
a :class:`~asyncio.Task` for concurrent execution.
Why does the server close the connection prematurely?
-----------------------------------------------------
Your connection handler exits prematurely. Wait for the work to be finished
before returning.
For example, if your handler has a structure similar to::
async def handler(websocket):
asyncio.create_task(do_some_work())
change it to::
async def handler(websocket):
await do_some_work()
Why does the server close the connection after one message?
-----------------------------------------------------------
Your connection handler exits after processing one message. Write a loop to
process multiple messages.
For example, if your handler looks like this::
async def handler(websocket):
print(websocket.recv())
change it like this::
async def handler(websocket):
async for message in websocket:
print(message)
If you have prior experience with an API that relies on callbacks, you may
assume that ``handler()`` is executed every time a message is received. The API
of websockets relies on coroutines instead.
The handler coroutine is started when a new connection is established. Then, it
is responsible for receiving or sending messages throughout the lifetime of that
connection.
Why can only one client connect at a time?
------------------------------------------
Your connection handler blocks the event loop. Look for blocking calls.
Any call that may take some time must be asynchronous.
For example, this connection handler prevents the event loop from running during
one second::
async def handler(websocket):
time.sleep(1)
...
Change it to::
async def handler(websocket):
await asyncio.sleep(1)
...
In addition, calling a coroutine doesn't guarantee that it will yield control to
the event loop.
For example, this connection handler blocks the event loop by sending messages
continuously::
async def handler(websocket):
while True:
await websocket.send("firehose!")
:meth:`~ServerConnection.send` completes synchronously as long as there's space
in send buffers. The event loop never runs. (This pattern is uncommon in
real-world applications. It occurs mostly in toy programs.)
You can avoid the issue by yielding control to the event loop explicitly::
async def handler(websocket):
while True:
await websocket.send("firehose!")
await asyncio.sleep(0)
All this is part of learning asyncio. It isn't specific to websockets.
See also Python's documentation about `running blocking code`_.
.. _running blocking code: https://docs.python.org/3/library/asyncio-dev.html#running-blocking-code
.. _send-message-to-all-users:
How do I send a message to all users?
-------------------------------------
Record all connections in a global variable::
CONNECTIONS = set()
async def handler(websocket):
CONNECTIONS.add(websocket)
try:
await websocket.wait_closed()
finally:
CONNECTIONS.remove(websocket)
Then, call :func:`~websockets.asyncio.server.broadcast`::
from websockets.asyncio.server import broadcast
def message_all(message):
broadcast(CONNECTIONS, message)
If you're running multiple server processes, make sure you call ``message_all``
in each process.
.. _send-message-to-single-user:
How do I send a message to a single user?
-----------------------------------------
Record connections in a global variable, keyed by user identifier::
CONNECTIONS = {}
async def handler(websocket):
user_id = ... # identify user in your app's context
CONNECTIONS[user_id] = websocket
try:
await websocket.wait_closed()
finally:
del CONNECTIONS[user_id]
Then, call :meth:`~ServerConnection.send`::
async def message_user(user_id, message):
websocket = CONNECTIONS[user_id] # raises KeyError if user disconnected
await websocket.send(message) # may raise websockets.exceptions.ConnectionClosed
Add error handling according to the behavior you want if the user disconnected
before the message could be sent.
This example supports only one connection per user. To support concurrent
connections by the same user, you can change ``CONNECTIONS`` to store a set of
connections for each user.
If you're running multiple server processes, call ``message_user`` in each
process. The process managing the user's connection sends the message; other
processes do nothing.
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 Redis_. Server processes would
subscribe their clients. Then, they would receive messages only for the
connections that they're managing.
.. _Redis: https://redis.io/
How do I send a message to a channel, a topic, or some users?
-------------------------------------------------------------
websockets doesn't provide built-in publish / subscribe functionality.
Record connections in a global variable, keyed by user identifier, as shown in
:ref:`How do I send a message to a single user?`
Then, build the set of recipients and broadcast the message to them, as shown in
:ref:`How do I send a message to all users?`
:doc:`../howto/django` contains a complete implementation of this pattern.
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 Redis_.
.. _Redis: https://redis.io/
How do I pass arguments to the connection handler?
--------------------------------------------------
You can bind additional arguments to the connection handler with
:func:`functools.partial`::
import functools
async def handler(websocket, extra_argument):
...
bound_handler = functools.partial(handler, extra_argument=42)
Another way to achieve this result is to define the ``handler`` coroutine in
a scope where the ``extra_argument`` variable exists instead of injecting it
through an argument.
How do I access the request path?
---------------------------------
It is available in the :attr:`~ServerConnection.request` object.
You may route a connection to different handlers depending on the request path::
async def handler(websocket):
if websocket.request.path == "/blue":
await blue_handler(websocket)
elif websocket.request.path == "/green":
await green_handler(websocket)
else:
# No handler for this path; close the connection.
return
You may also route the connection based on the first message received from the
client, as shown in the :doc:`tutorial <../intro/tutorial2>`. When you want to
authenticate the connection before routing it, this is usually more convenient.
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.
How do I access HTTP headers?
-----------------------------
You can access HTTP headers during the WebSocket handshake by providing a
``process_request`` callable or coroutine::
def process_request(connection, request):
authorization = request.headers["Authorization"]
...
async with serve(handler, process_request=process_request):
...
Once the connection is established, HTTP headers are available in the
:attr:`~ServerConnection.request` and :attr:`~ServerConnection.response`
objects::
async def handler(websocket):
authorization = websocket.request.headers["Authorization"]
How do I set HTTP headers?
--------------------------
To set the ``Sec-WebSocket-Extensions`` or ``Sec-WebSocket-Protocol`` headers in
the WebSocket handshake response, use the ``extensions`` or ``subprotocols``
arguments of :func:`~serve`.
To override the ``Server`` header, use the ``server_header`` argument. Set it to
:obj:`None` to remove the header.
To set other HTTP headers, provide a ``process_response`` callable or
coroutine::
def process_response(connection, request, response):
response.headers["X-Blessing"] = "May the network be with you"
async with serve(handler, process_response=process_response):
...
How do I get the IP address of the client?
------------------------------------------
It's available in :attr:`~ServerConnection.remote_address`::
async def handler(websocket):
remote_ip = websocket.remote_address[0]
How do I set the IP addresses that my server listens on?
--------------------------------------------------------
Use the ``host`` argument of :meth:`~serve`::
async with serve(handler, host="192.168.0.1", port=8080):
...
:func:`~serve` accepts the same arguments as
:meth:`~asyncio.loop.create_server` and passes them through.
What does ``OSError: [Errno 99] error while attempting to bind on address ('::1', 80, 0, 0): address not available`` mean?
--------------------------------------------------------------------------------------------------------------------------
You are calling :func:`~serve` without a ``host`` argument in a context where
IPv6 isn't available.
To listen only on IPv4, specify ``host="0.0.0.0"`` or ``family=socket.AF_INET``.
Refer to the documentation of :meth:`~asyncio.loop.create_server` for details.
How do I close a connection?
----------------------------
websockets takes care of closing the connection when the handler exits.
How do I stop a server?
-----------------------
Exit the :func:`~serve` context manager.
Here's an example that terminates cleanly when it receives SIGTERM on Unix:
.. literalinclude:: ../../example/faq/shutdown_server.py
:emphasize-lines: 13-16,19
How do I stop a server while keeping existing connections open?
---------------------------------------------------------------
Call the server's :meth:`~Server.close` method with ``close_connections=False``.
Here's how to adapt the example just above::
async def server():
...
server = await serve(echo, "localhost", 8765)
await stop
server.close(close_connections=False)
await server.wait_closed()
How do I implement a health check?
----------------------------------
Intercept requests with the ``process_request`` hook. When a request is sent to
the health check endpoint, treat is as an HTTP request and return a response:
.. literalinclude:: ../../example/faq/health_check_server.py
:emphasize-lines: 7-9,16
:meth:`~ServerConnection.respond` makes it easy to send a plain text response.
You can also construct a :class:`~websockets.http11.Response` object directly.
How do I run HTTP and WebSocket servers on the same port?
---------------------------------------------------------
You don't.
HTTP and WebSocket have widely different operational characteristics. Running
them with the same server becomes inconvenient when you scale.
Providing an HTTP server is out of scope for websockets. It only aims at
providing a WebSocket server.
There's limited support for returning HTTP responses with the
``process_request`` hook.
If you need more, pick an HTTP server and run it separately.
Alternatively, pick an HTTP framework that builds on top of ``websockets`` to
support WebSocket connections, like Sanic_.
.. _Sanic: https://sanicframework.org/en/
websockets-14.1/docs/howto/ 0000775 0000000 0000000 00000000000 14715046371 0015710 5 ustar 00root root 0000000 0000000 websockets-14.1/docs/howto/autoreload.rst 0000664 0000000 0000000 00000001777 14715046371 0020615 0 ustar 00root root 0000000 0000000 Reload on code changes
======================
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.
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's designed for production rather
than development.
However, you can achieve the same result easily.
Install watchdog_ with the ``watchmedo`` shell utility:
.. code-block:: console
$ pip install 'watchdog[watchmedo]'
.. _watchdog: https://pypi.org/project/watchdog/
Run your server with ``watchmedo auto-restart``:
.. code-block:: console
$ watchmedo auto-restart --pattern "*.py" --recursive --signal SIGTERM \
python app.py
This example assumes that the server is defined in a script called ``app.py``.
Adapt it as necessary.
websockets-14.1/docs/howto/cheatsheet.rst 0000664 0000000 0000000 00000006215 14715046371 0020563 0 ustar 00root root 0000000 0000000 Cheat sheet
===========
.. currentmodule:: websockets
Server
------
* Write a coroutine that handles a single connection. It receives a WebSocket
protocol instance and the URI path in argument.
* Call :meth:`~asyncio.connection.Connection.recv` and
:meth:`~asyncio.connection.Connection.send` to receive and send messages at
any time.
* When :meth:`~asyncio.connection.Connection.recv` or
:meth:`~asyncio.connection.Connection.send` raises
:exc:`~exceptions.ConnectionClosed`, clean up and exit. If you started other
:class:`asyncio.Task`, terminate them before exiting.
* If you aren't awaiting :meth:`~asyncio.connection.Connection.recv`, consider
awaiting :meth:`~asyncio.connection.Connection.wait_closed` to detect
quickly when the connection is closed.
* You may :meth:`~asyncio.connection.Connection.ping` or
:meth:`~asyncio.connection.Connection.pong` if you wish but it isn't needed
in general.
* Create a server with :func:`~asyncio.server.serve` which is similar to asyncio's
:meth:`~asyncio.loop.create_server`. You can also use it as an asynchronous
context manager.
* 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.
* For advanced customization, you may subclass
:class:`~asyncio.server.ServerConnection` and pass either this subclass or a
factory function as the ``create_connection`` argument.
Client
------
* Create a client with :func:`~asyncio.client.connect` which is similar to
asyncio's :meth:`~asyncio.loop.create_connection`. You can also use it as an
asynchronous context manager.
* For advanced customization, you may subclass
:class:`~asyncio.client.ClientConnection` and pass either this subclass or
a factory function as the ``create_connection`` argument.
* Call :meth:`~asyncio.connection.Connection.recv` and
:meth:`~asyncio.connection.Connection.send` to receive and send messages at
any time.
* You may :meth:`~asyncio.connection.Connection.ping` or
:meth:`~asyncio.connection.Connection.pong` if you wish but it isn't needed in
general.
* If you aren't using :func:`~asyncio.client.connect` as a context manager, call
:meth:`~asyncio.connection.Connection.close` to terminate the connection.
.. _debugging:
Debugging
---------
If you don't understand what websockets is doing, enable logging::
import logging
logger = logging.getLogger('websockets')
logger.setLevel(logging.DEBUG)
logger.addHandler(logging.StreamHandler())
The logs contain:
* Exceptions in the connection handler at the ``ERROR`` level
* Exceptions in the opening or closing handshake at the ``INFO`` level
* All frames at the ``DEBUG`` level — this can be very verbose
If you're new to ``asyncio``, you will certainly encounter issues that are
related to asynchronous programming in general rather than to websockets in
particular. Fortunately Python's official documentation provides advice to
`develop with asyncio`_. Check it out: it's invaluable!
.. _develop with asyncio: https://docs.python.org/3/library/asyncio-dev.html
websockets-14.1/docs/howto/django.rst 0000664 0000000 0000000 00000025155 14715046371 0017714 0 ustar 00root root 0000000 0000000 Integrate with Django
=====================
If you're looking at adding real-time capabilities to a Django project with
WebSocket, you have two main options.
1. Using Django Channels_, 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.
2. 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.
.. _Channels: https://channels.readthedocs.io/
This guide shows how to implement the second technique with websockets. It
assumes familiarity with Django.
Authenticate connections
------------------------
Since the websockets server runs outside of Django, we need to integrate it
with ``django.contrib.auth``.
We will generate authentication tokens in the Django project. Then we will
send them to the websockets server, where they will authenticate the user.
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.
Refer to the topic guide on :doc:`authentication <../topics/authentication>`
for details on this design.
Generate tokens
...............
We want secure, short-lived tokens containing the user ID. We'll rely on
`django-sesame`_, a small library designed exactly for this purpose.
.. _django-sesame: https://github.com/aaugustin/django-sesame
Add django-sesame to the dependencies of your Django project, install it, and
configure it in the settings of the project:
.. code-block:: python
AUTHENTICATION_BACKENDS = [
"django.contrib.auth.backends.ModelBackend",
"sesame.backends.ModelBackend",
]
(If your project already uses another authentication backend than the default
``"django.contrib.auth.backends.ModelBackend"``, adjust accordingly.)
You don't need ``"sesame.middleware.AuthenticationMiddleware"``. It is for
authenticating users in the Django server, while we're authenticating them in
the websockets server.
We'd 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:
.. code-block:: python
SESAME_MAX_AGE = 30
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.
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't seem like a good idea, both in terms of behavior and in terms of
performance.
Now you can generate tokens in a ``django-admin shell`` as follows:
.. code-block:: pycon
>>> from django.contrib.auth import get_user_model
>>> User = get_user_model()
>>> user = User.objects.get(username="")
>>> from sesame.utils import get_token
>>> get_token(user)
''
Keep this console open: since tokens expire after 30 seconds, you'll have to
generate a new token every time you want to test connecting to the server.
Validate tokens
...............
Let's move on to the websockets server.
Add websockets to the dependencies of your Django project and install it.
Indeed, we're going to reuse the environment of the Django project, so we can
call its APIs in the websockets server.
Now here's how to implement authentication.
.. literalinclude:: ../../example/django/authentication.py
Let's unpack this code.
We're calling ``django.setup()`` before doing anything with Django because
we're using Django in a `standalone script`_. This assumes that the
``DJANGO_SETTINGS_MODULE`` environment variable is set to the Python path to
your settings module.
.. _standalone script: https://docs.djangoproject.com/en/stable/topics/settings/#calling-django-setup-is-required-for-standalone-django-usage
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 ``get_user()``, the API for `authentication outside a view`_. If
authentication fails, it closes the connection and exits.
.. _authentication outside a view: https://django-sesame.readthedocs.io/en/stable/howto.html#outside-a-view
When we call an API that makes a database query such as ``get_user()``, we
wrap the call in :func:`~asyncio.to_thread`. Indeed, the Django ORM doesn't
support asynchronous I/O. It would block the event loop if it didn't run in a
separate thread.
Finally, we start a server with :func:`~websockets.asyncio.server.serve`.
We're ready to test!
Save this code to a file called ``authentication.py``, make sure the
``DJANGO_SETTINGS_MODULE`` environment variable is set properly, and start the
websockets server:
.. code-block:: console
$ python authentication.py
Generate a new token — remember, they're only valid for 30 seconds — and use
it to connect to your server. Paste your token and press Enter when you get a
prompt:
.. code-block:: console
$ python -m websockets ws://localhost:8888/
Connected to ws://localhost:8888/
>
< Hello !
Connection closed: 1000 (OK).
It works!
If you enter an expired or invalid token, authentication fails and the server
closes the connection:
.. code-block:: console
$ python -m websockets ws://localhost:8888/
Connected to ws://localhost:8888.
> not a token
Connection closed: 1011 (internal error) authentication failed.
You can also test from a browser by generating a new token and running the
following code in the JavaScript console of the browser:
.. code-block:: javascript
websocket = new WebSocket("ws://localhost:8888/");
websocket.onopen = (event) => websocket.send("");
websocket.onmessage = (event) => console.log(event.data);
If you don't want to import your entire Django project into the websockets
server, you can build a separate Django project with ``django.contrib.auth``,
``django-sesame``, a suitable ``User`` model, and a subset of the settings of
the main project.
Stream events
-------------
We can connect and authenticate but our server doesn't do anything useful yet!
Let's 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.
Many use cases for WebSocket with Django follow a similar pattern.
Set up event bus
................
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'll rely on `Redis
Pub/Sub`_.
.. _Redis Pub/Sub: https://redis.io/topics/pubsub
The easiest way to add Redis to a Django project is by configuring a cache
backend with `django-redis`_. This library manages connections to Redis
efficiently, persisting them between requests, and provides an API to access
the Redis connection directly.
.. _django-redis: https://github.com/jazzband/django-redis
Install Redis, add django-redis to the dependencies of your Django project,
install it, and configure it in the settings of the project:
.. code-block:: python
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379/1",
},
}
If you already have a default cache, add a new one with a different name and
change ``get_redis_connection("default")`` in the code below to the same name.
Publish events
..............
Now let's write events to the bus.
Add the following code to a module that is imported when your Django project
starts. Typically, you would put it in a ``signals.py`` module, which you
would import in the ``AppConfig.ready()`` method of one of your apps:
.. literalinclude:: ../../example/django/signals.py
This code runs every time the admin saves a ``LogEntry`` object to keep track
of a change. It extracts interesting data, serializes it to JSON, and writes
an event to Redis.
Let's check that it works:
.. code-block:: console
$ 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) "subscribe"
2) "events"
3) (integer) 1
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 ``"events"`` stream.
Broadcast events
................
Now let's turn to reading events and broadcasting them to connected clients.
We need to add several features:
* Keep track of connected clients so we can broadcast messages.
* Tell which content types the user has permission to view or to change.
* Connect to the message bus and read events.
* Broadcast these events to users who have corresponding permissions.
Here's a complete implementation.
.. literalinclude:: ../../example/django/notifications.py
Since the ``get_content_types()`` function makes a database query, it is
wrapped inside :func:`asyncio.to_thread()`. 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.
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.
The ``process_events()`` function reads events from Redis and broadcasts them
to all connections that should receive them. We don't 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're immediately ready to process the next event, even if it takes time to
send a message to a slow client.
Since Redis can publish a message to multiple subscribers, multiple instances
of this server can safely run in parallel.
Does it scale?
--------------
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.
websockets-14.1/docs/howto/extensions.rst 0000664 0000000 0000000 00000002137 14715046371 0020644 0 ustar 00root root 0000000 0000000 Write an extension
==================
.. currentmodule:: websockets.extensions
During the opening handshake, WebSocket clients and servers negotiate which
extensions_ will be used with which parameters. Then each frame is processed
by extensions before being sent or after being received.
.. _extensions: https://datatracker.ietf.org/doc/html/rfc6455.html#section-9
As a consequence, writing an extension requires implementing several classes:
* Extension Factory: it negotiates parameters and instantiates the extension.
Clients and servers require separate extension factories with distinct APIs.
Extension factories are the public API of an extension.
* Extension: it decodes incoming frames and encodes outgoing frames.
If the extension is symmetrical, clients and servers can use the same
class.
Extensions are initialized by extension factories, so they don't need to be
part of the public API of an extension.
websockets provides base classes for extension factories and extensions.
See :class:`ClientExtensionFactory`, :class:`ServerExtensionFactory`,
and :class:`Extension` for details.
websockets-14.1/docs/howto/fly.rst 0000664 0000000 0000000 00000011675 14715046371 0017246 0 ustar 00root root 0000000 0000000 Deploy to Fly
================
This guide describes how to deploy a websockets server to Fly_.
.. _Fly: https://fly.io/
.. admonition:: The free tier of Fly is sufficient for trying this guide.
:class: tip
The `free tier`__ include up to three small VMs. This guide uses only one.
__ https://fly.io/docs/about/pricing/
We're going to deploy a very simple app. The process would be identical for a
more realistic app.
Create application
------------------
Here's the implementation of the app, an echo server. Save it in a file called
``app.py``:
.. literalinclude:: ../../example/deployment/fly/app.py
:language: python
This app implements typical requirements for running on a Platform as a Service:
* it provides a health check at ``/healthz``;
* it closes connections and exits cleanly when it receives a ``SIGTERM`` signal.
Create a ``requirements.txt`` file containing this line to declare a dependency
on websockets:
.. literalinclude:: ../../example/deployment/fly/requirements.txt
:language: text
The app is ready. Let's deploy it!
Deploy application
------------------
Follow the instructions__ to install the Fly CLI, if you haven't done that yet.
__ https://fly.io/docs/hands-on/install-flyctl/
Sign up or log in to Fly.
Launch the app — you'll have to pick a different name because I'm already using
``websockets-echo``:
.. code-block:: console
$ 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 "fly deploy" to deploy your application.
.. admonition:: This will build the image with a generic buildpack.
:class: tip
Fly can `build images`__ with a Dockerfile or a buildpack. Here, ``fly
launch`` configures a generic Paketo buildpack.
If you'd rather package the app with a Dockerfile, check out the guide to
:ref:`containerize an application `.
__ https://fly.io/docs/reference/builders/
Replace the auto-generated ``fly.toml`` with:
.. literalinclude:: ../../example/deployment/fly/fly.toml
:language: toml
This configuration:
* listens on port 443, terminates TLS, and forwards to the app on port 8080;
* declares a health check at ``/healthz``;
* requests a ``SIGTERM`` for terminating the app.
Replace the auto-generated ``Procfile`` with:
.. literalinclude:: ../../example/deployment/fly/Procfile
:language: text
This tells Fly how to run the app.
Now you can deploy it:
.. code-block:: console
$ fly deploy
... lots of output...
==> Monitoring deployment
1 desired, 1 placed, 1 healthy, 0 unhealthy [health checks: 1 total, 1 passing]
--> v0 deployed successfully
Validate deployment
-------------------
Let's confirm that your application is running as expected.
Since it's a WebSocket server, you need a WebSocket client, such as the
interactive client that comes with websockets.
If you're currently building a websockets server, perhaps you're already in a
virtualenv where websockets is installed. If not, you can install it in a new
virtualenv as follows:
.. code-block:: console
$ python -m venv websockets-client
$ . websockets-client/bin/activate
$ pip install websockets
Connect the interactive client — you must replace ``websockets-echo`` with the
name of your Fly app in this command:
.. code-block:: console
$ python -m websockets wss://websockets-echo.fly.dev/
Connected to wss://websockets-echo.fly.dev/.
>
Great! Your app is running!
Once you're connected, you can send any message and the server will echo it,
or press Ctrl-D to terminate the connection:
.. code-block:: console
> Hello!
< Hello!
Connection closed: 1000 (OK).
You can also confirm that your application shuts down gracefully.
Connect an interactive client again — remember to replace ``websockets-echo``
with your app:
.. code-block:: console
$ python -m websockets wss://websockets-echo.fly.dev/
Connected to wss://websockets-echo.fly.dev/.
>
In another shell, restart the app — again, replace ``websockets-echo`` with your
app:
.. code-block:: console
$ fly restart websockets-echo
websockets-echo is being restarted
Go back to the first shell. The connection is closed with code 1001 (going
away).
.. code-block:: console
$ python -m websockets wss://websockets-echo.fly.dev/
Connected to wss://websockets-echo.fly.dev/.
Connection closed: 1001 (going away).
If graceful shutdown wasn't working, the server wouldn't perform a closing
handshake and the connection would be closed with code 1006 (abnormal closure).
websockets-14.1/docs/howto/haproxy.rst 0000664 0000000 0000000 00000003176 14715046371 0020143 0 ustar 00root root 0000000 0000000 Deploy behind HAProxy
=====================
This guide demonstrates a way to load balance connections across multiple
websockets server processes running on the same machine with HAProxy_.
We'll run server processes with Supervisor as described in :doc:`this guide
`.
.. _HAProxy: https://www.haproxy.org/
Run server processes
--------------------
Save this app to ``app.py``:
.. literalinclude:: ../../example/deployment/haproxy/app.py
:emphasize-lines: 24
Each server process listens on a different port by extracting an incremental
index from an environment variable set by Supervisor.
Save this configuration to ``supervisord.conf``:
.. literalinclude:: ../../example/deployment/haproxy/supervisord.conf
This configuration runs four instances of the app.
Install Supervisor and run it:
.. code-block:: console
$ supervisord -c supervisord.conf -n
Configure and run HAProxy
-------------------------
Here's a simple HAProxy configuration to load balance connections across four
processes:
.. literalinclude:: ../../example/deployment/haproxy/haproxy.cfg
In the backend configuration, we set the load balancing method to
``leastconn`` in order to balance the number of active connections across
servers. This is best for long running connections.
Save the configuration to ``haproxy.cfg``, install HAProxy, and run it:
.. code-block:: console
$ haproxy -f haproxy.cfg
You can confirm that HAProxy proxies connections properly:
.. code-block:: console
$ PYTHONPATH=src python -m websockets ws://localhost:8080/
Connected to ws://localhost:8080/.
> Hello!
< Hello!
Connection closed: 1000 (OK).
websockets-14.1/docs/howto/heroku.rst 0000664 0000000 0000000 00000012614 14715046371 0017743 0 ustar 00root root 0000000 0000000 Deploy to Heroku
================
This guide describes how to deploy a websockets server to Heroku_. The same
principles should apply to other Platform as a Service providers.
.. _Heroku: https://www.heroku.com/
.. admonition:: Heroku no longer offers a free tier.
:class: attention
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't updated anymore and may be removed in the future.
We're going to deploy a very simple app. The process would be identical for a
more realistic app.
Create repository
-----------------
Deploying to Heroku requires a git repository. Let's initialize one:
.. code-block:: console
$ mkdir websockets-echo
$ cd websockets-echo
$ git init -b main
Initialized empty Git repository in websockets-echo/.git/
$ git commit --allow-empty -m "Initial commit."
[main (root-commit) 1e7947d] Initial commit.
Create application
------------------
Here's the implementation of the app, an echo server. Save it in a file called
``app.py``:
.. literalinclude:: ../../example/deployment/heroku/app.py
:language: python
Heroku expects the server to `listen on a specific port`_, which is provided
in the ``$PORT`` environment variable. The app reads it and passes it to
:func:`~websockets.asyncio.server.serve`.
.. _listen on a specific port: https://devcenter.heroku.com/articles/preparing-a-codebase-for-heroku-deployment#4-listen-on-the-correct-port
Heroku sends a ``SIGTERM`` signal to all processes when `shutting down a
dyno`_. When the app receives this signal, it closes connections and exits
cleanly.
.. _shutting down a dyno: https://devcenter.heroku.com/articles/dynos#shutdown
Create a ``requirements.txt`` file containing this line to declare a dependency
on websockets:
.. literalinclude:: ../../example/deployment/heroku/requirements.txt
:language: text
Create a ``Procfile``.
.. literalinclude:: ../../example/deployment/heroku/Procfile
This tells Heroku how to run the app.
Confirm that you created the correct files and commit them to git:
.. code-block:: console
$ ls
Procfile app.py requirements.txt
$ git add .
$ git commit -m "Initial implementation."
[main 8418c62] Initial implementation.
3 files changed, 32 insertions(+)
create mode 100644 Procfile
create mode 100644 app.py
create mode 100644 requirements.txt
The app is ready. Let's deploy it!
Deploy application
------------------
Follow the instructions_ to install the Heroku CLI, if you haven't done that
yet.
.. _instructions: https://devcenter.heroku.com/articles/getting-started-with-python#set-up
Sign up or log in to Heroku.
Create a Heroku app — you'll have to pick a different name because I'm already
using ``websockets-echo``:
.. code-block:: console
$ heroku create websockets-echo
Creating ⬢ websockets-echo... done
https://websockets-echo.herokuapp.com/ | https://git.heroku.com/websockets-echo.git
.. code-block:: console
$ 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
Validate deployment
-------------------
Let's confirm that your application is running as expected.
Since it's a WebSocket server, you need a WebSocket client, such as the
interactive client that comes with websockets.
If you're currently building a websockets server, perhaps you're already in a
virtualenv where websockets is installed. If not, you can install it in a new
virtualenv as follows:
.. code-block:: console
$ python -m venv websockets-client
$ . websockets-client/bin/activate
$ pip install websockets
Connect the interactive client — you must replace ``websockets-echo`` with the
name of your Heroku app in this command:
.. code-block:: console
$ python -m websockets wss://websockets-echo.herokuapp.com/
Connected to wss://websockets-echo.herokuapp.com/.
>
Great! Your app is running!
Once you're connected, you can send any message and the server will echo it,
or press Ctrl-D to terminate the connection:
.. code-block:: console
> Hello!
< Hello!
Connection closed: 1000 (OK).
You can also confirm that your application shuts down gracefully.
Connect an interactive client again — remember to replace ``websockets-echo``
with your app:
.. code-block:: console
$ python -m websockets wss://websockets-echo.herokuapp.com/
Connected to wss://websockets-echo.herokuapp.com/.
>
In another shell, restart the app — again, replace ``websockets-echo`` with your
app:
.. code-block:: console
$ heroku dyno:restart -a websockets-echo
Restarting dynos on ⬢ websockets-echo... done
Go back to the first shell. The connection is closed with code 1001 (going
away).
.. code-block:: console
$ python -m websockets wss://websockets-echo.herokuapp.com/
Connected to wss://websockets-echo.herokuapp.com/.
Connection closed: 1001 (going away).
If graceful shutdown wasn't working, the server wouldn't perform a closing
handshake and the connection would be closed with code 1006 (abnormal closure).
websockets-14.1/docs/howto/index.rst 0000664 0000000 0000000 00000002006 14715046371 0017547 0 ustar 00root root 0000000 0000000 How-to guides
=============
In a hurry? Check out these examples.
.. toctree::
:titlesonly:
quickstart
Upgrading from the legacy :mod:`asyncio` implementation to the new one?
Read this.
.. toctree::
:titlesonly:
upgrade
If you're stuck, perhaps you'll find the answer here.
.. toctree::
:titlesonly:
cheatsheet
patterns
autoreload
This guide will help you integrate websockets into a broader system.
.. toctree::
:titlesonly:
django
The WebSocket protocol makes provisions for extending or specializing its
features, which websockets supports fully.
.. toctree::
:titlesonly:
extensions
.. _deployment-howto:
Once your application is ready, learn how to deploy it on various platforms.
.. toctree::
:titlesonly:
render
fly
heroku
kubernetes
supervisor
nginx
haproxy
If you're integrating the Sans-I/O layer of websockets into a library, rather
than building an application with websockets, follow this guide.
.. toctree::
:maxdepth: 2
sansio
websockets-14.1/docs/howto/kubernetes.rst 0000664 0000000 0000000 00000015217 14715046371 0020617 0 ustar 00root root 0000000 0000000 Deploy to Kubernetes
====================
This guide describes how to deploy a websockets server to Kubernetes_. It
assumes familiarity with Docker and Kubernetes.
We're going to deploy a simple app to a local Kubernetes cluster and to ensure
that it scales as expected.
In a more realistic context, you would follow your organization's practices
for deploying to Kubernetes, but you would apply the same principles as far as
websockets is concerned.
.. _Kubernetes: https://kubernetes.io/
.. _containerize-application:
Containerize application
------------------------
Here's the app we're going to deploy. Save it in a file called
``app.py``:
.. literalinclude:: ../../example/deployment/kubernetes/app.py
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.
The app exposes a health check on ``/healthz``. It also provides two other
endpoints for testing purposes: ``/inemuri`` will make the app unresponsive
for 10 seconds and ``/seppuku`` will terminate it.
The quest for the perfect Python container image is out of scope of this
guide, so we'll go for the simplest possible configuration instead:
.. literalinclude:: ../../example/deployment/kubernetes/Dockerfile
After saving this ``Dockerfile``, build the image:
.. code-block:: console
$ docker build -t websockets-test:1.0 .
Test your image by running:
.. code-block:: console
$ docker run --name run-websockets-test --publish 32080:80 --rm \
websockets-test:1.0
Then, in another shell, in a virtualenv where websockets is installed, connect
to the app and check that it echoes anything you send:
.. code-block:: console
$ python -m websockets ws://localhost:32080/
Connected to ws://localhost:32080/.
> Hey there!
< Hey there!
>
Now, in yet another shell, stop the app with:
.. code-block:: console
$ docker kill -s TERM run-websockets-test
Going to the shell where you connected to the app, you can confirm that it
shut down gracefully:
.. code-block:: console
$ python -m websockets ws://localhost:32080/
Connected to ws://localhost:32080/.
> Hey there!
< Hey there!
Connection closed: 1001 (going away).
If it didn't, you'd get code 1006 (abnormal closure).
Deploy application
------------------
Configuring Kubernetes is even further beyond the scope of this guide, so
we'll use a basic configuration for testing, with just one Service_ and one
Deployment_:
.. literalinclude:: ../../example/deployment/kubernetes/deployment.yaml
For local testing, a service of type NodePort_ is good enough. For deploying
to production, you would configure an Ingress_.
.. _Service: https://kubernetes.io/docs/concepts/services-networking/service/
.. _Deployment: https://kubernetes.io/docs/concepts/workloads/controllers/deployment/
.. _NodePort: https://kubernetes.io/docs/concepts/services-networking/service/#nodeport
.. _Ingress: https://kubernetes.io/docs/concepts/services-networking/ingress/
After saving this to a file called ``deployment.yaml``, you can deploy:
.. code-block:: console
$ kubectl apply -f deployment.yaml
service/websockets-test created
deployment.apps/websockets-test created
Now you have a deployment with one pod running:
.. code-block:: console
$ 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
You can connect to the service — press Ctrl-D to exit:
.. code-block:: console
$ python -m websockets ws://localhost:32080/
Connected to ws://localhost:32080/.
Connection closed: 1000 (OK).
Validate deployment
-------------------
First, let's ensure the liveness probe works by making the app unresponsive:
.. code-block:: console
$ curl http://localhost:32080/inemuri
Sleeping for 10s
Since we have only one pod, we know that this pod will go to sleep.
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.
Indeed, after a few seconds, the pod reports a restart:
.. code-block:: console
$ kubectl get pods -l app=websockets-test
NAME READY STATUS RESTARTS AGE
websockets-test-86b48f4bb7-nltfh 1/1 Running 1 42s
Next, let's take it one step further and crash the app:
.. code-block:: console
$ curl http://localhost:32080/seppuku
Terminating
The pod reports a second restart:
.. code-block:: console
$ kubectl get pods -l app=websockets-test
NAME READY STATUS RESTARTS AGE
websockets-test-86b48f4bb7-nltfh 1/1 Running 2 72s
All good — Kubernetes delivers on its promise to keep our app alive!
Scale deployment
----------------
Of course, Kubernetes is for scaling. Let's scale — modestly — to 10 pods:
.. code-block:: console
$ kubectl scale deployment.apps/websockets-test --replicas=10
deployment.apps/websockets-test scaled
After a few seconds, we have 10 pods running:
.. code-block:: console
$ kubectl get deployment websockets-test
NAME READY UP-TO-DATE AVAILABLE AGE
websockets-test 10/10 10 10 10m
Now let's generate load. We'll use this script:
.. literalinclude:: ../../example/deployment/kubernetes/benchmark.py
We'll 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.
Let's try it:
.. code-block:: console
$ 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
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'd expect from a quick benchmark without any effort to
stabilize the test setup.
Finally, we can scale back to one pod.
.. code-block:: console
$ 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
websockets-14.1/docs/howto/nginx.rst 0000664 0000000 0000000 00000005176 14715046371 0017576 0 ustar 00root root 0000000 0000000 Deploy behind nginx
===================
This guide demonstrates a way to load balance connections across multiple
websockets server processes running on the same machine with nginx_.
We'll run server processes with Supervisor as described in :doc:`this guide
`.
.. _nginx: https://nginx.org/
Run server processes
--------------------
Save this app to ``app.py``:
.. literalinclude:: ../../example/deployment/nginx/app.py
:emphasize-lines: 21,23
We'd like 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.
We start the app with :func:`~websockets.asyncio.server.unix_serve`. Each server
process listens on a different socket thanks to an environment variable set by
Supervisor to a different value.
Save this configuration to ``supervisord.conf``:
.. literalinclude:: ../../example/deployment/nginx/supervisord.conf
This configuration runs four instances of the app.
Install Supervisor and run it:
.. code-block:: console
$ supervisord -c supervisord.conf -n
Configure and run nginx
-----------------------
Here's a simple nginx configuration to load balance connections across four
processes:
.. literalinclude:: ../../example/deployment/nginx/nginx.conf
We set ``daemon off`` so we can run nginx in the foreground for testing.
Then we combine the `WebSocket proxying`_ and `load balancing`_ guides:
* 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.
* The WebSocket handshake involves the ``Connection`` and ``Upgrade`` HTTP
headers. We must pass them to the upstream explicitly, else nginx drops
them because they're hop-by-hop headers.
We deviate from the `WebSocket proxying`_ guide because its example adds a
``Connection: Upgrade`` header to every upstream request, even if the
original request didn't contain that header.
* In the upstream configuration, we set the load balancing method to
``least_conn`` in order to balance the number of active connections across
servers. This is best for long running connections.
.. _WebSocket proxying: http://nginx.org/en/docs/http/websocket.html
.. _load balancing: http://nginx.org/en/docs/http/load_balancing.html
Save the configuration to ``nginx.conf``, install nginx, and run it:
.. code-block:: console
$ nginx -c nginx.conf -p .
You can confirm that nginx proxies connections properly:
.. code-block:: console
$ PYTHONPATH=src python -m websockets ws://localhost:8080/
Connected to ws://localhost:8080/.
> Hello!
< Hello!
Connection closed: 1000 (OK).
websockets-14.1/docs/howto/patterns.rst 0000664 0000000 0000000 00000007123 14715046371 0020305 0 ustar 00root root 0000000 0000000 Patterns
========
.. currentmodule:: websockets
Here are typical patterns for processing messages in a WebSocket server or
client. You will certainly implement some of them in your application.
This page gives examples of connection handlers for a server. However, they're
also applicable to a client, simply by assuming that ``websocket`` is a
connection created with :func:`~asyncio.client.connect`.
WebSocket connections are long-lived. You will usually write a loop to process
several messages during the lifetime of a connection.
Consumer
--------
To receive messages from the WebSocket connection::
async def consumer_handler(websocket):
async for message in websocket:
await consumer(message)
In this example, ``consumer()`` is a coroutine implementing your business
logic for processing a message received on the WebSocket connection. Each
message may be :class:`str` or :class:`bytes`.
Iteration terminates when the client disconnects.
Producer
--------
To send messages to the WebSocket connection::
async def producer_handler(websocket):
while True:
message = await producer()
await websocket.send(message)
In this example, ``producer()`` is a coroutine implementing your business
logic for generating the next message to send on the WebSocket connection.
Each message must be :class:`str` or :class:`bytes`.
Iteration terminates when the client disconnects because
:meth:`~asyncio.server.ServerConnection.send` raises a
:exc:`~exceptions.ConnectionClosed` exception, which breaks out of the ``while
True`` loop.
Consumer and producer
---------------------
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::
async def handler(websocket):
await asyncio.gather(
consumer_handler(websocket),
producer_handler(websocket),
)
If a task terminates, :func:`~asyncio.gather` doesn't cancel the other task.
This can result in a situation where the producer keeps running after the
consumer finished, which may leak resources.
Here's a way to exit and close the WebSocket connection as soon as a task
terminates, after canceling the other task::
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()
Registration
------------
To keep track of currently connected clients, you can register them when they
connect and unregister them when they disconnect::
connected = set()
async def handler(websocket):
# Register.
connected.add(websocket)
try:
# Broadcast a message to all connected clients.
broadcast(connected, "Hello!")
await asyncio.sleep(10)
finally:
# Unregister.
connected.remove(websocket)
This example maintains the set of connected clients in memory. This works as
long as you run a single process. It doesn't scale to multiple processes.
Publish–subscribe
-----------------
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.
A complete implementation of this idea with Redis is described in
the :doc:`Django integration guide <../howto/django>`.
websockets-14.1/docs/howto/quickstart.rst 0000664 0000000 0000000 00000011575 14715046371 0020645 0 ustar 00root root 0000000 0000000 Quick start
===========
.. currentmodule:: websockets
Here are a few examples to get you started quickly with websockets.
Say "Hello world!"
------------------
Here's a WebSocket server.
It receives a name from the client, sends a greeting, and closes the connection.
.. literalinclude:: ../../example/quickstart/server.py
:caption: server.py
:language: python
:linenos:
:func:`~asyncio.server.serve` executes the connection handler coroutine
``hello()`` once for each WebSocket connection. It closes the WebSocket
connection when the handler returns.
Here's a corresponding WebSocket client.
It sends a name to the server, receives a greeting, and closes the connection.
.. literalinclude:: ../../example/quickstart/client.py
:caption: client.py
:language: python
:linenos:
Using :func:`~asyncio.client.connect` as an asynchronous context manager ensures
the WebSocket connection is closed.
.. _secure-server-example:
Encrypt connections
-------------------
Secure WebSocket connections improve confidentiality and also reliability
because they reduce the risk of interference by bad proxies.
The ``wss`` protocol is to ``ws`` what ``https`` is to ``http``. The
connection is encrypted with TLS_ (Transport Layer Security). ``wss``
requires certificates like ``https``.
.. _TLS: https://developer.mozilla.org/en-US/docs/Web/Security/Transport_Layer_Security
.. admonition:: TLS vs. SSL
:class: tip
TLS is sometimes referred to as SSL (Secure Sockets Layer). SSL was an
earlier encryption protocol; the name stuck.
Here's how to adapt the server to encrypt connections. You must download
:download:`localhost.pem <../../example/quickstart/localhost.pem>` and save it
in the same directory as ``server_secure.py``.
.. literalinclude:: ../../example/quickstart/server_secure.py
:caption: server_secure.py
:language: python
:linenos:
Here's how to adapt the client similarly.
.. literalinclude:: ../../example/quickstart/client_secure.py
:caption: client_secure.py
:language: python
:linenos:
In this example, the client needs a TLS context because the server uses a
self-signed certificate.
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 ``ssl=True`` to :func:`~asyncio.client.connect`.
.. admonition:: Configure the TLS context securely
:class: attention
This example demonstrates the ``ssl`` argument with a TLS certificate shared
between the client and the server. This is a simplistic setup.
Please review the advice and security considerations in the documentation of
the :mod:`ssl` module to configure the TLS context securely.
Connect from a browser
----------------------
The WebSocket protocol was invented for the web — as the name says!
Here's how to connect to a WebSocket server from a browser.
Run this script in a console:
.. literalinclude:: ../../example/quickstart/show_time.py
:caption: show_time.py
:language: python
:linenos:
Save this file as ``show_time.html``:
.. literalinclude:: ../../example/quickstart/show_time.html
:caption: show_time.html
:language: html
:linenos:
Save this file as ``show_time.js``:
.. literalinclude:: ../../example/quickstart/show_time.js
:caption: show_time.js
:language: js
:linenos:
Then, open ``show_time.html`` in several browsers. Clocks tick irregularly.
Broadcast messages
------------------
Let's change the previous example to send the same timestamps to all browsers,
instead of generating independent sequences for each client.
Stop the previous script if it's still running and run this script in a console:
.. literalinclude:: ../../example/quickstart/show_time_2.py
:caption: show_time_2.py
:language: python
:linenos:
Refresh ``show_time.html`` in all browsers. Clocks tick in sync.
Manage application state
------------------------
A WebSocket server can receive events from clients, process them to update the
application state, and broadcast the updated state to all connected clients.
Here's an example where any client can increment or decrement a counter. The
concurrency model of :mod:`asyncio` guarantees that updates are serialized.
Run this script in a console:
.. literalinclude:: ../../example/quickstart/counter.py
:caption: counter.py
:language: python
:linenos:
Save this file as ``counter.html``:
.. literalinclude:: ../../example/quickstart/counter.html
:caption: counter.html
:language: html
:linenos:
Save this file as ``counter.css``:
.. literalinclude:: ../../example/quickstart/counter.css
:caption: counter.css
:language: css
:linenos:
Save this file as ``counter.js``:
.. literalinclude:: ../../example/quickstart/counter.js
:caption: counter.js
:language: js
:linenos:
Then open ``counter.html`` file in several browsers and play with [+] and [-].
websockets-14.1/docs/howto/render.rst 0000664 0000000 0000000 00000011754 14715046371 0017731 0 ustar 00root root 0000000 0000000 Deploy to Render
================
This guide describes how to deploy a websockets server to Render_.
.. _Render: https://render.com/
.. admonition:: The free plan of Render is sufficient for trying this guide.
:class: tip
However, on a `free plan`__, connections are dropped after five minutes,
which is quite short for WebSocket application.
__ https://render.com/docs/free
We're going to deploy a very simple app. The process would be identical for a
more realistic app.
Create repository
-----------------
Deploying to Render requires a git repository. Let's initialize one:
.. code-block:: console
$ mkdir websockets-echo
$ cd websockets-echo
$ git init -b main
Initialized empty Git repository in websockets-echo/.git/
$ git commit --allow-empty -m "Initial commit."
[main (root-commit) 816c3b1] Initial commit.
Render requires the git repository to be hosted at GitHub or GitLab.
Sign up or log in to GitHub. Create a new repository named ``websockets-echo``.
Don't enable any of the initialization options offered by GitHub. Then, follow
instructions for pushing an existing repository from the command line.
After pushing, refresh your repository's homepage on GitHub. You should see an
empty repository with an empty initial commit.
Create application
------------------
Here's the implementation of the app, an echo server. Save it in a file called
``app.py``:
.. literalinclude:: ../../example/deployment/render/app.py
:language: python
This app implements requirements for `zero downtime deploys`_:
* it provides a health check at ``/healthz``;
* it closes connections and exits cleanly when it receives a ``SIGTERM`` signal.
.. _zero downtime deploys: https://render.com/docs/deploys#zero-downtime-deploys
Create a ``requirements.txt`` file containing this line to declare a dependency
on websockets:
.. literalinclude:: ../../example/deployment/render/requirements.txt
:language: text
Confirm that you created the correct files and commit them to git:
.. code-block:: console
$ ls
app.py requirements.txt
$ git add .
$ git commit -m "Initial implementation."
[main f26bf7f] Initial implementation.
2 files changed, 37 insertions(+)
create mode 100644 app.py
create mode 100644 requirements.txt
Push the changes to GitHub:
.. code-block:: console
$ git push
...
To github.com:/websockets-echo.git
816c3b1..f26bf7f main -> main
The app is ready. Let's deploy it!
Deploy application
------------------
Sign up or log in to Render.
Create a new web service. Connect the git repository that you just created.
Then, finalize the configuration of your app as follows:
* **Name**: websockets-echo
* **Start Command**: ``python app.py``
If you're just experimenting, select the free plan. Create the web service.
To configure the health check, go to Settings, scroll down to Health & Alerts,
and set:
* **Health Check Path**: /healthz
This triggers a new deployment.
Validate deployment
-------------------
Let's confirm that your application is running as expected.
Since it's a WebSocket server, you need a WebSocket client, such as the
interactive client that comes with websockets.
If you're currently building a websockets server, perhaps you're already in a
virtualenv where websockets is installed. If not, you can install it in a new
virtualenv as follows:
.. code-block:: console
$ python -m venv websockets-client
$ . websockets-client/bin/activate
$ pip install websockets
Connect the interactive client — you must replace ``websockets-echo`` with the
name of your Render app in this command:
.. code-block:: console
$ python -m websockets wss://websockets-echo.onrender.com/
Connected to wss://websockets-echo.onrender.com/.
>
Great! Your app is running!
Once you're connected, you can send any message and the server will echo it,
or press Ctrl-D to terminate the connection:
.. code-block:: console
> Hello!
< Hello!
Connection closed: 1000 (OK).
You can also confirm that your application shuts down gracefully when you deploy
a new version. Due to limitations of Render's free plan, you must upgrade to a
paid plan before you perform this test.
Connect an interactive client again — remember to replace ``websockets-echo``
with your app:
.. code-block:: console
$ python -m websockets wss://websockets-echo.onrender.com/
Connected to wss://websockets-echo.onrender.com/.
>
Trigger a new deployment with Manual Deploy > Deploy latest commit. When the
deployment completes, the connection is closed with code 1001 (going away).
.. code-block:: console
$ python -m websockets wss://websockets-echo.onrender.com/
Connected to wss://websockets-echo.onrender.com/.
Connection closed: 1001 (going away).
If graceful shutdown wasn't working, the server wouldn't perform a closing
handshake and the connection would be closed with code 1006 (abnormal closure).
Remember to downgrade to a free plan if you upgraded just for testing this feature.
websockets-14.1/docs/howto/sansio.rst 0000664 0000000 0000000 00000026106 14715046371 0017743 0 ustar 00root root 0000000 0000000 Integrate the Sans-I/O layer
============================
.. currentmodule:: websockets
This guide explains how to integrate the `Sans-I/O`_ layer of websockets to
add support for WebSocket in another library.
.. _Sans-I/O: https://sans-io.readthedocs.io/
As a prerequisite, you should decide how you will handle network I/O and
asynchronous control flow.
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:: ../topics/data-flow.svg
:align: center
Opening a connection
--------------------
Client-side
...........
If you're building a client, parse the URI you'd like to connect to::
from websockets.uri import parse_uri
wsuri = parse_uri("ws://example.com/")
Open a TCP connection to ``(wsuri.host, wsuri.port)`` and perform a TLS
handshake if ``wsuri.secure`` is :obj:`True`.
Initialize a :class:`~client.ClientProtocol`::
from websockets.client import ClientProtocol
protocol = ClientProtocol(wsuri)
Create a WebSocket handshake request
with :meth:`~client.ClientProtocol.connect` and send it
with :meth:`~client.ClientProtocol.send_request`::
request = protocol.connect()
protocol.send_request(request)
Then, call :meth:`~protocol.Protocol.data_to_send` and send its output to
the network, as described in `Send data`_ below.
Once you receive enough data, as explained in `Receive data`_ below, the first
event returned by :meth:`~protocol.Protocol.events_received` is the WebSocket
handshake response.
When the handshake fails, the reason is available in
:attr:`~client.ClientProtocol.handshake_exc`::
if protocol.handshake_exc is not None:
raise protocol.handshake_exc
Else, the WebSocket connection is open.
A WebSocket client API usually performs the handshake then returns a wrapper
around the network socket and the :class:`~client.ClientProtocol`.
Server-side
...........
If you're building a server, accept network connections from clients and
perform a TLS handshake if desired.
For each connection, initialize a :class:`~server.ServerProtocol`::
from websockets.server import ServerProtocol
protocol = ServerProtocol()
Once you receive enough data, as explained in `Receive data`_ below, the first
event returned by :meth:`~protocol.Protocol.events_received` is the WebSocket
handshake request.
Create a WebSocket handshake response
with :meth:`~server.ServerProtocol.accept` and send it
with :meth:`~server.ServerProtocol.send_response`::
response = protocol.accept(request)
protocol.send_response(response)
Alternatively, you may reject the WebSocket handshake and return an HTTP
response with :meth:`~server.ServerProtocol.reject`::
response = protocol.reject(status, explanation)
protocol.send_response(response)
Then, call :meth:`~protocol.Protocol.data_to_send` and send its output to
the network, as described in `Send data`_ below.
Even when you call :meth:`~server.ServerProtocol.accept`, the WebSocket
handshake may fail if the request is incorrect or unsupported.
When the handshake fails, the reason is available in
:attr:`~server.ServerProtocol.handshake_exc`::
if protocol.handshake_exc is not None:
raise protocol.handshake_exc
Else, the WebSocket connection is open.
A WebSocket server API usually builds a wrapper around the network socket and
the :class:`~server.ServerProtocol`. Then it invokes a connection handler that
accepts the wrapper in argument.
It may also provide a way to close all connections and to shut down the server
gracefully.
Going forwards, this guide focuses on handling an individual connection.
From the network to the application
-----------------------------------
Go through the five steps below until you reach the end of the data stream.
Receive data
............
When receiving data from the network, feed it to the protocol's
:meth:`~protocol.Protocol.receive_data` method.
When reaching the end of the data stream, call the protocol's
:meth:`~protocol.Protocol.receive_eof` method.
For example, if ``sock`` is a :obj:`~socket.socket`::
try:
data = sock.recv(65536)
except OSError: # socket closed
data = b""
if data:
protocol.receive_data(data)
else:
protocol.receive_eof()
These methods aren't expected to raise exceptions — unless you call them again
after calling :meth:`~protocol.Protocol.receive_eof`, which is an error.
(If you get an exception, please file a bug!)
Send data
.........
Then, call :meth:`~protocol.Protocol.data_to_send` and send its output to
the network::
for data in protocol.data_to_send():
if data:
sock.sendall(data)
else:
sock.shutdown(socket.SHUT_WR)
The empty bytestring signals the end of the data stream. When you see it, you
must half-close the TCP connection.
Sending data right after receiving data is necessary because websockets
responds to ping frames, close frames, and incorrect inputs automatically.
Expect TCP connection to close
..............................
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.
To prevent dangling TCP connections when you expect the end of the data stream
but you never reach it, call :meth:`~protocol.Protocol.close_expected`
and, if it returns :obj:`True`, schedule closing the TCP connection after a
short timeout::
# start a new execution thread to run this code
sleep(10)
sock.close() # does nothing if the socket is already closed
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.
Close TCP connection
....................
If you called :meth:`~protocol.Protocol.receive_eof`, close the TCP
connection now. This is a clean closure because the receive buffer is empty.
After :meth:`~protocol.Protocol.receive_eof` signals the end of the read
stream, :meth:`~protocol.Protocol.data_to_send` 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.
Now you can exit the loop relaying data from the network to the application.
Receive events
..............
Finally, call :meth:`~protocol.Protocol.events_received` to obtain events
parsed from the data provided to :meth:`~protocol.Protocol.receive_data`::
events = connection.events_received()
The first event will be the WebSocket opening handshake request or response.
See `Opening a connection`_ above for details.
All later events are WebSocket frames. There are two types of frames:
* Data frames contain messages transferred over the WebSocket connections. You
should provide them to the application. See `Fragmentation`_ below for
how to reassemble messages from frames.
* Control frames provide information about the connection's 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't duplicate this functionality!
From the application to the network
-----------------------------------
The connection object provides one method for each type of WebSocket frame.
For sending a data frame:
* :meth:`~protocol.Protocol.send_continuation`
* :meth:`~protocol.Protocol.send_text`
* :meth:`~protocol.Protocol.send_binary`
These methods raise :exc:`~exceptions.ProtocolError` if you don't set
the :attr:`FIN ` bit correctly in fragmented
messages.
For sending a control frame:
* :meth:`~protocol.Protocol.send_close`
* :meth:`~protocol.Protocol.send_ping`
* :meth:`~protocol.Protocol.send_pong`
:meth:`~protocol.Protocol.send_close` initiates the closing handshake.
See `Closing a connection`_ below for details.
If you encounter an unrecoverable error and you must fail the WebSocket
connection, call :meth:`~protocol.Protocol.fail`.
After any of the above, call :meth:`~protocol.Protocol.data_to_send` and
send its output to the network, as shown in `Send data`_ above.
If you called :meth:`~protocol.Protocol.send_close`
or :meth:`~protocol.Protocol.fail`, you expect the end of the data
stream. You should follow the process described in `Close TCP connection`_
above in order to prevent dangling TCP connections.
Closing a connection
--------------------
Under normal circumstances, when a server wants to close the TCP connection:
* it closes the write side;
* it reads until the end of the stream, because it expects the client to close
the read side;
* it closes the socket.
When a client wants to close the TCP connection:
* it reads until the end of the stream, because it expects the server to close
the read side;
* it closes the write side;
* it closes the socket.
Applying the rules described earlier in this document gives the intended
result. As a reminder, the rules are:
* When :meth:`~protocol.Protocol.data_to_send` returns the empty
bytestring, close the write side of the TCP connection.
* When you reach the end of the read stream, close the TCP connection.
* When :meth:`~protocol.Protocol.close_expected` returns :obj:`True`, if
you don't reach the end of the read stream quickly, close the TCP connection.
Fragmentation
-------------
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.
To reassemble a message, read data frames until you get a frame where
the :attr:`FIN ` bit is set, then concatenate
the payloads of all frames.
You will never receive an inconsistent sequence of frames because websockets
raises a :exc:`~exceptions.ProtocolError` 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.
Tips
----
Serialize operations
....................
The Sans-I/O layer is designed to run sequentially. If you interact with it from
multiple threads or coroutines, you must ensure correct serialization.
Usually, this comes for free in a cooperative multitasking environment. In a
preemptive multitasking environment, it requires mutual exclusion.
Furthermore, you must serialize writes to the network. When
:meth:`~protocol.Protocol.data_to_send` returns several values, you must write
them all before starting the next write.
Minimize buffers
................
The Sans-I/O layer doesn't perform any buffering. It makes events available in
:meth:`~protocol.Protocol.events_received` as soon as they're received.
You should make incoming messages available to the application immediately.
A small buffer of incoming messages will usually result in the best performance.
It will reduce context switching between the library and the application while
ensuring that backpressure is propagated.
websockets-14.1/docs/howto/supervisor.rst 0000664 0000000 0000000 00000010334 14715046371 0020664 0 ustar 00root root 0000000 0000000 Deploy with Supervisor
======================
This guide proposes a simple way to deploy a websockets server directly on a
Linux or BSD operating system.
We'll configure Supervisor_ to run several server processes and to restart
them if needed.
.. _Supervisor: http://supervisord.org/
We'll bind all servers to the same port. The OS will take care of balancing
connections.
Create and activate a virtualenv:
.. code-block:: console
$ python -m venv supervisor-websockets
$ . supervisor-websockets/bin/activate
Install websockets and Supervisor:
.. code-block:: console
$ pip install websockets
$ pip install supervisor
Save this app to a file called ``app.py``:
.. literalinclude:: ../../example/deployment/supervisor/app.py
This is an echo server with two features added for the purpose of this guide:
* It shuts down gracefully when receiving a ``SIGTERM`` signal;
* It enables the ``reuse_port`` option of :meth:`~asyncio.loop.create_server`,
which in turns sets ``SO_REUSEPORT`` on the accept socket.
Save this Supervisor configuration to ``supervisord.conf``:
.. literalinclude:: ../../example/deployment/supervisor/supervisord.conf
This is the minimal configuration required to keep four instances of the app
running, restarting them if they exit.
Now start Supervisor in the foreground:
.. code-block:: console
$ supervisord -c supervisord.conf -n
INFO Increased RLIMIT_NOFILE limit to 1024
INFO supervisord started with pid 43596
INFO spawned: 'websockets-test_00' with pid 43597
INFO spawned: 'websockets-test_01' with pid 43598
INFO spawned: 'websockets-test_02' with pid 43599
INFO spawned: 'websockets-test_03' 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)
In another shell, after activating the virtualenv, we can connect to the app —
press Ctrl-D to exit:
.. code-block:: console
$ python -m websockets ws://localhost:8080/
Connected to ws://localhost:8080/.
> Hello!
< Hello!
Connection closed: 1000 (OK).
Look at the pid of an instance of the app in the logs and terminate it:
.. code-block:: console
$ kill -TERM 43597
The logs show that Supervisor restarted this instance:
.. code-block:: console
INFO exited: websockets-test_00 (exit status 0; expected)
INFO spawned: 'websockets-test_00' with pid 43629
INFO success: websockets-test_00 entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)
Now let's check what happens when we shut down Supervisor, but first let's
establish a connection and leave it open:
.. code-block:: console
$ python -m websockets ws://localhost:8080/
Connected to ws://localhost:8080/.
>
Look at the pid of supervisord itself in the logs and terminate it:
.. code-block:: console
$ kill -TERM 43596
The logs show that Supervisor terminated all instances of the app before
exiting:
.. code-block:: console
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)
And you can see that the connection to the app was closed gracefully:
.. code-block:: console
$ python -m websockets ws://localhost:8080/
Connected to ws://localhost:8080/.
Connection closed: 1001 (going away).
In this example, we've been sharing the same virtualenv for supervisor and
websockets.
In a real deployment, you would likely:
* Install Supervisor with the package manager of the OS.
* Create a virtualenv dedicated to your application.
* Add ``environment=PATH="path/to/your/virtualenv/bin"`` in the Supervisor
configuration. Then ``python app.py`` runs in that virtualenv.
websockets-14.1/docs/howto/upgrade.rst 0000664 0000000 0000000 00000055670 14715046371 0020106 0 ustar 00root root 0000000 0000000 Upgrade to the new :mod:`asyncio` implementation
================================================
.. currentmodule:: websockets
The new :mod:`asyncio` implementation, which is now the default, 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 code doesn't use any `deprecated APIs`_. If it doesn't
raise warnings, you're fine.
#. `Update import paths`_. For straightforward use cases, this could be the only
step you need to take.
#. Check out `new features and improvements`_. Consider taking advantage of them
in your code.
#. Review `API changes`_. If needed, update your application to preserve its
current behavior.
In the interest of brevity, only :func:`~asyncio.client.connect` and
:func:`~asyncio.server.serve` are discussed below but everything also applies
to :func:`~asyncio.client.unix_connect` and :func:`~asyncio.server.unix_serve`
respectively.
.. admonition:: What will happen to the original implementation?
:class: hint
The original implementation is deprecated. It will be maintained for five
years after deprecation according to the :ref:`backwards-compatibility
policy `. Then, by 2030, it will be removed.
.. _deprecated APIs:
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 :class:`DeprecationWarning`, follow upgrade instructions from
the release notes of the version in which the feature was deprecated.
* The ``path`` argument of connection handlers — unnecessary since :ref:`10.1`
and deprecated in :ref:`13.0`.
* The ``loop`` and ``legacy_recv`` arguments of :func:`~legacy.client.connect`
and :func:`~legacy.server.serve`, which were removed — deprecated in
:ref:`10.0`.
* The ``timeout`` and ``klass`` arguments of :func:`~legacy.client.connect` and
:func:`~legacy.server.serve`, which were renamed to ``close_timeout`` and
``create_protocol`` — deprecated in :ref:`7.0` and :ref:`3.4` respectively.
* An empty string in the ``origins`` argument of :func:`~legacy.server.serve` —
deprecated in :ref:`7.0`.
* The ``host``, ``port``, and ``secure`` attributes of connections — deprecated
in :ref:`8.0`.
.. _Update import paths:
Import paths
------------
For context, the ``websockets`` package is structured as follows:
* The new implementation is found in the ``websockets.asyncio`` package.
* The original implementation was moved to the ``websockets.legacy`` package
and deprecated.
* The ``websockets`` package provides aliases for convenience. They were
switched to the new implementation in version 14.0 or deprecated when there
wasn't an equivalent API.
* The ``websockets.client`` and ``websockets.server`` packages provide aliases
for backwards-compatibility with earlier versions of websockets. They were
deprecated.
To upgrade to the new :mod:`asyncio` implementation, change import paths as
shown in the tables below.
.. |br| raw:: html
Client APIs
...........
+-------------------------------------------------------------------+-----------------------------------------------------+
| Legacy :mod:`asyncio` implementation | New :mod:`asyncio` implementation |
+===================================================================+=====================================================+
| ``websockets.connect()`` *(before 14.0)* |br| | ``websockets.connect()`` *(since 14.0)* |br| |
| ``websockets.client.connect()`` |br| | :func:`websockets.asyncio.client.connect` |
| :func:`websockets.legacy.client.connect` | |
+-------------------------------------------------------------------+-----------------------------------------------------+
| ``websockets.unix_connect()`` *(before 14.0)* |br| | ``websockets.unix_connect()`` *(since 14.0)* |br| |
| ``websockets.client.unix_connect()`` |br| | :func:`websockets.asyncio.client.unix_connect` |
| :func:`websockets.legacy.client.unix_connect` | |
+-------------------------------------------------------------------+-----------------------------------------------------+
| ``websockets.WebSocketClientProtocol`` |br| | :class:`websockets.asyncio.client.ClientConnection` |
| ``websockets.client.WebSocketClientProtocol`` |br| | |
| :class:`websockets.legacy.client.WebSocketClientProtocol` | |
+-------------------------------------------------------------------+-----------------------------------------------------+
Server APIs
...........
+-------------------------------------------------------------------+-----------------------------------------------------+
| Legacy :mod:`asyncio` implementation | New :mod:`asyncio` implementation |
+===================================================================+=====================================================+
| ``websockets.serve()`` *(before 14.0)* |br| | ``websockets.serve()`` *(since 14.0)* |br| |
| ``websockets.server.serve()`` |br| | :func:`websockets.asyncio.server.serve` |
| :func:`websockets.legacy.server.serve` | |
+-------------------------------------------------------------------+-----------------------------------------------------+
| ``websockets.unix_serve()`` *(before 14.0)* |br| | ``websockets.unix_serve()`` *(since 14.0)* |br| |
| ``websockets.server.unix_serve()`` |br| | :func:`websockets.asyncio.server.unix_serve` |
| :func:`websockets.legacy.server.unix_serve` | |
+-------------------------------------------------------------------+-----------------------------------------------------+
| ``websockets.WebSocketServer`` |br| | :class:`websockets.asyncio.server.Server` |
| ``websockets.server.WebSocketServer`` |br| | |
| :class:`websockets.legacy.server.WebSocketServer` | |
+-------------------------------------------------------------------+-----------------------------------------------------+
| ``websockets.WebSocketServerProtocol`` |br| | :class:`websockets.asyncio.server.ServerConnection` |
| ``websockets.server.WebSocketServerProtocol`` |br| | |
| :class:`websockets.legacy.server.WebSocketServerProtocol` | |
+-------------------------------------------------------------------+-----------------------------------------------------+
| ``websockets.broadcast()`` *(before 14.0)* |br| | ``websockets.broadcast()`` *(since 14.0)* |br| |
| :func:`websockets.legacy.server.broadcast()` | :func:`websockets.asyncio.server.broadcast` |
+-------------------------------------------------------------------+-----------------------------------------------------+
| ``websockets.BasicAuthWebSocketServerProtocol`` |br| | See below :ref:`how to migrate ` to |
| ``websockets.auth.BasicAuthWebSocketServerProtocol`` |br| | :func:`websockets.asyncio.server.basic_auth`. |
| :class:`websockets.legacy.auth.BasicAuthWebSocketServerProtocol` | |
+-------------------------------------------------------------------+-----------------------------------------------------+
| ``websockets.basic_auth_protocol_factory()`` |br| | See below :ref:`how to migrate ` to |
| ``websockets.auth.basic_auth_protocol_factory()`` |br| | :func:`websockets.asyncio.server.basic_auth`. |
| :func:`websockets.legacy.auth.basic_auth_protocol_factory` | |
+-------------------------------------------------------------------+-----------------------------------------------------+
.. _new features and improvements:
New features and improvements
-----------------------------
Customizing the opening handshake
.................................
On the server side, if you're customizing how :func:`~legacy.server.serve`
processes the opening handshake with ``process_request``, ``extra_headers``, or
``select_subprotocol``, you must update your code. Probably you can simplify it!
``process_request`` and ``select_subprotocol`` have new signatures.
``process_response`` replaces ``extra_headers`` and provides more flexibility.
See process_request_, select_subprotocol_, and process_response_ below.
Customizing automatic reconnection
..................................
On the client side, if you're reconnecting automatically with ``async for ... in
connect(...)``, the behavior when a connection attempt fails was enhanced and
made configurable.
The original implementation retried on any error. The new implementation uses an
heuristic to determine whether an error is retryable or fatal. By default, only
network errors and server errors (HTTP 500, 502, 503, or 504) are considered
retryable. You can customize this behavior with the ``process_exception``
argument of :func:`~asyncio.client.connect`.
See :func:`~asyncio.client.process_exception` for more information.
Here's how to revert to the behavior of the original implementation::
async for ... in connect(..., process_exception=lambda exc: exc):
...
Tracking open connections
.........................
The new implementation of :class:`~asyncio.server.Server` provides a
:attr:`~asyncio.server.Server.connections` property, which is a set of all open
connections. This didn't exist in the original implementation.
If you're keeping track of open connections in order to broadcast messages to
all of them, you can simplify your code by using this property.
Controlling UTF-8 decoding
..........................
The new implementation of the :meth:`~asyncio.connection.Connection.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 :meth:`~str.encode` on a :class:`str` object returned by
:meth:`~asyncio.connection.Connection.recv`, using ``decode=False`` and removing
:meth:`~str.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 :class:`bytes`
object returned by :meth:`~asyncio.connection.Connection.recv`.
Receiving fragmented messages
.............................
The new implementation provides the
:meth:`~asyncio.connection.Connection.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.
.. _API changes:
API changes
-----------
Attributes of connection objects
................................
``path``, ``request_headers``, and ``response_headers``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The :attr:`~legacy.protocol.WebSocketCommonProtocol.path`,
:attr:`~legacy.protocol.WebSocketCommonProtocol.request_headers` and
:attr:`~legacy.protocol.WebSocketCommonProtocol.response_headers` properties are
replaced by :attr:`~asyncio.connection.Connection.request` and
:attr:`~asyncio.connection.Connection.response`.
If your code uses them, you can update it as follows.
========================================== ==========================================
Legacy :mod:`asyncio` implementation New :mod:`asyncio` implementation
========================================== ==========================================
``connection.path`` ``connection.request.path``
``connection.request_headers`` ``connection.request.headers``
``connection.response_headers`` ``connection.response.headers``
========================================== ==========================================
``open`` and ``closed``
~~~~~~~~~~~~~~~~~~~~~~~
The :attr:`~legacy.protocol.WebSocketCommonProtocol.open` and
:attr:`~legacy.protocol.WebSocketCommonProtocol.closed` properties are removed.
Using them was discouraged.
Instead, you should call :meth:`~asyncio.connection.Connection.recv` or
:meth:`~asyncio.connection.Connection.send` and handle
:exc:`~exceptions.ConnectionClosed` exceptions.
If your code uses them, you can update it as follows.
========================================== ==========================================
Legacy :mod:`asyncio` implementation New :mod:`asyncio` implementation
========================================== ==========================================
.. ``from websockets.protocol import State``
``connection.open`` ``connection.state is State.OPEN``
``connection.closed`` ``connection.state is State.CLOSED``
========================================== ==========================================
Arguments of :func:`~asyncio.client.connect`
............................................
``extra_headers`` → ``additional_headers``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you're adding headers to the handshake request sent by
:func:`~legacy.client.connect` with the ``extra_headers`` argument, you must
rename it to ``additional_headers``.
Arguments of :func:`~asyncio.server.serve`
..........................................
``ws_handler`` → ``handler``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The first argument of :func:`~asyncio.server.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.
.. _process_request:
``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"
# 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, if you wanted to make the connection object available in a
``process_request`` method, you had to write a subclass of
:class:`~legacy.server.WebSocketServerProtocol` and pass it in the
``create_protocol`` argument. 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:
``extra_headers`` → ``process_response``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you're adding headers to the handshake response sent by
:func:`~legacy.server.serve` with the ``extra_headers`` argument, you must write
a ``process_response`` callable instead.
``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:
``select_subprotocol``
~~~~~~~~~~~~~~~~~~~~~~
If you're selecting a subprotocol, you must update your code because 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
:class:`~legacy.server.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`` has to know which subprotocols it may select and under
which conditions.
Furthermore, the default behavior when ``select_subprotocol`` isn't provided
changed in two ways:
1. In the original implementation, a server with a list of subprotocols accepted
to continue without a subprotocol. In the new implementation, a server that
is configured with subprotocols rejects connections that don't support any.
2. In the original implementation, when several subprotocols were available, the
server averaged the client's preferences with its own preferences. In the new
implementation, the server just picks the first subprotocol from its list.
If you had a ``select_subprotocol`` for the sole purpose of rejecting
connections without a subprotocol, you can remove it and keep only the
``subprotocols`` argument.
Arguments of :func:`~asyncio.client.connect` and :func:`~asyncio.server.serve`
..............................................................................
``max_queue``
~~~~~~~~~~~~~
The ``max_queue`` argument of :func:`~asyncio.client.connect` and
:func:`~asyncio.server.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
:meth:`~asyncio.connection.Connection.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
:class:`~asyncio.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 :func:`~asyncio.client.connect` and
:func:`~asyncio.server.serve` defaults to 32 KiB instead of 64 KiB.
``create_protocol`` → ``create_connection``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The keyword argument of :func:`~asyncio.server.serve` for customizing the
creation of the connection object is now called ``create_connection`` instead of
``create_protocol``. It must return a :class:`~asyncio.server.ServerConnection`
instead of a :class:`~legacy.server.WebSocketServerProtocol`.
If you were customizing connection objects, probably you need to redo your
customization. Consider switching to ``process_request`` and
``select_subprotocol`` as their new design removes most use cases for
``create_connection``.
.. _basic-auth:
Performing HTTP Basic Authentication
....................................
.. admonition:: This section applies only to servers.
:class: tip
On the client side, :func:`~asyncio.client.connect` performs HTTP Basic
Authentication automatically when the URI contains credentials.
In the original implementation, the recommended way to add HTTP Basic
Authentication to a server was to set the ``create_protocol`` argument of
:func:`~legacy.server.serve` to a factory function generated by
:func:`~legacy.auth.basic_auth_protocol_factory`::
from websockets.legacy.auth import basic_auth_protocol_factory
from websockets.legacy.server import serve
async with serve(..., create_protocol=basic_auth_protocol_factory(...)):
...
In the new implementation, the :func:`~asyncio.server.basic_auth` function
generates a ``process_request`` coroutine that performs HTTP Basic
Authentication::
from websockets.asyncio.server import basic_auth, serve
async with serve(..., process_request=basic_auth(...)):
...
:func:`~asyncio.server.basic_auth` accepts either hard coded ``credentials`` or
a ``check_credentials`` coroutine as well as an optional ``realm`` just like
:func:`~legacy.auth.basic_auth_protocol_factory`. Furthermore,
``check_credentials`` may be a function instead of a coroutine.
This new API has more obvious semantics. That makes it easier to understand and
also easier to extend.
In the original implementation, overriding ``create_protocol`` changes the type
of connection objects to :class:`~legacy.auth.BasicAuthWebSocketServerProtocol`,
a subclass of :class:`~legacy.server.WebSocketServerProtocol` that performs HTTP
Basic Authentication in its ``process_request`` method.
To customize ``process_request`` further, you had only bad options:
* the ill-defined option: add a ``process_request`` argument to
:func:`~legacy.server.serve`; to tell which one would run first, you had to
experiment or read the code;
* the cumbersome option: subclass
:class:`~legacy.auth.BasicAuthWebSocketServerProtocol`, then pass that
subclass in the ``create_protocol`` argument of
:func:`~legacy.auth.basic_auth_protocol_factory`.
In the new implementation, you just write a ``process_request`` coroutine::
from websockets.asyncio.server import basic_auth, serve
process_basic_auth = basic_auth(...)
async def process_request(connection, request):
... # some logic here
response = await process_basic_auth(connection, request)
if response is not None:
return response
... # more logic here
async with serve(..., process_request=process_request):
...
websockets-14.1/docs/index.rst 0000664 0000000 0000000 00000007140 14715046371 0016413 0 ustar 00root root 0000000 0000000 websockets
==========
|licence| |version| |pyversions| |tests| |docs| |openssf|
.. |licence| image:: https://img.shields.io/pypi/l/websockets.svg
:target: https://pypi.python.org/pypi/websockets
.. |version| image:: https://img.shields.io/pypi/v/websockets.svg
:target: https://pypi.python.org/pypi/websockets
.. |pyversions| image:: https://img.shields.io/pypi/pyversions/websockets.svg
:target: https://pypi.python.org/pypi/websockets
.. |tests| image:: https://img.shields.io/github/checks-status/python-websockets/websockets/main?label=tests
:target: https://github.com/python-websockets/websockets/actions/workflows/tests.yml
.. |docs| image:: https://img.shields.io/readthedocs/websockets.svg
:target: https://websockets.readthedocs.io/
.. |openssf| image:: https://bestpractices.coreinfrastructure.org/projects/6475/badge
:target: https://bestpractices.coreinfrastructure.org/projects/6475
websockets is a library for building WebSocket_ servers and clients in Python
with a focus on correctness, simplicity, robustness, and performance.
.. _WebSocket: https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API
It supports several network I/O and control flow paradigms.
1. The default implementation builds upon :mod:`asyncio`, Python's built-in
asynchronous I/O library. It provides an elegant coroutine-based API. It's
ideal for servers that handle many client connections.
2. The :mod:`threading` implementation is a good alternative for clients,
especially if you aren't familiar with :mod:`asyncio`. It may also be used
for servers that handle few client connections.
3. The `Sans-I/O`_ implementation is designed for integrating in third-party
libraries, typically application servers, in addition being used internally
by websockets.
.. _Sans-I/O: https://sans-io.readthedocs.io/
Refer to the :doc:`feature support matrices ` for the full
list of features provided by each implementation.
.. admonition:: The :mod:`asyncio` implementation was rewritten.
:class: tip
The new implementation in ``websockets.asyncio`` builds upon the Sans-I/O
implementation. It adds features that were impossible to provide in the
original design. It was introduced in version 13.0.
The historical implementation in ``websockets.legacy`` traces its roots to
early versions of websockets. While it's stable and robust, it was deprecated
in version 14.0 and it will be removed by 2030.
The new implementation provides the same features as the historical
implementation, and then some. If you're using the historical implementation,
you should :doc:`ugrade to the new implementation `.
Here's an echo server and corresponding client.
.. tab:: asyncio
.. literalinclude:: ../example/asyncio/echo.py
.. tab:: threading
.. literalinclude:: ../example/sync/echo.py
.. tab:: asyncio
:new-set:
.. literalinclude:: ../example/asyncio/hello.py
.. tab:: threading
.. literalinclude:: ../example/sync/hello.py
Don't 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!
Also, websockets provides an interactive client:
.. code-block:: console
$ python -m websockets ws://localhost:8765/
Connected to ws://localhost:8765/.
> Hello world!
< Hello world!
Connection closed: 1000 (OK).
Do you like it? :doc:`Let's dive in! `
.. toctree::
:hidden:
intro/index
howto/index
faq/index
reference/index
topics/index
project/index
websockets-14.1/docs/intro/ 0000775 0000000 0000000 00000000000 14715046371 0015703 5 ustar 00root root 0000000 0000000 websockets-14.1/docs/intro/index.rst 0000664 0000000 0000000 00000001340 14715046371 0017542 0 ustar 00root root 0000000 0000000 Getting started
===============
.. currentmodule:: websockets
Requirements
------------
websockets requires Python ≥ 3.9.
.. admonition:: Use the most recent Python release
:class: tip
For each minor version (3.x), only the latest bugfix or security release
(3.x.y) is officially supported.
It doesn't have any dependencies.
.. _install:
Installation
------------
Install websockets with:
.. code-block:: console
$ pip install websockets
Wheels are available for all platforms.
Tutorial
--------
Learn how to build an real-time web application with websockets.
.. toctree::
tutorial1
tutorial2
tutorial3
In a hurry?
-----------
Look at the :doc:`quick start guide <../howto/quickstart>`.
websockets-14.1/docs/intro/tutorial1.rst 0000664 0000000 0000000 00000044301 14715046371 0020363 0 ustar 00root root 0000000 0000000 Part 1 - Send & receive
=======================
.. currentmodule:: websockets
In this tutorial, you're going to build a web-based `Connect Four`_ game.
.. _Connect Four: https://en.wikipedia.org/wiki/Connect_Four
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.
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.
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.
Enter `WebSocket `_.
The WebSocket protocol provides two-way communication between a browser and a
server over a persistent connection. That's exactly what you need to exchange
moves between players, via a server.
.. admonition:: This is the first part of the tutorial.
* In this :doc:`first part `, you will create a server and
connect one browser; you can play if you share the same browser.
* In the :doc:`second part `, you will connect a second
browser; you can play from different browsers on a local network.
* In the :doc:`third part `, you will deploy the game to the
web; you can play from any browser connected to the Internet.
Prerequisites
-------------
This tutorial assumes basic knowledge of Python and JavaScript.
If you're comfortable with :doc:`virtual environments `,
you can use one for this tutorial. Else, don't worry: websockets doesn't have
any dependencies; it shouldn't create trouble in the default environment.
If you haven't installed websockets yet, do it now:
.. code-block:: console
$ pip install websockets
Confirm that websockets is installed:
.. code-block:: console
$ python -m websockets --version
.. admonition:: This tutorial is written for websockets |release|.
:class: tip
If you installed another version, you should switch to the corresponding
version of the documentation.
Download the starter kit
------------------------
Create a directory and download these three files:
:download:`connect4.js <../../example/tutorial/start/connect4.js>`,
:download:`connect4.css <../../example/tutorial/start/connect4.css>`,
and :download:`connect4.py <../../example/tutorial/start/connect4.py>`.
The JavaScript module, along with the CSS file, provides a web-based user
interface. Here's its API.
.. js:module:: connect4
.. js:data:: PLAYER1
Color of the first player.
.. js:data:: PLAYER2
Color of the second player.
.. js:function:: createBoard(board)
Draw a board.
:param board: DOM element containing the board; must be initially empty.
.. js:function:: playMove(board, player, column, row)
Play a move.
:param board: DOM element containing the board.
:param player: :js:data:`PLAYER1` or :js:data:`PLAYER2`.
:param column: between ``0`` and ``6``.
:param row: between ``0`` and ``5``.
The Python module provides a class to record moves and tell when a player
wins. Here's its API.
.. module:: connect4
.. data:: PLAYER1
:value: "red"
Color of the first player.
.. data:: PLAYER2
:value: "yellow"
Color of the second player.
.. class:: Connect4
A Connect Four game.
.. method:: play(player, column)
Play a move.
:param player: :data:`~connect4.PLAYER1` or :data:`~connect4.PLAYER2`.
:param column: between ``0`` and ``6``.
:returns: Row where the checker lands, between ``0`` and ``5``.
:raises ValueError: if the move is illegal.
.. attribute:: moves
List of moves played during this game, as ``(player, column, row)``
tuples.
.. attribute:: winner
:data:`~connect4.PLAYER1` or :data:`~connect4.PLAYER2` if they
won; :obj:`None` if the game is still ongoing.
.. currentmodule:: websockets
Bootstrap the web UI
--------------------
Create an ``index.html`` file next to ``connect4.js`` and ``connect4.css``
with this content:
.. literalinclude:: ../../example/tutorial/step1/index.html
:language: html
This HTML page contains an empty ``
`` element where you will draw the
Connect Four board. It loads a ``main.js`` script where you will write all
your JavaScript code.
Create a ``main.js`` file next to ``index.html``. In this script, when the
page loads, draw the board:
.. code-block:: javascript
import { createBoard, playMove } from "./connect4.js";
window.addEventListener("DOMContentLoaded", () => {
// Initialize the UI.
const board = document.querySelector(".board");
createBoard(board);
});
Open a shell, navigate to the directory containing these files, and start an
HTTP server:
.. code-block:: console
$ python -m http.server
Open http://localhost:8000/ in a web browser. The page displays an empty board
with seven columns and six rows. You will play moves in this board later.
Bootstrap the server
--------------------
Create an ``app.py`` file next to ``connect4.py`` with this content:
.. code-block:: python
#!/usr/bin/env python
import asyncio
from websockets.asyncio.server import serve
async def handler(websocket):
while True:
message = await websocket.recv()
print(message)
async def main():
async with serve(handler, "", 8001):
await asyncio.get_running_loop().create_future() # run forever
if __name__ == "__main__":
asyncio.run(main())
The entry point of this program is ``asyncio.run(main())``. It creates an
asyncio event loop, runs the ``main()`` coroutine, and shuts down the loop.
The ``main()`` coroutine calls :func:`~asyncio.server.serve` to start a
websockets server. :func:`~asyncio.server.serve` takes three positional
arguments:
* ``handler`` is a coroutine that manages a connection. When a client
connects, websockets calls ``handler`` with the connection in argument.
When ``handler`` terminates, websockets closes the connection.
* 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.
* The third argument is the port on which the server listens.
Invoking :func:`~asyncio.server.serve` as an asynchronous context manager, in an
``async with`` block, ensures that the server shuts down properly when
terminating the program.
For each connection, the ``handler()`` coroutine runs an infinite loop that
receives messages from the browser and prints them.
Open a shell, navigate to the directory containing ``app.py``, and start the
server:
.. code-block:: console
$ python app.py
This doesn't display anything. Hopefully the WebSocket server is running.
Let's 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' interactive client.
Open another shell and run this command:
.. code-block:: console
$ python -m websockets ws://localhost:8001/
You get a prompt. Type a message and press "Enter". Switch to the shell where
the server is running and check that the server received the message. Good!
Exit the interactive client with Ctrl-C or Ctrl-D.
Now, if you look at the console where you started the server, you can see the
stack trace of an exception:
.. code-block:: pytb
connection handler failed
Traceback (most recent call last):
...
File "app.py", line 22, in handler
message = await websocket.recv()
...
websockets.exceptions.ConnectionClosedOK: received 1000 (OK); then sent 1000 (OK)
Indeed, the server was waiting for the next message with
:meth:`~asyncio.server.ServerConnection.recv` when the client disconnected.
When this happens, websockets raises a :exc:`~exceptions.ConnectionClosedOK`
exception to let you know that you won't receive another message on this
connection.
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
``handler()`` coroutine:
.. code-block:: python
from websockets.exceptions import ConnectionClosedOK
async def handler(websocket):
while True:
try:
message = await websocket.recv()
except ConnectionClosedOK:
break
print(message)
Stop the server with Ctrl-C and start it again:
.. code-block:: console
$ python app.py
.. admonition:: You must restart the WebSocket server when you make changes.
:class: tip
The WebSocket server loads the Python code in ``app.py`` then serves every
WebSocket request with this version of the code. As a consequence,
changes to ``app.py`` aren't visible until you restart the server.
This is unlike the HTTP server that you started earlier with ``python -m
http.server``. For every request, this HTTP server reads the target file
and sends it. That's why changes are immediately visible.
It is possible to :doc:`restart the WebSocket server automatically
<../howto/autoreload>` but this isn't necessary for this tutorial.
Try connecting and disconnecting the interactive client again.
The :exc:`~exceptions.ConnectionClosedOK` exception doesn't appear anymore.
This pattern is so common that websockets provides a shortcut for iterating
over messages received on the connection until the client disconnects:
.. code-block:: python
async def handler(websocket):
async for message in websocket:
print(message)
Restart the server and check with the interactive client that its behavior
didn't change.
At this point, you bootstrapped a web application and a WebSocket server.
Let's connect them.
Transmit from browser to server
-------------------------------
In JavaScript, you open a WebSocket connection as follows:
.. code-block:: javascript
const websocket = new WebSocket("ws://localhost:8001/");
Before you exchange messages with the server, you need to decide their format.
There is no universal convention for this.
Let's use JSON objects with a ``type`` key identifying the type of the event
and the rest of the object containing properties of the event.
Here's an event describing a move in the middle slot of the board:
.. code-block:: javascript
const event = {type: "play", column: 3};
Here's how to serialize this event to JSON and send it to the server:
.. code-block:: javascript
websocket.send(JSON.stringify(event));
Now you have all the building blocks to send moves to the server.
Add this function to ``main.js``:
.. literalinclude:: ../../example/tutorial/step1/main.js
:language: js
:start-at: function sendMoves
:end-before: window.addEventListener
``sendMoves()`` registers a listener for ``click`` events on the board. The
listener figures out which column was clicked, builds a event of type
``"play"``, serializes it, and sends it to the server.
Modify the initialization to open the WebSocket connection and call the
``sendMoves()`` function:
.. code-block:: javascript
window.addEventListener("DOMContentLoaded", () => {
// Initialize the UI.
const board = document.querySelector(".board");
createBoard(board);
// Open the WebSocket connection and register event handlers.
const websocket = new WebSocket("ws://localhost:8001/");
sendMoves(board, websocket);
});
Check that the HTTP server and the WebSocket server are still running. If you
stopped them, here are the commands to start them again:
.. code-block:: console
$ python -m http.server
.. code-block:: console
$ python app.py
Refresh http://localhost:8000/ in your web browser. Click various columns in
the board. The server receives messages with the expected column number.
There isn't any feedback in the board because you haven't implemented that
yet. Let's do it.
Transmit from server to browser
-------------------------------
In JavaScript, you receive WebSocket messages by listening to ``message``
events. Here's how to receive a message from the server and deserialize it
from JSON:
.. code-block:: javascript
websocket.addEventListener("message", ({ data }) => {
const event = JSON.parse(data);
// do something with event
});
You're going to need three types of messages from the server to the browser:
.. code-block:: javascript
{type: "play", player: "red", column: 3, row: 0}
{type: "win", player: "red"}
{type: "error", message: "This slot is full."}
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 ``"play"`` by displaying the move on the board with
the :js:func:`~connect4.playMove` function.
Add this function to ``main.js``:
.. literalinclude:: ../../example/tutorial/step1/main.js
:language: js
:start-at: function showMessage
:end-before: function sendMoves
.. admonition:: Why does ``showMessage`` use ``window.setTimeout``?
:class: hint
When :js:func:`playMove` modifies the state of the board, the browser
renders changes asynchronously. Conversely, ``window.alert()`` runs
synchronously and blocks rendering while the alert is visible.
If you called ``window.alert()`` immediately after :js:func:`playMove`,
the browser could display the alert before rendering the move. You could
get a "Player red wins!" alert without seeing red's last move.
We're using ``window.alert()`` for simplicity in this tutorial. A real
application would display these messages in the user interface instead.
It wouldn't be vulnerable to this problem.
Modify the initialization to call the ``receiveMoves()`` function:
.. literalinclude:: ../../example/tutorial/step1/main.js
:language: js
:start-at: window.addEventListener
At this point, the user interface should receive events properly. Let's test
it by modifying the server to send some events.
Sending an event from Python is quite similar to JavaScript:
.. code-block:: python
event = {"type": "play", "player": "red", "column": 3, "row": 0}
await websocket.send(json.dumps(event))
.. admonition:: Don't forget to serialize the event with :func:`json.dumps`.
:class: tip
Else, websockets raises ``TypeError: data is a dict-like object``.
Modify the ``handler()`` coroutine in ``app.py`` as follows:
.. code-block:: python
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 = {
"type": "play",
"player": player,
"column": column,
"row": row,
}
await websocket.send(json.dumps(event))
await asyncio.sleep(0.5)
event = {
"type": "win",
"player": PLAYER1,
}
await websocket.send(json.dumps(event))
Restart the WebSocket server and refresh http://localhost:8000/ in your web
browser. Seven moves appear at 0.5 second intervals. Then an alert announces
the winner.
Good! Now you know how to communicate both ways.
Once you plug the game engine to process moves, you will have a fully
functional game.
Add the game logic
------------------
In the ``handler()`` coroutine, you're going to initialize a game:
.. code-block:: python
from connect4 import Connect4
async def handler(websocket):
# Initialize a Connect Four game.
game = Connect4()
...
Then, you're going to iterate over incoming messages and take these steps:
* parse an event of type ``"play"``, the only type of event that the user
interface sends;
* play the move in the board with the :meth:`~connect4.Connect4.play` method,
alternating between the two players;
* if :meth:`~connect4.Connect4.play` raises :exc:`ValueError` because the
move is illegal, send an event of type ``"error"``;
* else, send an event of type ``"play"`` to tell the user interface where the
checker lands;
* if the move won the game, send an event of type ``"win"``.
Try to implement this by yourself!
Keep in mind that you must restart the WebSocket server and reload the page in
the browser when you make changes.
When it works, you can play the game from a single browser, with players
taking alternate turns.
.. admonition:: Enable debug logs to see all messages sent and received.
:class: tip
Here's how to enable debug logs:
.. code-block:: python
import logging
logging.basicConfig(format="%(message)s", level=logging.DEBUG)
If you're stuck, a solution is available at the bottom of this document.
Summary
-------
In this first part of the tutorial, you learned how to:
* build and run a WebSocket server in Python with :func:`~asyncio.server.serve`;
* receive a message in a connection handler with
:meth:`~asyncio.server.ServerConnection.recv`;
* send a message in a connection handler with
:meth:`~asyncio.server.ServerConnection.send`;
* iterate over incoming messages with ``async for message in websocket: ...``;
* open a WebSocket connection in JavaScript with the ``WebSocket`` API;
* send messages in a browser with ``WebSocket.send()``;
* receive messages in a browser by listening to ``message`` events;
* design a set of events to be exchanged between the browser and the server.
You can now play a Connect Four game in a browser, communicating over a
WebSocket connection with a server where the game logic resides!
However, the two players share a browser, so the constraint of being in the
same room still applies.
Move on to the :doc:`second part ` of the tutorial to break this
constraint and play from separate browsers.
Solution
--------
.. literalinclude:: ../../example/tutorial/step1/app.py
:caption: app.py
:language: python
:linenos:
.. literalinclude:: ../../example/tutorial/step1/index.html
:caption: index.html
:language: html
:linenos:
.. literalinclude:: ../../example/tutorial/step1/main.js
:caption: main.js
:language: js
:linenos:
websockets-14.1/docs/intro/tutorial2.rst 0000664 0000000 0000000 00000044640 14715046371 0020372 0 ustar 00root root 0000000 0000000 Part 2 - Route & broadcast
==========================
.. currentmodule:: websockets
.. admonition:: This is the second part of the tutorial.
* In the :doc:`first part `, you created a server and
connected one browser; you could play if you shared the same browser.
* In this :doc:`second part `, you will connect a second
browser; you can play from different browsers on a local network.
* In the :doc:`third part `, you will deploy the game to the
web; you can play from any browser connected to the Internet.
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 :class:`~connect4.Connect4` class,
referenced as a local variable in the connection handler coroutine.
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.
Share game state
----------------
As long as you're running a single server process, you can share state by
storing it in a global variable.
.. admonition:: What if you need to scale to multiple server processes?
:class: hint
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.
How can you make two connection handlers agree on which game they're 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.
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't need to treat the two connections differently; you can store both
in the same set.
Let's sketch this in code.
A module-level :class:`dict` enables lookups by identifier:
.. code-block:: python
JOIN = {}
When the first player starts the game, initialize and store it:
.. code-block:: python
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]
When the second player joins the game, look it up:
.. code-block:: python
async def handler(websocket):
...
join_key = ...
# 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)
Notice how we're carefully cleaning up global state with ``try: ...
finally: ...`` blocks. Else, we could leave references to games or
connections in global state, which would cause a memory leak.
In both connection handlers, you have a ``game`` pointing to the same
:class:`~connect4.Connect4` instance, so you can interact with the game,
and a ``connected`` set of connections, so you can send game events to
both players as follows:
.. code-block:: python
async def handler(websocket):
...
for connection in connected:
await connection.send(json.dumps(event))
...
Perhaps you spotted a major piece missing from the puzzle. How does the second
player obtain ``join_key``? Let's design new events to carry this information.
To start a game, the first player sends an ``"init"`` event:
.. code-block:: javascript
{type: "init"}
The connection handler for the first player creates a game as shown above and
responds with:
.. code-block:: javascript
{type: "init", join: ""}
With this information, the user interface of the first player can create a
link to ``http://localhost:8000/?join=``. 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.
To join the game, the second player sends a different ``"init"`` event:
.. code-block:: javascript
{type: "init", join: ""}
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.
Let's dive into the details of implementing this design.
Start a game
------------
We'll start with the initialization sequence for the first player.
In ``main.js``, define a function to send an initialization event when the
WebSocket connection is established, which triggers an ``open`` event:
.. code-block:: javascript
function initGame(websocket) {
websocket.addEventListener("open", () => {
// Send an "init" event for the first player.
const event = { type: "init" };
websocket.send(JSON.stringify(event));
});
}
Update the initialization sequence to call ``initGame()``:
.. literalinclude:: ../../example/tutorial/step2/main.js
:language: js
:start-at: window.addEventListener
In ``app.py``, define a new ``handler`` coroutine — keep a copy of the
previous one to reuse it later:
.. code-block:: python
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'll be used for building a "join" link.
event = {
"type": "init",
"join": join_key,
}
await websocket.send(json.dumps(event))
# Temporary - for testing.
print("first player started game", id(game))
async for message in websocket:
print("first player sent", message)
finally:
del JOIN[join_key]
async def handler(websocket):
# Receive and parse the "init" event from the UI.
message = await websocket.recv()
event = json.loads(message)
assert event["type"] == "init"
# First player starts a new game.
await start(websocket)
In ``index.html``, add an ```` element to display the link to share with
the other player.
.. code-block:: html
In ``main.js``, modify ``receiveMoves()`` to handle the ``"init"`` message and
set the target of that link:
.. code-block:: javascript
switch (event.type) {
case "init":
// Create link for inviting the second player.
document.querySelector(".join").href = "?join=" + event.join;
break;
// ...
}
Restart the WebSocket server and reload http://localhost:8000/ in the browser.
There's a link labeled JOIN below the board with a target that looks like
http://localhost:8000/?join=95ftAaU5DJVP1zvb.
The server logs say ``first player started game ...``. If you click the board,
you see ``"play"`` events. There is no feedback in the UI, though, because
you haven't restored the game logic yet.
Before we get there, let's handle links with a ``join`` query parameter.
Join a game
-----------
We'll now update the initialization sequence to account for the second
player.
In ``main.js``, update ``initGame()`` to send the join key in the ``"init"``
message when it's in the URL:
.. code-block:: javascript
function initGame(websocket) {
websocket.addEventListener("open", () => {
// Send an "init" event according to who is connecting.
const params = new URLSearchParams(window.location.search);
let event = { type: "init" };
if (params.has("join")) {
// Second player joins an existing game.
event.join = params.get("join");
} else {
// First player starts a new game.
}
websocket.send(JSON.stringify(event));
});
}
In ``app.py``, update the ``handler`` coroutine to look for the join key in
the ``"init"`` message, then load that game:
.. code-block:: python
async def error(websocket, message):
event = {
"type": "error",
"message": 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, "Game not found.")
return
# Register to receive moves from this game.
connected.add(websocket)
try:
# Temporary - for testing.
print("second player joined game", id(game))
async for message in websocket:
print("second player sent", message)
finally:
connected.remove(websocket)
async def handler(websocket):
# Receive and parse the "init" event from the UI.
message = await websocket.recv()
event = json.loads(message)
assert event["type"] == "init"
if "join" in event:
# Second player joins an existing game.
await join(websocket, event["join"])
else:
# First player starts a new game.
await start(websocket)
Restart the WebSocket server and reload http://localhost:8000/ in the browser.
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.
.. admonition:: You must start a new game when you restart the server.
:class: tip
Since games are stored in the memory of the Python process, they're lost
when you stop the server.
Whenever you make changes to ``app.py``, you must restart the server,
create a new game in a browser, and join it in another browser.
The server logs say ``first player started game ...`` and ``second player
joined game ...``. The numbers match, proving that the ``game`` local
variable in both connection handlers points to same object in the memory of
the Python process.
Click the board in either browser. The server receives ``"play"`` events from
the corresponding player.
In the initialization sequence, you're routing connections to ``start()`` or
``join()`` depending on the first message received by the server. This is a
common pattern in servers that handle different clients.
.. admonition:: Why not use different URIs for ``start()`` and ``join()``?
:class: hint
Instead of sending an initialization event, you could encode the join key
in the WebSocket URI e.g. ``ws://localhost:8001/join/``. The
WebSocket server would parse ``websocket.path`` and route the connection,
similar to how HTTP servers route requests.
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.
For the purposes of this tutorial, both approaches are equivalent because
the join key comes from an HTTP URL. There isn't much at risk anyway!
Now you can restore the logic for playing moves and you'll have a fully
functional two-player game.
Add the game logic
------------------
Once the initialization is done, the game is symmetrical, so you can write a
single coroutine to process the moves of both players:
.. code-block:: python
async def play(websocket, game, player, connected):
...
With such a coroutine, you can replace the temporary code for testing in
``start()`` by:
.. code-block:: python
await play(websocket, game, PLAYER1, connected)
and in ``join()`` by:
.. code-block:: python
await play(websocket, game, PLAYER2, connected)
The ``play()`` coroutine will reuse much of the code you wrote in the first
part of the tutorial.
Try to implement this by yourself!
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.
When ``play()`` works, you can play the game from two separate browsers,
possibly running on separate computers on the same local network.
A complete solution is available at the bottom of this document.
Watch a game
------------
Let's add one more feature: allow spectators to watch the game.
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:
- declare a ``WATCH`` global variable similar to ``JOIN``;
- 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;
- include the watch key in the ``"init"`` event sent to the first player;
- generate a WATCH link in the UI with a ``watch`` query parameter;
- update the ``initGame()`` function to handle such links;
- update the ``handler()`` coroutine to invoke a ``watch()`` coroutine for
spectators;
- prevent ``sendMoves()`` from sending ``"play"`` events for spectators.
Once the initialization sequence is done, watching a game is as simple as
registering the WebSocket connection in the ``connected`` set in order to
receive game events and doing nothing until the spectator disconnects. You
can wait for a connection to terminate with
:meth:`~asyncio.server.ServerConnection.wait_closed`:
.. code-block:: python
async def watch(websocket, watch_key):
...
connected.add(websocket)
try:
await websocket.wait_closed()
finally:
connected.remove(websocket)
The connection can terminate because the ``receiveMoves()`` function closed it
explicitly after receiving a ``"win"`` event, because the spectator closed
their browser, or because the network failed.
Again, try to implement this by yourself.
When ``watch()`` works, you can invite spectators to watch the game from other
browsers, as long as they're on the same local network.
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 ``connected`` set. Past moves are available in
the :attr:`~connect4.Connect4.moves` attribute of the game.
This feature is included in the solution proposed below.
Broadcast
---------
When you need to send a message to the two players and to all spectators,
you're using this pattern:
.. code-block:: python
async def handler(websocket):
...
for connection in connected:
await connection.send(json.dumps(event))
...
Since this is a very common pattern in WebSocket servers, websockets provides
the :func:`~asyncio.server.broadcast` helper for this purpose:
.. code-block:: python
from websockets.asyncio.server import broadcast
async def handler(websocket):
...
broadcast(connected, json.dumps(event))
...
Calling :func:`~asyncio.server.broadcast` once is more efficient than
calling :meth:`~asyncio.server.ServerConnection.send` in a loop.
However, there's a subtle difference in behavior. Did you notice that there's no
``await`` in the second version? Indeed, :func:`~asyncio.server.broadcast` is a
function, not a coroutine like :meth:`~asyncio.server.ServerConnection.send` or
:meth:`~asyncio.server.ServerConnection.recv`.
It's quite obvious why :meth:`~asyncio.server.ServerConnection.recv`
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.
It's less obvious why :meth:`~asyncio.server.ServerConnection.send` 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.
To avoid this problem, :meth:`~asyncio.server.ServerConnection.send`
waits until the write buffer drains. By slowing down the application as
necessary, this ensures that the server doesn't send data too quickly. This
is called backpressure and it's useful for building robust systems.
That said, when you're sending the same messages to many clients in a loop,
applying backpressure in this way can become counterproductive. When you're
broadcasting, you don't 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's why :func:`~asyncio.server.broadcast` doesn't wait until write buffers
drain and therefore doesn't need to be a coroutine.
For our Connect Four game, there's no difference in practice. The total amount
of data sent on a connection for a game of Connect Four is so small that the
write buffer cannot fill up. As a consequence, backpressure never kicks in.
Summary
-------
In this second part of the tutorial, you learned how to:
* configure a connection by exchanging initialization messages;
* keep track of connections within a single server process;
* wait until a client disconnects in a connection handler;
* broadcast a message to many connections efficiently.
You can now play a Connect Four game from separate browser, communicating over
WebSocket connections with a server that synchronizes the game logic!
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.
Head over to the :doc:`third part ` of the tutorial to deploy the
game to the web and remove this constraint.
Solution
--------
.. literalinclude:: ../../example/tutorial/step2/app.py
:caption: app.py
:language: python
:linenos:
.. literalinclude:: ../../example/tutorial/step2/index.html
:caption: index.html
:language: html
:linenos:
.. literalinclude:: ../../example/tutorial/step2/main.js
:caption: main.js
:language: js
:linenos:
websockets-14.1/docs/intro/tutorial3.rst 0000664 0000000 0000000 00000021644 14715046371 0020372 0 ustar 00root root 0000000 0000000 Part 3 - Deploy to the web
==========================
.. currentmodule:: websockets
.. admonition:: This is the third part of the tutorial.
* In the :doc:`first part `, you created a server and
connected one browser; you could play if you shared the same browser.
* In this :doc:`second part `, you connected a second browser;
you could play from different browsers on a local network.
* In this :doc:`third part `, you will deploy the game to the
web; you can play from any browser connected to the Internet.
In the first and second parts of the tutorial, for local development, you ran
an HTTP server on ``http://localhost:8000/`` with:
.. code-block:: console
$ python -m http.server
and a WebSocket server on ``ws://localhost:8001/`` with:
.. code-block:: console
$ python app.py
Now you want to deploy these servers on the Internet. There's a vast range of
hosting providers to choose from. For the sake of simplicity, we'll rely on:
* GitHub Pages for the HTTP server;
* Heroku for the WebSocket server.
Commit project to git
---------------------
Perhaps you committed your work to git while you were progressing through the
tutorial. If you didn't, now is a good time, because GitHub and Heroku offer
git-based deployment workflows.
Initialize a git repository:
.. code-block:: console
$ git init -b main
Initialized empty Git repository in websockets-tutorial/.git/
$ git commit --allow-empty -m "Initial commit."
[main (root-commit) ...] Initial commit.
Add all files and commit:
.. code-block:: console
$ git add .
$ git commit -m "Initial implementation of Connect Four game."
[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
Prepare the WebSocket server
----------------------------
Before you deploy the server, you must adapt it to meet requirements of
Heroku's runtime. This involves two small changes:
1. Heroku expects the server to `listen on a specific port`_, provided in the
``$PORT`` environment variable.
2. Heroku sends a ``SIGTERM`` signal when `shutting down a dyno`_, which
should trigger a clean exit.
.. _listen on a specific port: https://devcenter.heroku.com/articles/preparing-a-codebase-for-heroku-deployment#4-listen-on-the-correct-port
.. _shutting down a dyno: https://devcenter.heroku.com/articles/dynos#shutdown
Adapt the ``main()`` coroutine accordingly:
.. code-block:: python
import os
import signal
.. literalinclude:: ../../example/tutorial/step3/app.py
:pyobject: main
To catch the ``SIGTERM`` signal, ``main()`` creates a :class:`~asyncio.Future`
called ``stop`` and registers a signal handler that sets the result of this
future. The value of the future doesn't matter; it's only for waiting for
``SIGTERM``.
Then, by using :func:`~asyncio.server.serve` as a context manager and exiting
the context when ``stop`` has a result, ``main()`` ensures that the server
closes connections cleanly and exits on ``SIGTERM``.
The app is now fully compatible with Heroku.
Deploy the WebSocket server
---------------------------
Create a ``requirements.txt`` file with this content to install ``websockets``
when building the image:
.. literalinclude:: ../../example/tutorial/step3/requirements.txt
:language: text
.. admonition:: Heroku treats ``requirements.txt`` as a signal to `detect a Python app`_.
:class: tip
That's why you don't need to declare that you need a Python runtime.
.. _detect a Python app: https://devcenter.heroku.com/articles/python-support#recognizing-a-python-app
Create a ``Procfile`` file with this content to configure the command for
running the server:
.. literalinclude:: ../../example/tutorial/step3/Procfile
:language: text
Commit your changes:
.. code-block:: console
$ git add .
$ git commit -m "Deploy to Heroku."
[main ...] Deploy to Heroku.
3 files changed, 12 insertions(+), 2 deletions(-)
create mode 100644 Procfile
create mode 100644 requirements.txt
Follow the `set-up instructions`_ to install the Heroku CLI and to log in, if
you haven't done that yet.
.. _set-up instructions: https://devcenter.heroku.com/articles/getting-started-with-python#set-up
Create a Heroku app. You must choose a unique name and replace
``websockets-tutorial`` by this name in the following command:
.. code-block:: console
$ heroku create websockets-tutorial
Creating ⬢ websockets-tutorial... done
https://websockets-tutorial.herokuapp.com/ | https://git.heroku.com/websockets-tutorial.git
If you reuse a name that someone else already uses, you will receive this
error; if this happens, try another name:
.. code-block:: console
$ heroku create websockets-tutorial
Creating ⬢ websockets-tutorial... !
▸ Name websockets-tutorial is already taken
Deploy by pushing the code to Heroku:
.. code-block:: console
$ 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
You can test the WebSocket server with the interactive client exactly like you
did in the first part of the tutorial. Replace ``websockets-tutorial`` by the
name of your app in the following command:
.. code-block:: console
$ python -m websockets wss://websockets-tutorial.herokuapp.com/
Connected to wss://websockets-tutorial.herokuapp.com/.
> {"type": "init"}
< {"type": "init", "join": "54ICxFae_Ip7TJE2", "watch": "634w44TblL5Dbd9a"}
Connection closed: 1000 (OK).
It works!
Prepare the web application
---------------------------
Before you deploy the web application, perhaps you're wondering how it will
locate the WebSocket server? Indeed, at this point, its address is hard-coded
in ``main.js``:
.. code-block:: javascript
const websocket = new WebSocket("ws://localhost:8001/");
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.
Add this function to ``main.js``; replace ``python-websockets`` by your GitHub
username and ``websockets-tutorial`` by the name of your app on Heroku:
.. literalinclude:: ../../example/tutorial/step3/main.js
:language: js
:start-at: function getWebSocketServer
:end-before: function initGame
Then, update the initialization to connect to this address instead:
.. code-block:: javascript
const websocket = new WebSocket(getWebSocketServer());
Commit your changes:
.. code-block:: console
$ git add .
$ git commit -m "Configure WebSocket server address."
[main ...] Configure WebSocket server address.
1 file changed, 11 insertions(+), 1 deletion(-)
Deploy the web application
--------------------------
Go to GitHub and create a new repository called ``websockets-tutorial``.
Push your code to this repository. You must replace ``python-websockets`` by
your GitHub username in the following command:
.. code-block:: console
$ 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 'main' set up to track remote branch 'main' from 'origin'.
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.
Follow the link and start a game!
Summary
-------
In this third part of the tutorial, you learned how to deploy a WebSocket
application with Heroku.
You can start a Connect Four game, send the JOIN link to a friend, and play
over the Internet!
Congratulations for completing the tutorial. Enjoy building real-time web
applications with websockets!
Solution
--------
.. literalinclude:: ../../example/tutorial/step3/app.py
:caption: app.py
:language: python
:linenos:
.. literalinclude:: ../../example/tutorial/step3/index.html
:caption: index.html
:language: html
:linenos:
.. literalinclude:: ../../example/tutorial/step3/main.js
:caption: main.js
:language: js
:linenos:
.. literalinclude:: ../../example/tutorial/step3/Procfile
:caption: Procfile
:language: text
:linenos:
.. literalinclude:: ../../example/tutorial/step3/requirements.txt
:caption: requirements.txt
:language: text
:linenos:
websockets-14.1/docs/make.bat 0000664 0000000 0000000 00000001433 14715046371 0016156 0 ustar 00root root 0000000 0000000 @ECHO OFF
pushd %~dp0
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=.
set BUILDDIR=_build
if "%1" == "" goto help
%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.http://sphinx-doc.org/
exit /b 1
)
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
goto end
:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
:end
popd
websockets-14.1/docs/project/ 0000775 0000000 0000000 00000000000 14715046371 0016216 5 ustar 00root root 0000000 0000000 websockets-14.1/docs/project/changelog.rst 0000664 0000000 0000000 00000120200 14715046371 0020672 0 ustar 00root root 0000000 0000000 Changelog
=========
.. currentmodule:: websockets
.. _backwards-compatibility policy:
Backwards-compatibility policy
------------------------------
websockets is intended for production use. Therefore, stability is a goal.
websockets also aims at providing the best API for WebSocket in Python.
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.
When possible with reasonable effort, we preserve backwards-compatibility for
five years after the release that introduced the change.
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.
Only documented APIs are public. Undocumented, private APIs may change without
notice.
.. _14.1:
14.1
----
*November 13, 2024*
Improvements
............
* Supported ``max_queue=None`` in the :mod:`asyncio` and :mod:`threading`
implementations for consistency with the legacy implementation, even though
this is never a good idea.
* Added ``close_code`` and ``close_reason`` attributes in the :mod:`asyncio` and
:mod:`threading` implementations for consistency with the legacy
implementation.
Bug fixes
.........
* Once the connection is closed, messages previously received and buffered can
be read in the :mod:`asyncio` and :mod:`threading` implementations, just like
in the legacy implementation.
.. _14.0:
14.0
----
*November 9, 2024*
Backwards-incompatible changes
..............................
.. admonition:: websockets 14.0 requires Python ≥ 3.9.
:class: tip
websockets 13.1 is the last version supporting Python 3.8.
.. admonition:: The new :mod:`asyncio` implementation is now the default.
:class: danger
The following aliases in the ``websockets`` package were switched to the new
:mod:`asyncio` implementation::
from websockets import connect, unix_connext
from websockets import broadcast, serve, unix_serve
If you're using any of them, then you must follow the :doc:`upgrade guide
<../howto/upgrade>` immediately.
Alternatively, you may stick to the legacy :mod:`asyncio` implementation for
now by importing it explicitly::
from websockets.legacy.client import connect, unix_connect
from websockets.legacy.server import broadcast, serve, unix_serve
.. admonition:: The legacy :mod:`asyncio` implementation is now deprecated.
:class: caution
The :doc:`upgrade guide <../howto/upgrade>` provides complete instructions
to migrate your application.
Aliases for deprecated API were removed from ``websockets.__all__``, meaning
that they cannot be imported with ``from websockets import *`` anymore.
.. admonition:: Several API raise :exc:`ValueError` instead of :exc:`TypeError`
on invalid arguments.
:class: note
:func:`~asyncio.client.connect`, :func:`~asyncio.client.unix_connect`, and
:func:`~asyncio.server.basic_auth` in the :mod:`asyncio` implementation as
well as :func:`~sync.client.connect`, :func:`~sync.client.unix_connect`,
:func:`~sync.server.serve`, :func:`~sync.server.unix_serve`, and
:func:`~sync.server.basic_auth` in the :mod:`threading` implementation now
raise :exc:`ValueError` when a required argument isn't provided or an
argument that is incompatible with others is provided.
.. admonition:: :attr:`Frame.data ` is now a bytes-like object.
:class: note
In addition to :class:`bytes`, it may be a :class:`bytearray` or a
:class:`memoryview`. If you wrote an :class:`~extensions.Extension` that
relies on methods not provided by these types, you must update your code.
.. admonition:: The signature of :exc:`~exceptions.PayloadTooBig` changed.
:class: note
If you wrote an extension that raises :exc:`~exceptions.PayloadTooBig` in
:meth:`~extensions.Extension.decode`, for example, you must replace
``PayloadTooBig(f"over size limit ({size} > {max_size} bytes)")`` with
``PayloadTooBig(size, max_size)``.
New features
............
* Added an option to receive text frames as :class:`bytes`, without decoding,
in the :mod:`threading` implementation; also binary frames as :class:`str`.
* Added an option to send :class:`bytes` in a text frame in the :mod:`asyncio`
and :mod:`threading` implementations; also :class:`str` in a binary frame.
Improvements
............
* The :mod:`threading` implementation receives messages faster.
* Sending or receiving large compressed messages is now faster.
* Errors when a fragmented message is too large are clearer.
* Log messages at the :data:`~logging.WARNING` and :data:`~logging.INFO` levels
no longer include stack traces.
Bug fixes
.........
* Clients no longer crash when the server rejects the opening handshake and the
HTTP response doesn't Include a ``Content-Length`` header.
* Returning an HTTP response in ``process_request`` or ``process_response``
doesn't generate a log message at the :data:`~logging.ERROR` level anymore.
* Connections are closed with code 1007 (invalid data) when receiving invalid
UTF-8 in a text frame.
.. _13.1:
13.1
----
*September 21, 2024*
Backwards-incompatible changes
..............................
.. admonition:: The ``code`` and ``reason`` attributes of
:exc:`~exceptions.ConnectionClosed` are deprecated.
:class: note
They were removed from the documentation in version 10.0, due to their
spec-compliant but counter-intuitive behavior, but they were kept in
the code for backwards compatibility. They're now formally deprecated.
New features
............
* Added support for reconnecting automatically by using
:func:`~asyncio.client.connect` as an asynchronous iterator to the new
:mod:`asyncio` implementation.
* :func:`~asyncio.client.connect` now follows redirects in the new
:mod:`asyncio` implementation.
* Added HTTP Basic Auth to the new :mod:`asyncio` and :mod:`threading`
implementations of servers.
* Made the set of active connections available in the :attr:`Server.connections
` property.
Improvements
............
* Improved reporting of errors during the opening handshake.
* Raised :exc:`~exceptions.ConcurrencyError` on unsupported concurrent calls.
Previously, :exc:`RuntimeError` was raised. For backwards compatibility,
:exc:`~exceptions.ConcurrencyError` is a subclass of :exc:`RuntimeError`.
Bug fixes
.........
* The new :mod:`asyncio` and :mod:`threading` implementations of servers don't
start the connection handler anymore when ``process_request`` or
``process_response`` returns an HTTP response.
* Fixed a bug in the :mod:`threading` implementation that could lead to
incorrect error reporting when closing a connection while
:meth:`~sync.connection.Connection.recv` is running.
13.0.1
------
*August 28, 2024*
Bug fixes
.........
* Restored the C extension in the source distribution.
.. _13.0:
13.0
----
*August 20, 2024*
Backwards-incompatible changes
..............................
.. admonition:: Receiving the request path in the second parameter of connection
handlers is deprecated.
:class: note
If you implemented the connection handler of a server as::
async def handler(request, path):
...
You should switch to the pattern recommended since version 10.1::
async def handler(request):
path = request.path # only if handler() uses the path argument
...
.. admonition:: The ``ssl_context`` argument of :func:`~sync.client.connect`
and :func:`~sync.server.serve` in the :mod:`threading` implementation is
renamed to ``ssl``.
:class: note
This aligns the API of the :mod:`threading` implementation with the
:mod:`asyncio` implementation.
For backwards compatibility, ``ssl_context`` is still supported.
.. admonition:: The ``WebSocketServer`` class in the :mod:`threading`
implementation is renamed to :class:`~sync.server.Server`.
:class: note
This change should be transparent because this class shouldn't be
instantiated directly; :func:`~sync.server.serve` returns an instance.
Regardless, an alias provides backwards compatibility.
New features
............
.. admonition:: websockets 11.0 introduces a new :mod:`asyncio` implementation.
:class: important
This new implementation is intended to be a drop-in replacement for the
current implementation. It will become the default in a future release.
Please try it and report any issue that you encounter! The :doc:`upgrade
guide <../howto/upgrade>` explains everything you need to know about the
upgrade process.
* Validated compatibility with Python 3.12 and 3.13.
* Added an option to receive text frames as :class:`bytes`, without decoding,
in the :mod:`asyncio` implementation; also binary frames as :class:`str`.
* Added :doc:`environment variables <../reference/variables>` to configure debug
logs, the ``Server`` and ``User-Agent`` headers, as well as security limits.
If you were monkey-patching constants, be aware that they were renamed, which
will break your configuration. You must switch to the environment variables.
Improvements
............
* The error message in server logs when a header is too long is more explicit.
Bug fixes
.........
* Fixed a bug in the :mod:`threading` implementation that could prevent the
program from exiting when a connection wasn't closed properly.
* Redirecting from a ``ws://`` URI to a ``wss://`` URI now works.
* ``broadcast(raise_exceptions=True)`` no longer crashes when there isn't any
exception.
.. _12.0:
12.0
----
*October 21, 2023*
Backwards-incompatible changes
..............................
.. admonition:: websockets 12.0 requires Python ≥ 3.8.
:class: tip
websockets 11.0 is the last version supporting Python 3.7.
Improvements
............
* Made convenience imports from ``websockets`` compatible with static code
analysis tools such as auto-completion in an IDE or type checking with mypy_.
.. _mypy: https://github.com/python/mypy
* Accepted a plain :class:`int` where an :class:`~http.HTTPStatus` is expected.
* Added :class:`~frames.CloseCode`.
11.0.3
------
*May 7, 2023*
Bug fixes
.........
* Fixed the :mod:`threading` implementation of servers on Windows.
11.0.2
------
*April 18, 2023*
Bug fixes
.........
* Fixed a deadlock in the :mod:`threading` implementation when closing a
connection without reading all messages.
11.0.1
------
*April 6, 2023*
Bug fixes
.........
* Restored the C extension in the source distribution.
.. _11.0:
11.0
----
*April 2, 2023*
Backwards-incompatible changes
..............................
.. admonition:: The Sans-I/O implementation was moved.
:class: caution
Aliases provide compatibility for all previously public APIs according to
the `backwards-compatibility policy`_.
* The ``connection`` module was renamed to ``protocol``.
* The ``connection.Connection``, ``server.ServerConnection``, and
``client.ClientConnection`` classes were renamed to ``protocol.Protocol``,
``server.ServerProtocol``, and ``client.ClientProtocol``.
.. admonition:: Sans-I/O protocol constructors now use keyword-only arguments.
:class: caution
If you instantiate :class:`~server.ServerProtocol` or
:class:`~client.ClientProtocol` directly, make sure you are using keyword
arguments.
.. admonition:: Closing a connection without an empty close frame is OK.
:class: note
Receiving an empty close frame now results in
:exc:`~exceptions.ConnectionClosedOK` instead of
:exc:`~exceptions.ConnectionClosedError`.
As a consequence, calling ``WebSocket.close()`` without arguments in a
browser isn't reported as an error anymore.
.. admonition:: :func:`~legacy.server.serve` times out on the opening handshake
after 10 seconds by default.
:class: note
You can adjust the timeout with the ``open_timeout`` parameter. Set it to
:obj:`None` to disable the timeout entirely.
New features
............
.. admonition:: websockets 11.0 introduces a :mod:`threading` implementation.
:class: important
It may be more convenient if you don't need to manage many connections and
you're more comfortable with :mod:`threading` than :mod:`asyncio`.
It is particularly suited to client applications that establish only one
connection. It may be used for servers handling few connections.
See :func:`websockets.sync.client.connect` and
:func:`websockets.sync.server.serve` for details.
* Added ``open_timeout`` to :func:`~legacy.server.serve`.
* Made it possible to close a server without closing existing connections.
* Added :attr:`~server.ServerProtocol.select_subprotocol` to customize
negotiation of subprotocols in the Sans-I/O layer.
Improvements
............
* Added platform-independent wheels.
* Improved error handling in :func:`~legacy.server.broadcast`.
* Set ``server_hostname`` automatically on TLS connections when providing a
``sock`` argument to :func:`~sync.client.connect`.
.. _10.4:
10.4
----
*October 25, 2022*
New features
............
* Validated compatibility with Python 3.11.
* Added the :attr:`~legacy.protocol.WebSocketCommonProtocol.latency` property to
protocols.
* Changed :attr:`~legacy.protocol.WebSocketCommonProtocol.ping` to return the
latency of the connection.
* Supported overriding or removing the ``User-Agent`` header in clients and the
``Server`` header in servers.
* Added deployment guides for more Platform as a Service providers.
Improvements
............
* Improved FAQ.
.. _10.3:
10.3
----
*April 17, 2022*
Backwards-incompatible changes
..............................
.. admonition:: The ``exception`` attribute of :class:`~http11.Request` and
:class:`~http11.Response` is deprecated.
:class: note
Use the ``handshake_exc`` attribute of :class:`~server.ServerProtocol` and
:class:`~client.ClientProtocol` instead.
See :doc:`../howto/sansio` for details.
Improvements
............
* Reduced noise in logs when :mod:`ssl` or :mod:`zlib` raise exceptions.
.. _10.2:
10.2
----
*February 21, 2022*
Improvements
............
* Made compression negotiation more lax for compatibility with Firefox.
* Improved FAQ and quick start guide.
Bug fixes
.........
* Fixed backwards-incompatibility in 10.1 for connection handlers created with
:func:`functools.partial`.
* Avoided leaking open sockets when :func:`~legacy.client.connect` is canceled.
.. _10.1:
10.1
----
*November 14, 2021*
New features
............
* Added a tutorial.
* Made the second parameter of connection handlers optional. The request path is
available in the :attr:`~legacy.protocol.WebSocketCommonProtocol.path`
attribute of the first argument.
If you implemented the connection handler of a server as::
async def handler(request, path):
...
You should replace it with::
async def handler(request):
path = request.path # only if handler() uses the path argument
...
* Added ``python -m websockets --version``.
Improvements
............
* Added wheels for Python 3.10, PyPy 3.7, and for more platforms.
* Reverted optimization of default compression settings for clients, mainly to
avoid triggering bugs in poorly implemented servers like `AWS API Gateway`_.
.. _AWS API Gateway: https://github.com/python-websockets/websockets/issues/1065
* Mirrored the entire :class:`~asyncio.Server` API in
:class:`~legacy.server.WebSocketServer`.
* Improved performance for large messages on ARM processors.
* Documented how to auto-reload on code changes in development.
Bug fixes
.........
* Avoided half-closing TCP connections that are already closed.
.. _10.0:
10.0
----
*September 9, 2021*
Backwards-incompatible changes
..............................
.. admonition:: websockets 10.0 requires Python ≥ 3.7.
:class: tip
websockets 9.1 is the last version supporting Python 3.6.
.. admonition:: The ``loop`` parameter is deprecated from all APIs.
:class: caution
This reflects a decision made in Python 3.8. See the release notes of
Python 3.10 for details.
The ``loop`` parameter is also removed
from :class:`~legacy.server.WebSocketServer`. This should be transparent.
.. admonition:: :func:`~legacy.client.connect` times out after 10 seconds by default.
:class: note
You can adjust the timeout with the ``open_timeout`` parameter. Set it to
:obj:`None` to disable the timeout entirely.
.. admonition:: The ``legacy_recv`` option is deprecated.
:class: note
See the release notes of websockets 3.0 for details.
.. admonition:: The signature of :exc:`~exceptions.ConnectionClosed` changed.
:class: note
If you raise :exc:`~exceptions.ConnectionClosed` or a subclass, rather
than catch them when websockets raises them, you must change your code.
.. admonition:: A ``msg`` parameter was added to :exc:`~exceptions.InvalidURI`.
:class: note
If you raise :exc:`~exceptions.InvalidURI`, rather than catch it when
websockets raises it, you must change your code.
New features
............
.. admonition:: websockets 10.0 introduces a `Sans-I/O API
`_ for easier integration
in third-party libraries.
:class: important
If you're integrating websockets in a library, rather than just using it,
look at the :doc:`Sans-I/O integration guide <../howto/sansio>`.
* Added compatibility with Python 3.10.
* Added :func:`~legacy.server.broadcast` to send a message to many clients.
* Added support for reconnecting automatically by using
:func:`~legacy.client.connect` as an asynchronous iterator.
* Added ``open_timeout`` to :func:`~legacy.client.connect`.
* Documented how to integrate with `Django `_.
* Documented how to deploy websockets in production, with several options.
* Documented how to authenticate connections.
* Documented how to broadcast messages to many connections.
Improvements
............
* Improved logging. See the :doc:`logging guide <../topics/logging>`.
* Optimized default compression settings to reduce memory usage.
* Optimized processing of client-to-server messages when the C extension isn't
available.
* Supported relative redirects in :func:`~legacy.client.connect`.
* Handled TCP connection drops during the opening handshake.
* Made it easier to customize authentication with
:meth:`~legacy.auth.BasicAuthWebSocketServerProtocol.check_credentials`.
* Provided additional information in :exc:`~exceptions.ConnectionClosed`
exceptions.
* Clarified several exceptions or log messages.
* Restructured documentation.
* Improved API documentation.
* Extended FAQ.
Bug fixes
.........
* Avoided a crash when receiving a ping while the connection is closing.
.. _9.1:
9.1
---
*May 27, 2021*
Security fix
............
.. admonition:: websockets 9.1 fixes a security issue introduced in 8.0.
:class: important
Version 8.0 was vulnerable to timing attacks on HTTP Basic Auth passwords
(`CVE-2021-33880`_).
.. _CVE-2021-33880: https://nvd.nist.gov/vuln/detail/CVE-2021-33880
9.0.2
-----
*May 15, 2021*
Bug fixes
.........
* Restored compatibility of ``python -m websockets`` with Python < 3.9.
* Restored compatibility with mypy.
9.0.1
-----
*May 2, 2021*
Bug fixes
.........
* Fixed issues with the packaging of the 9.0 release.
.. _9.0:
9.0
---
*May 1, 2021*
Backwards-incompatible changes
..............................
.. admonition:: Several modules are moved or deprecated.
:class: caution
Aliases provide compatibility for all previously public APIs according to
the `backwards-compatibility policy`_
* :class:`~datastructures.Headers` and
:exc:`~datastructures.MultipleValuesError` are moved from
``websockets.http`` to :mod:`websockets.datastructures`. If you're using
them, you should adjust the import path.
* The ``client``, ``server``, ``protocol``, and ``auth`` modules were
moved from the ``websockets`` package to a ``websockets.legacy``
sub-package. Despite the name, they're still fully supported.
* The ``framing``, ``handshake``, ``headers``, ``http``, and ``uri``
modules in the ``websockets`` package are deprecated. These modules
provided low-level APIs for reuse by other projects, but they didn't
reach that goal. Keeping these APIs public makes it more difficult to
improve websockets.
These changes pave the path for a refactoring that should be a transparent
upgrade for most uses and facilitate integration by other projects.
.. admonition:: Convenience imports from ``websockets`` are performed lazily.
:class: note
While Python supports this, tools relying on static code analysis don't.
This breaks auto-completion in an IDE or type checking with mypy_.
.. _mypy: https://github.com/python/mypy
If you depend on such tools, use the real import paths, which can be found
in the API documentation, for example::
from websockets.client import connect
from websockets.server import serve
New features
............
* Added compatibility with Python 3.9.
Improvements
............
* Added support for IRIs in addition to URIs.
* Added close codes 1012, 1013, and 1014.
* Raised an error when passing a :class:`dict` to
:meth:`~legacy.protocol.WebSocketCommonProtocol.send`.
* Improved error reporting.
Bug fixes
.........
* Fixed sending fragmented, compressed messages.
* Fixed ``Host`` header sent when connecting to an IPv6 address.
* Fixed creating a client or a server with an existing Unix socket.
* Aligned maximum cookie size with popular web browsers.
* Ensured cancellation always propagates, even on Python versions where
:exc:`~asyncio.CancelledError` inherits from :exc:`Exception`.
.. _8.1:
8.1
---
*November 1, 2019*
New features
............
* Added compatibility with Python 3.8.
8.0.2
-----
*July 31, 2019*
Bug fixes
.........
* Restored the ability to pass a socket with the ``sock`` parameter of
:func:`~legacy.server.serve`.
* Removed an incorrect assertion when a connection drops.
8.0.1
-----
*July 21, 2019*
Bug fixes
.........
* Restored the ability to import ``WebSocketProtocolError`` from
``websockets``.
.. _8.0:
8.0
---
*July 7, 2019*
Backwards-incompatible changes
..............................
.. admonition:: websockets 8.0 requires Python ≥ 3.6.
:class: tip
websockets 7.0 is the last version supporting Python 3.4 and 3.5.
.. admonition:: ``process_request`` is now expected to be a coroutine.
:class: note
If you're passing a ``process_request`` argument to
:func:`~legacy.server.serve` or
:class:`~legacy.server.WebSocketServerProtocol`, or if you're overriding
:meth:`~legacy.server.WebSocketServerProtocol.process_request` in a
subclass, define it with ``async def`` instead of ``def``. Previously, both
were supported.
For backwards compatibility, functions are still accepted, but mixing
functions and coroutines won't work in some inheritance scenarios.
.. admonition:: ``max_queue`` must be :obj:`None` to disable the limit.
:class: note
If you were setting ``max_queue=0`` to make the queue of incoming messages
unbounded, change it to ``max_queue=None``.
.. admonition:: The ``host``, ``port``, and ``secure`` attributes
of :class:`~legacy.protocol.WebSocketCommonProtocol` are deprecated.
:class: note
Use :attr:`~legacy.protocol.WebSocketCommonProtocol.local_address` in
servers and
:attr:`~legacy.protocol.WebSocketCommonProtocol.remote_address` in clients
instead of ``host`` and ``port``.
.. admonition:: ``WebSocketProtocolError`` is renamed
to :exc:`~exceptions.ProtocolError`.
:class: note
An alias provides backwards compatibility.
.. admonition:: ``read_response()`` now returns the reason phrase.
:class: note
If you're using this low-level API, you must change your code.
New features
............
* Added :func:`~legacy.auth.basic_auth_protocol_factory` to enforce HTTP Basic
Auth on the server side.
* :func:`~legacy.client.connect` handles redirects from the server during the
handshake.
* :func:`~legacy.client.connect` supports overriding ``host`` and ``port``.
* Added :func:`~legacy.client.unix_connect` for connecting to Unix sockets.
* Added support for asynchronous generators
in :meth:`~legacy.protocol.WebSocketCommonProtocol.send`
to generate fragmented messages incrementally.
* Enabled readline in the interactive client.
* Added type hints (:pep:`484`).
* Added a FAQ to the documentation.
* Added documentation for extensions.
* Documented how to optimize memory usage.
Improvements
............
* :meth:`~legacy.protocol.WebSocketCommonProtocol.send`,
:meth:`~legacy.protocol.WebSocketCommonProtocol.ping`, and
:meth:`~legacy.protocol.WebSocketCommonProtocol.pong` support bytes-like
types :class:`bytearray` and :class:`memoryview` in addition to
:class:`bytes`.
* Added :exc:`~exceptions.ConnectionClosedOK` and
:exc:`~exceptions.ConnectionClosedError` subclasses of
:exc:`~exceptions.ConnectionClosed` to tell apart normal connection
termination from errors.
* Changed :meth:`WebSocketServer.close() `
to perform a proper closing handshake instead of failing the connection.
* Improved error messages when HTTP parsing fails.
* Improved API documentation.
Bug fixes
.........
* Prevented spurious log messages about :exc:`~exceptions.ConnectionClosed`
exceptions in keepalive ping task. If you were using ``ping_timeout=None``
as a workaround, you can remove it.
* Avoided a crash when a ``extra_headers`` callable returns :obj:`None`.
.. _7.0:
7.0
---
*November 1, 2018*
Backwards-incompatible changes
..............................
.. admonition:: Keepalive is enabled by default.
:class: important
websockets now sends Ping frames at regular intervals and closes the
connection if it doesn't receive a matching Pong frame.
See :class:`~legacy.protocol.WebSocketCommonProtocol` for details.
.. admonition:: Termination of connections by :meth:`WebSocketServer.close()
` changes.
:class: caution
Previously, connections handlers were canceled. Now, connections are
closed with close code 1001 (going away).
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
:exc:`~asyncio.CancelledError` in connection handlers.
You can restore the previous behavior by adding the following line at the
beginning of connection handlers::
def handler(websocket, path):
closed = asyncio.ensure_future(websocket.wait_closed())
closed.add_done_callback(lambda task: task.cancel())
.. admonition:: Calling :meth:`~legacy.protocol.WebSocketCommonProtocol.recv`
concurrently raises a :exc:`RuntimeError`.
:class: note
Concurrent calls lead to non-deterministic behavior because there are no
guarantees about which coroutine will receive which message.
.. admonition:: The ``timeout`` argument of :func:`~legacy.server.serve`
and :func:`~legacy.client.connect` is renamed to ``close_timeout`` .
:class: note
This prevents confusion with ``ping_timeout``.
For backwards compatibility, ``timeout`` is still supported.
.. admonition:: The ``origins`` argument of :func:`~legacy.server.serve`
changes.
:class: note
Include :obj:`None` in the list rather than ``''`` to allow requests that
don't contain an Origin header.
.. admonition:: Pending pings aren't canceled when the connection is closed.
:class: note
A ping — as in ``ping = await websocket.ping()`` — for which no pong was
received yet used to be canceled when the connection is closed, so that
``await ping`` raised :exc:`~asyncio.CancelledError`.
Now ``await ping`` raises :exc:`~exceptions.ConnectionClosed` like other
public APIs.
New features
............
* Added ``process_request`` and ``select_subprotocol`` arguments to
:func:`~legacy.server.serve` and
:class:`~legacy.server.WebSocketServerProtocol` to facilitate customization of
:meth:`~legacy.server.WebSocketServerProtocol.process_request` and
:meth:`~legacy.server.WebSocketServerProtocol.select_subprotocol`.
* Added support for sending fragmented messages.
* Added the :meth:`~legacy.protocol.WebSocketCommonProtocol.wait_closed`
method to protocols.
* Added an interactive client: ``python -m websockets ``.
Improvements
............
* Improved handling of multiple HTTP headers with the same name.
* Improved error messages when a required HTTP header is missing.
Bug fixes
.........
* Fixed a data loss bug in
:meth:`~legacy.protocol.WebSocketCommonProtocol.recv`:
canceling it at the wrong time could result in messages being dropped.
.. _6.0:
6.0
---
*July 16, 2018*
Backwards-incompatible changes
..............................
.. admonition:: The :class:`~datastructures.Headers` class is introduced and
several APIs are updated to use it.
:class: caution
* The ``request_headers`` argument of
:meth:`~legacy.server.WebSocketServerProtocol.process_request` is now a
:class:`~datastructures.Headers` instead of an
``http.client.HTTPMessage``.
* The ``request_headers`` and ``response_headers`` attributes of
:class:`~legacy.protocol.WebSocketCommonProtocol` are now
:class:`~datastructures.Headers` instead of ``http.client.HTTPMessage``.
* The ``raw_request_headers`` and ``raw_response_headers`` attributes of
:class:`~legacy.protocol.WebSocketCommonProtocol` are removed. Use
:meth:`~datastructures.Headers.raw_items` instead.
* Functions defined in the ``handshake`` module now receive
:class:`~datastructures.Headers` in argument instead of ``get_header``
or ``set_header`` functions. This affects libraries that rely on
low-level APIs.
* Functions defined in the ``http`` module now return HTTP headers as
:class:`~datastructures.Headers` instead of lists of ``(name, value)``
pairs.
Since :class:`~datastructures.Headers` and ``http.client.HTTPMessage``
provide similar APIs, much of the code dealing with HTTP headers won't
require changes.
New features
............
* Added compatibility with Python 3.7.
5.0.1
-----
*May 24, 2018*
Bug fixes
.........
* Fixed a regression in 5.0 that broke some invocations of
:func:`~legacy.server.serve` and :func:`~legacy.client.connect`.
.. _5.0:
5.0
---
*May 22, 2018*
Security fix
............
.. admonition:: websockets 5.0 fixes a security issue introduced in 4.0.
:class: important
Version 4.0 was vulnerable to denial of service by memory exhaustion
because it didn't enforce ``max_size`` when decompressing compressed
messages (`CVE-2018-1000518`_).
.. _CVE-2018-1000518: https://nvd.nist.gov/vuln/detail/CVE-2018-1000518
Backwards-incompatible changes
..............................
.. admonition:: A ``user_info`` field is added to the return value of
``parse_uri`` and ``WebSocketURI``.
:class: note
If you're unpacking ``WebSocketURI`` into four variables, adjust your code
to account for that fifth field.
New features
............
* :func:`~legacy.client.connect` performs HTTP Basic Auth when the URI contains
credentials.
* :func:`~legacy.server.unix_serve` can be used as an asynchronous context
manager on Python ≥ 3.5.1.
* Added the :attr:`~legacy.protocol.WebSocketCommonProtocol.closed` property
to protocols.
* Added new examples in the documentation.
Improvements
............
* Iterating on incoming messages no longer raises an exception when the
connection terminates with close code 1001 (going away).
* A plain HTTP request now receives a 426 Upgrade Required response and
doesn't log a stack trace.
* If a :meth:`~legacy.protocol.WebSocketCommonProtocol.ping` doesn't receive a
pong, it's canceled when the connection is closed.
* Reported the cause of :exc:`~exceptions.ConnectionClosed` exceptions.
* Stopped logging stack traces when the TCP connection dies prematurely.
* Prevented writing to a closing TCP connection during unclean shutdowns.
* Made connection termination more robust to network congestion.
* Prevented processing of incoming frames after failing the connection.
* Updated documentation with new features from Python 3.6.
* Improved several sections of the documentation.
Bug fixes
.........
* Prevented :exc:`TypeError` due to missing close code on connection close.
* Fixed a race condition in the closing handshake that raised
:exc:`~exceptions.InvalidState`.
4.0.1
-----
*November 2, 2017*
Bug fixes
.........
* Fixed issues with the packaging of the 4.0 release.
.. _4.0:
4.0
---
*November 2, 2017*
Backwards-incompatible changes
..............................
.. admonition:: websockets 4.0 requires Python ≥ 3.4.
:class: tip
websockets 3.4 is the last version supporting Python 3.3.
.. admonition:: Compression is enabled by default.
:class: important
In August 2017, Firefox and Chrome support the permessage-deflate
extension, but not Safari and IE.
Compression should improve performance but it increases RAM and CPU use.
If you want to disable compression, add ``compression=None`` when calling
:func:`~legacy.server.serve` or :func:`~legacy.client.connect`.
.. admonition:: The ``state_name`` attribute of protocols is deprecated.
:class: note
Use ``protocol.state.name`` instead of ``protocol.state_name``.
New features
............
* :class:`~legacy.protocol.WebSocketCommonProtocol` instances can be used as
asynchronous iterators on Python ≥ 3.6. They yield incoming messages.
* Added :func:`~legacy.server.unix_serve` for listening on Unix sockets.
* Added the :attr:`~legacy.server.WebSocketServer.sockets` attribute to the
return value of :func:`~legacy.server.serve`.
* Allowed ``extra_headers`` to override ``Server`` and ``User-Agent`` headers.
Improvements
............
* Reorganized and extended documentation.
* Rewrote connection termination to increase robustness in edge cases.
* Reduced verbosity of "Failing the WebSocket connection" logs.
Bug fixes
.........
* Aborted connections if they don't close within the configured ``timeout``.
* Stopped leaking pending tasks when :meth:`~asyncio.Task.cancel` is called on
a connection while it's being closed.
.. _3.4:
3.4
---
*August 20, 2017*
Backwards-incompatible changes
..............................
.. admonition:: ``InvalidStatus`` is replaced
by :class:`~exceptions.InvalidStatusCode`.
:class: note
This exception is raised when :func:`~legacy.client.connect` receives an invalid
response status code from the server.
New features
............
* :func:`~legacy.server.serve` can be used as an asynchronous context manager
on Python ≥ 3.5.1.
* Added support for customizing handling of incoming connections with
:meth:`~legacy.server.WebSocketServerProtocol.process_request`.
* Made read and write buffer sizes configurable.
Improvements
............
* Renamed :func:`~legacy.server.serve` and :func:`~legacy.client.connect`'s
``klass`` argument to ``create_protocol`` to reflect that it can also be a
callable. For backwards compatibility, ``klass`` is still supported.
* Rewrote HTTP handling for simplicity and performance.
* Added an optional C extension to speed up low-level operations.
Bug fixes
.........
* Providing a ``sock`` argument to :func:`~legacy.client.connect` no longer
crashes.
.. _3.3:
3.3
---
*March 29, 2017*
New features
............
* Ensured compatibility with Python 3.6.
Improvements
............
* Reduced noise in logs caused by connection resets.
Bug fixes
.........
* Avoided crashing on concurrent writes on slow connections.
.. _3.2:
3.2
---
*August 17, 2016*
New features
............
* Added ``timeout``, ``max_size``, and ``max_queue`` arguments to
:func:`~legacy.client.connect` and :func:`~legacy.server.serve`.
Improvements
............
* Made server shutdown more robust.
.. _3.1:
3.1
---
*April 21, 2016*
New features
............
* Added flow control for incoming data.
Bug fixes
.........
* Avoided a warning when closing a connection before the opening handshake.
.. _3.0:
3.0
---
*December 25, 2015*
Backwards-incompatible changes
..............................
.. admonition:: :meth:`~legacy.protocol.WebSocketCommonProtocol.recv` now
raises an exception when the connection is closed.
:class: caution
:meth:`~legacy.protocol.WebSocketCommonProtocol.recv` used to return
:obj:`None` when the connection was closed. This required checking the
return value of every call::
message = await websocket.recv()
if message is None:
return
Now it raises a :exc:`~exceptions.ConnectionClosed` exception instead.
This is more Pythonic. The previous code can be simplified to::
message = await websocket.recv()
When implementing a server, there's no strong reason to handle such
exceptions. Let them bubble up, terminate the handler coroutine, and the
server will simply ignore them.
In order to avoid stranding projects built upon an earlier version, the
previous behavior can be restored by passing ``legacy_recv=True`` to
:func:`~legacy.server.serve`, :func:`~legacy.client.connect`,
:class:`~legacy.server.WebSocketServerProtocol`, or
:class:`~legacy.client.WebSocketClientProtocol`.
New features
............
* :func:`~legacy.client.connect` can be used as an asynchronous context manager
on Python ≥ 3.5.1.
* :meth:`~legacy.protocol.WebSocketCommonProtocol.ping` and
:meth:`~legacy.protocol.WebSocketCommonProtocol.pong` support data passed as
:class:`str` in addition to :class:`bytes`.
* Made ``state_name`` attribute on protocols a public API.
Improvements
............
* Updated documentation with ``await`` and ``async`` syntax from Python 3.5.
* Worked around an :mod:`asyncio` bug affecting connection termination under
load.
* Improved documentation.
.. _2.7:
2.7
---
*November 18, 2015*
New features
............
* Added compatibility with Python 3.5.
Improvements
............
* Refreshed documentation.
.. _2.6:
2.6
---
*August 18, 2015*
New features
............
* Added ``local_address`` and ``remote_address`` attributes on protocols.
* Closed open connections with code 1001 when a server shuts down.
Bug fixes
.........
* Avoided TCP fragmentation of small frames.
.. _2.5:
2.5
---
*July 28, 2015*
New features
............
* Provided access to handshake request and response HTTP headers.
* Allowed customizing handshake request and response HTTP headers.
* Added support for running on a non-default event loop.
Improvements
............
* Improved documentation.
* Sent a 403 status code instead of 400 when request Origin isn't allowed.
* Clarified that the closing handshake can be initiated by the client.
* Set the close code and reason more consistently.
* Strengthened connection termination.
Bug fixes
.........
* Canceling :meth:`~legacy.protocol.WebSocketCommonProtocol.recv` no longer
drops the next message.
.. _2.4:
2.4
---
*January 31, 2015*
New features
............
* Added support for subprotocols.
* Added ``loop`` argument to :func:`~legacy.client.connect` and
:func:`~legacy.server.serve`.
.. _2.3:
2.3
---
*November 3, 2014*
Improvements
............
* Improved compliance of close codes.
.. _2.2:
2.2
---
*July 28, 2014*
New features
............
* Added support for limiting message size.
.. _2.1:
2.1
---
*April 26, 2014*
New features
............
* Added ``host``, ``port`` and ``secure`` attributes on protocols.
* Added support for providing and checking Origin_.
.. _Origin: https://datatracker.ietf.org/doc/html/rfc6455.html#section-10.2
.. _2.0:
2.0
---
*February 16, 2014*
Backwards-incompatible changes
..............................
.. admonition:: :meth:`~legacy.protocol.WebSocketCommonProtocol.send`,
:meth:`~legacy.protocol.WebSocketCommonProtocol.ping`, and
:meth:`~legacy.protocol.WebSocketCommonProtocol.pong` are now coroutines.
:class: caution
They used to be functions.
Instead of::
websocket.send(message)
you must write::
await websocket.send(message)
New features
............
* Added flow control for outgoing data.
.. _1.0:
1.0
---
*November 14, 2013*
New features
............
* Initial public release.
websockets-14.1/docs/project/contributing.rst 0000664 0000000 0000000 00000004324 14715046371 0021462 0 ustar 00root root 0000000 0000000 Contributing
============
Thanks for taking the time to contribute to websockets!
Code of Conduct
---------------
This project and everyone participating in it is governed by the `Code of
Conduct`_. By participating, you are expected to uphold this code. Please
report inappropriate behavior to aymeric DOT augustin AT fractalideas DOT com.
.. _Code of Conduct: https://github.com/python-websockets/websockets/blob/main/CODE_OF_CONDUCT.md
*(If I'm the person with the inappropriate behavior, please accept my
apologies. I know I can mess up. I can't expect you to tell me, but if you
choose to do so, I'll do my best to handle criticism constructively.
-- Aymeric)*
Contributing
------------
Bug reports, patches and suggestions are welcome!
Please open an issue_ or send a `pull request`_.
Feedback about the documentation is especially valuable, as the primary author
feels more confident about writing code than writing docs :-)
If you're wondering why things are done in a certain way, the :doc:`design
document <../topics/design>` provides lots of details about the internals of
websockets.
.. _issue: https://github.com/python-websockets/websockets/issues/new
.. _pull request: https://github.com/python-websockets/websockets/compare/
Packaging
---------
Some distributions package websockets so that it can be installed with the
system package manager rather than with pip, possibly in a virtualenv.
If you're packaging websockets for a distribution, you must use `releases
published on PyPI`_ as input. You may check `SLSA attestations on GitHub`_.
.. _releases published on PyPI: https://pypi.org/project/websockets/#files
.. _SLSA attestations on GitHub: https://github.com/python-websockets/websockets/attestations
You mustn't rely on the git repository as input. Specifically, you mustn't
attempt to run the main test suite. It isn't treated as a deliverable of the
project. It doesn't do what you think it does. It's designed for the needs of
developers, not packagers.
On a typical build farm for a distribution, tests that exercise timeouts will
fail randomly. Indeed, the test suite is optimized for running very fast, with a
tolerable level of flakiness, on a high-end laptop without noisy neighbors. This
isn't your context.
websockets-14.1/docs/project/index.rst 0000664 0000000 0000000 00000000352 14715046371 0020057 0 ustar 00root root 0000000 0000000 About websockets
================
This is about websockets-the-project rather than websockets-the-software.
.. toctree::
:titlesonly:
changelog
contributing
sponsoring
For enterprise
support
license
websockets-14.1/docs/project/license.rst 0000664 0000000 0000000 00000000054 14715046371 0020371 0 ustar 00root root 0000000 0000000 License
=======
.. include:: ../../LICENSE
websockets-14.1/docs/project/sponsoring.rst 0000664 0000000 0000000 00000000425 14715046371 0021152 0 ustar 00root root 0000000 0000000 Sponsoring
==========
You may sponsor the development of websockets through:
* `GitHub Sponsors`_
* `Open Collective`_
* :doc:`Tidelift `
.. _GitHub Sponsors: https://github.com/sponsors/python-websockets
.. _Open Collective: https://opencollective.com/websockets
websockets-14.1/docs/project/support.rst 0000664 0000000 0000000 00000003236 14715046371 0020470 0 ustar 00root root 0000000 0000000 Getting support
===============
.. admonition:: There are no free support channels.
:class: tip
websockets is an open-source project. It's primarily maintained by one
person as a hobby.
For this reason, the focus is on flawless code and self-service
documentation, not support.
Enterprise
----------
websockets is maintained with high standards, making it suitable for enterprise
use cases. Additional guarantees are available via :doc:`Tidelift `.
If you're using it in a professional setting, consider subscribing.
Questions
---------
GitHub issues aren't a good medium for handling questions. There are better
places to ask questions, for example Stack Overflow.
If you want to ask a question anyway, please make sure that:
- it's a question about websockets and not about :mod:`asyncio`;
- it isn't answered in the documentation;
- it wasn't asked already.
A good question can be written as a suggestion to improve the documentation.
Cryptocurrency users
--------------------
websockets appears to be quite popular for interfacing with Bitcoin or other
cryptocurrency trackers. I'm strongly opposed to Bitcoin's carbon footprint.
I'm aware of efforts to build proof-of-stake models. I'll care once the total
energy consumption of all cryptocurrencies drops to a non-bullshit level.
You already negated all of humanity's efforts to develop renewable energy.
Please stop heating the planet where my children will have to live.
Since websockets is released under an open-source license, you can use it for
any purpose you like. However, I won't spend any of my time to help you.
I will summarily close issues related to cryptocurrency in any way.
websockets-14.1/docs/project/tidelift.rst 0000664 0000000 0000000 00000010310 14715046371 0020547 0 ustar 00root root 0000000 0000000 websockets for enterprise
=========================
Available as part of the Tidelift Subscription
----------------------------------------------
.. image:: ../_static/tidelift.png
:height: 150px
:width: 150px
:align: left
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.
.. raw:: html
Enterprise-ready open source software—managed for you
-----------------------------------------------------
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.
Your subscription includes:
* **Security updates**
* 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.
* **Licensing verification and indemnification**
* 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.
* **Maintenance and code improvement**
* 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.
* **Package selection and version guidance**
* 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.
* **Roadmap input**
* 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.
* **Tooling and cloud integration**
* Tidelift works with GitHub, GitLab, BitBucket, and more. We support every
cloud platform (and other deployment targets, too).
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.
.. raw:: html
websockets-14.1/docs/reference/ 0000775 0000000 0000000 00000000000 14715046371 0016506 5 ustar 00root root 0000000 0000000 websockets-14.1/docs/reference/asyncio/ 0000775 0000000 0000000 00000000000 14715046371 0020153 5 ustar 00root root 0000000 0000000 websockets-14.1/docs/reference/asyncio/client.rst 0000664 0000000 0000000 00000002314 14715046371 0022163 0 ustar 00root root 0000000 0000000 Client (new :mod:`asyncio`)
===========================
.. automodule:: websockets.asyncio.client
Opening a connection
--------------------
.. autofunction:: connect
:async:
.. autofunction:: unix_connect
:async:
.. autofunction:: process_exception
Using a connection
------------------
.. autoclass:: ClientConnection
.. automethod:: __aiter__
.. automethod:: recv
.. automethod:: recv_streaming
.. automethod:: send
.. automethod:: close
.. automethod:: wait_closed
.. automethod:: ping
.. automethod:: pong
WebSocket connection objects also provide these attributes:
.. autoattribute:: id
.. autoattribute:: logger
.. autoproperty:: local_address
.. autoproperty:: remote_address
.. autoattribute:: latency
.. autoproperty:: state
The following attributes are available after the opening handshake,
once the WebSocket connection is open:
.. autoattribute:: request
.. autoattribute:: response
.. autoproperty:: subprotocol
The following attributes are available after the closing handshake,
once the WebSocket connection is closed:
.. autoproperty:: close_code
.. autoproperty:: close_reason
websockets-14.1/docs/reference/asyncio/common.rst 0000664 0000000 0000000 00000002022 14715046371 0022171 0 ustar 00root root 0000000 0000000 :orphan:
Both sides (new :mod:`asyncio`)
===============================
.. automodule:: websockets.asyncio.connection
.. autoclass:: Connection
.. automethod:: __aiter__
.. automethod:: recv
.. automethod:: recv_streaming
.. automethod:: send
.. automethod:: close
.. automethod:: wait_closed
.. automethod:: ping
.. automethod:: pong
WebSocket connection objects also provide these attributes:
.. autoattribute:: id
.. autoattribute:: logger
.. autoproperty:: local_address
.. autoproperty:: remote_address
.. autoattribute:: latency
.. autoproperty:: state
The following attributes are available after the opening handshake,
once the WebSocket connection is open:
.. autoattribute:: request
.. autoattribute:: response
.. autoproperty:: subprotocol
The following attributes are available after the closing handshake,
once the WebSocket connection is closed:
.. autoproperty:: close_code
.. autoproperty:: close_reason
websockets-14.1/docs/reference/asyncio/server.rst 0000664 0000000 0000000 00000003412 14715046371 0022213 0 ustar 00root root 0000000 0000000 Server (new :mod:`asyncio`)
===========================
.. automodule:: websockets.asyncio.server
Creating a server
-----------------
.. autofunction:: serve
:async:
.. autofunction:: unix_serve
:async:
Running a server
----------------
.. autoclass:: Server
.. autoattribute:: connections
.. automethod:: close
.. automethod:: wait_closed
.. automethod:: get_loop
.. automethod:: is_serving
.. automethod:: start_serving
.. automethod:: serve_forever
.. autoattribute:: sockets
Using a connection
------------------
.. autoclass:: ServerConnection
.. automethod:: __aiter__
.. automethod:: recv
.. automethod:: recv_streaming
.. automethod:: send
.. automethod:: close
.. automethod:: wait_closed
.. automethod:: ping
.. automethod:: pong
.. automethod:: respond
WebSocket connection objects also provide these attributes:
.. autoattribute:: id
.. autoattribute:: logger
.. autoproperty:: local_address
.. autoproperty:: remote_address
.. autoattribute:: latency
.. autoproperty:: state
The following attributes are available after the opening handshake,
once the WebSocket connection is open:
.. autoattribute:: request
.. autoattribute:: response
.. autoproperty:: subprotocol
The following attributes are available after the closing handshake,
once the WebSocket connection is closed:
.. autoproperty:: close_code
.. autoproperty:: close_reason
Broadcast
---------
.. autofunction:: websockets.asyncio.server.broadcast
HTTP Basic Authentication
-------------------------
websockets supports HTTP Basic Authentication according to
:rfc:`7235` and :rfc:`7617`.
.. autofunction:: websockets.asyncio.server.basic_auth
websockets-14.1/docs/reference/datastructures.rst 0000664 0000000 0000000 00000002653 14715046371 0022323 0 ustar 00root root 0000000 0000000 Data structures
===============
WebSocket events
----------------
.. automodule:: websockets.frames
.. autoclass:: Frame
.. autoclass:: Opcode
.. autoattribute:: CONT
.. autoattribute:: TEXT
.. autoattribute:: BINARY
.. autoattribute:: CLOSE
.. autoattribute:: PING
.. autoattribute:: PONG
.. autoclass:: Close
.. autoclass:: CloseCode
.. autoattribute:: NORMAL_CLOSURE
.. autoattribute:: GOING_AWAY
.. autoattribute:: PROTOCOL_ERROR
.. autoattribute:: UNSUPPORTED_DATA
.. autoattribute:: NO_STATUS_RCVD
.. autoattribute:: ABNORMAL_CLOSURE
.. autoattribute:: INVALID_DATA
.. autoattribute:: POLICY_VIOLATION
.. autoattribute:: MESSAGE_TOO_BIG
.. autoattribute:: MANDATORY_EXTENSION
.. autoattribute:: INTERNAL_ERROR
.. autoattribute:: SERVICE_RESTART
.. autoattribute:: TRY_AGAIN_LATER
.. autoattribute:: BAD_GATEWAY
.. autoattribute:: TLS_HANDSHAKE
HTTP events
-----------
.. automodule:: websockets.http11
.. autoclass:: Request
.. autoclass:: Response
.. automodule:: websockets.datastructures
.. autoclass:: Headers
.. automethod:: get_all
.. automethod:: raw_items
.. autoexception:: MultipleValuesError
URIs
----
.. automodule:: websockets.uri
.. autofunction:: parse_uri
.. autoclass:: WebSocketURI
websockets-14.1/docs/reference/exceptions.rst 0000664 0000000 0000000 00000003523 14715046371 0021424 0 ustar 00root root 0000000 0000000 Exceptions
==========
.. automodule:: websockets.exceptions
.. autoexception:: WebSocketException
Connection closed
-----------------
:meth:`~websockets.asyncio.connection.Connection.recv`,
:meth:`~websockets.asyncio.connection.Connection.send`, and similar methods
raise the exceptions below when the connection is closed. This is the expected
way to detect disconnections.
.. autoexception:: ConnectionClosed
.. autoexception:: ConnectionClosedOK
.. autoexception:: ConnectionClosedError
Connection failed
-----------------
These exceptions are raised by :func:`~websockets.asyncio.client.connect` when
the opening handshake fails and the connection cannot be established. They are
also reported by :func:`~websockets.asyncio.server.serve` in logs.
.. autoexception:: InvalidURI
.. autoexception:: InvalidHandshake
.. autoexception:: SecurityError
.. autoexception:: InvalidStatus
.. autoexception:: InvalidHeader
.. autoexception:: InvalidHeaderFormat
.. autoexception:: InvalidHeaderValue
.. autoexception:: InvalidOrigin
.. autoexception:: InvalidUpgrade
.. autoexception:: NegotiationError
.. autoexception:: DuplicateParameter
.. autoexception:: InvalidParameterName
.. autoexception:: InvalidParameterValue
Sans-I/O exceptions
-------------------
These exceptions are only raised by the Sans-I/O implementation. They are
translated to :exc:`ConnectionClosedError` in the other implementations.
.. autoexception:: ProtocolError
.. autoexception:: PayloadTooBig
.. autoexception:: InvalidState
Miscellaneous exceptions
------------------------
.. autoexception:: ConcurrencyError
Legacy exceptions
-----------------
These exceptions are only used by the legacy :mod:`asyncio` implementation.
.. autoexception:: InvalidMessage
.. autoexception:: InvalidStatusCode
.. autoexception:: AbortHandshake
.. autoexception:: RedirectHandshake
websockets-14.1/docs/reference/extensions.rst 0000664 0000000 0000000 00000003022 14715046371 0021434 0 ustar 00root root 0000000 0000000 Extensions
==========
.. currentmodule:: websockets.extensions
The WebSocket protocol supports extensions_.
At the time of writing, there's only one `registered extension`_ with a public
specification, WebSocket Per-Message Deflate.
.. _extensions: https://datatracker.ietf.org/doc/html/rfc6455.html#section-9
.. _registered extension: https://www.iana.org/assignments/websocket/websocket.xhtml#extension-name
Per-Message Deflate
-------------------
.. automodule:: websockets.extensions.permessage_deflate
:mod:`websockets.extensions.permessage_deflate` implements WebSocket
Per-Message Deflate.
This extension is specified in :rfc:`7692`.
Refer to the :doc:`topic guide on compression <../topics/compression>` to
learn more about tuning compression settings.
.. autoclass:: ClientPerMessageDeflateFactory
.. autoclass:: ServerPerMessageDeflateFactory
Base classes
------------
.. automodule:: websockets.extensions
:mod:`websockets.extensions` defines base classes for implementing
extensions.
Refer to the :doc:`how-to guide on extensions <../howto/extensions>` to
learn more about writing an extension.
.. autoclass:: Extension
.. autoattribute:: name
.. automethod:: decode
.. automethod:: encode
.. autoclass:: ClientExtensionFactory
.. autoattribute:: name
.. automethod:: get_request_params
.. automethod:: process_response_params
.. autoclass:: ServerExtensionFactory
.. automethod:: process_request_params
websockets-14.1/docs/reference/features.rst 0000664 0000000 0000000 00000030634 14715046371 0021064 0 ustar 00root root 0000000 0000000 Features
========
.. currentmodule:: websockets
Feature support matrices summarize which implementations support which features.
.. raw:: html
.. |aio| replace:: :mod:`asyncio` (new)
.. |sync| replace:: :mod:`threading`
.. |sans| replace:: `Sans-I/O`_
.. |leg| replace:: :mod:`asyncio` (legacy)
.. _Sans-I/O: https://sans-io.readthedocs.io/
Both sides
----------
.. table::
:class: support-matrix-table
+------------------------------------+--------+--------+--------+--------+
| | |aio| | |sync| | |sans| | |leg| |
+====================================+========+========+========+========+
| Perform the opening handshake | ✅ | ✅ | ✅ | ✅ |
+------------------------------------+--------+--------+--------+--------+
| Enforce opening timeout | ✅ | ✅ | — | ✅ |
+------------------------------------+--------+--------+--------+--------+
| Send a message | ✅ | ✅ | ✅ | ✅ |
+------------------------------------+--------+--------+--------+--------+
| Broadcast a message | ✅ | ❌ | — | ✅ |
+------------------------------------+--------+--------+--------+--------+
| Receive a message | ✅ | ✅ | ✅ | ✅ |
+------------------------------------+--------+--------+--------+--------+
| Iterate over received messages | ✅ | ✅ | — | ✅ |
+------------------------------------+--------+--------+--------+--------+
| Send a fragmented message | ✅ | ✅ | ✅ | ✅ |
+------------------------------------+--------+--------+--------+--------+
| Receive a fragmented message frame | ✅ | ✅ | — | ❌ |
| by frame | | | | |
+------------------------------------+--------+--------+--------+--------+
| Receive a fragmented message after | ✅ | ✅ | — | ✅ |
| reassembly | | | | |
+------------------------------------+--------+--------+--------+--------+
| Force sending a message as Text or | ✅ | ✅ | — | ❌ |
| Binary | | | | |
+------------------------------------+--------+--------+--------+--------+
| Force receiving a message as | ✅ | ✅ | — | ❌ |
| :class:`bytes` or :class:`str` | | | | |
+------------------------------------+--------+--------+--------+--------+
| Send a ping | ✅ | ✅ | ✅ | ✅ |
+------------------------------------+--------+--------+--------+--------+
| Respond to pings automatically | ✅ | ✅ | ✅ | ✅ |
+------------------------------------+--------+--------+--------+--------+
| Send a pong | ✅ | ✅ | ✅ | ✅ |
+------------------------------------+--------+--------+--------+--------+
| Keepalive | ✅ | ❌ | — | ✅ |
+------------------------------------+--------+--------+--------+--------+
| Heartbeat | ✅ | ❌ | — | ✅ |
+------------------------------------+--------+--------+--------+--------+
| Measure latency | ✅ | ❌ | — | ✅ |
+------------------------------------+--------+--------+--------+--------+
| Perform the closing handshake | ✅ | ✅ | ✅ | ✅ |
+------------------------------------+--------+--------+--------+--------+
| Enforce closing timeout | ✅ | ✅ | — | ✅ |
+------------------------------------+--------+--------+--------+--------+
| Report close codes and reasons | ✅ | ✅ | ✅ | ❌ |
| from both sides | | | | |
+------------------------------------+--------+--------+--------+--------+
| Compress messages (:rfc:`7692`) | ✅ | ✅ | ✅ | ✅ |
+------------------------------------+--------+--------+--------+--------+
| Tune memory usage for compression | ✅ | ✅ | ✅ | ✅ |
+------------------------------------+--------+--------+--------+--------+
| Negotiate extensions | ✅ | ✅ | ✅ | ✅ |
+------------------------------------+--------+--------+--------+--------+
| Implement custom extensions | ✅ | ✅ | ✅ | ✅ |
+------------------------------------+--------+--------+--------+--------+
| Negotiate a subprotocol | ✅ | ✅ | ✅ | ✅ |
+------------------------------------+--------+--------+--------+--------+
| Enforce security limits | ✅ | ✅ | ✅ | ✅ |
+------------------------------------+--------+--------+--------+--------+
| Log events | ✅ | ✅ | ✅ | ✅ |
+------------------------------------+--------+--------+--------+--------+
Server
------
.. table::
:class: support-matrix-table
+------------------------------------+--------+--------+--------+--------+
| | |aio| | |sync| | |sans| | |leg| |
+====================================+========+========+========+========+
| Listen on a TCP socket | ✅ | ✅ | — | ✅ |
+------------------------------------+--------+--------+--------+--------+
| Listen on a Unix socket | ✅ | ✅ | — | ✅ |
+------------------------------------+--------+--------+--------+--------+
| Listen using a preexisting socket | ✅ | ✅ | — | ✅ |
+------------------------------------+--------+--------+--------+--------+
| Encrypt connection with TLS | ✅ | ✅ | — | ✅ |
+------------------------------------+--------+--------+--------+--------+
| Close server on context exit | ✅ | ✅ | — | ✅ |
+------------------------------------+--------+--------+--------+--------+
| Close connection on handler exit | ✅ | ✅ | — | ✅ |
+------------------------------------+--------+--------+--------+--------+
| Shut down server gracefully | ✅ | ✅ | — | ✅ |
+------------------------------------+--------+--------+--------+--------+
| Check ``Origin`` header | ✅ | ✅ | ✅ | ✅ |
+------------------------------------+--------+--------+--------+--------+
| Customize subprotocol selection | ✅ | ✅ | ✅ | ✅ |
+------------------------------------+--------+--------+--------+--------+
| Configure ``Server`` header | ✅ | ✅ | ✅ | ✅ |
+------------------------------------+--------+--------+--------+--------+
| Alter opening handshake request | ✅ | ✅ | ✅ | ❌ |
+------------------------------------+--------+--------+--------+--------+
| Alter opening handshake response | ✅ | ✅ | ✅ | ❌ |
+------------------------------------+--------+--------+--------+--------+
| Force an HTTP response | ✅ | ✅ | ✅ | ✅ |
+------------------------------------+--------+--------+--------+--------+
| Perform HTTP Basic Authentication | ✅ | ✅ | ❌ | ✅ |
+------------------------------------+--------+--------+--------+--------+
| Perform HTTP Digest Authentication | ❌ | ❌ | ❌ | ❌ |
+------------------------------------+--------+--------+--------+--------+
Client
------
.. table::
:class: support-matrix-table
+------------------------------------+--------+--------+--------+--------+
| | |aio| | |sync| | |sans| | |leg| |
+====================================+========+========+========+========+
| Connect to a TCP socket | ✅ | ✅ | — | ✅ |
+------------------------------------+--------+--------+--------+--------+
| Connect to a Unix socket | ✅ | ✅ | — | ✅ |
+------------------------------------+--------+--------+--------+--------+
| Connect using a preexisting socket | ✅ | ✅ | — | ✅ |
+------------------------------------+--------+--------+--------+--------+
| Encrypt connection with TLS | ✅ | ✅ | — | ✅ |
+------------------------------------+--------+--------+--------+--------+
| Close connection on context exit | ✅ | ✅ | — | ✅ |
+------------------------------------+--------+--------+--------+--------+
| Reconnect automatically | ✅ | ❌ | — | ✅ |
+------------------------------------+--------+--------+--------+--------+
| Configure ``Origin`` header | ✅ | ✅ | ✅ | ✅ |
+------------------------------------+--------+--------+--------+--------+
| Configure ``User-Agent`` header | ✅ | ✅ | ✅ | ✅ |
+------------------------------------+--------+--------+--------+--------+
| Modify opening handshake request | ✅ | ✅ | ✅ | ✅ |
+------------------------------------+--------+--------+--------+--------+
| Modify opening handshake response | ✅ | ✅ | ✅ | ❌ |
+------------------------------------+--------+--------+--------+--------+
| Connect to non-ASCII IRIs | ✅ | ✅ | ✅ | ✅ |
+------------------------------------+--------+--------+--------+--------+
| Follow HTTP redirects | ✅ | ❌ | — | ✅ |
+------------------------------------+--------+--------+--------+--------+
| Perform HTTP Basic Authentication | ✅ | ✅ | ✅ | ✅ |
+------------------------------------+--------+--------+--------+--------+
| Perform HTTP Digest Authentication | ❌ | ❌ | ❌ | ❌ |
| (`#784`_) | | | | |
+------------------------------------+--------+--------+--------+--------+
| Connect via HTTP proxy (`#364`_) | ❌ | ❌ | — | ❌ |
+------------------------------------+--------+--------+--------+--------+
| Connect via SOCKS5 proxy (`#475`_) | ❌ | ❌ | — | ❌ |
+------------------------------------+--------+--------+--------+--------+
.. _#364: https://github.com/python-websockets/websockets/issues/364
.. _#475: https://github.com/python-websockets/websockets/issues/475
.. _#784: https://github.com/python-websockets/websockets/issues/784
Known limitations
-----------------
There is no way to control compression of outgoing frames on a per-frame basis
(`#538`_). If compression is enabled, all frames are compressed.
.. _#538: https://github.com/python-websockets/websockets/issues/538
The server doesn't check the Host header and doesn't respond with HTTP 400 Bad
Request if it is missing or invalid (`#1246`).
.. _#1246: https://github.com/python-websockets/websockets/issues/1246
The client API doesn't attempt to guarantee that there is no more than one
connection to a given IP address in a CONNECTING state. This behavior is
mandated by :rfc:`6455`, section 4.1. However, :func:`~asyncio.client.connect()`
isn't the right layer for enforcing this constraint. It's the caller's
responsibility.
It is possible to send or receive a text message containing invalid UTF-8 with
``send(not_utf8_bytes, text=True)`` and ``not_utf8_bytes = recv(decode=False)``
respectively. As a side effect of disabling UTF-8 encoding and decoding, these
options also disable UTF-8 validation.
websockets-14.1/docs/reference/index.rst 0000664 0000000 0000000 00000003316 14715046371 0020352 0 ustar 00root root 0000000 0000000 API reference
=============
.. currentmodule:: websockets
Features
--------
Check which implementations support which features and known limitations.
.. toctree::
:titlesonly:
features
:mod:`asyncio`
--------------
It's ideal for servers that handle many clients concurrently.
This is the default implementation.
.. toctree::
:titlesonly:
asyncio/server
asyncio/client
:mod:`threading`
----------------
This alternative implementation can be a good choice for clients.
.. toctree::
:titlesonly:
sync/server
sync/client
`Sans-I/O`_
-----------
This layer is designed for integrating in third-party libraries, typically
application servers.
.. _Sans-I/O: https://sans-io.readthedocs.io/
.. toctree::
:titlesonly:
sansio/server
sansio/client
:mod:`asyncio` (legacy)
-----------------------
This is the historical implementation.
It is deprecated and will be removed.
.. toctree::
:titlesonly:
legacy/server
legacy/client
Extensions
----------
The Per-Message Deflate extension is built in. You may also define custom
extensions.
.. toctree::
:titlesonly:
extensions
Shared
------
These low-level APIs are shared by all implementations.
.. toctree::
:titlesonly:
datastructures
exceptions
types
variables
API stability
-------------
Public APIs documented in this API reference are subject to the
:ref:`backwards-compatibility policy `.
Anything that isn't listed in the API reference is a private API. There's no
guarantees of behavior or backwards-compatibility for private APIs.
Convenience imports
-------------------
For convenience, some public APIs can be imported directly from the
``websockets`` package.
websockets-14.1/docs/reference/legacy/ 0000775 0000000 0000000 00000000000 14715046371 0017752 5 ustar 00root root 0000000 0000000 websockets-14.1/docs/reference/legacy/client.rst 0000664 0000000 0000000 00000004522 14715046371 0021765 0 ustar 00root root 0000000 0000000 Client (legacy :mod:`asyncio`)
==============================
.. admonition:: The legacy :mod:`asyncio` implementation is deprecated.
:class: caution
The :doc:`upgrade guide <../../howto/upgrade>` provides complete instructions
to migrate your application.
.. automodule:: websockets.legacy.client
Opening a connection
--------------------
.. autofunction:: connect(uri, *, create_protocol=None, logger=None, compression="deflate", origin=None, extensions=None, subprotocols=None, extra_headers=None, user_agent_header="Python/x.y.z websockets/X.Y", 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)
:async:
.. autofunction:: unix_connect(path, uri="ws://localhost/", *, create_protocol=None, logger=None, compression="deflate", origin=None, extensions=None, subprotocols=None, extra_headers=None, user_agent_header="Python/x.y.z websockets/X.Y", 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)
:async:
Using a connection
------------------
.. autoclass:: WebSocketClientProtocol(*, logger=None, origin=None, extensions=None, subprotocols=None, extra_headers=None, user_agent_header="Python/x.y.z websockets/X.Y", 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)
.. automethod:: recv
.. automethod:: send
.. automethod:: close
.. automethod:: wait_closed
.. automethod:: ping
.. automethod:: pong
WebSocket connection objects also provide these attributes:
.. autoattribute:: id
.. autoattribute:: logger
.. autoproperty:: local_address
.. autoproperty:: remote_address
.. autoproperty:: open
.. autoproperty:: closed
.. autoattribute:: latency
The following attributes are available after the opening handshake,
once the WebSocket connection is open:
.. autoattribute:: path
.. autoattribute:: request_headers
.. autoattribute:: response_headers
.. autoattribute:: subprotocol
The following attributes are available after the closing handshake,
once the WebSocket connection is closed:
.. autoproperty:: close_code
.. autoproperty:: close_reason
websockets-14.1/docs/reference/legacy/common.rst 0000664 0000000 0000000 00000002615 14715046371 0022000 0 ustar 00root root 0000000 0000000 :orphan:
Both sides (legacy :mod:`asyncio`)
==================================
.. admonition:: The legacy :mod:`asyncio` implementation is deprecated.
:class: caution
The :doc:`upgrade guide <../../howto/upgrade>` provides complete instructions
to migrate your application.
.. automodule:: websockets.legacy.protocol
.. autoclass:: WebSocketCommonProtocol(*, logger=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)
.. automethod:: recv
.. automethod:: send
.. automethod:: close
.. automethod:: wait_closed
.. automethod:: ping
.. automethod:: pong
WebSocket connection objects also provide these attributes:
.. autoattribute:: id
.. autoattribute:: logger
.. autoproperty:: local_address
.. autoproperty:: remote_address
.. autoproperty:: open
.. autoproperty:: closed
.. autoattribute:: latency
The following attributes are available after the opening handshake,
once the WebSocket connection is open:
.. autoattribute:: path
.. autoattribute:: request_headers
.. autoattribute:: response_headers
.. autoattribute:: subprotocol
The following attributes are available after the closing handshake,
once the WebSocket connection is closed:
.. autoproperty:: close_code
.. autoproperty:: close_reason
websockets-14.1/docs/reference/legacy/server.rst 0000664 0000000 0000000 00000006623 14715046371 0022021 0 ustar 00root root 0000000 0000000 Server (legacy :mod:`asyncio`)
==============================
.. admonition:: The legacy :mod:`asyncio` implementation is deprecated.
:class: caution
The :doc:`upgrade guide <../../howto/upgrade>` provides complete instructions
to migrate your application.
.. automodule:: websockets.legacy.server
Starting a server
-----------------
.. autofunction:: serve(ws_handler, host=None, port=None, *, create_protocol=None, logger=None, compression="deflate", origins=None, extensions=None, subprotocols=None, extra_headers=None, server_header="Python/x.y.z websockets/X.Y", process_request=None, select_subprotocol=None, 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)
:async:
.. autofunction:: unix_serve(ws_handler, path=None, *, create_protocol=None, logger=None, compression="deflate", origins=None, extensions=None, subprotocols=None, extra_headers=None, server_header="Python/x.y.z websockets/X.Y", process_request=None, select_subprotocol=None, 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)
:async:
Stopping a server
-----------------
.. autoclass:: WebSocketServer
.. automethod:: close
.. automethod:: wait_closed
.. automethod:: get_loop
.. automethod:: is_serving
.. automethod:: start_serving
.. automethod:: serve_forever
.. autoattribute:: sockets
Using a connection
------------------
.. autoclass:: WebSocketServerProtocol(ws_handler, ws_server, *, logger=None, origins=None, extensions=None, subprotocols=None, extra_headers=None, server_header="Python/x.y.z websockets/X.Y", process_request=None, select_subprotocol=None, 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)
.. automethod:: recv
.. automethod:: send
.. automethod:: close
.. automethod:: wait_closed
.. automethod:: ping
.. automethod:: pong
You can customize the opening handshake in a subclass by overriding these methods:
.. automethod:: process_request
.. automethod:: select_subprotocol
WebSocket connection objects also provide these attributes:
.. autoattribute:: id
.. autoattribute:: logger
.. autoproperty:: local_address
.. autoproperty:: remote_address
.. autoproperty:: open
.. autoproperty:: closed
.. autoattribute:: latency
The following attributes are available after the opening handshake,
once the WebSocket connection is open:
.. autoattribute:: path
.. autoattribute:: request_headers
.. autoattribute:: response_headers
.. autoattribute:: subprotocol
The following attributes are available after the closing handshake,
once the WebSocket connection is closed:
.. autoproperty:: close_code
.. autoproperty:: close_reason
Broadcast
---------
.. autofunction:: websockets.legacy.server.broadcast
Basic authentication
--------------------
.. automodule:: websockets.legacy.auth
websockets supports HTTP Basic Authentication according to
:rfc:`7235` and :rfc:`7617`.
.. autofunction:: basic_auth_protocol_factory
.. autoclass:: BasicAuthWebSocketServerProtocol
.. autoattribute:: realm
.. autoattribute:: username
.. automethod:: check_credentials
websockets-14.1/docs/reference/sansio/ 0000775 0000000 0000000 00000000000 14715046371 0020002 5 ustar 00root root 0000000 0000000 websockets-14.1/docs/reference/sansio/client.rst 0000664 0000000 0000000 00000002167 14715046371 0022020 0 ustar 00root root 0000000 0000000 Client (`Sans-I/O`_)
====================
.. _Sans-I/O: https://sans-io.readthedocs.io/
.. currentmodule:: websockets.client
.. autoclass:: ClientProtocol
.. automethod:: receive_data
.. automethod:: receive_eof
.. automethod:: connect
.. automethod:: send_request
.. automethod:: send_continuation
.. automethod:: send_text
.. automethod:: send_binary
.. automethod:: send_close
.. automethod:: send_ping
.. automethod:: send_pong
.. automethod:: fail
.. automethod:: events_received
.. automethod:: data_to_send
.. automethod:: close_expected
WebSocket protocol objects also provide these attributes:
.. autoattribute:: id
.. autoattribute:: logger
.. autoproperty:: state
The following attributes are available after the opening handshake,
once the WebSocket connection is open:
.. autoattribute:: handshake_exc
The following attributes are available after the closing handshake,
once the WebSocket connection is closed:
.. autoproperty:: close_code
.. autoproperty:: close_reason
.. autoproperty:: close_exc
websockets-14.1/docs/reference/sansio/common.rst 0000664 0000000 0000000 00000001766 14715046371 0022036 0 ustar 00root root 0000000 0000000 :orphan:
Both sides (`Sans-I/O`_)
=========================
.. _Sans-I/O: https://sans-io.readthedocs.io/
.. automodule:: websockets.protocol
.. autoclass:: Protocol
.. automethod:: receive_data
.. automethod:: receive_eof
.. automethod:: send_continuation
.. automethod:: send_text
.. automethod:: send_binary
.. automethod:: send_close
.. automethod:: send_ping
.. automethod:: send_pong
.. automethod:: fail
.. automethod:: events_received
.. automethod:: data_to_send
.. automethod:: close_expected
.. autoattribute:: id
.. autoattribute:: logger
.. autoproperty:: state
.. autoproperty:: close_code
.. autoproperty:: close_reason
.. autoproperty:: close_exc
.. autoclass:: Side
.. autoattribute:: SERVER
.. autoattribute:: CLIENT
.. autoclass:: State
.. autoattribute:: CONNECTING
.. autoattribute:: OPEN
.. autoattribute:: CLOSING
.. autoattribute:: CLOSED
.. autodata:: SEND_EOF
websockets-14.1/docs/reference/sansio/server.rst 0000664 0000000 0000000 00000002273 14715046371 0022046 0 ustar 00root root 0000000 0000000 Server (`Sans-I/O`_)
====================
.. _Sans-I/O: https://sans-io.readthedocs.io/
.. currentmodule:: websockets.server
.. autoclass:: ServerProtocol
.. automethod:: receive_data
.. automethod:: receive_eof
.. automethod:: accept
.. automethod:: select_subprotocol
.. automethod:: reject
.. automethod:: send_response
.. automethod:: send_continuation
.. automethod:: send_text
.. automethod:: send_binary
.. automethod:: send_close
.. automethod:: send_ping
.. automethod:: send_pong
.. automethod:: fail
.. automethod:: events_received
.. automethod:: data_to_send
.. automethod:: close_expected
WebSocket protocol objects also provide these attributes:
.. autoattribute:: id
.. autoattribute:: logger
.. autoproperty:: state
The following attributes are available after the opening handshake,
once the WebSocket connection is open:
.. autoattribute:: handshake_exc
The following attributes are available after the closing handshake,
once the WebSocket connection is closed:
.. autoproperty:: close_code
.. autoproperty:: close_reason
.. autoproperty:: close_exc
websockets-14.1/docs/reference/sync/ 0000775 0000000 0000000 00000000000 14715046371 0017462 5 ustar 00root root 0000000 0000000 websockets-14.1/docs/reference/sync/client.rst 0000664 0000000 0000000 00000002106 14715046371 0021471 0 ustar 00root root 0000000 0000000 Client (:mod:`threading`)
=========================
.. automodule:: websockets.sync.client
Opening a connection
--------------------
.. autofunction:: connect
.. autofunction:: unix_connect
Using a connection
------------------
.. autoclass:: ClientConnection
.. automethod:: __iter__
.. automethod:: recv
.. automethod:: recv_streaming
.. automethod:: send
.. automethod:: close
.. automethod:: ping
.. automethod:: pong
WebSocket connection objects also provide these attributes:
.. autoattribute:: id
.. autoattribute:: logger
.. autoproperty:: local_address
.. autoproperty:: remote_address
.. autoproperty:: state
The following attributes are available after the opening handshake,
once the WebSocket connection is open:
.. autoattribute:: request
.. autoattribute:: response
.. autoproperty:: subprotocol
The following attributes are available after the closing handshake,
once the WebSocket connection is closed:
.. autoproperty:: close_code
.. autoproperty:: close_reason
websockets-14.1/docs/reference/sync/common.rst 0000664 0000000 0000000 00000001711 14715046371 0021504 0 ustar 00root root 0000000 0000000 :orphan:
Both sides (:mod:`threading`)
=============================
.. automodule:: websockets.sync.connection
.. autoclass:: Connection
.. automethod:: __iter__
.. automethod:: recv
.. automethod:: recv_streaming
.. automethod:: send
.. automethod:: close
.. automethod:: ping
.. automethod:: pong
WebSocket connection objects also provide these attributes:
.. autoattribute:: id
.. autoattribute:: logger
.. autoproperty:: local_address
.. autoproperty:: remote_address
.. autoproperty:: state
The following attributes are available after the opening handshake,
once the WebSocket connection is open:
.. autoattribute:: request
.. autoattribute:: response
.. autoproperty:: subprotocol
The following attributes are available after the closing handshake,
once the WebSocket connection is closed:
.. autoproperty:: close_code
.. autoproperty:: close_reason
websockets-14.1/docs/reference/sync/server.rst 0000664 0000000 0000000 00000002663 14715046371 0021531 0 ustar 00root root 0000000 0000000 Server (:mod:`threading`)
=========================
.. automodule:: websockets.sync.server
Creating a server
-----------------
.. autofunction:: serve
.. autofunction:: unix_serve
Running a server
----------------
.. autoclass:: Server
.. automethod:: serve_forever
.. automethod:: shutdown
.. automethod:: fileno
Using a connection
------------------
.. autoclass:: ServerConnection
.. automethod:: __iter__
.. automethod:: recv
.. automethod:: recv_streaming
.. automethod:: send
.. automethod:: close
.. automethod:: ping
.. automethod:: pong
.. automethod:: respond
WebSocket connection objects also provide these attributes:
.. autoattribute:: id
.. autoattribute:: logger
.. autoproperty:: local_address
.. autoproperty:: remote_address
.. autoproperty:: state
The following attributes are available after the opening handshake,
once the WebSocket connection is open:
.. autoattribute:: request
.. autoattribute:: response
.. autoproperty:: subprotocol
The following attributes are available after the closing handshake,
once the WebSocket connection is closed:
.. autoproperty:: close_code
.. autoproperty:: close_reason
HTTP Basic Authentication
-------------------------
websockets supports HTTP Basic Authentication according to
:rfc:`7235` and :rfc:`7617`.
.. autofunction:: websockets.sync.server.basic_auth
websockets-14.1/docs/reference/types.rst 0000664 0000000 0000000 00000000641 14715046371 0020405 0 ustar 00root root 0000000 0000000 Types
=====
.. automodule:: websockets.typing
.. autodata:: Data
.. autodata:: LoggerLike
.. autodata:: StatusLike
.. autodata:: Origin
.. autodata:: Subprotocol
.. autodata:: ExtensionName
.. autodata:: ExtensionParameter
.. autodata:: websockets.protocol.Event
.. autodata:: websockets.datastructures.HeadersLike
.. autodata:: websockets.datastructures.SupportsKeysAndGetItem
websockets-14.1/docs/reference/variables.rst 0000664 0000000 0000000 00000004012 14715046371 0021205 0 ustar 00root root 0000000 0000000 Environment variables
=====================
.. currentmodule:: websockets
Logging
-------
.. envvar:: WEBSOCKETS_MAX_LOG_SIZE
How much of each frame to show in debug logs.
The default value is ``75``.
See the :doc:`logging guide <../topics/logging>` for details.
Security
--------
.. envvar:: WEBSOCKETS_SERVER
Server header sent by websockets.
The default value uses the format ``"Python/x.y.z websockets/X.Y"``.
.. envvar:: WEBSOCKETS_USER_AGENT
User-Agent header sent by websockets.
The default value uses the format ``"Python/x.y.z websockets/X.Y"``.
.. envvar:: WEBSOCKETS_MAX_LINE_LENGTH
Maximum length of the request or status line in the opening handshake.
The default value is ``8192`` bytes.
.. envvar:: WEBSOCKETS_MAX_NUM_HEADERS
Maximum number of HTTP headers in the opening handshake.
The default value is ``128`` bytes.
.. envvar:: WEBSOCKETS_MAX_BODY_SIZE
Maximum size of the body of an HTTP response in the opening handshake.
The default value is ``1_048_576`` bytes (1 MiB).
See the :doc:`security guide <../topics/security>` for details.
Reconnection
------------
Reconnection attempts are spaced out with truncated exponential backoff.
.. envvar:: BACKOFF_INITIAL_DELAY
The first attempt is delayed by a random amount of time between ``0`` and
``BACKOFF_INITIAL_DELAY`` seconds.
The default value is ``5.0`` seconds.
.. envvar:: BACKOFF_MIN_DELAY
The second attempt is delayed by ``BACKOFF_MIN_DELAY`` seconds.
The default value is ``3.1`` seconds.
.. envvar:: BACKOFF_FACTOR
After the second attempt, the delay is multiplied by ``BACKOFF_FACTOR``
between each attempt.
The default value is ``1.618``.
.. envvar:: BACKOFF_MAX_DELAY
The delay between attempts is capped at ``BACKOFF_MAX_DELAY`` seconds.
The default value is ``90.0`` seconds.
Redirects
---------
.. envvar:: WEBSOCKETS_MAX_REDIRECTS
Maximum number of redirects that :func:`~asyncio.client.connect` follows.
The default value is ``10``.
websockets-14.1/docs/requirements.txt 0000664 0000000 0000000 00000000200 14715046371 0020024 0 ustar 00root root 0000000 0000000 furo
sphinx
sphinx-autobuild
sphinx-copybutton
sphinx-inline-tabs
sphinxcontrib-spelling
sphinxcontrib-trio
sphinxext-opengraph
websockets-14.1/docs/spelling_wordlist.txt 0000664 0000000 0000000 00000001363 14715046371 0021060 0 ustar 00root root 0000000 0000000 augustin
auth
autoscaler
aymeric
backend
backoff
backpressure
balancer
balancers
bottlenecked
bufferbloat
bugfix
buildpack
bytestring
bytestrings
changelog
coroutine
coroutines
cryptocurrencies
cryptocurrency
css
ctrl
deserialize
dev
django
Dockerfile
dyno
formatter
fractalideas
gunicorn
healthz
html
hypercorn
iframe
IPv
istio
iterable
js
keepalive
KiB
kubernetes
lifecycle
linkerd
liveness
lookups
MiB
middleware
mutex
mypy
nginx
Paketo
permessage
pid
procfile
proxying
py
pythonic
reconnection
redis
redistributions
retransmit
retryable
runtime
scalable
stateful
subclasses
subclassing
submodule
subpackages
subprotocol
subprotocols
supervisord
tidelift
tls
tox
txt
unregister
uple
uvicorn
uvloop
virtualenv
websocket
WebSocket
websockets
ws
wsgi
www
websockets-14.1/docs/topics/ 0000775 0000000 0000000 00000000000 14715046371 0016051 5 ustar 00root root 0000000 0000000 websockets-14.1/docs/topics/authentication.rst 0000664 0000000 0000000 00000026157 14715046371 0021635 0 ustar 00root root 0000000 0000000 Authentication
==============
The WebSocket protocol is designed for creating web applications that require
bidirectional communication between browsers and servers.
In most practical use cases, WebSocket servers need to authenticate clients in
order to route communications appropriately and securely.
:rfc:`6455` remains elusive when it comes to authentication:
This protocol doesn't prescribe any particular way that servers can
authenticate clients during the WebSocket handshake. The WebSocket
server can use any client authentication mechanism available to a
generic HTTP server, such as cookies, HTTP authentication, or TLS
authentication.
None of these three mechanisms works well in practice. Using cookies is
cumbersome, HTTP authentication isn't supported by all mainstream browsers,
and TLS authentication in a browser is an esoteric user experience.
Fortunately, there are better alternatives! Let's discuss them.
System design
-------------
Consider a setup where the WebSocket server is separate from the HTTP server.
Most servers built with websockets adopt this design because they're a component
in a web application and websockets doesn't aim at supporting HTTP.
The following diagram illustrates the authentication flow.
.. image:: authentication.svg
Assuming the current user is authenticated with the HTTP server (1), the
application needs to obtain credentials from the HTTP server (2) in order to
send them to the WebSocket server (3), who can check them against the database
of user accounts (4).
Usernames and passwords aren't a good choice of credentials here, if only
because passwords aren't available in clear text in the database.
Tokens linked to user accounts are a better choice. These tokens must be
impossible to forge by an attacker. For additional security, they can be
short-lived or even single-use.
Sending credentials
-------------------
Assume the web application obtained authentication credentials, likely a
token, from the HTTP server. There's four options for passing them to the
WebSocket server.
1. **Sending credentials as the first message in the WebSocket connection.**
This is fully reliable and the most secure mechanism in this discussion. It
has two minor downsides:
* Authentication is performed at the application layer. Ideally, it would
be managed at the protocol layer.
* Authentication is performed after the WebSocket handshake, making it
impossible to monitor authentication failures with HTTP response codes.
2. **Adding credentials to the WebSocket URI in a query parameter.**
This is also fully reliable but less secure. Indeed, it has a major
downside:
* URIs end up in logs, which leaks credentials. Even if that risk could be
lowered with single-use tokens, it is usually considered unacceptable.
Authentication is still performed at the application layer but it can
happen before the WebSocket handshake, which improves separation of
concerns and enables responding to authentication failures with HTTP 401.
3. **Setting a cookie on the domain of the WebSocket URI.**
Cookies are undoubtedly the most common and hardened mechanism for sending
credentials from a web application to a server. In an HTTP application,
credentials would be a session identifier or a serialized, signed session.
Unfortunately, when the WebSocket server runs on a different domain from
the web application, this idea hits the wall of the `Same-Origin Policy`_.
For security reasons, setting a cookie on a different origin is impossible.
The proper workaround consists in:
* creating a hidden iframe_ served from the domain of the WebSocket server
* sending the token to the iframe with postMessage_
* setting the cookie in the iframe
before opening the WebSocket connection.
Sharing a parent domain (e.g. example.com) between the HTTP server (e.g.
www.example.com) and the WebSocket server (e.g. ws.example.com) and setting
the cookie on that parent domain would work too.
However, the cookie would be shared with all subdomains of the parent
domain. For a cookie containing credentials, this is unacceptable.
.. _Same-Origin Policy: https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy
.. _iframe: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe
.. _postMessage: https://developer.mozilla.org/en-US/docs/Web/API/MessagePort/postMessage
4. **Adding credentials to the WebSocket URI in user information.**
Letting the browser perform HTTP Basic Auth is a nice idea in theory.
In practice it doesn't work due to browser support limitations:
* Chrome behaves as expected.
* Firefox caches credentials too aggressively.
When connecting again to the same server with new credentials, it reuses
the old credentials, which may be expired, resulting in an HTTP 401. Then
the next connection succeeds. Perhaps errors clear the cache.
When tokens are short-lived or single-use, this bug produces an
interesting effect: every other WebSocket connection fails.
* Safari behaves as expected.
Two other options are off the table:
1. **Setting a custom HTTP header**
This would be the most elegant mechanism, solving all issues with the options
discussed above.
Unfortunately, it doesn't work because the `WebSocket API`_ doesn't support
`setting custom headers`_.
.. _WebSocket API: https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API
.. _setting custom headers: https://github.com/whatwg/html/issues/3062
2. **Authenticating with a TLS certificate**
While this is suggested by the RFC, installing a TLS certificate is too far
from the mainstream experience of browser users. This could make sense in
high security contexts.
I hope that developers working on projects in this category don't take
security advice from the documentation of random open source projects :-)
Let's experiment!
-----------------
The `experiments/authentication`_ directory demonstrates these techniques.
Run the experiment in an environment where websockets is installed:
.. _experiments/authentication: https://github.com/python-websockets/websockets/tree/main/experiments/authentication
.. code-block:: console
$ python experiments/authentication/app.py
Running on http://localhost:8000/
When you browse to the HTTP server at http://localhost:8000/ and you submit a
username, the server creates a token and returns a testing web page.
This page opens WebSocket connections to four WebSocket servers running on
four different origins. It attempts to authenticate with the token in four
different ways.
First message
.............
As soon as the connection is open, the client sends a message containing the
token:
.. code-block:: javascript
const websocket = new WebSocket("ws://.../");
websocket.onopen = () => websocket.send(token);
// ...
At the beginning of the connection handler, the server receives this message
and authenticates the user. If authentication fails, the server closes the
connection:
.. code-block:: python
from websockets.frames import CloseCode
async def first_message_handler(websocket):
token = await websocket.recv()
user = get_user(token)
if user is None:
await websocket.close(CloseCode.INTERNAL_ERROR, "authentication failed")
return
...
Query parameter
...............
The client adds the token to the WebSocket URI in a query parameter before
opening the connection:
.. code-block:: javascript
const uri = `ws://.../?token=${token}`;
const websocket = new WebSocket(uri);
// ...
The server intercepts the HTTP request, extracts the token and authenticates
the user. If authentication fails, it returns an HTTP 401:
.. code-block:: python
async def query_param_auth(connection, request):
token = get_query_param(request.path, "token")
if token is None:
return connection.respond(http.HTTPStatus.UNAUTHORIZED, "Missing token\n")
user = get_user(token)
if user is None:
return connection.respond(http.HTTPStatus.UNAUTHORIZED, "Invalid token\n")
connection.username = user
Cookie
......
The client sets a cookie containing the token before opening the connection.
The cookie must be set by an iframe loaded from the same origin as the
WebSocket server. This requires passing the token to this iframe.
.. code-block:: javascript
// in main window
iframe.contentWindow.postMessage(token, "http://...");
// in iframe
document.cookie = `token=${data}; SameSite=Strict`;
// in main window
const websocket = new WebSocket("ws://.../");
// ...
This sequence must be synchronized between the main window and the iframe.
This involves several events. Look at the full implementation for details.
The server intercepts the HTTP request, extracts the token and authenticates
the user. If authentication fails, it returns an HTTP 401:
.. code-block:: python
async def cookie_auth(connection, request):
# Serve iframe on non-WebSocket requests
...
token = get_cookie(request.headers.get("Cookie", ""), "token")
if token is None:
return connection.respond(http.HTTPStatus.UNAUTHORIZED, "Missing token\n")
user = get_user(token)
if user is None:
return connection.respond(http.HTTPStatus.UNAUTHORIZED, "Invalid token\n")
connection.username = user
User information
................
The client adds the token to the WebSocket URI in user information before
opening the connection:
.. code-block:: javascript
const uri = `ws://token:${token}@.../`;
const websocket = new WebSocket(uri);
// ...
Since HTTP Basic Auth is designed to accept a username and a password rather
than a token, we send ``token`` as username and the token as password.
The server intercepts the HTTP request, extracts the token and authenticates
the user. If authentication fails, it returns an HTTP 401:
.. code-block:: python
from websockets.asyncio.server import basic_auth as websockets_basic_auth
def check_credentials(username, password):
return username == get_user(password)
basic_auth = websockets_basic_auth(check_credentials=check_credentials)
Machine-to-machine authentication
---------------------------------
When the WebSocket client is a standalone program rather than a script running
in a browser, there are far fewer constraints. HTTP Authentication is the best
solution in this scenario.
To authenticate a websockets client with HTTP Basic Authentication
(:rfc:`7617`), include the credentials in the URI:
.. code-block:: python
from websockets.asyncio.client import connect
async with connect(f"wss://{username}:{password}@.../") as websocket:
...
(You must :func:`~urllib.parse.quote` ``username`` and ``password`` if they
contain unsafe characters.)
To authenticate a websockets client with HTTP Bearer Authentication
(:rfc:`6750`), add a suitable ``Authorization`` header:
.. code-block:: python
from websockets.asyncio.client import connect
headers = {"Authorization": f"Bearer {token}"}
async with connect("wss://.../", additional_headers=headers) as websocket:
...
websockets-14.1/docs/topics/authentication.svg 0000664 0000000 0000000 00000027417 14715046371 0021624 0 ustar 00root root 0000000 0000000