Skip to content

Commit 82bd679

Browse files
authored
Merge pull request #71 from HyperionGray/create_docs
Create docs
2 parents 8d6d355 + 5fdc3c8 commit 82bd679

17 files changed

+991
-320
lines changed

README.md

Lines changed: 32 additions & 206 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,34 @@
1+
# Trio WebSocket
2+
3+
This library implements [the WebSocket
4+
protocol](https://tools.ietf.org/html/rfc6455), striving for safety,
5+
correctness, and ergonomics. It is based on the [wsproto
6+
project](https://wsproto.readthedocs.io/en/latest/), which is a
7+
[Sans-IO](https://sans-io.readthedocs.io/) state machine that implements the
8+
majority of the WebSocket protocol, including framing, codecs, and events. This
9+
library handles I/O using [the Trio
10+
framework](https://trio.readthedocs.io/en/latest/). This library passes the
11+
[Autobahn Test Suite](https://github.com/crossbario/autobahn-testsuite).
12+
13+
This README contains a brief introduction to the project. Full documentation [is
14+
available here](https://trio-websocket.readthedocs.io).
15+
116
[![PyPI](https://img.shields.io/pypi/v/trio-websocket.svg?style=flat-square)](https://pypi.org/project/trio-websocket/)
217
![Python Versions](https://img.shields.io/pypi/pyversions/trio-websocket.svg?style=flat-square)
318
![MIT License](https://img.shields.io/github/license/HyperionGray/trio-websocket.svg?style=flat-square)
419
[![Build Status](https://img.shields.io/travis/HyperionGray/trio-websocket.svg?style=flat-square)](https://travis-ci.org/HyperionGray/trio-websocket)
520
[![Coverage](https://img.shields.io/coveralls/github/HyperionGray/trio-websocket.svg?style=flat-square)](https://coveralls.io/github/HyperionGray/trio-websocket?branch=master)
6-
7-
# Trio WebSocket
8-
9-
This project implements [the WebSocket
10-
protocol](https://tools.ietf.org/html/rfc6455). It is based on the [wsproto
11-
project](https://wsproto.readthedocs.io/en/latest/), which is a [Sans-IO](https://sans-io.readthedocs.io/) state machine that implements the majority of
12-
the WebSocket protocol, including framing, codecs, and events. This library
13-
implements the I/O using [Trio](https://trio.readthedocs.io/en/latest/).
21+
[![Read the Docs](https://img.shields.io/readthedocs/trio-websocket.svg)](https://trio-websocket.readthedocs.io)
1422

1523
## Installation
1624

17-
`trio-websocket` requires Python v3.5 or greater. To install from PyPI:
25+
This library requires Python 3.5 or greater. To install from PyPI:
1826

1927
pip install trio-websocket
2028

21-
If you want to help develop `trio-websocket`, clone [the
22-
repository](https://github.com/hyperiongray/trio-websocket) and run this command
23-
from the repository root:
24-
25-
pip install --editable .[dev]
29+
## Client Example
2630

27-
## Sample client
28-
29-
The following example demonstrates opening a WebSocket by URL. The connection
30-
may also be opened with `open_websocket(…)`, which takes a host, port, and
31-
resource as arguments.
31+
This example demonstrates how to open a WebSocket URL:
3232

3333
```python
3434
import trio
@@ -37,25 +37,25 @@ from trio_websocket import open_websocket_url
3737

3838
async def main():
3939
try:
40-
async with open_websocket_url('ws://localhost/foo') as ws:
40+
async with open_websocket_url('wss://localhost/foo') as ws:
4141
await ws.send_message('hello world!')
42+
message = await ws.get_message()
43+
logging.info('Received message: %s', message)
4244
except OSError as ose:
4345
logging.error('Connection attempt failed: %s', ose)
4446

4547
trio.run(main)
4648
```
4749

48-
A more detailed example is in `examples/client.py`. **Note:** if you want to run
49-
this example client with SSL, you'll need to install the `trustme` module from
50-
PyPI (installed automtically if you used the `[dev]` extras when installing
51-
`trio-websocket`) and then generate a self-signed certificate by running
52-
`example/generate-cert.py`.
50+
The WebSocket context manager connects automatically before entering the block
51+
and disconnects automatically before exiting the block. The full API offers a
52+
lot of flexibility and additional options.
5353

54-
## Sample server
54+
## Server Example
5555

5656
A WebSocket server requires a bind address, a port, and a coroutine to handle
57-
incoming connections. This example demonstrates an "echo server" that replies
58-
to each incoming message with an identical outgoing message.
57+
incoming connections. This example demonstrates an "echo server" that replies to
58+
each incoming message with an identical outgoing message.
5959

6060
```python
6161
import trio
@@ -76,181 +76,7 @@ async def main():
7676
trio.run(main)
7777
```
7878

79-
A longer example is in `examples/server.py`. **See the note above about using
80-
SSL with the example client.**
81-
82-
## Heartbeat recipe
83-
84-
If you wish to keep a connection open for long periods of time but do not need
85-
to send messages frequently, then a heartbeat holds the connection open and also
86-
detects when the connection drops unexpectedly. The following recipe
87-
demonstrates how to implement a connection heartbeat using WebSocket's ping/pong
88-
feature.
89-
90-
```python
91-
async def heartbeat(ws, timeout, interval):
92-
'''
93-
Send periodic pings on WebSocket ``ws``.
94-
95-
Wait up to ``timeout`` seconds to send a ping and receive a pong. Raises
96-
``TooSlowError`` if the timeout is exceeded. If a pong is received, then
97-
wait ``interval`` seconds before sending the next ping.
98-
99-
This function runs until cancelled.
100-
101-
:param ws: A WebSocket to send heartbeat pings on.
102-
:param float timeout: Timeout in seconds.
103-
:param float interval: Interval between receiving pong and sending next
104-
ping, in seconds.
105-
:raises: ``ConnectionClosed`` if ``ws`` is closed.
106-
:raises: ``TooSlowError`` if the timeout expires.
107-
:returns: This function runs until cancelled.
108-
'''
109-
while True:
110-
with trio.fail_after(timeout):
111-
await ws.ping()
112-
await trio.sleep(interval)
113-
114-
async def main():
115-
async with open_websocket_url('ws://localhost/foo') as ws:
116-
async with trio.open_nursery() as nursery:
117-
nursery.start_soon(heartbeat, ws, 5, 1)
118-
# Your application code goes here:
119-
pass
120-
121-
trio.run(main)
122-
```
123-
124-
Note that the `ping()` method waits until it receives a pong frame, so it
125-
ensures that the remote endpoint is still responsive. If the connection is
126-
dropped unexpectedly or takes too long to respond, then `heartbeat()` will raise
127-
an exception that will cancel the nursery. You may wish to implement additional
128-
logic to automatically reconnect.
129-
130-
A heartbeat feature can be enabled in the example client with the
131-
``--heartbeat`` flag.
132-
133-
**Note that the WebSocket RFC does not require a WebSocket to send a pong for each
134-
ping:**
135-
136-
> If an endpoint receives a Ping frame and has not yet sent Pong frame(s) in
137-
> response to previous Ping frame(s), the endpoint MAY elect to send a Pong
138-
> frame for only the most recently processed Ping frame.
139-
140-
Therefore, if you have multiple pings in flight at the same time, you may not
141-
get an equal number of pongs in response. The simplest strategy for dealing with
142-
this is to only have one ping in flight at a time, as seen in the example above.
143-
As an alternative, you can send a `bytes` payload with each ping. The server
144-
will return the payload with the pong:
145-
146-
```python
147-
await ws.ping(b'my payload')
148-
pong == await ws.wait_pong()
149-
assert pong == b'my payload'
150-
```
151-
152-
You may want to embed a nonce or counter in the payload in order to correlate
153-
pong events to the pings you have sent.
154-
155-
## Unit Tests
156-
157-
Unit tests are written in the pytest style. You must install the development
158-
dependencies as described in the installation section above. The
159-
``--cov=trio_websocket`` flag turns on code coverage.
160-
161-
$ pytest --cov=trio_websocket
162-
=== test session starts ===
163-
platform linux -- Python 3.6.6, pytest-3.8.0, py-1.6.0, pluggy-0.7.1
164-
rootdir: /home/mhaase/code/trio-websocket, inifile: pytest.ini
165-
plugins: trio-0.5.0, cov-2.6.0
166-
collected 21 items
167-
168-
tests/test_connection.py ..................... [100%]
169-
170-
--- coverage: platform linux, python 3.6.6-final-0 ---
171-
Name Stmts Miss Cover
172-
------------------------------------------------
173-
trio_websocket/__init__.py 297 40 87%
174-
trio_websocket/_channel.py 140 52 63%
175-
trio_websocket/version.py 1 0 100%
176-
------------------------------------------------
177-
TOTAL 438 92 79%
178-
179-
=== 21 passed in 0.54 seconds ===
180-
181-
## Integration Testing with Autobahn
182-
183-
The Autobahn Test Suite contains over 500 integration tests for WebSocket
184-
servers and clients. These test suites are contained in a
185-
[Docker](https://www.docker.com/) container. You will need to install Docker
186-
before you can run these integration tests.
187-
188-
### Client Tests
189-
190-
To test the client, you will need two terminal windows. In the first terminal,
191-
run the following commands:
192-
193-
$ cd autobahn
194-
$ docker run -it --rm \
195-
-v "${PWD}/config:/config" \
196-
-v "${PWD}/reports:/reports" \
197-
-p 9001:9001 \
198-
--name autobahn \
199-
crossbario/autobahn-testsuite
200-
201-
The first time you run this command, Docker will download some files, which may
202-
take a few minutes. When the test suite is ready, it will display:
203-
204-
Autobahn WebSocket 0.8.0/0.10.9 Fuzzing Server (Port 9001)
205-
Ok, will run 249 test cases for any clients connecting
206-
207-
Now in the second terminal, run the Autobahn client:
208-
209-
$ cd autobahn
210-
$ python client.py ws://localhost:9001
211-
INFO:client:Case count=249
212-
INFO:client:Running test case 1 of 249
213-
INFO:client:Running test case 2 of 249
214-
INFO:client:Running test case 3 of 249
215-
INFO:client:Running test case 4 of 249
216-
INFO:client:Running test case 5 of 249
217-
<snip>
218-
219-
When the client finishes running, an HTML report is published to the
220-
`autobahn/reports/clients` directory. If any tests fail, you can debug
221-
individual tests by specifying the integer test case ID (not the dotted test
222-
case ID), e.g. to run test case #29:
223-
224-
$ python client.py ws://localhost:9001 29
225-
226-
### Server Tests
227-
228-
Once again, you will need two terminal windows. In the first terminal, run:
229-
230-
$ cd autobahn
231-
$ python server.py
232-
233-
In the second terminal, you will run the Docker image.
234-
235-
$ cd autobahn
236-
$ docker run -it --rm \
237-
-v "${PWD}/config:/config" \
238-
-v "${PWD}/reports:/reports" \
239-
--name autobahn \
240-
crossbario/autobahn-testsuite \
241-
/usr/local/bin/wstest --mode fuzzingclient --spec /config/fuzzingclient.json
242-
243-
If a test fails, `server.py` does not support the same `debug_cases` argument as
244-
`client.py`, but you can modify `fuzzingclient.json` to specify a subset of
245-
cases to run, e.g. `3.*` to run all test cases in section 3.
246-
247-
## Release Process
248-
249-
* Remove `-dev` suffix from `version.py`.
250-
* Commit and push version change.
251-
* Create and push tag, e.g. `git tag 1.0.0 && git push origin 1.0.0`.
252-
* Clean build directory: `rm -fr dist`
253-
* Build package: `python setup.py sdist`
254-
* Upload to PyPI: `twine upload dist/*`
255-
* Increment version and add `-dev` suffix.
256-
* Commit and push version change.
79+
The server's handler ``echo_server(…)`` receives a connection request object.
80+
This object can be used to inspect the client's request and modify the
81+
handshake, then it can be exchanged for an actual WebSocket object ``ws``.
82+
Again, the full API offers a lot of flexibility and additional options.

docs/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
_build
2+

docs/Makefile

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Minimal makefile for Sphinx documentation
2+
#
3+
4+
# You can set these variables from the command line.
5+
SPHINXOPTS =
6+
SPHINXBUILD = sphinx-build
7+
SOURCEDIR = .
8+
BUILDDIR = _build
9+
10+
# Put it first so that "make" without argument is like "make help".
11+
help:
12+
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
13+
14+
.PHONY: help Makefile
15+
16+
# Catch-all target: route all unknown targets to Sphinx using the new
17+
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
18+
%: Makefile
19+
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

docs/_static/README.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
This is just a placeholder file because this project doesn't
2+
have any static assets.
3+

docs/api.rst

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
API
2+
===
3+
4+
.. currentmodule:: trio_websocket
5+
6+
In addition to the convenience functions documented in :ref:`websocket-clients`
7+
and :ref:`websocket-servers`, the API has several important classes described
8+
on this page.
9+
10+
Requests
11+
--------
12+
13+
.. class:: WebSocketRequest
14+
15+
A request object presents the client's handshake to a server handler. The
16+
server can inspect handshake properties like HTTP headers, subprotocols, etc.
17+
The server can also set some handshake properties like subprotocol. The
18+
server should call :meth:`accept` to complete the handshake and obtain a
19+
connection object.
20+
21+
.. autoattribute:: headers
22+
.. autoattribute:: proposed_subprotocols
23+
.. autoattribute:: subprotocol
24+
.. autoattribute:: url
25+
.. automethod:: accept
26+
27+
Connections
28+
-----------
29+
30+
.. class:: WebSocketConnection
31+
32+
A connection object has functionality for sending and receiving messages,
33+
pinging the remote endpoint, and closing the WebSocket.
34+
35+
.. note::
36+
37+
The preferred way to obtain a connection is to use one of the
38+
convenience functions described in :ref:`websocket-clients` or
39+
:ref:`websocket-servers`. Instantiating a connection instance directly is
40+
tricky and is not recommended.
41+
42+
This object has properties that expose connection metadata.
43+
44+
.. autoattribute:: is_closed
45+
.. autoattribute:: close_reason
46+
.. autoattribute:: is_client
47+
.. autoattribute:: is_server
48+
.. autoattribute:: path
49+
.. autoattribute:: subprotocol
50+
51+
A connection object has a pair of methods for sending and receiving
52+
WebSocket messages. Messages can be ``str`` or ``bytes`` objects.
53+
54+
.. automethod:: send_message
55+
.. automethod:: get_message
56+
57+
A connection object also has methods for sending pings and pongs. Each ping
58+
is sent with a unique payload, and the function blocks until a corresponding
59+
pong is received from the remote endpoint. This feature can be used to
60+
implement a bidirectional heartbeat.
61+
62+
A pong, on the other hand, sends an unsolicited pong to the remote endpoint
63+
and does not expect or wait for a response. This feature can be used to
64+
implement a unidirectional heartbeat.
65+
66+
.. automethod:: ping
67+
.. automethod:: pong
68+
69+
Finally, the socket offers a method to close the connection. The connection
70+
context managers in :ref:`websocket-clients` and :ref:`websocket-servers`
71+
will automatically close the connection for you, but you may want to close
72+
the connection explicity if you are not using a context manager or if you
73+
want to customize the close reason.
74+
75+
.. automethod:: aclose
76+
77+
.. autoclass:: CloseReason
78+
:members:
79+
80+
.. autoexception:: ConnectionClosed

0 commit comments

Comments
 (0)