Commit 38ee5798 authored by Scott Wittenburg's avatar Scott Wittenburg

Updating to Autobahn python version 0.8.13 to get http long poll endpoints.

Change-Id: Ic484a5d2df36508faf9fc3019fb32b9e9dfb153e
parent 6c957980
Metadata-Version: 1.0
Metadata-Version: 1.1
Name: autobahn
Version: 0.8.9
Summary: AutobahnPython - WebSocket/WAMP implementation for Python/Twisted.
Version: 0.8.13
Summary: WebSocket client & server library, WAMP real-time framework
Home-page: http://autobahn.ws/python
Author: Tavendo GmbH
Author-email: autobahnws@googlegroups.com
License: Apache License 2.0
Description:
Twisted-based WebSocket/WAMP client and server framework.
AutobahnPython provides a WebSocket (RFC6455, Hybi-10 to -17, Hixie-76)
framework for creating WebSocket-based clients and servers.
AutobahnPython also includes an implementation of WAMP
(The WebSockets Application Messaging Protocol), a light-weight,
asynchronous RPC/PubSub over JSON/WebSocket protocol.
Description:
.. |ab| replace:: **Autobahn**\|Python
|ab| is a networking library that is part of the `Autobahn <http://autobahn.ws>`__
project and provides implementations of
* `The WebSocket Protocol <http://tools.ietf.org/html/rfc6455>`__
* `The Web Application Messaging Protocol (WAMP) <http://wamp.ws>`__
for `Twisted <http://www.twistedmatrix.com/>`__ and
`asyncio <https://docs.python.org/3/library/asyncio.html>`__,
on Python 2 & 3 and for writing servers and clients.
WebSocket allows bidirectional real-time messaging on the Web and WAMP
adds asynchronous *Remote Procedure Calls* and *Publish & Subscribe* on
top of WebSocket.
More information:
* http://autobahn.ws/python
* http://wamp.ws
Source Code:
* https://github.com/tavendo/AutobahnPython
Keywords: autobahn autobahn.ws websocket realtime rfc6455 wamp rpc pubsub
* `Project Site <http://autobahn.ws/python>`__
* `Source Code <https://github.com/tavendo/AutobahnPython>`__
Keywords: autobahn autobahn.ws websocket realtime rfc6455 wamp rpc pubsub twisted asyncio
Platform: Any
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Console
Classifier: Environment :: No Input/Output (Daemon)
Classifier: Framework :: Twisted
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 2.6
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.3
Classifier: Programming Language :: Python :: 3.4
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: Implementation :: PyPy
Classifier: Programming Language :: Python :: Implementation :: Jython
Classifier: Topic :: Internet
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Communications
Classifier: Topic :: System :: Distributed Computing
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Software Development :: Object Brokering
......@@ -16,5 +16,5 @@
##
###############################################################################
__version__ = "0.8.9"
__version__ = "0.8.13"
version = __version__ # backward compat.
......@@ -22,17 +22,29 @@ __all__ = ['ApplicationSession',
'ApplicationSessionFactory',
'ApplicationRunner',
'RouterSession',
'RouterSessionFactory']
'RouterSessionFactory',
'Broker',
'Dealer',
'Router',
'RouterFactory',
'FutureMixin']
import sys
import asyncio
from asyncio.tasks import iscoroutine
from asyncio import Future
try:
import asyncio
from asyncio.tasks import iscoroutine
from asyncio import Future
except ImportError:
## Trollius >= 0.3 was renamed
import trollius as asyncio
from trollius.tasks import iscoroutine
from trollius import Future
from autobahn.wamp import protocol
from autobahn.websocket.protocol import parseWsUrl
from autobahn.wamp.types import ComponentConfig
from autobahn.wamp import router, broker, dealer
from autobahn.websocket.protocol import parseWsUrl
from autobahn.asyncio.websocket import WampWebSocketClientFactory
......@@ -42,10 +54,12 @@ class FutureMixin:
Mixin for Asyncio style Futures.
"""
def _create_future(self):
@staticmethod
def _create_future():
return Future()
def _as_future(self, fun, *args, **kwargs):
@staticmethod
def _as_future(fun, *args, **kwargs):
try:
res = fun(*args, **kwargs)
except Exception as e:
......@@ -62,13 +76,16 @@ class FutureMixin:
f.set_result(res)
return f
def _resolve_future(self, future, value):
@staticmethod
def _resolve_future(future, value):
future.set_result(value)
def _reject_future(self, future, value):
@staticmethod
def _reject_future(future, value):
future.set_exception(value)
def _add_future_callbacks(self, future, callback, errback):
@staticmethod
def _add_future_callbacks(future, callback, errback):
def done(f):
try:
res = f.result()
......@@ -77,22 +94,71 @@ class FutureMixin:
errback(e)
return future.add_done_callback(done)
def _gather_futures(self, futures, consume_exceptions = True):
@staticmethod
def _gather_futures(futures, consume_exceptions = True):
return asyncio.gather(*futures, return_exceptions = consume_exceptions)
class Broker(FutureMixin, broker.Broker):
"""
Basic WAMP broker for asyncio-based applications.
"""
class Dealer(FutureMixin, dealer.Dealer):
"""
Basic WAMP dealer for asyncio-based applications.
"""
class Router(FutureMixin, router.Router):
"""
Basic WAMP router for asyncio-based applications.
"""
broker = Broker
"""
The broker class this router will use. Defaults to :class:`autobahn.asyncio.wamp.Broker`
"""
dealer = Dealer
"""
The dealer class this router will use. Defaults to :class:`autobahn.asyncio.wamp.Dealer`
"""
class RouterFactory(FutureMixin, router.RouterFactory):
"""
Basic WAMP router factory for asyncio-based applications.
"""
router = Router
"""
The router class this router factory will use. Defaults to :class:`autobahn.asyncio.wamp.Router`
"""
class ApplicationSession(FutureMixin, protocol.ApplicationSession):
"""
WAMP application session for asyncio-based applications.
"""
class ApplicationSessionFactory(FutureMixin, protocol.ApplicationSessionFactory):
"""
WAMP application session factory for asyncio-based applications.
"""
session = ApplicationSession
"""
The application session class this application session factory will use. Defaults to :class:`autobahn.asyncio.wamp.ApplicationSession`.
"""
......@@ -102,11 +168,16 @@ class RouterSession(FutureMixin, protocol.RouterSession):
"""
class RouterSessionFactory(FutureMixin, protocol.RouterSessionFactory):
"""
WAMP router session factory for asyncio-based applications.
"""
session = RouterSession
"""
The router session class this router session factory will use. Defaults to :class:`autobahn.asyncio.wamp.RouterSession`.
"""
......@@ -122,7 +193,6 @@ class ApplicationRunner:
def __init__(self, url, realm, extra = None,
debug = False, debug_wamp = False, debug_app = False):
"""
Constructor.
:param url: The WebSocket URL of the WAMP router to connect to (e.g. `ws://somehost.com:8090/somepath`)
:type url: str
......@@ -159,13 +229,13 @@ class ApplicationRunner:
cfg = ComponentConfig(self.realm, self.extra)
try:
session = make(cfg)
except Exception:
except Exception as e:
## the app component could not be created .. fatal
print(traceback.format_exc())
print(e)
asyncio.get_event_loop().stop()
session.debug_app = self.debug_app
return session
else:
session.debug_app = self.debug_app
return session
isSecure, host, port, resource, path, params = parseWsUrl(self.url)
......@@ -175,7 +245,7 @@ class ApplicationRunner:
## 3) start the client
loop = asyncio.get_event_loop()
coro = loop.create_connection(transport_factory, host, port)
coro = loop.create_connection(transport_factory, host, port, ssl = isSecure)
loop.run_until_complete(coro)
## 4) now enter the asyncio event loop
......
###############################################################################
##
## Copyright (C) 2013 Tavendo GmbH
## Copyright (C) 2013-2014 Tavendo GmbH
##
## Licensed under the Apache License, Version 2.0 (the "License");
## you may not use this file except in compliance with the License.
......@@ -16,16 +16,31 @@
##
###############################################################################
__all__ = ['WebSocketServerProtocol',
'WebSocketServerFactory',
'WebSocketClientProtocol',
'WebSocketClientFactory']
__all__ = [
'WebSocketAdapterProtocol',
'WebSocketServerProtocol',
'WebSocketClientProtocol',
'WebSocketAdapterFactory',
'WebSocketServerFactory',
'WebSocketClientFactory',
'WampWebSocketServerProtocol',
'WampWebSocketClientProtocol',
'WampWebSocketServerFactory',
'WampWebSocketClientFactory'
]
from collections import deque
import asyncio
from asyncio.tasks import iscoroutine
from asyncio import Future
try:
import asyncio
from asyncio.tasks import iscoroutine
from asyncio import Future
except ImportError:
## Trollius >= 0.3 was renamed
import trollius as asyncio
from trollius.tasks import iscoroutine
from trollius import Future
from autobahn.wamp import websocket
from autobahn.websocket import protocol
......@@ -75,11 +90,7 @@ class WebSocketAdapterProtocol(asyncio.Protocol):
while len(self.receive_queue):
data = self.receive_queue.popleft()
if self.transport:
try:
self._dataReceived(data)
except Exception as e:
raise e
#print("WebSocketAdapterProtocol._consume: {}".format(e))
self._dataReceived(data)
else:
print("WebSocketAdapterProtocol._consume: no transport")
self._consume()
......@@ -93,6 +104,7 @@ class WebSocketAdapterProtocol(asyncio.Protocol):
self.waiter.set_result(None)
# noinspection PyUnusedLocal
def _closeConnection(self, abort = False):
self.transport.close()
......@@ -173,7 +185,7 @@ class WebSocketServerProtocol(WebSocketAdapterProtocol, protocol.WebSocketServer
# res = yield from res
except http.HttpException as exc:
self.failHandshake(exc.reason, exc.code)
except Exception as exc:
except Exception:
self.failHandshake(http.INTERNAL_SERVER_ERROR[1], http.INTERNAL_SERVER_ERROR[0])
else:
self.succeedHandshake(res)
......@@ -289,6 +301,7 @@ class WampWebSocketServerFactory(websocket.WampWebSocketServerFactory, WebSocket
kwargs['protocols'] = self._protocols
# noinspection PyCallByClass
WebSocketServerFactory.__init__(self, *args, **kwargs)
......
......@@ -70,10 +70,10 @@ class WampRawSocketProtocol(Int32StringReceiver):
if self.factory.debug:
log.msg("RX octets: {}".format(binascii.hexlify(payload)))
try:
msg = self.factory._serializer.unserialize(payload)
if self.factory.debug:
log.msg("RX WAMP message: {}".format(msg))
self._session.onMessage(msg)
for msg in self.factory._serializer.unserialize(payload):
if self.factory.debug:
log.msg("RX WAMP message: {}".format(msg))
self._session.onMessage(msg)
except ProtocolError as e:
if self.factory.debug:
......@@ -159,12 +159,14 @@ class WampRawSocketFactory(Factory):
def __init__(self, factory, serializer, debug = False):
"""
Ctor.
:param factory: A callable that produces instances that implement
:class:`autobahn.wamp.interfaces.ITransportHandler`
:class:`autobahn.wamp.interfaces.ITransportHandler`
:type factory: callable
:param serializer: A WAMP serializer to use. A serializer must implement
:class:`autobahn.wamp.interfaces.ISerializer`.
type serializer: list
:class:`autobahn.wamp.interfaces.ISerializer`.
:type serializer: list
"""
assert(callable(factory))
self._factory = factory
......
......@@ -21,6 +21,8 @@ __all__ = ("WebSocketResource",
"WSGIRootResource",)
from six.moves.urllib import parse
from zope.interface import implementer
from twisted.protocols.policies import ProtocolWrapper
......@@ -117,6 +119,7 @@ class WebSocketResource(object):
self._factory = factory
# noinspection PyUnusedLocal
def getChildWithDefault(self, name, request):
"""
This resource cannot have children, hence this will always fail.
......
......@@ -23,7 +23,16 @@ from twisted.internet.defer import Deferred
def sleep(delay, reactor = None):
"""
Inline sleep for use in Twisted inlineCallbacks.
Inline sleep for use in coroutines (Twisted ``inlineCallback`` decorated functions).
.. seealso::
* `twisted.internet.defer.inlineCallbacks <http://twistedmatrix.com/documents/current/api/twisted.internet.defer.html#inlineCallbacks>`__
* `twisted.internet.interfaces.IReactorTime <http://twistedmatrix.com/documents/current/api/twisted.internet.interfaces.IReactorTime.html>`__
:param delay: Time to sleep in seconds.
:type delay: float
:param reactor: The Twisted reactor to use.
:type reactor: None or provider of IReactorTime
"""
if not reactor:
from twisted.internet import reactor
......
......@@ -18,18 +18,28 @@
from __future__ import absolute_import
__all__ = ['WebSocketServerProtocol',
'WebSocketServerFactory',
'WebSocketClientProtocol',
'WebSocketClientFactory',
'WrappingWebSocketServerFactory',
'WrappingWebSocketClientFactory',
'listenWS',
'connectWS',
'WampWebSocketServerProtocol',
'WampWebSocketServerFactory',
'WampWebSocketClientProtocol',
'WampWebSocketClientFactory']
__all__ = [
'WebSocketAdapterProtocol',
'WebSocketServerProtocol',
'WebSocketClientProtocol',
'WebSocketAdapterFactory',
'WebSocketServerFactory',
'WebSocketClientFactory',
'WrappingWebSocketAdapter',
'WrappingWebSocketServerProtocol',
'WrappingWebSocketClientProtocol',
'WrappingWebSocketServerFactory',
'WrappingWebSocketClientFactory',
'listenWS',
'connectWS',
'WampWebSocketServerProtocol',
'WampWebSocketServerFactory',
'WampWebSocketClientProtocol',
'WampWebSocketClientFactory',
]
from base64 import b64encode, b64decode
......@@ -324,12 +334,20 @@ class WrappingWebSocketAdapter:
def writeSequence(self, data):
## part of ITransport
for d in data:
self.write(data)
self.write(d)
def loseConnection(self):
## part of ITransport
self.sendClose()
def getPeer(self):
## part of ITransport
return self.transport.getPeer()
def getHost(self):
## part of ITransport
return self.transport.getHost()
class WrappingWebSocketServerProtocol(WrappingWebSocketAdapter, WebSocketServerProtocol):
......@@ -580,6 +598,7 @@ class WampWebSocketServerFactory(websocket.WampWebSocketServerFactory, WebSocket
kwargs['protocols'] = self._protocols
# noinspection PyCallByClass
WebSocketServerFactory.__init__(self, *args, **kwargs)
......
......@@ -25,13 +25,14 @@ __all__ = ("utcnow",
"newid",
"rtime",
"Stopwatch",
"Tracker",)
"Tracker",
"EqualityMixin")
import time
import random
import sys
from datetime import datetime
from datetime import datetime, timedelta
from pprint import pformat
......@@ -40,7 +41,8 @@ def utcnow():
"""
Get current time in UTC as ISO 8601 string.
:returns str -- Current time as string in ISO 8601 format.
:returns: Current time as string in ISO 8601 format.
:rtype: unicode
"""
now = datetime.utcnow()
return now.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z"
......@@ -51,9 +53,11 @@ def utcstr(ts):
"""
Format UTC timestamp in ISO 8601 format.
:param ts: Timestamp.
:type ts: instance of datetime.
:returns str -- Timestamp formatted in ISO 8601 format.
:param ts: The timestamp to format.
:type ts: instance of :py:class:`datetime.datetime`
:returns: Timestamp formatted in ISO 8601 format.
:rtype: unicode
"""
if ts:
return ts.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z"
......@@ -62,15 +66,22 @@ def utcstr(ts):
def parseutc(s):
def parseutc(datestr):
"""
Parse an ISO 8601 combined date and time string, like i.e. 2011-11-23T12:23Z
Parse an ISO 8601 combined date and time string, like i.e. ``"2011-11-23T12:23:00Z"``
into a UTC datetime instance.
@deprecated: Use the iso8601 module (eg, iso8601.parse_date("2014-05-23T13:03:44.123Z"))
.. deprecated:: 0.8.12
Use the **iso8601** module instead (e.g. ``iso8601.parse_date("2014-05-23T13:03:44.123Z")``)
:param datestr: The datetime string to parse.
:type datestr: unicode
:returns: The converted datetime object.
:rtype: instance of :py:class:`datetime.datetime`
"""
try:
return datetime.strptime(s, "%Y-%m-%dT%H:%M:%SZ")
return datetime.strptime(datestr, "%Y-%m-%dT%H:%M:%SZ")
except:
return None
......@@ -78,11 +89,16 @@ def parseutc(s):
def id():
"""
Generate a new random object ID from range [0, 2**53]. The upper bound 2**53
is chosen since it is the maximum integer that can be represented as
a IEEE double such that all smaller integers are representable as well.
Generate a new random object ID from range **[0, 2**53]**.
The upper bound **2**53** is chosen since it is the maximum integer that can be
represented as a IEEE double such that all smaller integers are representable as well.
Hence, IDs can be safely used with languages that use IEEE double as their
main (or only) number type (JavaScript, Lua, ..).
main (or only) number type (JavaScript, Lua, etc).
:returns: A random object ID.
:rtype: int
"""
return random.randint(0, 9007199254740992)
......@@ -91,8 +107,14 @@ def id():
def newid(len = 16):
"""
Generate a new random object ID.
:param len: The length (in chars) of the ID to generate.
:type len: int
:returns: A random object ID.
:rtype: str
"""
return ''.join([random.choice("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_") for i in xrange(len)])
return ''.join([random.choice("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_") for _ in xrange(len)])
......@@ -104,27 +126,39 @@ if sys.platform.startswith('win'):
## first call to this function, as a floating point number, based on the
## Win32 function QueryPerformanceCounter(). The resolution is typically
## better than one microsecond
rtime = time.clock
_ = rtime()
_rtime = time.clock
_ = _rtime() # this starts wallclock
else:
## On Unix-like platforms, this used the first available from this list:
## (1) gettimeofday() -- resolution in microseconds
## (2) ftime() -- resolution in milliseconds
## (3) time() -- resolution in seconds
rtime = time.time
_rtime = time.time
rtime = _rtime
"""
Precise wallclock time.
:returns: The current wallclock in seconds. Returned values are only guaranteed
to be meaningful relative to each other.
:rtype: float
"""
class Stopwatch:
"""
Stopwatch based on walltime. Can be used to do code timing and uses the
most precise walltime measurement available on the platform. This is
a very light-weight object, so create/dispose is very cheap.
Stopwatch based on walltime.
This can be used to do code timing and uses the most precise walltime measurement
available on the platform. This is a very light-weight object,
so create/dispose is very cheap.
"""
def __init__(self, start = True):
"""
Creates a new stopwatch and by default immediately starts (= resumes) it.
:param start: If ``True``, immediately start the stopwatch.
:type start: bool
"""
self._elapsed = 0
if start:
......@@ -137,6 +171,9 @@ class Stopwatch:
def elapsed(self):
"""
Return total time elapsed in seconds during which the stopwatch was running.
:returns: The elapsed time in seconds.
:rtype: float
"""
if self._running:
now = rtime()
......@@ -148,6 +185,9 @@ class Stopwatch:
"""
Pauses the stopwatch and returns total time elapsed in seconds during which
the stopwatch was running.
:returns: The elapsed time in seconds.
:rtype: float
"""
if self._running:
now = rtime()
......@@ -161,6 +201,9 @@ class Stopwatch:
"""
Resumes a paused stopwatch and returns total elapsed time in seconds
during which the stopwatch was running.
:returns: The elapsed time in seconds.
:rtype: float
"""
if not self._running:
self._started = rtime()
......@@ -174,6 +217,9 @@ class Stopwatch:
"""
Stops the stopwatch and returns total time elapsed in seconds during which
the stopwatch was (previously) running.
:returns: The elapsed time in seconds.
:rtype: float
"""
elapsed = self.pause()
self._elapsed = 0
......@@ -184,6 +230,9 @@ class Stopwatch:
class Tracker:
"""
A key-based statistics tracker.
"""
def __init__(self, tracker, tracked):
"""
......@@ -191,7 +240,8 @@ class Tracker:
self.tracker = tracker
self.tracked = tracked
self._timings = {}
self._stopwatch = Stopwatch()
self._offset = rtime()
self._dt_offset = datetime.utcnow()
def track(self, key):
......@@ -201,7 +251,7 @@ class Tracker:
:param key: Key under which to track the timing.
:type key: str
"""
self._timings[key] = self._stopwatch.elapsed()
self._timings[key] = rtime()