Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Switch from bytes_to_send to send returning bytes #98

Merged
merged 2 commits into from
Jan 14, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@
-----------------

* Introduce a send method on the conenction which accepts the new
events. This requires the following usage changes,
events. This requires the following usage changes, ::

connection.accept(subprotocol=subprotocol) -> connection.send(AcceptConnection(subprotocol=subprotocol))
connection.send_data(data) -> connection.send(Message(payload=payload))
connection.close(code) -> connection.send(CloseConnection(code=code))
connection.ping() -> connection.send(Ping())
connection.pong() -> connection.send(Pong())

* The Event structure is altered to allow for events to be sent and
received, this requires the following name changes in existing code,
received, this requires the following name changes in existing code, ::

ConnectionRequested -> Request
ConnectionEstablished -> AcceptConnection
ConnectionClosed -> CloseConnection
Expand All @@ -18,6 +21,7 @@
BytesReceived -> BytesMessage
PingReceived -> Ping
PongReceived -> Pong

* Introduce RejectConnection and RejectData events to be used by a
server connection to reject rather than accept a connection or by a
client connection to emit the rejection response. The RejectData
Expand All @@ -33,6 +37,14 @@
* Enforce version checking in SERVER mode, only 13 is supported.
* Add an event_hint to RemoteProtocolErrors to hint at how to respond
to issues.
* Switch from a ``bytes_to_send`` method to the ``send`` method
returning the bytes to send directly. Responses to Ping and Close
messages must now be sent (via ``send``), with the ``Ping`` and
``CloseConnection`` events gaining a ``response`` method. This
allows ::

if isinstance(event, Ping):
bytes_to_send = connection.send(event.response())

0.12.0 2018-09-23
-----------------
Expand Down
40 changes: 22 additions & 18 deletions compliance/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from wsproto.compat import PY2
from wsproto.connection import WSConnection, CLIENT
from wsproto.events import AcceptConnection, CloseConnection, Request, TextMessage, Message
from wsproto.events import AcceptConnection, CloseConnection, Ping, Request, TextMessage, Message
from wsproto.extensions import PerMessageDeflate
from wsproto.frame_protocol import CloseReason

Expand All @@ -23,25 +23,25 @@
def get_case_count(server):
uri = urlparse(server + '/getCaseCount')
connection = WSConnection(CLIENT)
connection.send(Request(host=uri.netloc, target=uri.path))
sock = socket.socket()
sock.connect((uri.hostname, uri.port or 80))

sock.sendall(connection.bytes_to_send())
sock.sendall(connection.send(Request(host=uri.netloc, target=uri.path)))

case_count = None
while case_count is None:
data = sock.recv(65535)
connection.receive_bytes(data)
data = ""
out_data = b""
for event in connection.events():
if isinstance(event, TextMessage):
data += event.data
if event.message_finished:
case_count = json.loads(data)
connection.send(CloseConnection(code=CloseReason.NORMAL_CLOSURE))
out_data += connection.send(CloseConnection(code=CloseReason.NORMAL_CLOSURE))
try:
sock.sendall(connection.bytes_to_send())
sock.sendall(out_data)
except CONNECTION_EXCEPTIONS:
break

Expand All @@ -51,14 +51,15 @@ def get_case_count(server):
def run_case(server, case, agent):
uri = urlparse(server + '/runCase?case=%d&agent=%s' % (case, agent))
connection = WSConnection(CLIENT)
connection.send(Request(
host=uri.netloc, target='%s?%s' % (uri.path, uri.query),
extensions=[PerMessageDeflate()],
))
sock = socket.socket()
sock.connect((uri.hostname, uri.port or 80))

sock.sendall(connection.bytes_to_send())
sock.sendall(
connection.send(Request(
host=uri.netloc, target='%s?%s' % (uri.path, uri.query),
extensions=[PerMessageDeflate()],
))
)
closed = False

while not closed:
Expand All @@ -67,39 +68,42 @@ def run_case(server, case, agent):
except CONNECTION_EXCEPTIONS:
data = None
connection.receive_bytes(data or None)
out_data = b""
for event in connection.events():
if isinstance(event, Message):
connection.send(Message(data=event.data, message_finished=event.message_finished))
out_data += connection.send(Message(data=event.data, message_finished=event.message_finished))
elif isinstance(event, Ping):
out_data += connection.send(event.response())
elif isinstance(event, CloseConnection):
closed = True
out_data += connection.send(event.response())
# else:
# print("??", event)
if data is None:
if out_data is None:
break
try:
data = connection.bytes_to_send()
sock.sendall(data)
sock.sendall(out_data)
except CONNECTION_EXCEPTIONS:
closed = True
break

def update_reports(server, agent):
uri = urlparse(server + '/updateReports?agent=%s' % agent)
connection = WSConnection(CLIENT)
connection.send(Request(host=uri.netloc, target='%s?%s' % (uri.path, uri.query)))
sock = socket.socket()
sock.connect((uri.hostname, uri.port or 80))

