Skip to content

Commit 73e9d98

Browse files
mehaaseKriechi
authored andcommitted
Update docs for 0.14 tag (python-hyper#118)
Flesh out some missing pydocs, clean up the introduction, and make the examples more common wsproto idioms.
1 parent 76fc8e9 commit 73e9d98

File tree

8 files changed

+216
-183
lines changed

8 files changed

+216
-183
lines changed

Diff for: docs/source/api.rst

+10
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,13 @@ Connection
1616
----------
1717

1818
.. autoclass:: wsproto.WSConnection
19+
:special-members: __init__
20+
:members:
21+
22+
.. autoclass:: wsproto.ConnectionType
23+
:members:
24+
25+
.. autoclass:: wsproto.connection.ConnectionState
1926
:members:
2027

2128
Handshake
@@ -31,6 +38,9 @@ Handshake
3138
Events
3239
------
3340

41+
Event constructors accept any field as a keyword argument. Some fields are
42+
required, while others have default values.
43+
3444
.. autoclass:: wsproto.events.Event
3545
:members:
3646

Diff for: docs/source/basic-usage.rst

+76-99
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
Getting Started
22
===============
33

4+
.. currentmodule:: wsproto
5+
46
This document explains how to get started using wsproto to connect to
57
WebSocket servers as well as how to write your own.
68

@@ -13,93 +15,82 @@ behind writing a network protocol library that doesn't do any network I/O.
1315
Connections
1416
-----------
1517

16-
The main class you'll be working with is the
17-
:class:`WSConnection <wsproto.WSConnection>` object. This object
18-
represents a connection to a WebSocket client or server and contains all the
19-
state needed to communicate with the entity at the other end. Whether you're
20-
connecting to a server or receiving a connection from a client, this is the
21-
object you'll use.
18+
The main class you'll be working with is the :class:`WSConnection` object. This
19+
object represents a connection to a WebSocket peer. This class can handle both
20+
WebSocket clients and WebSocket servers.
2221

23-
`wsproto` provides two layers of abstractions. You need to write code that
22+
``wsproto`` provides two layers of abstractions. You need to write code that
2423
interfaces with both of these layers. The following diagram illustrates how your
25-
code is like a sandwich around `wsproto`.
26-
27-
+--------------------+
28-
| Application |
29-
+--------------------+
30-
| <APPLICATION GLUE> |
31-
+--------------------+
32-
| wsproto |
33-
+--------------------+
34-
| <NETWORK GLUE> |
35-
+--------------------+
36-
| Network Layer |
37-
+--------------------+
38-
39-
`wsproto` does not do perform any network I/O, so ``<NETWORK GLUE>``
40-
represents the code you need to write to glue `wsproto` to the actual
41-
network layer, i.e. code that can send and receive data over the
42-
network. The :class:`WSConnection <wsproto.WSConnection>`
43-
class provides two methods for this purpose. When data has been
44-
received on a network socket, you feed this data into `wsproto` by
45-
calling :meth:`receive_data
46-
<wsproto.WSConnection.receive_data>`. When `wsproto` sends
47-
events the :meth:`send <wsproto.WSConnection.send>` will
48-
return the bytes that need to be sent over the network. Your code is
49-
responsible for actually sending that data over the network.
24+
code is like a sandwich around ``wsproto``.
25+
26+
+----------------------+
27+
| Application |
28+
+----------------------+
29+
| **APPLICATION GLUE** |
30+
+----------------------+
31+
| wsproto |
32+
+----------------------+
33+
| **NETWORK GLUE** |
34+
+----------------------+
35+
| Network Layer |
36+
+----------------------+
37+
38+
``wsproto`` does not do perform any network I/O, so **NETWORK GLUE** represents
39+
the code you need to write to glue ``wsproto`` to an actual network, for example
40+
using Python's `socket <https://docs.python.org/3/library/socket.html>`_ module.
41+
The :class:`WSConnection` class provides two methods for this purpose. When data
42+
has been received on a network socket, you should feed this data into a
43+
connection instance by calling :meth:`WSConnection.receive_data`. When you want
44+
to communicate with the remote peer, e.g. send a message, ping, or close the
45+
connection, you should create an instance of one of the
46+
:class:`wsproto.events.Event` subclasses and pass it to
47+
:meth:`WSConnection.send` to get the corresponding bytes that need to be sent.
48+
Your code is responsible for actually sending that data over the network.
5049

5150
.. note::
5251

5352
If the connection drops, a standard Python ``socket.recv()`` will return
54-
zero. You should call ``receive_data(None)`` to update the internal
55-
`wsproto` state to indicate that the connection has been closed.
56-
57-
Internally, `wsproto` process the raw network data you feed into it and turns it
58-
into higher level representations of WebSocket events. In ``<APPLICATION
59-
GLUE>``, you need to write code to process these events. The
60-
:class:`WSConnection <wsproto.WSConnection>` class contains a
61-
generator method :meth:`events <wsproto.WSConnection.events>` that
62-
yields WebSocket events. To send a message, you call the :meth:`send
63-
<wsproto.WSConnection.send>` method.
64-
65-
Connecting to a WebSocket server
66-
--------------------------------
67-
68-
Begin by instantiating a connection object in the client mode and then
69-
create a :class:`Request <wsproto.events.Request>` instance to
70-
send. The Request must specify ``host`` and ``target`` arguments. If
71-
the WebSocket server is located at ``http://example.com/foo``, then you
72-
would instantiate the connection as follows::
53+
zero bytes. You should call ``receive_data(None)`` to update the internal
54+
``wsproto`` state to indicate that the connection has been closed.
55+
56+
Internally, ``wsproto`` processes the raw network data you feed into it and
57+
turns it into higher level representations of WebSocket events. In **APPLICATION
58+
GLUE**, you need to write code to process these events. Incoming data is exposed
59+
though the generator method :meth:`WSConnection.events`, which yields WebSocket
60+
events. Each event is an instance of an :class:`.events.Event` subclass.
61+
62+
WebSocket Clients
63+
-----------------
7364

65+
Begin by instantiating a connection object in client mode and then create a
66+
:class:`wsproto.events.Request` instance. The Request must specify ``host`` and
67+
``target`` arguments. If the WebSocket server is located at
68+
``http://example.com/foo``, then you would instantiate the connection as
69+
follows::
70+
71+
from wsproto import ConnectionType, WSConnection
72+
from wsproto.events import Request
7473
ws = WSConnection(ConnectionType.CLIENT)
75-
ws.send(Request(host="example.com", target='foo'))
74+
request = Request(host="example.com", target='foo')
75+
data = ws.send(request)
7676

77-
Now you need to provide the network glue. For the sake of example, we will use
78-
standard Python sockets here, but `wsproto` can be integrated with any network
79-
layer::
77+
Keep in mind that ``wsproto`` does not do any network I/O. Instead,
78+
:meth:`WSConnection.send` returns data that you must send to the remote peer.
79+
Here is an example using a standard Python socket::
8080

8181
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
8282
sock.connect(("example.com", 80))
83+
sock.send(data)
8384

84-
To read from the network::
85+
To receive communications from the peer, you must pass the data received from
86+
the peer into the connection instance::
8587

8688
data = sock.recv(4096)
8789
ws.receive_data(data)
8890

89-
You also need to send data returned by the send method::
90-
91-
data = ws.send(Message(data=b"Hello"))
92-
sock.send(data)
93-
94-
A standard Python socket will block on the call to ``sock.recv()``, so you
95-
will probably need to use a non-blocking socket or some form of concurrency like
96-
threading, greenlets, asyncio, etc.
97-
98-
You also need to provide the application glue. To send a WebSocket message::
99-
100-
ws.send(Message(data="Hello world!"))
101-
102-
And to receive WebSocket events::
91+
The connection instance parses the received data and determines if any high-level
92+
events have occurred, such as receiving a ping or a message. To retrieve these
93+
events, use the generator function :meth:`WSConnection.events`::
10394

10495
for event in ws.events():
10596
if isinstance(event, AcceptConnection):
@@ -126,7 +117,7 @@ And to receive WebSocket events::
126117
print('Unknown event: {!r}'.format(event))
127118

128119
The method ``events()`` returns a generator which will yield events for all of
129-
the data currently in the `wsproto` internal buffer and then exit. Therefore,
120+
the data currently in the ``wsproto`` internal buffer and then exit. Therefore,
130121
you should iterate over this generator after receiving new network data.
131122

132123
For a more complete example, see `synchronous_client.py
@@ -135,9 +126,13 @@ For a more complete example, see `synchronous_client.py
135126
WebSocket Servers
136127
-----------------
137128

138-
A WebSocket server is similar to a client except that it uses a different
139-
constant::
129+
A WebSocket server is similar to a client, but it uses a different
130+
:class:`wsproto.ConnectionType` constant.
140131

132+
::
133+
134+
from wsproto import ConnectionType, WSConnection
135+
from wsproto.events import Request
141136
ws = WSConnection(ConnectionType.SERVER)
142137

143138
A server also needs to explicitly send an :class:`AcceptConnection
@@ -148,24 +143,7 @@ A server also needs to explicitly send an :class:`AcceptConnection
148143
if isinstance(event, Request):
149144
print('Accepting connection request')
150145
sock.send(ws.send(AcceptConnection()))
151-
elif isinstance(event, CloseConnection):
152-
print('Connection closed: code={} reason={}'.format(
153-
event.code, event.reason
154-
))
155-
sock.send(ws.send(event.response()))
156-
elif isinstance(event, Ping):
157-
print('Received Ping frame with payload {}'.format(event.payload))
158-
sock.send(ws.send(event.response()))
159-
elif isinstance(event, TextMessage):
160-
print('Received TEXT data: {}'.format(event.data))
161-
if event.message_finished:
162-
print('TEXT Message finished.')
163-
elif isinstance(event, BinaryMessage):
164-
print('Received BINARY data: {}'.format(event.data))
165-
if event.message_finished:
166-
print('BINARY Message finished.')
167-
else:
168-
print('Unknown event: {!r}'.format(event))
146+
elif...
169147

170148
Alternatively a server can explicitly reject the connection by sending
171149
:class:`RejectConnection <wsproto.events.RejectConnection>` after
@@ -193,9 +171,9 @@ send one frame and receive one frame. Sending a
193171
:class:`CloseConnection <wsproto.events.CloseConnection>` instance
194172
sets the state to ``LOCAL_CLOSING``. When a close frame is received,
195173
it yields a ``CloseConnection`` event, sets the state to
196-
``REMOTE_CLOSING`` **and requires a reply to be sent**, this reply
174+
``REMOTE_CLOSING`` **and requires a reply to be sent**. This reply
197175
should be a ``CloseConnection`` event. To aid with this the
198-
``CloseConnection`` class has a :func:`response()
176+
``CloseConnection`` class has a :meth:`response()
199177
<wsproto.events.CloseConnection.response>` method to create the
200178
appropriate reply. For example,
201179

@@ -207,11 +185,10 @@ appropriate reply. For example,
207185
When the reply has been received by the initiator, it will also yield
208186
a ``CloseConnection`` event.
209187

210-
Regardless of which endpoint initiates the closing handshake, the
211-
server is responsible for tearing down the underlying connection. When
212-
the server receives a ``CloseConnection`` event, it should send
213-
pending `wsproto` data (if any) and then it can start tearing down the
214-
underlying connection.
188+
Regardless of which endpoint initiates the closing handshake, the server is
189+
responsible for tearing down the underlying connection. When a
190+
``CloseConnection`` event is generated, it should send pending any ``wsproto``
191+
data and then tear down the underlying connection.
215192

216193
.. note::
217194

@@ -226,7 +203,7 @@ sending WebSocket ping and pong frames via sending :class:`Ping
226203
<wsproto.events.Ping>` and :class:`Pong <wsproto.events.Pong>`. When a
227204
``Ping`` frame is received it **requires a reply**, this reply should be
228205
a ``Pong`` event. To aid with this the ``Ping`` class has a
229-
:func:`response() <wsproto.events.Ping.response>` method to create the
206+
:meth:`response() <wsproto.events.Ping.response>` method to create the
230207
appropriate reply. For example,
231208

232209
.. code-block:: python

Diff for: example/synchronous_client.py

+17-26
Original file line numberDiff line numberDiff line change
@@ -51,46 +51,25 @@ def wsproto_demo(host, port):
5151
# 1) Negotiate WebSocket opening handshake
5252
print('Opening WebSocket')
5353
ws = WSConnection(ConnectionType.CLIENT)
54+
# Because this is a client WebSocket, we need to initiate the connection
55+
# handshake by sending a Request event.
5456
net_send(ws.send(Request(host=host, target='server')), conn)
5557
net_recv(ws, conn)
56-
57-
# events is a generator that yields websocket event objects. Usually you
58-
# would say `for event in ws.events()`, but the synchronous nature of this
59-
# client requires us to use next(event) instead so that we can interleave
60-
# the network I/O. It will raise StopIteration when it runs out of events
61-
# (i.e. needs more network data), but since this script is synchronous, we
62-
# will explicitly resume the generator whenever we have new network data.
63-
events = ws.events()
64-
65-
# Because this is a client WebSocket, wsproto has automatically queued up
66-
# a handshake, and we need to send it and wait for a response.
67-
event = next(events)
68-
if isinstance(event, AcceptConnection):
69-
print('WebSocket negotiation complete')
70-
else:
71-
raise Exception('Expected AcceptConnection event!')
58+
handle_events(ws)
7259

7360
# 2) Send a message and display response
7461
message = "wsproto is great"
7562
print('Sending message: {}'.format(message))
7663
net_send(ws.send(Message(data=message)), conn)
7764
net_recv(ws, conn)
78-
event = next(events)
79-
if isinstance(event, TextMessage):
80-
print('Received message: {}'.format(event.data))
81-
else:
82-
raise Exception('Expected TextMessage event!')
65+
handle_events(ws)
8366

8467
# 3) Send ping and display pong
8568
payload = b"table tennis"
8669
print('Sending ping: {}'.format(payload))
8770
net_send(ws.send(Ping(payload=payload)), conn)
8871
net_recv(ws, conn)
89-
event = next(events)
90-
if isinstance(event, Pong):
91-
print('Received pong: {}'.format(event.payload))
92-
else:
93-
raise Exception('Expected Pong event!')
72+
handle_events(ws)
9473

9574
# 4) Negotiate WebSocket closing handshake
9675
print('Closing WebSocket')
@@ -122,5 +101,17 @@ def net_recv(ws, conn):
122101
ws.receive_data(in_data)
123102

124103

104+
def handle_events(ws):
105+
for event in ws.events():
106+
if isinstance(event, AcceptConnection):
107+
print('WebSocket negotiation complete')
108+
elif isinstance(event, TextMessage):
109+
print('Received message: {}'.format(event.data))
110+
elif isinstance(event, Pong):
111+
print('Received pong: {}'.format(event.payload))
112+
else:
113+
raise Exception('Do not know how to handle event: ' + str(event))
114+
115+
125116
if __name__ == '__main__':
126117
main()

0 commit comments

Comments
 (0)