Skip to content

Commit

Permalink
version bump
Browse files Browse the repository at this point in the history
  • Loading branch information
willmcgugan committed May 15, 2018
1 parent 4fb623d commit 9a44ff3
Show file tree
Hide file tree
Showing 9 changed files with 195 additions and 19 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,19 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).


## [0.2.4] - Unreleased
- Fixed incorrect name for Closing event
- Added integration tests

## [0.2.3] - 2018-05-14

### Fixed non-graceful close https://github.com/wildfoundry/dataplicity-lomond/issues/54

## [0.2.2] - 2018-05-09

### Fixed

- Fixed handling of non-ws URLs on Windows
- Fixed broken close timeout

Expand Down
52 changes: 40 additions & 12 deletions docs/guide.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,40 @@ available::

pip install wsaccel

Example
-------

To whet your appetite, the following is an example of how to connect
to a WebSocket endpoint and interact with the server::

from lomond import WebSocket
websocket = WebSocket('wss://ws-feed.gdax.com')

for event in websocket:
if event.name == "ready":
websocket.send_json(
type='subscribe',
product_ids=['BTC-USD'],
channels=['ticker']
)
elif event.name == "text":
print(event.json)

This code connects to a Gdax, a Bitcoin exchange, and subscribes to
realtime notifications about the price of Bitcoin--which it writes to
the terminal.

This example is in the Lomond library. You can run it with the
following::

python -m lomond.examples.btcticker

Basic Usage
-----------

To connect to a websocket server, first construct a
:class:`~lomond.websocket.WebSocket` object, with a `ws://` or `wss://` URL.
:class:`~lomond.websocket.WebSocket` object, with a ``ws://`` or
``wss://`` URL.
Here is an example::

from lomond.websocket import WebSocket
Expand Down Expand Up @@ -81,8 +110,8 @@ Events inform your application when data is received from the server or
when the websocket state changes.

All events are derived from :class:`~lomond.events.Event` and will
contain at least 2 attributes; `received_time` is the epoch time the
event was received, and `name` is the name of the event. Some events
contain at least 2 attributes; ``received_time`` is the epoch time the
event was received, and ``name`` is the name of the event. Some events
have additional attributes with more information. See the :ref:`events`
for details.

Expand All @@ -98,7 +127,7 @@ or::
if event.name == "ready":

.. note::
The `isinstance` method is possibly uglier, but has the advantage
The ``isinstance`` method is possibly uglier, but has the advantage
that you are less likely to introduce a bug with a typo in the event
name.

Expand All @@ -123,11 +152,11 @@ events, such as the following::
websocket.close()


Closing the Websocket
Closing the WebSocket
---------------------

The websocket protocol specifies how to close the websocket cleanly. The
procedure for handling closes, depends on whether it is initiated by the
procedure for closing depends on whether the close is initiated by the
client or the server.

Client
Expand All @@ -145,7 +174,7 @@ of the connection to finish what they are doing without worrying the
remote end has stopped responding to messages.

.. note::
When you call the `close()` method, you will no longer be able to
When you call the ``close()`` method, you will no longer be able to
*send* data, but you may still *receive* packets from the server
until the close has completed.

Expand Down Expand Up @@ -180,8 +209,8 @@ completing the closing handshake. This can occur if the server is
misbehaving or if connectivity has been interrupted.

The :class:`~lomond.events.Disconnected` event contains a boolean
attribute `graceful`, which will be `False` if the closing handshake was
not completed.
attribute ``graceful``, which will be ``False`` if the closing handshake
was not completed.

Pings and Pongs
---------------
Expand All @@ -205,7 +234,7 @@ requirement of the websocket specification, you probably don't want to
change this behaviour. But it may be disabled with the `auto_pong` flag
in :meth:`~lomond.websocket.WebSocket.connect`.

When Lomond recieves a ping packet from the server, a
When Lomond receives a ping packet from the server, a
:class:`~lomond.events.Ping` event will be generated. When the server
sends you a pong packet, a :class:`~lomond.events.Pong` event will be
generated.
Expand Down Expand Up @@ -254,7 +283,7 @@ example::
'wss://echo.example.org',
proxies = {
'http': 'http://127.0.0.1:8888',
'https: 'http://127.0.0.1:8888'
'https': 'http://127.0.0.1:8888'
}
)

Expand Down Expand Up @@ -304,4 +333,3 @@ maximum delay is reached.
The exponential backoff prevents a client from hammering a server that
may already be overloaded. It also prevents the client from being stuck
in a cpu intensive spin loop.

2 changes: 1 addition & 1 deletion lomond/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
from ._version import __version__

from .websocket import WebSocket
from .websocket import WebSocket
2 changes: 1 addition & 1 deletion lomond/_version.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
from __future__ import unicode_literals

