Skip to content

Commit

Permalink
Introduce a send method to the Connection class
Browse files Browse the repository at this point in the history
This allows a new event based API to be used in preference to the
previous method based API. The basic concept being that callingg
``connection.foo()`` becomes ``connection.send(Foo())``. This follows
the h11 convention.

This requires the following changes to be made to existing code,
(assuming connection is a Connection instance),

    connection.accept(subprotocol=subprotocol) -> connection.send(AcceptConnection(subprotocol=subprotocol))
    connection.send_data(data) -> connection.send(Data(payload=payload))
    connection.close(code) -> connection.send(CloseConnection(code=code))
    connection.ping() -> connection.send(Ping())
    connection.pong() -> connection.send(Pong())
  • Loading branch information
pgjones committed Dec 22, 2018
1 parent 3e9721b commit 5b57524
Show file tree
Hide file tree
Showing 9 changed files with 154 additions and 200 deletions.
25 changes: 16 additions & 9 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
0.13.0 Unreleased
-----------------

* Introduce a send method on the conenction which accepts the new
events. This requires the following usage changes,
connection.accept(subprotocol=subprotocol) -> connection.send(AcceptConnection(subprotocol=subprotocol))
connection.send_data(data) -> connection.send(Data(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,
ConnectionRequested -> Request
ConnectionEstablished -> AcceptConnection
ConnectionClosed -> CloseConnection
ConnectionFailed -> Fail
DataReceived -> Data
TextReceived -> TextMessage
BytesReceived -> BytesMessage
PingReceived -> Ping
PongReceived -> Pong
ConnectionRequested -> Request
ConnectionEstablished -> AcceptConnection
ConnectionClosed -> CloseConnection
ConnectionFailed -> Fail
DataReceived -> Data
TextReceived -> TextMessage
BytesReceived -> BytesMessage
PingReceived -> Ping
PongReceived -> Pong

0.12.0 2018-09-23
-----------------
Expand Down
8 changes: 6 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,17 @@ handshake. To create a WebSocket client connection:
.. code-block:: python
from wsproto.connection import WSConnection, ConnectionType
ws = WSConnection(ConnectionType.CLIENT, host='echo.websocket.org', resource='/')
from wsproto.events import Request
ws = WSConnection(ConnectionType.CLIENT)
ws.send(Request(host='echo.websocket.org', target='/'))
To create a WebSocket server connection:

.. code-block:: python
from wsproto.connection import WSConnection, ConnectionType
ws = WSConnection(ConnectionType.SERVER)
Every time you send a message, or call a ping, or simply if you receive incoming
Expand All @@ -71,7 +75,7 @@ And wsproto will issue events if the data contains any WebSocket messages or sta
for event in ws.events():
if isinstance(event, Request):
# only client connections get this event
ws.accept(event)
ws.send(AcceptConnection())
elif isinstance(event, CloseConnection):
# guess nobody wants to talk to us any more...
elif isinstance(event, TextMessage):
Expand Down
24 changes: 14 additions & 10 deletions compliance/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@

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

if PY2:
from urlparse import urlparse
Expand All @@ -21,7 +22,8 @@

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

Expand All @@ -37,7 +39,7 @@ def get_case_count(server):
data += event.data
if event.message_finished:
case_count = json.loads(data)
connection.close()
connection.send(CloseConnection(code=CloseReason.NORMAL_CLOSURE))
try:
sock.sendall(connection.bytes_to_send())
except CONNECTION_EXCEPTIONS:
Expand All @@ -48,9 +50,11 @@ def get_case_count(server):

def run_case(server, case, agent):
uri = urlparse(server + '/runCase?case=%d&agent=%s' % (case, agent))
connection = WSConnection(CLIENT,
uri.netloc, '%s?%s' % (uri.path, uri.query),
extensions=[PerMessageDeflate()])
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))

Expand All @@ -65,7 +69,7 @@ def run_case(server, case, agent):
connection.receive_bytes(data or None)
for event in connection.events():
if isinstance(event, Data):
connection.send_data(event.data, event.message_finished)
connection.send(Data(data=event.data, message_finished=event.message_finished))
elif isinstance(event, CloseConnection):
closed = True
# else:
Expand All @@ -81,8 +85,8 @@ def run_case(server, case, agent):

