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
3434import trio
@@ -37,25 +37,25 @@ from trio_websocket import open_websocket_url
3737
3838async 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
4547trio.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
5656A 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
6161import trio
@@ -76,181 +76,7 @@ async def main():
7676trio.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.
0 commit comments