__version__ = "0.2.3"
__version__ = "0.2.4"
2 changes: 1 addition & 1 deletion lomond/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ class Closing(Event):
"""
__slots__ = ['code', 'reason']
name = 'closed'
name = 'closing'

def __init__(self, code, reason):
self.code = code
Expand Down
5 changes: 2 additions & 3 deletions lomond/frame_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,9 @@
import logging
import struct

from six import text_type

from . import errors
from .frame import Frame
from .parser import ParseError, Parser
from .parser import Parser
from .utf8validator import Utf8Validator


Expand Down Expand Up @@ -90,6 +88,7 @@ def parse(self):

if frame.fin and (frame.is_text or frame.is_continuation):
self._utf8_validator.reset()
if frame.fin:
self._is_text = False

yield frame
3 changes: 2 additions & 1 deletion lomond/stream.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
from six import text_type

from . import errors
from .frame_parser import ParseError, FrameParser
from .parser import ParseError
from .frame_parser import FrameParser
from .message import Message
from .response import Response

Expand Down
141 changes: 141 additions & 0 deletions tests/test_integration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
from __future__ import unicode_literals

"""
Test against a Tornado WebSocket server
"""

import time
import threading

from tornado import gen, ioloop, httpserver, web, websocket

from lomond import WebSocket


class GracefulHandler(websocket.WebSocketHandler):
"""Writes text/binary then closes gracefully."""

def check_origin(self, origin):
return True

@gen.coroutine
def open(self):
self.set_nodelay(True)
yield self.write_message(u'foo')
yield self.write_message(b'bar', binary=True)
yield self.close()


class NonGracefulHandler(websocket.WebSocketHandler):
"""Writes text/binary then closes the socket."""

def check_origin(self, origin):
return True

@gen.coroutine
def open(self):
self.set_nodelay(True)
yield self.write_message(u'foo')
yield self.write_message(b'bar', binary=True)
yield self.stream.close()


class EchoHandler(websocket.WebSocketHandler):
"""Echos any message sent to it."""

def check_origin(self, origin):
return True

@gen.coroutine
def on_message(self, message):
yield self.write_message(message, binary=isinstance(message, bytes))


class TestIntegration(object):

WS_URL = 'ws://127.0.0.1:8080/'

@classmethod
def run_server(cls, port=8080):
app = web.Application([
(r'^/graceful$', GracefulHandler),
(r'^/non-graceful$', NonGracefulHandler),
(r'^/echo$', EchoHandler)
])
cls.server = server = httpserver.HTTPServer(app)
cls.loop = ioloop.IOLoop.current()
server.bind(port, reuse_port=True)
server.start(1)
cls.loop.start()

@classmethod
def setup_class(cls):
server_thread = threading.Thread(target=cls.run_server)
server_thread.daemon = True
server_thread.start()
time.sleep(0.1)

@classmethod
def teardown_class(cls):
cls.loop.add_callback(cls.loop.stop)

def test_graceful(self):
"""Test server that closes gracefully."""
ws = WebSocket(self.WS_URL + 'graceful')
events = list(ws.connect(ping_rate=0))

assert len(events) == 8
assert events[0].name == 'connecting'
assert events[1].name == 'connected'
assert events[2].name == 'ready'
assert events[3].name == 'poll'
assert events[4].name == 'text'
assert events[4].text == u'foo'
assert events[5].name == 'binary'
assert events[5].data == b'bar'
assert events[6].name == 'closing'
assert events[7].name == 'disconnected'
assert events[7].graceful

def test_non_graceful(self):
"""Test server that closes socket."""
ws = WebSocket(self.WS_URL + 'non-graceful')
events = list(ws.connect(ping_rate=0))

assert len(events) == 7
assert events[0].name == 'connecting'
assert events[1].name == 'connected'
assert events[2].name == 'ready'
assert events[3].name == 'poll'
assert events[4].name == 'text'
assert events[4].text == u'foo'
assert events[5].name == 'binary'
assert events[5].data == b'bar'
assert events[6].name == 'disconnected'
assert not events[6].graceful

def test_echo(self):
"""Test echo server."""
ws = WebSocket(self.WS_URL + 'echo')
events = []
for event in ws.connect(poll=60, ping_rate=0, auto_pong=False):
events.append(event)
if event.name == 'ready':
ws.send_text(u'echofoo')
ws.send_binary(b'echobar')
ws.close()

assert len(events) == 8
assert events[0].name == 'connecting'
assert events[1].name == 'connected'
assert events[2].name == 'ready'
assert events[3].name == 'poll'
assert events[4].name == 'text'
assert events[4].text == u'echofoo'
assert events[5].name == 'binary'
assert events[5].data == b'echobar'
assert events[6].name == 'closed'
assert events[7].name == 'disconnected'
assert events[7].graceful
1 change: 1 addition & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ deps =
pytest-cov
pytest-mock
mocket
tornado==4.5.3
freezegun
wsaccel: wsaccel

Expand Down

0 comments on commit 9a44ff3

Please sign in to comment.