def update_reports(server, agent):
uri = urlparse(server + '/updateReports?agent=%s' % agent)
connection = WSConnection(CLIENT,
uri.netloc, '%s?%s' % (uri.path, uri.query))
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))

Expand All @@ -94,7 +98,7 @@ def update_reports(server, agent):
connection.receive_bytes(data)
for event in connection.events():
if isinstance(event, AcceptConnection):
connection.close()
connection.send(CloseConnection(code=CloseReason.NORMAL_CLOSURE))
sock.sendall(connection.bytes_to_send())
try:
sock.close()
Expand Down
10 changes: 5 additions & 5 deletions compliance/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import socket

from wsproto.connection import WSConnection, SERVER
from wsproto.events import Accept, Close, Data, Request
from wsproto.events import AcceptConnection, CloseConnection, Data, Request
from wsproto.extensions import PerMessageDeflate

count = 0
Expand All @@ -11,7 +11,7 @@ def new_conn(sock):
global count
print("test_server.py received connection {}".format(count))
count += 1
ws = WSConnection(SERVER, extensions=[PerMessageDeflate()])
ws = WSConnection(SERVER)
closed = False
while not closed:
try:
Expand All @@ -23,10 +23,10 @@ def new_conn(sock):

for event in ws.events():
if isinstance(event, Request):
ws.accept(event)
ws.send(AcceptConnection(extensions=[PerMessageDeflate()]))
elif isinstance(event, Data):
ws.send_data(event.data, event.message_finished)
elif isinstance(event, Close):
ws.send(Data(data=event.data, message_finished=event.message_finished))
elif isinstance(event, CloseConnection):
closed = True

if not data:
Expand Down
11 changes: 6 additions & 5 deletions example/synchronous_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import sys

from wsproto.connection import ConnectionType, WSConnection
from wsproto.events import AcceptConnection, TextMessage, Pong
from wsproto.events import AcceptConnection, CloseConnection, Data, Ping, Pong, Request, TextMessage


RECEIVE_BYTES = 4096
Expand Down Expand Up @@ -49,7 +49,8 @@ def wsproto_demo(host, port):

# 1) Negotiate WebSocket opening handshake
print('Opening WebSocket')
ws = WSConnection(ConnectionType.CLIENT, host=host, resource='server')
ws = WSConnection(ConnectionType.CLIENT)
ws.send(Request(host=host, target='server'))

# 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 @@ -71,7 +72,7 @@ def wsproto_demo(host, port):
# 2) Send a message and display response
message = "wsproto is great"
print('Sending message: {}'.format(message))
ws.send_data(message)
ws.send(Data(data=message))
net_send_recv(ws, conn)
event = next(events)
if isinstance(event, TextMessage):
Expand All @@ -82,7 +83,7 @@ def wsproto_demo(host, port):
# 3) Send ping and display pong
payload = b"table tennis"
print('Sending ping: {}'.format(payload))
ws.ping(payload)
ws.send(Ping(payload=payload))
net_send_recv(ws, conn)
event = next(events)
if isinstance(event, Pong):
Expand All @@ -92,7 +93,7 @@ def wsproto_demo(host, port):

# 4) Negotiate WebSocket closing handshake
print('Closing WebSocket')
ws.close(code=1000, reason='sample reason')
ws.send(CloseConnection(code=1000, reason='sample reason'))
# 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:
Expand Down
6 changes: 3 additions & 3 deletions example/synchronous_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import sys

from wsproto.connection import ConnectionType, WSConnection
from wsproto.events import CloseConnection, Ping, Request, TextMessage
from wsproto.events import AcceptConnection, CloseConnection, Data, Ping, Request, TextMessage