sock.sendall(connection.bytes_to_send())
sock.sendall(
connection.send(Request(host=uri.netloc, target='%s?%s' % (uri.path, uri.query)))
)
closed = False

while not closed:
data = sock.recv(65535)
connection.receive_bytes(data)
for event in connection.events():
if isinstance(event, AcceptConnection):
connection.send(CloseConnection(code=CloseReason.NORMAL_CLOSURE))
sock.sendall(connection.bytes_to_send())
sock.sendall(connection.send(CloseConnection(code=CloseReason.NORMAL_CLOSURE)))
try:
sock.close()
except CONNECTION_EXCEPTIONS:
Expand Down
16 changes: 10 additions & 6 deletions compliance/test_server.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import select
import socket

from wsproto.connection import WSConnection, SERVER
from wsproto.events import AcceptConnection, CloseConnection, Message, Request
from wsproto.connection import ConnectionState, WSConnection, SERVER
from wsproto.events import AcceptConnection, CloseConnection, Message, Ping, Request
from wsproto.extensions import PerMessageDeflate

count = 0
Expand All @@ -21,20 +21,24 @@ def new_conn(sock):

ws.receive_bytes(data or None)

outgoing_data = b""
for event in ws.events():
if isinstance(event, Request):
ws.send(AcceptConnection(extensions=[PerMessageDeflate()]))
outgoing_data += ws.send(AcceptConnection(extensions=[PerMessageDeflate()]))
elif isinstance(event, Message):
ws.send(Message(data=event.data, message_finished=event.message_finished))
outgoing_data += ws.send(Message(data=event.data, message_finished=event.message_finished))
elif isinstance(event, Ping):
outgoing_data += ws.send(event.response())
elif isinstance(event, CloseConnection):
closed = True
if ws.state is not ConnectionState.CLOSED:
outgoing_data += ws.send(event.response())

if not data:
closed = True

try:
data = ws.bytes_to_send()
sock.sendall(data)
sock.sendall(outgoing_data)
except socket.error:
closed = True

Expand Down
29 changes: 13 additions & 16 deletions docs/source/basic-usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,17 @@ code is like a sandwich around `wsproto`.
| Network Layer |
+--------------------+

`wsproto` does not do perform any network I/O, so ``<NETWORK GLUE>`` represents
the code you need to write to glue `wsproto` to the actual network layer, i.e.
code that can send and receive data over the network. The
:class:`WSConnection <wsproto.connection.WSConnection>` class provides two
methods for this purpose. When data has been received on a network socket, you
feed this data into `wsproto` by calling :meth:`receive_bytes
<wsproto.connection.WSConnection.receive_bytes>`. When `wsproto` has data that
needs to be sent over the network, you retrieve that data by calling
:meth:`bytes_to_send <wsproto.connection.WSConnection.bytes_to_send>`, and your
code is responsible for actually sending that data over the network.
`wsproto` does not do perform any network I/O, so ``<NETWORK GLUE>``
represents the code you need to write to glue `wsproto` to the actual
network layer, i.e. code that can send and receive data over the
network. The :class:`WSConnection <wsproto.connection.WSConnection>`
class provides two methods for this purpose. When data has been
received on a network socket, you feed this data into `wsproto` by
calling :meth:`receive_bytes
<wsproto.connection.WSConnection.receive_bytes>`. When `wsproto` sends
events the :meth:`send <wsproto.connection.WSConnection.send>` will
return the bytes that need to be sent over the network. Your code is
responsible for actually sending that data over the network.

.. note::

Expand Down Expand Up @@ -85,15 +86,11 @@ To read from the network::
data = stream.recv(4096)
ws.receive_bytes(data)

You also need to check if `wsproto` has data to send to the network::
You also need to send data returned by the send method::

data = ws.bytes_to_send()
data = ws.send(Message(data=b"Hello"))
stream.send(data)

Note that ``bytes_to_send()`` will return zero bytes if the protocol has no
pending data. You can either poll this method or call it only when you expect
to have pending data.

A standard Python socket will block on the call to ``stream.recv()``, so you
will probably need to use a non-blocking socket or some form of concurrency like
threading, greenlets, asyncio, etc.
Expand Down
25 changes: 9 additions & 16 deletions example/synchronous_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ def wsproto_demo(host, port):
# 1) Negotiate WebSocket opening handshake
print('Opening WebSocket')
ws = WSConnection(ConnectionType.CLIENT)
ws.send(Request(host=host, target='server'))
net_send(ws.send(Request(host=host, target='server')), conn)
net_recv(ws, conn)

# events is a generator that yields websocket event objects. Usually you
# would say `for event in ws.events()`, but the synchronous nature of this
Expand All @@ -62,7 +63,6 @@ def wsproto_demo(host, port):

