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
+
1
16
[ ![ PyPI] ( https://img.shields.io/pypi/v/trio-websocket.svg?style=flat-square )] ( https://pypi.org/project/trio-websocket/ )
2
17
![ Python Versions] ( https://img.shields.io/pypi/pyversions/trio-websocket.svg?style=flat-square )
3
18
![ MIT License] ( https://img.shields.io/github/license/HyperionGray/trio-websocket.svg?style=flat-square )
4
19
[ ![ Build Status] ( https://img.shields.io/travis/HyperionGray/trio-websocket.svg?style=flat-square )] ( https://travis-ci.org/HyperionGray/trio-websocket )
5
20
[ ![ 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 )
14
22
15
23
## Installation
16
24
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:
18
26
19
27
pip install trio-websocket
20
28
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
26
30
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:
32
32
33
33
``` python
34
34
import trio
@@ -37,25 +37,25 @@ from trio_websocket import open_websocket_url
37
37
38
38
async def main ():
39
39
try :
40
- async with open_websocket_url(' ws ://localhost/foo' ) as ws:
40
+ async with open_websocket_url(' wss ://localhost/foo' ) as ws:
41
41
await ws.send_message(' hello world!' )
42
+ message = await ws.get_message()
43
+ logging.info(' Received message: %s ' , message)
42
44
except OSError as ose:
43
45
logging.error(' Connection attempt failed: %s ' , ose)
44
46
45
47
trio.run(main)
46
48
```
47
49
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.
53
53
54
- ## Sample server
54
+ ## Server Example
55
55
56
56
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.
59
59
60
60
``` python
61
61
import trio
@@ -76,181 +76,7 @@ async def main():
76
76
trio.run(main)
77
77
```
78
78
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