MAX_CONNECTS = 5
Expand Down Expand Up @@ -81,7 +81,7 @@ def handle_connection(stream):
if isinstance(event, Request):
# Negotiate new WebSocket connection
print('Accepting WebSocket upgrade')
ws.accept(event)
ws.send(AcceptConnection())
elif isinstance(event, CloseConnection):
# Print log message and break out
print('Connection closed: code={}/{} reason={}'.format(
Expand All @@ -90,7 +90,7 @@ def handle_connection(stream):
elif isinstance(event, TextMessage):
# Reverse text and send it back to wsproto
print('Received request and sending response')
ws.send_data(event.data[::-1])
ws.send(Data(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
Expand Down
36 changes: 17 additions & 19 deletions test/test_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
AcceptConnection,
BytesMessage,
CloseConnection,
Data,
Ping,
Pong,
Request,
Expand All @@ -20,13 +21,14 @@
class TestConnection(object):
def create_connection(self):
server = WSConnection(SERVER)
client = WSConnection(CLIENT, host="localhost", resource="foo")
client = WSConnection(CLIENT)
client.send(Request(host="localhost", target="foo"))

server.receive_bytes(client.bytes_to_send())
event = next(server.events())
assert isinstance(event, Request)

server.accept(event)
server.send(AcceptConnection())
client.receive_bytes(server.bytes_to_send())
assert isinstance(next(client.events()), AcceptConnection)

Expand All @@ -35,12 +37,6 @@ def create_connection(self):
def test_negotiation(self):
self.create_connection()

def test_default_args(self):
with pytest.raises(ValueError, match="Host must not be None"):
WSConnection(CLIENT, resource="/ws")
with pytest.raises(ValueError, match="Resource must not be None"):
WSConnection(CLIENT, host="localhost")

@pytest.mark.parametrize(
"as_client,final", [(True, True), (True, False), (False, True), (False, False)]
)
Expand All @@ -55,7 +51,7 @@ def test_send_and_receive(self, as_client, final):

data = b"x" * 23

me.send_data(data, final)
me.send(Data(data=data, message_finished=final))
them.receive_bytes(me.bytes_to_send())

event = next(them.events())
Expand All @@ -81,7 +77,7 @@ def test_close(self, as_client, code, reason):
me = server
them = client

me.close(code, reason)
me.send(CloseConnection(code=code, reason=reason))
them.receive_bytes(me.bytes_to_send())

event = next(them.events())
Expand All @@ -97,7 +93,7 @@ def test_normal_closure(self, swap):
initiator, completor = self.create_connection()

# initiator sends CLOSE to completor
initiator.close()
initiator.send(CloseConnection(code=CloseReason.NORMAL_CLOSURE))
completor.receive_bytes(initiator.bytes_to_send())

# completor emits Close
Expand All @@ -119,7 +115,7 @@ def test_normal_closure(self, swap):
with pytest.raises(StopIteration):
next(initiator.events())

completor.ping()
completor.send(Ping())
with pytest.raises(ValueError):
initiator.receive_bytes(completor.bytes_to_send())

Expand Down Expand Up @@ -158,7 +154,7 @@ def test_ping_pong(self, as_client):
payload = b"x" * 23

# Send a PING message
me.ping(payload)
me.send(Ping(payload=payload))
wire_data = me.bytes_to_send()

# Verify that the peer emits the Ping event with the correct
Expand Down Expand Up @@ -192,13 +188,13 @@ def test_ping_pong(self, as_client):
repr(next(me.events()))

@pytest.mark.parametrize(
"args, expected_payload",
[((), b""), ((b"abcdef",), b"abcdef")],
"payload, expected_payload",
[(b"", b""), (b"abcdef", b"abcdef")],
ids=["nopayload", "payload"],
)
def test_unsolicited_pong(self, args, expected_payload):
def test_unsolicited_pong(self, payload, expected_payload):
client, server = self.create_connection()
client.pong(*args)
client.send(Pong(payload=payload))
wire_data = client.bytes_to_send()
server.receive_bytes(wire_data)
events = list(server.events())
Expand Down Expand Up @@ -237,7 +233,8 @@ def test_data_events(self, text, payload, full_message, full_frame):

frame = opcode + length + encoded_payload

connection = WSConnection(CLIENT, host="localhost", resource="foo")
connection = WSConnection(CLIENT)
connection.send(Request(host="localhost", target="foo"))
connection._proto = FrameProtocol(True, [])
connection._state = ConnectionState.OPEN
connection.bytes_to_send()
Expand Down Expand Up @@ -265,7 +262,8 @@ def receive_bytes(self, data):
def received_frames(self):
return [FailFrame()]

connection = WSConnection(CLIENT, host="localhost", resource="foo")
connection = WSConnection(CLIENT)
connection.send(Request(host="localhost", target="foo"))
connection._proto = DoomProtocol()
connection._state = ConnectionState.OPEN
connection.bytes_to_send()
Expand Down
Loading

0 comments on commit 5b57524

Please sign in to comment.