# Because this is a client WebSocket, wsproto has automatically queued up
# a handshake, and we need to send it and wait for a response.
net_send_recv(ws, conn)
event = next(events)
if isinstance(event, AcceptConnection):
print('WebSocket negotiation complete')
Expand All @@ -72,8 +72,8 @@ def wsproto_demo(host, port):
# 2) Send a message and display response
message = "wsproto is great"
print('Sending message: {}'.format(message))
ws.send(Message(data=message))
net_send_recv(ws, conn)
net_send(ws.send(Message(data=message)), conn)
net_recv(ws, conn)
event = next(events)
if isinstance(event, TextMessage):
print('Received message: {}'.format(event.data))
Expand All @@ -83,8 +83,8 @@ def wsproto_demo(host, port):
# 3) Send ping and display pong
payload = b"table tennis"
print('Sending ping: {}'.format(payload))
ws.send(Ping(payload=payload))
net_send_recv(ws, conn)
net_send(ws.send(Ping(payload=payload)), conn)
net_recv(ws, conn)
event = next(events)
if isinstance(event, Pong):
print('Received pong: {}'.format(event.payload))
Expand All @@ -93,18 +93,17 @@ def wsproto_demo(host, port):

# 4) Negotiate WebSocket closing handshake
print('Closing WebSocket')
ws.send(CloseConnection(code=1000, reason='sample reason'))
net_send(ws.send(CloseConnection(code=1000, reason='sample reason')), conn)
# After sending the closing frame, we won't get any more events. The server
# should send a reply and then close the connection, so we need to receive
# twice:
net_send_recv(ws, conn)
net_recv(ws, conn)
conn.shutdown(socket.SHUT_WR)
net_recv(ws, conn)


def net_send(ws, conn):
def net_send(out_data, conn):
''' Write pending data from websocket to network. '''
out_data = ws.bytes_to_send()
print('Sending {} bytes'.format(len(out_data)))
conn.send(out_data)

Expand All @@ -122,11 +121,5 @@ def net_recv(ws, conn):
ws.receive_bytes(in_data)


def net_send_recv(ws, conn):
''' Send pending data and then wait for response. '''
net_send(ws, conn)
net_recv(ws, conn)


if __name__ == '__main__':
main()
7 changes: 4 additions & 3 deletions example/synchronous_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,26 +81,27 @@ def handle_connection(stream):
if isinstance(event, Request):
# Negotiate new WebSocket connection
print('Accepting WebSocket upgrade')
ws.send(AcceptConnection())
out_data = ws.send(AcceptConnection())
elif isinstance(event, CloseConnection):
# Print log message and break out
print('Connection closed: code={}/{} reason={}'.format(
event.code.value, event.code.name, event.reason))
out_data = ws.send(event.response())
running = False
elif isinstance(event, TextMessage):
# Reverse text and send it back to wsproto
print('Received request and sending response')
ws.send(Message(data=event.data[::-1]))
out_data = ws.send(Message(data=event.data[::-1]))
elif isinstance(event, Ping):
# wsproto handles ping events for you by placing a pong frame in
# the outgoing buffer. You should not call pong() unless you want to
# send an unsolicited pong frame.
print('Received ping and sending pong')
out_data = ws.send(event.response())
else:
print('Unknown event: {!r}'.format(event))

# 4) Send data from wsproto to network
out_data = ws.bytes_to_send()
print('Sending {} bytes'.format(len(out_data)))
stream.send(out_data)

Expand Down
21 changes: 10 additions & 11 deletions test/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ def _make_connection_request(request):
# type: (Request) -> h11.Request
client = WSConnection(CLIENT)
server = h11.Connection(h11.SERVER)
client.send(request)
server.receive_data(client.bytes_to_send())
server.receive_data(client.send(request))
return server.next_event()


Expand Down Expand Up @@ -103,15 +102,16 @@ def _make_handshake(
):
client = WSConnection(CLIENT)
server = h11.Connection(h11.SERVER)
client.send(
Request(
host="localhost",
target="/",
subprotocols=subprotocols or [],
extensions=extensions or [],
server.receive_data(
client.send(
Request(
host="localhost",
target="/",
subprotocols=subprotocols or [],
extensions=extensions or [],
)
)
)
server.receive_data(client.bytes_to_send())
request = server.next_event()
if auto_accept_key:
full_request_headers = normed_header_dict(request.headers)
Expand Down Expand Up @@ -232,8 +232,7 @@ def test_protocol_error():
def _make_handshake_rejection(status_code, body=None):
client = WSConnection(CLIENT)
server = h11.Connection(h11.SERVER)
client.send(Request(host="localhost", target="/"))
server.receive_data(client.bytes_to_send())
server.receive_data(client.send(Request(host="localhost", target="/")))
headers = []
if body is not None:
headers.append(("Content-Length", str(len(body))))
Expand Down
Loading