Merge remote-tracking branch 'upstream/master' into more-twisted-tox-versions
This commit is contained in:
commit
8f253822d6
|
@ -1,28 +1,29 @@
|
|||
*.pyc
|
||||
*.pyo
|
||||
*.dat
|
||||
build
|
||||
dist
|
||||
autobahn.egg-info
|
||||
*.sublime-workspace
|
||||
*.sublime-project
|
||||
__pycache__
|
||||
dropin.cache
|
||||
*.egg
|
||||
*.tar.gz
|
||||
_trial_temp
|
||||
_trial_temp*
|
||||
.idea
|
||||
.sconsign.dblite
|
||||
_html
|
||||
_upload
|
||||
build
|
||||
egg-info
|
||||
egg\-info
|
||||
egg
|
||||
.egg*
|
||||
.tox
|
||||
htmlcov/
|
||||
.coverage
|
||||
*~
|
||||
*.log
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.dat
|
||||
build
|
||||
dist
|
||||
autobahn.egg-info
|
||||
*.sublime-workspace
|
||||
*.sublime-project
|
||||
__pycache__
|
||||
dropin.cache
|
||||
*.egg
|
||||
*.tar.gz
|
||||
_trial_temp
|
||||
_trial_temp*
|
||||
.idea
|
||||
.sconsign.dblite
|
||||
_html
|
||||
_upload
|
||||
build
|
||||
egg-info
|
||||
egg\-info
|
||||
egg
|
||||
.egg*
|
||||
.tox
|
||||
htmlcov/
|
||||
.coverage
|
||||
*~
|
||||
*.log
|
||||
*.pid
|
||||
|
|
6
Makefile
6
Makefile
|
@ -55,6 +55,12 @@ test_twisted_coverage:
|
|||
coverage html
|
||||
coverage report --show-missing
|
||||
|
||||
test_coverage:
|
||||
-rm .coverage
|
||||
tox -e py27twisted,py27asyncio,py34asyncio
|
||||
coverage html
|
||||
coverage report --show-missing
|
||||
|
||||
# test under asyncio
|
||||
test_asyncio:
|
||||
USE_ASYNCIO=1 python -m pytest -rsx
|
||||
|
|
10
README.md
10
README.md
|
@ -19,13 +19,19 @@ WAMP provides asynchronous **Remote Procedure Calls** and **Publish & Subscribe*
|
|||
|
||||
It is ideal for distributed, multi-client and server applications, such as multi-user database-drive business applications, sensor networks (IoT), instant messaging or MMOGs (massively multi-player online games) .
|
||||
|
||||
WAMP enables application architectures with application code distributed freely across processes and devices according to functional aspects. Since WAMP implementations exist for multiple languages, WAMP applications can be polyglott. Application components can be implemented in a language and run on a device which best fit the particular use case.
|
||||
WAMP enables application architectures with application code distributed freely across processes and devices according to functional aspects. Since WAMP implementations exist for multiple languages, WAMP applications can be polyglot. Application components can be implemented in a language and run on a device which best fit the particular use case.
|
||||
|
||||
**Note** that WAMP is a *routed* protocol, so you need to run something that plays the Broker and Dealer roles from the [WAMP Specification](http://wamp.ws/spec/). We provide [Crossbar.io](http://crossbar.io) but there are [other options](http://wamp.ws/implementations/#routers) as well.
|
||||
|
||||
|
||||
## Show me some code
|
||||
|
||||
A simple WebSocket echo server:
|
||||
|
||||
```python
|
||||
from autobahn.twisted.websocket import WebSocketServerProtocol
|
||||
# or: from autobahn.asyncio.websocket import WebSocketServerProtocol
|
||||
|
||||
class MyServerProtocol(WebSocketServerProtocol):
|
||||
|
||||
def onConnect(self, request):
|
||||
|
@ -50,6 +56,8 @@ class MyServerProtocol(WebSocketServerProtocol):
|
|||
... and a sample WAMP application component:
|
||||
|
||||
```python
|
||||
from autobahn.twisted.wamp import ApplicationSession
|
||||
# or: from autobahn.asyncio.wamp import ApplicationSession
|
||||
|
||||
class MyComponent(ApplicationSession):
|
||||
|
||||
|
|
|
@ -24,5 +24,5 @@
|
|||
#
|
||||
###############################################################################
|
||||
|
||||
__version__ = "0.10.2"
|
||||
__version__ = "0.10.5-2"
|
||||
version = __version__ # backward compat.
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
###############################################################################
|
||||
|
||||
from __future__ import absolute_import
|
||||
import signal
|
||||
|
||||
from autobahn.wamp import protocol
|
||||
from autobahn.wamp.types import ComponentConfig
|
||||
|
@ -33,94 +34,28 @@ from autobahn.asyncio.websocket import WampWebSocketClientFactory
|
|||
|
||||
try:
|
||||
import asyncio
|
||||
from asyncio import iscoroutine
|
||||
from asyncio import Future
|
||||
except ImportError:
|
||||
# Trollius >= 0.3 was renamed
|
||||
# Trollius >= 0.3 was renamed to asyncio
|
||||
# noinspection PyUnresolvedReferences
|
||||
import trollius as asyncio
|
||||
from trollius import iscoroutine
|
||||
from trollius import Future
|
||||
|
||||
import txaio
|
||||
txaio.use_asyncio()
|
||||
|
||||
__all__ = (
|
||||
'FutureMixin',
|
||||
'ApplicationSession',
|
||||
'ApplicationSessionFactory',
|
||||
'ApplicationRunner'
|
||||
)
|
||||
|
||||
|
||||
class FutureMixin(object):
|
||||
"""
|
||||
Mixin for Asyncio style Futures.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def _create_future():
|
||||
return Future()
|
||||
|
||||
@staticmethod
|
||||
def _create_future_success(result=None):
|
||||
f = Future()
|
||||
f.set_result(result)
|
||||
return f
|
||||
|
||||
@staticmethod
|
||||
def _create_future_error(error=None):
|
||||
f = Future()
|
||||
f.set_exception(error)
|
||||
return f
|
||||
|
||||
@staticmethod
|
||||
def _as_future(fun, *args, **kwargs):
|
||||
try:
|
||||
res = fun(*args, **kwargs)
|
||||
except Exception as e:
|
||||
f = Future()
|
||||
f.set_exception(e)
|
||||
return f
|
||||
else:
|
||||
if isinstance(res, Future):
|
||||
return res
|
||||
elif iscoroutine(res):
|
||||
return asyncio.Task(res)
|
||||
else:
|
||||
f = Future()
|
||||
f.set_result(res)
|
||||
return f
|
||||
|
||||
@staticmethod
|
||||
def _resolve_future(future, result=None):
|
||||
future.set_result(result)
|
||||
|
||||
@staticmethod
|
||||
def _reject_future(future, error):
|
||||
future.set_exception(error)
|
||||
|
||||
@staticmethod
|
||||
def _add_future_callbacks(future, callback, errback):
|
||||
def done(f):
|
||||
try:
|
||||
res = f.result()
|
||||
if callback:
|
||||
callback(res)
|
||||
except Exception as e:
|
||||
if errback:
|
||||
errback(e)
|
||||
return future.add_done_callback(done)
|
||||
|
||||
@staticmethod
|
||||
def _gather_futures(futures, consume_exceptions=True):
|
||||
return asyncio.gather(*futures, return_exceptions=consume_exceptions)
|
||||
|
||||
|
||||
class ApplicationSession(FutureMixin, protocol.ApplicationSession):
|
||||
class ApplicationSession(protocol.ApplicationSession):
|
||||
"""
|
||||
WAMP application session for asyncio-based applications.
|
||||
"""
|
||||
|
||||
|
||||
class ApplicationSessionFactory(FutureMixin, protocol.ApplicationSessionFactory):
|
||||
class ApplicationSessionFactory(protocol.ApplicationSessionFactory):
|
||||
"""
|
||||
WAMP application session factory for asyncio-based applications.
|
||||
"""
|
||||
|
@ -141,24 +76,36 @@ class ApplicationRunner(object):
|
|||
"""
|
||||
|
||||
def __init__(self, url, realm, extra=None, serializers=None,
|
||||
debug=False, debug_wamp=False, debug_app=False):
|
||||
debug=False, debug_wamp=False, debug_app=False,
|
||||
ssl=None):
|
||||
"""
|
||||
|
||||
:param url: The WebSocket URL of the WAMP router to connect to (e.g. `ws://somehost.com:8090/somepath`)
|
||||
:type url: unicode
|
||||
|
||||
:param realm: The WAMP realm to join the application session to.
|
||||
:type realm: unicode
|
||||
|
||||
:param extra: Optional extra configuration to forward to the application component.
|
||||
:type extra: dict
|
||||
|
||||
:param serializers: A list of WAMP serializers to use (or None for default serializers).
|
||||
Serializers must implement :class:`autobahn.wamp.interfaces.ISerializer`.
|
||||
:type serializers: list
|
||||
|
||||
:param debug: Turn on low-level debugging.
|
||||
:type debug: bool
|
||||
|
||||
:param debug_wamp: Turn on WAMP-level debugging.
|
||||
:type debug_wamp: bool
|
||||
|
||||
:param debug_app: Turn on app-level debugging.
|
||||
:type debug_app: bool
|
||||
|
||||
:param ssl: An (optional) SSL context instance or a bool. See
|
||||
the documentation for the `loop.create_connection` asyncio
|
||||
method, to which this value is passed as the ``ssl=``
|
||||
kwarg.
|
||||
:type ssl: :class:`ssl.SSLContext` or bool
|
||||
"""
|
||||
self.url = url
|
||||
self.realm = realm
|
||||
|
@ -168,6 +115,7 @@ class ApplicationRunner(object):
|
|||
self.debug_app = debug_app
|
||||
self.make = None
|
||||
self.serializers = serializers
|
||||
self.ssl = ssl
|
||||
|
||||
def run(self, make):
|
||||
"""
|
||||
|
@ -192,15 +140,37 @@ class ApplicationRunner(object):
|
|||
|
||||
isSecure, host, port, resource, path, params = parseWsUrl(self.url)
|
||||
|
||||
if self.ssl is None:
|
||||
ssl = isSecure
|
||||
else:
|
||||
if self.ssl and not isSecure:
|
||||
raise RuntimeError(
|
||||
'ssl argument value passed to %s conflicts with the "ws:" '
|
||||
'prefix of the url argument. Did you mean to use "wss:"?' %
|
||||
self.__class__.__name__)
|
||||
ssl = self.ssl
|
||||
|
||||
# 2) create a WAMP-over-WebSocket transport client factory
|
||||
transport_factory = WampWebSocketClientFactory(create, url=self.url, serializers=self.serializers,
|
||||
debug=self.debug, debug_wamp=self.debug_wamp)
|
||||
|
||||
# 3) start the client
|
||||
loop = asyncio.get_event_loop()
|
||||
coro = loop.create_connection(transport_factory, host, port, ssl=isSecure)
|
||||
loop.run_until_complete(coro)
|
||||
txaio.use_asyncio()
|
||||
txaio.config.loop = loop
|
||||
coro = loop.create_connection(transport_factory, host, port, ssl=ssl)
|
||||
(transport, protocol) = loop.run_until_complete(coro)
|
||||
loop.add_signal_handler(signal.SIGTERM, loop.stop)
|
||||
|
||||
# 4) now enter the asyncio event loop
|
||||
loop.run_forever()
|
||||
try:
|
||||
loop.run_forever()
|
||||
except KeyboardInterrupt:
|
||||
# wait until we send Goodbye if user hit ctrl-c
|
||||
# (done outside this except so SIGTERM gets the same handling)
|
||||
pass
|
||||
# give Goodbye message a chance to go through, if we still
|
||||
# have an active session
|
||||
if protocol._session:
|
||||
loop.run_until_complete(protocol._session.leave())
|
||||
loop.close()
|
||||
|
|
|
@ -41,6 +41,9 @@ except ImportError:
|
|||
from trollius import iscoroutine
|
||||
from trollius import Future
|
||||
|
||||
from autobahn.logger import make_logger
|
||||
|
||||
|
||||
__all__ = (
|
||||
'WebSocketAdapterProtocol',
|
||||
'WebSocketServerProtocol',
|
||||
|
@ -212,12 +215,7 @@ class WebSocketAdapterFactory(object):
|
|||
"""
|
||||
Adapter class for asyncio-based WebSocket client and server factories.
|
||||
"""
|
||||
|
||||
def _log(self, msg):
|
||||
print(msg)
|
||||
|
||||
def _callLater(self, delay, fun):
|
||||
return self.loop.call_later(delay, fun)
|
||||
log = make_logger()
|
||||
|
||||
def __call__(self):
|
||||
proto = self.protocol()
|
||||
|
|
|
@ -22,21 +22,19 @@
|
|||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
###############################################################################
|
||||
##############################################################################
|
||||
|
||||
import binascii
|
||||
|
||||
from autobahn.wamp.serializer import JsonObjectSerializer, MsgPackObjectSerializer
|
||||
|
||||
# ser = JsonObjectSerializer(batched = True)
|
||||
ser = MsgPackObjectSerializer(batched=True)
|
||||
from __future__ import absolute_import
|
||||
|
||||
|
||||
o1 = [1, "hello", [1, 2, 3]]
|
||||
o2 = [3, {"a": 23, "b": 24}]
|
||||
def make_logger(logger_type=None):
|
||||
if logger_type == "twisted":
|
||||
# If we've been asked for the Twisted logger, try and get the new one
|
||||
try:
|
||||
from twisted.logger import Logger
|
||||
return Logger()
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
d1 = ser.serialize(o1) + ser.serialize(o2) + ser.serialize(o1)
|
||||
|
||||
m = ser.unserialize(d1)
|
||||
|
||||
print m
|
||||
from logging import getLogger
|
||||
return getLogger()
|
|
@ -39,6 +39,10 @@ def install_optimal_reactor(verbose=False):
|
|||
"""
|
||||
import sys
|
||||
from twisted.python import reflect
|
||||
import txaio
|
||||
txaio.use_twisted() # just to be sure...
|
||||
# XXX should I configure txaio.config.loop in here too, or just in
|
||||
# install_reactor()? (I am: see bottom of function)
|
||||
|
||||
# determine currently installed reactor, if any
|
||||
##
|
||||
|
@ -110,6 +114,9 @@ def install_optimal_reactor(verbose=False):
|
|||
except Exception as e:
|
||||
print("WARNING: Could not install default Twisted reactor for this platform ({0}).".format(e))
|
||||
|
||||
from twisted.internet import reactor
|
||||
txaio.config.loop = reactor
|
||||
|
||||
|
||||
def install_reactor(explicitReactor=None, verbose=False):
|
||||
"""
|
||||
|
@ -121,6 +128,8 @@ def install_reactor(explicitReactor=None, verbose=False):
|
|||
:type verbose: bool
|
||||
"""
|
||||
import sys
|
||||
import txaio
|
||||
txaio.use_twisted() # just to be sure...
|
||||
|
||||
if explicitReactor:
|
||||
# install explicitly given reactor
|
||||
|
@ -141,6 +150,7 @@ def install_reactor(explicitReactor=None, verbose=False):
|
|||
|
||||
# now the reactor is installed, import it
|
||||
from twisted.internet import reactor
|
||||
txaio.config.loop = reactor
|
||||
|
||||
if verbose:
|
||||
from twisted.python.reflect import qual
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
###############################################################################
|
||||
########################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
|
@ -22,7 +22,7 @@
|
|||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
###############################################################################
|
||||
########################################
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
|
@ -76,7 +76,7 @@ class WampLongPollResourceSessionSend(Resource):
|
|||
"""
|
||||
payload = request.content.read()
|
||||
if self._debug:
|
||||
log.msg("WampLongPoll: receiving data for transport '{0}'\n{1}".format(self._parent._transportid, binascii.hexlify(payload)))
|
||||
log.msg("WampLongPoll: receiving data for transport '{0}'\n{1}".format(self._parent._transport_id, binascii.hexlify(payload)))
|
||||
|
||||
try:
|
||||
# process (batch of) WAMP message(s)
|
||||
|
@ -115,7 +115,7 @@ class WampLongPollResourceSessionReceive(Resource):
|
|||
if self._debug:
|
||||
def logqueue():
|
||||
if not self._killed:
|
||||
log.msg("WampLongPoll: transport '{0}' - currently polled {1}, pending messages {2}".format(self._parent._transportid, self._request is not None, len(self._queue)))
|
||||
log.msg("WampLongPoll: transport '{0}' - currently polled {1}, pending messages {2}".format(self._parent._transport_id, self._request is not None, len(self._queue)))
|
||||
self.reactor.callLater(1, logqueue)
|
||||
logqueue()
|
||||
|
||||
|
@ -173,7 +173,7 @@ class WampLongPollResourceSessionReceive(Resource):
|
|||
|
||||
def cancel(_):
|
||||
if self._debug:
|
||||
log.msg("WampLongPoll: poll request for transport '{0}' has gone away".format(self._parent._transportid))
|
||||
log.msg("WampLongPoll: poll request for transport '{0}' has gone away".format(self._parent._transport_id))
|
||||
self._request = None
|
||||
|
||||
request.notifyFinish().addErrback(cancel)
|
||||
|
@ -205,13 +205,13 @@ class WampLongPollResourceSessionClose(Resource):
|
|||
by issuing a HTTP/POST with empty body to this resource.
|
||||
"""
|
||||
if self._debug:
|
||||
log.msg("WampLongPoll: closing transport '{0}'".format(self._parent._transportid))
|
||||
log.msg("WampLongPoll: closing transport '{0}'".format(self._parent._transport_id))
|
||||
|
||||
# now actually close the session
|
||||
self._parent.close()
|
||||
|
||||
if self._debug:
|
||||
log.msg("WampLongPoll: session ended and transport {0} closed".format(self._parent._transportid))
|
||||
log.msg("WampLongPoll: session ended and transport {0} closed".format(self._parent._transport_id))
|
||||
|
||||
request.setResponseCode(http.NO_CONTENT)
|
||||
self._parent._parent._setStandardHeaders(request)
|
||||
|
@ -223,14 +223,14 @@ class WampLongPollResourceSession(Resource):
|
|||
A Web resource representing an open WAMP session.
|
||||
"""
|
||||
|
||||
def __init__(self, parent, transportid, serializer):
|
||||
def __init__(self, parent, transport_details):
|
||||
"""
|
||||
Create a new Web resource representing a WAMP session.
|
||||
|
||||
:param parent: The parent Web resource.
|
||||
:type parent: Instance of :class:`autobahn.twisted.longpoll.WampLongPollResource`.
|
||||
:param serializer: The WAMP serializer in use for this session.
|
||||
:type serializer: An object that implements :class:`autobahn.wamp.interfaces.ISerializer`.
|
||||
:param transport_details: Details on the WAMP-over-Longpoll transport session.
|
||||
:type transport_details: dict
|
||||
"""
|
||||
Resource.__init__(self)
|
||||
|
||||
|
@ -239,15 +239,16 @@ class WampLongPollResourceSession(Resource):
|
|||
self._debug_wamp = True
|
||||
self.reactor = self._parent.reactor
|
||||
|
||||
self._transportid = transportid
|
||||
self._serializer = serializer
|
||||
self._transport_id = transport_details['transport']
|
||||
self._serializer = transport_details['serializer']
|
||||
self._session = None
|
||||
|
||||
# session authentication information
|
||||
##
|
||||
#
|
||||
self._authid = None
|
||||
self._authrole = None
|
||||
self._authmethod = None
|
||||
self._authprovider = None
|
||||
|
||||
self._send = WampLongPollResourceSessionSend(self)
|
||||
self._receive = WampLongPollResourceSessionReceive(self)
|
||||
|
@ -260,21 +261,21 @@ class WampLongPollResourceSession(Resource):
|
|||
self._isalive = False
|
||||
|
||||
# kill inactive sessions after this timeout
|
||||
##
|
||||
#
|
||||
killAfter = self._parent._killAfter
|
||||
if killAfter > 0:
|
||||
def killIfDead():
|
||||
if not self._isalive:
|
||||
if self._debug:
|
||||
log.msg("WampLongPoll: killing inactive WAMP session with transport '{0}'".format(self._transportid))
|
||||
log.msg("WampLongPoll: killing inactive WAMP session with transport '{0}'".format(self._transport_id))
|
||||
|
||||
self.onClose(False, 5000, "session inactive")
|
||||
self._receive._kill()
|
||||
if self._transportid in self._parent._transports:
|
||||
del self._parent._transports[self._transportid]
|
||||
if self._transport_id in self._parent._transports:
|
||||
del self._parent._transports[self._transport_id]
|
||||
else:
|
||||
if self._debug:
|
||||
log.msg("WampLongPoll: transport '{0}' is still alive".format(self._transportid))
|
||||
log.msg("WampLongPoll: transport '{0}' is still alive".format(self._transport_id))
|
||||
|
||||
self._isalive = False
|
||||
self.reactor.callLater(killAfter, killIfDead)
|
||||
|
@ -282,10 +283,10 @@ class WampLongPollResourceSession(Resource):
|
|||
self.reactor.callLater(killAfter, killIfDead)
|
||||
else:
|
||||
if self._debug:
|
||||
log.msg("WampLongPoll: transport '{0}' automatic killing of inactive session disabled".format(self._transportid))
|
||||
log.msg("WampLongPoll: transport '{0}' automatic killing of inactive session disabled".format(self._transport_id))
|
||||
|
||||
if self._debug:
|
||||
log.msg("WampLongPoll: session resource for transport '{0}' initialized)".format(self._transportid))
|
||||
log.msg("WampLongPoll: session resource for transport '{0}' initialized)".format(self._transport_id))
|
||||
|
||||
self.onOpen()
|
||||
|
||||
|
@ -296,7 +297,7 @@ class WampLongPollResourceSession(Resource):
|
|||
if self.isOpen():
|
||||
self.onClose(True, 1000, u"session closed")
|
||||
self._receive._kill()
|
||||
del self._parent._transports[self._transportid]
|
||||
del self._parent._transports[self._transport_id]
|
||||
else:
|
||||
raise TransportLost()
|
||||
|
||||
|
@ -307,7 +308,7 @@ class WampLongPollResourceSession(Resource):
|
|||
if self.isOpen():
|
||||
self.onClose(True, 1000, u"session aborted")
|
||||
self._receive._kill()
|
||||
del self._parent._transports[self._transportid]
|
||||
del self._parent._transports[self._transport_id]
|
||||
else:
|
||||
raise TransportLost()
|
||||
|
||||
|
@ -405,7 +406,7 @@ class WampLongPollResourceOpen(Resource):
|
|||
return self._parent._failRequest(request, "missing attribute 'protocols' in WAMP session open request")
|
||||
|
||||
# determine the protocol to speak
|
||||
##
|
||||
#
|
||||
protocol = None
|
||||
serializer = None
|
||||
for p in options['protocols']:
|
||||
|
@ -419,19 +420,36 @@ class WampLongPollResourceOpen(Resource):
|
|||
return self.__failRequest(request, "no common protocol to speak (I speak: {0})".format(["wamp.2.{0}".format(s) for s in self._parent._serializers.keys()]))
|
||||
|
||||
# make up new transport ID
|
||||
##
|
||||
#
|
||||
if self._parent._debug_transport_id:
|
||||
# use fixed transport ID for debugging purposes
|
||||
transport = self._parent._debug_transport_id
|
||||
else:
|
||||
transport = newid()
|
||||
|
||||
# this doesn't contain all the info (when a header key appears multiple times)
|
||||
# http_headers_received = request.getAllHeaders()
|
||||
http_headers_received = {}
|
||||
for key, values in request.requestHeaders.getAllRawHeaders():
|
||||
if key not in http_headers_received:
|
||||
http_headers_received[key] = []
|
||||
http_headers_received[key].extend(values)
|
||||
|
||||
transport_details = {
|
||||
'transport': transport,
|
||||
'serializer': serializer,
|
||||
'protocol': protocol,
|
||||
'peer': request.getClientIP(),
|
||||
'http_headers_received': http_headers_received,
|
||||
'http_headers_sent': None
|
||||
}
|
||||
|
||||
# create instance of WampLongPollResourceSession or subclass thereof ..
|
||||
##
|
||||
self._parent._transports[transport] = self._parent.protocol(self._parent, transport, serializer)
|
||||
#
|
||||
self._parent._transports[transport] = self._parent.protocol(self._parent, transport_details)
|
||||
|
||||
# create response
|
||||
##
|
||||
#
|
||||
self._parent._setStandardHeaders(request)
|
||||
request.setHeader('content-type', 'application/json; charset=utf-8')
|
||||
|
||||
|
@ -549,7 +567,7 @@ class WampLongPollResource(Resource):
|
|||
self._transports = {}
|
||||
|
||||
# <Base URL>/open
|
||||
##
|
||||
#
|
||||
self.putChild("open", WampLongPollResourceOpen(self))
|
||||
|
||||
if self._debug:
|
||||
|
|
|
@ -51,10 +51,10 @@ class WampRawSocketProtocol(Int32StringReceiver):
|
|||
|
||||
def connectionMade(self):
|
||||
if self.factory.debug:
|
||||
log.msg("WAMP-over-RawSocket connection made")
|
||||
log.msg("WampRawSocketProtocol: connection made")
|
||||
|
||||
# the peer we are connected to
|
||||
##
|
||||
#
|
||||
try:
|
||||
peer = self.transport.getPeer()
|
||||
except AttributeError:
|
||||
|
@ -63,44 +63,71 @@ class WampRawSocketProtocol(Int32StringReceiver):
|
|||
else:
|
||||
self.peer = peer2str(peer)
|
||||
|
||||
# this will hold an ApplicationSession object
|
||||
# once the RawSocket opening handshake has been
|
||||
# completed
|
||||
#
|
||||
self._session = None
|
||||
|
||||
# Will hold the negotiated serializer once the opening handshake is complete
|
||||
#
|
||||
self._serializer = None
|
||||
|
||||
# Will be set to True once the opening handshake is complete
|
||||
#
|
||||
self._handshake_complete = False
|
||||
|
||||
# Buffer for opening handshake received bytes.
|
||||
#
|
||||
self._handshake_bytes = b''
|
||||
|
||||
# Clinet requested maximum length of serialized messages.
|
||||
#
|
||||
self._max_len_send = None
|
||||
|
||||
def _on_handshake_complete(self):
|
||||
try:
|
||||
self._session = self.factory._factory()
|
||||
self._session.onOpen(self)
|
||||
except Exception as e:
|
||||
# Exceptions raised in onOpen are fatal ..
|
||||
if self.factory.debug:
|
||||
log.msg("ApplicationSession constructor / onOpen raised ({0})".format(e))
|
||||
log.msg("WampRawSocketProtocol: ApplicationSession constructor / onOpen raised ({0})".format(e))
|
||||
self.abort()
|
||||
else:
|
||||
if self.factory.debug:
|
||||
log.msg("ApplicationSession started.")
|
||||
|
||||
def connectionLost(self, reason):
|
||||
if self.factory.debug:
|
||||
log.msg("WAMP-over-RawSocket connection lost: reason = '{0}'".format(reason))
|
||||
log.msg("WampRawSocketProtocol: connection lost: reason = '{0}'".format(reason))
|
||||
try:
|
||||
wasClean = isinstance(reason.value, ConnectionDone)
|
||||
self._session.onClose(wasClean)
|
||||
except Exception as e:
|
||||
# silently ignore exceptions raised here ..
|
||||
if self.factory.debug:
|
||||
log.msg("ApplicationSession.onClose raised ({0})".format(e))
|
||||
log.msg("WampRawSocketProtocol: ApplicationSession.onClose raised ({0})".format(e))
|
||||
self._session = None
|
||||
|
||||
def stringReceived(self, payload):
|
||||
if self.factory.debug:
|
||||
log.msg("RX octets: {0}".format(binascii.hexlify(payload)))
|
||||
log.msg("WampRawSocketProtocol: RX octets: {0}".format(binascii.hexlify(payload)))
|
||||
try:
|
||||
for msg in self.factory._serializer.unserialize(payload):
|
||||
for msg in self._serializer.unserialize(payload):
|
||||
if self.factory.debug:
|
||||
log.msg("RX WAMP message: {0}".format(msg))
|
||||
log.msg("WampRawSocketProtocol: RX WAMP message: {0}".format(msg))
|
||||
self._session.onMessage(msg)
|
||||
|
||||
except ProtocolError as e:
|
||||
log.msg(str(e))
|
||||
if self.factory.debug:
|
||||
log.msg("WAMP Protocol Error ({0}) - aborting connection".format(e))
|
||||
log.msg("WampRawSocketProtocol: WAMP Protocol Error ({0}) - aborting connection".format(e))
|
||||
self.abort()
|
||||
|
||||
except Exception as e:
|
||||
if self.factory.debug:
|
||||
log.msg("WAMP Internal Error ({0}) - aborting connection".format(e))
|
||||
log.msg("WampRawSocketProtocol: WAMP Internal Error ({0}) - aborting connection".format(e))
|
||||
self.abort()
|
||||
|
||||
def send(self, msg):
|
||||
|
@ -109,16 +136,16 @@ class WampRawSocketProtocol(Int32StringReceiver):
|
|||
"""
|
||||
if self.isOpen():
|
||||
if self.factory.debug:
|
||||
log.msg("TX WAMP message: {0}".format(msg))
|
||||
log.msg("WampRawSocketProtocol: TX WAMP message: {0}".format(msg))
|
||||
try:
|
||||
payload, _ = self.factory._serializer.serialize(msg)
|
||||
payload, _ = self._serializer.serialize(msg)
|
||||
except Exception as e:
|
||||
# all exceptions raised from above should be serialization errors ..
|
||||
raise SerializationError("Unable to serialize WAMP application payload ({0})".format(e))
|
||||
raise SerializationError("WampRawSocketProtocol: unable to serialize WAMP application payload ({0})".format(e))
|
||||
else:
|
||||
self.sendString(payload)
|
||||
if self.factory.debug:
|
||||
log.msg("TX octets: {0}".format(binascii.hexlify(payload)))
|
||||
log.msg("WampRawSocketProtocol: TX octets: {0}".format(binascii.hexlify(payload)))
|
||||
else:
|
||||
raise TransportLost()
|
||||
|
||||
|
@ -156,33 +183,138 @@ class WampRawSocketServerProtocol(WampRawSocketProtocol):
|
|||
Base class for Twisted-based WAMP-over-RawSocket server protocols.
|
||||
"""
|
||||
|
||||
def dataReceived(self, data):
|
||||
|
||||
if self._handshake_complete:
|
||||
WampRawSocketProtocol.dataReceived(self, data)
|
||||
else:
|
||||
remaining = 4 - len(self._handshake_bytes)
|
||||
self._handshake_bytes += data[:remaining]
|
||||
|
||||
if len(self._handshake_bytes) == 4:
|
||||
|
||||
if self.factory.debug:
|
||||
log.msg("WampRawSocketProtocol: opening handshake received - {0}".format(binascii.b2a_hex(self._handshake_bytes)))
|
||||
|
||||
if ord(self._handshake_bytes[0]) != 0x7f:
|
||||
if self.factory.debug:
|
||||
log.msg("WampRawSocketProtocol: invalid magic byte (octet 1) in opening handshake: was 0x{0}, but expected 0x7f".format(binascii.b2a_hex(self._handshake_bytes[0])))
|
||||
self.abort()
|
||||
|
||||
# peer requests us to send messages of maximum length 2**max_len_exp
|
||||
#
|
||||
self._max_len_send = 2 ** (9 + (ord(self._handshake_bytes[1]) >> 4))
|
||||
if self.factory.debug:
|
||||
log.msg("WampRawSocketProtocol: client requests us to send out most {} bytes per message".format(self._max_len_send))
|
||||
|
||||
# client wants to speak this serialization format
|
||||
#
|
||||
ser_id = ord(self._handshake_bytes[1]) & 0x0F
|
||||
if ser_id in self.factory._serializers:
|
||||
self._serializer = self.factory._serializers[ser_id]
|
||||
if self.factory.debug:
|
||||
log.msg("WampRawSocketProtocol: client wants to use serializer {}".format(ser_id))
|
||||
else:
|
||||
if self.factory.debug:
|
||||
log.msg("WampRawSocketProtocol: opening handshake - no suitable serializer found (client requested {0}, and we have {1})".format(ser_id, self.factory._serializers.keys()))
|
||||
self.abort()
|
||||
|
||||
# we request the peer to send message of maximum length 2**reply_max_len_exp
|
||||
#
|
||||
reply_max_len_exp = 24
|
||||
|
||||
# send out handshake reply
|
||||
#
|
||||
reply_octet2 = chr(((reply_max_len_exp - 9) << 4) | self._serializer.RAWSOCKET_SERIALIZER_ID)
|
||||
self.transport.write(b'\x7F') # magic byte
|
||||
self.transport.write(reply_octet2) # max length / serializer
|
||||
self.transport.write(b'\x00\x00') # reserved octets
|
||||
|
||||
self._handshake_complete = True
|
||||
|
||||
self._on_handshake_complete()
|
||||
|
||||
if self.factory.debug:
|
||||
log.msg("WampRawSocketProtocol: opening handshake completed", self._serializer)
|
||||
|
||||
# consume any remaining data received already ..
|
||||
#
|
||||
data = data[remaining:]
|
||||
if data:
|
||||
self.dataReceived(data)
|
||||
|
||||
|
||||
class WampRawSocketClientProtocol(WampRawSocketProtocol):
|
||||
"""
|
||||
Base class for Twisted-based WAMP-over-RawSocket client protocols.
|
||||
"""
|
||||
|
||||
def connectionMade(self):
|
||||
WampRawSocketProtocol.connectionMade(self)
|
||||
self._serializer = self.factory._serializer
|
||||
|
||||
# we request the peer to send message of maximum length 2**reply_max_len_exp
|
||||
#
|
||||
request_max_len_exp = 24
|
||||
|
||||
# send out handshake reply
|
||||
#
|
||||
request_octet2 = chr(((request_max_len_exp - 9) << 4) | self._serializer.RAWSOCKET_SERIALIZER_ID)
|
||||
self.transport.write(b'\x7F') # magic byte
|
||||
self.transport.write(request_octet2) # max length / serializer
|
||||
self.transport.write(b'\x00\x00') # reserved octets
|
||||
|
||||
def dataReceived(self, data):
|
||||
|
||||
if self._handshake_complete:
|
||||
WampRawSocketProtocol.dataReceived(self, data)
|
||||
else:
|
||||
remaining = 4 - len(self._handshake_bytes)
|
||||
self._handshake_bytes += data[:remaining]
|
||||
|
||||
if len(self._handshake_bytes) == 4:
|
||||
|
||||
if self.factory.debug:
|
||||
log.msg("WampRawSocketProtocol: opening handshake received - {0}".format(binascii.b2a_hex(self._handshake_bytes)))
|
||||
|
||||
if ord(self._handshake_bytes[0]) != 0x7f:
|
||||
if self.factory.debug:
|
||||
log.msg("WampRawSocketProtocol: invalid magic byte (octet 1) in opening handshake: was 0x{0}, but expected 0x7f".format(binascii.b2a_hex(self._handshake_bytes[0])))
|
||||
self.abort()
|
||||
|
||||
# peer requests us to send messages of maximum length 2**max_len_exp
|
||||
#
|
||||
self._max_len_send = 2 ** (9 + (ord(self._handshake_bytes[1]) >> 4))
|
||||
if self.factory.debug:
|
||||
log.msg("WampRawSocketProtocol: server requests us to send out most {} bytes per message".format(self._max_len_send))
|
||||
|
||||
# client wants to speak this serialization format
|
||||
#
|
||||
ser_id = ord(self._handshake_bytes[1]) & 0x0F
|
||||
if ser_id != self._serializer.RAWSOCKET_SERIALIZER_ID:
|
||||
if self.factory.debug:
|
||||
log.msg("WampRawSocketProtocol: opening handshake - no suitable serializer found (server replied {0}, and we requested {1})".format(ser_id, self._serializer.RAWSOCKET_SERIALIZER_ID))
|
||||
self.abort()
|
||||
|
||||
self._handshake_complete = True
|
||||
|
||||
self._on_handshake_complete()
|
||||
|
||||
if self.factory.debug:
|
||||
log.msg("WampRawSocketProtocol: opening handshake completed", self._serializer)
|
||||
|
||||
# consume any remaining data received already ..
|
||||
#
|
||||
data = data[remaining:]
|
||||
if data:
|
||||
self.dataReceived(data)
|
||||
|
||||
|
||||
class WampRawSocketFactory(Factory):
|
||||
"""
|
||||
Base class for Twisted-based WAMP-over-RawSocket factories.
|
||||
"""
|
||||
|
||||
def __init__(self, factory, serializer, debug=False):
|
||||
"""
|
||||
|
||||
:param factory: A callable that produces instances that implement
|
||||
: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: obj
|
||||
"""
|
||||
assert(callable(factory))
|
||||
self._factory = factory
|
||||
self._serializer = serializer
|
||||
self.debug = debug
|
||||
|
||||
|
||||
class WampRawSocketServerFactory(WampRawSocketFactory):
|
||||
"""
|
||||
|
@ -190,9 +322,89 @@ class WampRawSocketServerFactory(WampRawSocketFactory):
|
|||
"""
|
||||
protocol = WampRawSocketServerProtocol
|
||||
|
||||
def __init__(self, factory, serializers=None, debug=False):
|
||||
"""
|
||||
|
||||
:param factory: A callable that produces instances that implement
|
||||
:class:`autobahn.wamp.interfaces.ITransportHandler`
|
||||
:type factory: callable
|
||||
:param serializers: A list of WAMP serializers to use (or None for default
|
||||
serializers). Serializers must implement
|
||||
:class:`autobahn.wamp.interfaces.ISerializer`.
|
||||
:type serializers: list
|
||||
"""
|
||||
assert(callable(factory))
|
||||
self._factory = factory
|
||||
|
||||
self.debug = debug
|
||||
|
||||
if serializers is None:
|
||||
serializers = []
|
||||
|
||||
# try MsgPack WAMP serializer
|
||||
try:
|
||||
from autobahn.wamp.serializer import MsgPackSerializer
|
||||
serializers.append(MsgPackSerializer(batched=True))
|
||||
serializers.append(MsgPackSerializer())
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
# try JSON WAMP serializer
|
||||
try:
|
||||
from autobahn.wamp.serializer import JsonSerializer
|
||||
serializers.append(JsonSerializer(batched=True))
|
||||
serializers.append(JsonSerializer())
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
if not serializers:
|
||||
raise Exception("could not import any WAMP serializers")
|
||||
|
||||
self._serializers = {}
|
||||
for ser in serializers:
|
||||
self._serializers[ser.RAWSOCKET_SERIALIZER_ID] = ser
|
||||
|
||||
|
||||
class WampRawSocketClientFactory(WampRawSocketFactory):
|
||||
"""
|
||||
Base class for Twisted-based WAMP-over-RawSocket client factories.
|
||||
"""
|
||||
protocol = WampRawSocketClientProtocol
|
||||
|
||||
def __init__(self, factory, serializer=None, debug=False):
|
||||
"""
|
||||
|
||||
:param factory: A callable that produces instances that implement
|
||||
:class:`autobahn.wamp.interfaces.ITransportHandler`
|
||||
:type factory: callable
|
||||
:param serializer: The WAMP serializer to use (or None for default
|
||||
serializer). Serializers must implement
|
||||
:class:`autobahn.wamp.interfaces.ISerializer`.
|
||||
:type serializer: obj
|
||||
"""
|
||||
assert(callable(factory))
|
||||
self._factory = factory
|
||||
|
||||
self.debug = debug
|
||||
|
||||
if serializer is None:
|
||||
|
||||
# try MsgPack WAMP serializer
|
||||
try:
|
||||
from autobahn.wamp.serializer import MsgPackSerializer
|
||||
serializer = MsgPackSerializer()
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
if serializer is None:
|
||||
# try JSON WAMP serializer
|
||||
try:
|
||||
from autobahn.wamp.serializer import JsonSerializer
|
||||
serializer = JsonSerializer()
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
if serializer is None:
|
||||
raise Exception("could not import any WAMP serializer")
|
||||
|
||||
self._serializer = serializer
|
||||
|
|
|
@ -34,13 +34,14 @@ except ImportError:
|
|||
# starting from Twisted 12.2, NoResource has moved
|
||||
from twisted.web.resource import NoResource
|
||||
from twisted.web.resource import IResource, Resource
|
||||
from six import PY3
|
||||
|
||||
# The following imports reactor at module level
|
||||
# See: https://twistedmatrix.com/trac/ticket/6849
|
||||
from twisted.web.http import HTTPChannel
|
||||
|
||||
# .. and this also, since it imports t.w.http
|
||||
##
|
||||
#
|
||||
from twisted.web.server import NOT_DONE_YET
|
||||
|
||||
__all__ = (
|
||||
|
@ -139,21 +140,21 @@ class WebSocketResource(object):
|
|||
and let that do any subsequent communication.
|
||||
"""
|
||||
# Create Autobahn WebSocket protocol.
|
||||
##
|
||||
#
|
||||
protocol = self._factory.buildProtocol(request.transport.getPeer())
|
||||
if not protocol:
|
||||
# If protocol creation fails, we signal "internal server error"
|
||||
request.setResponseCode(500)
|
||||
return ""
|
||||
return b""
|
||||
|
||||
# Take over the transport from Twisted Web
|
||||
##
|
||||
#
|
||||
transport, request.transport = request.transport, None
|
||||
|
||||
# Connect the transport to our protocol. Once #3204 is fixed, there
|
||||
# may be a cleaner way of doing this.
|
||||
# http://twistedmatrix.com/trac/ticket/3204
|
||||
##
|
||||
#
|
||||
if isinstance(transport, ProtocolWrapper):
|
||||
# i.e. TLS is a wrapping protocol
|
||||
transport.wrappedProtocol = protocol
|
||||
|
@ -165,12 +166,21 @@ class WebSocketResource(object):
|
|||
# silly (since Twisted Web already did the HTTP request parsing
|
||||
# which we will do a 2nd time), but it's totally non-invasive to our
|
||||
# code. Maybe improve this.
|
||||
##
|
||||
data = "%s %s HTTP/1.1\x0d\x0a" % (request.method, request.uri)
|
||||
for h in request.requestHeaders.getAllRawHeaders():
|
||||
data += "%s: %s\x0d\x0a" % (h[0], ",".join(h[1]))
|
||||
data += "\x0d\x0a"
|
||||
data += request.content.read() # we need this for Hixie-76
|
||||
#
|
||||
if PY3:
|
||||
|
||||
data = request.method + b' ' + request.uri + b' HTTP/1.1\x0d\x0a'
|
||||
for h in request.requestHeaders.getAllRawHeaders():
|
||||
data += h[0] + b': ' + b",".join(h[1]) + b'\x0d\x0a'
|
||||
data += b"\x0d\x0a"
|
||||
data += request.content.read()
|
||||
|
||||
else:
|
||||
data = "%s %s HTTP/1.1\x0d\x0a" % (request.method, request.uri)
|
||||
for h in request.requestHeaders.getAllRawHeaders():
|
||||
data += "%s: %s\x0d\x0a" % (h[0], ",".join(h[1]))
|
||||
data += "\x0d\x0a"
|
||||
data += request.content.read() # we need this for Hixie-76
|
||||
protocol.dataReceived(data)
|
||||
|
||||
return NOT_DONE_YET
|
||||
|
|
|
@ -52,12 +52,8 @@ if os.environ.get('USE_TWISTED', False):
|
|||
self.assertRaises(RuntimeError, runner.run, raise_error)
|
||||
|
||||
# both reactor.run and reactor.stop should have been called
|
||||
run_calls = list(filter(lambda mc: mc[0] == 'run',
|
||||
fakereactor.method_calls))
|
||||
stop_calls = list(filter(lambda mc: mc[0] == 'stop',
|
||||
fakereactor.method_calls))
|
||||
self.assertEqual(len(run_calls), 1)
|
||||
self.assertEqual(len(stop_calls), 1)
|
||||
fakereactor.run.assert_called()
|
||||
fakereactor.stop.assert_called()
|
||||
|
||||
@patch('twisted.internet.reactor')
|
||||
@inlineCallbacks
|
||||
|
@ -75,12 +71,8 @@ if os.environ.get('USE_TWISTED', False):
|
|||
|
||||
# neither reactor.run() NOR reactor.stop() should have been called
|
||||
# (just connectTCP() will have been called)
|
||||
run_calls = list(filter(lambda mc: mc[0] == 'run',
|
||||
fakereactor.method_calls))
|
||||
stop_calls = list(filter(lambda mc: mc[0] == 'stop',
|
||||
fakereactor.method_calls))
|
||||
self.assertEqual(len(run_calls), 0)
|
||||
self.assertEqual(len(stop_calls), 0)
|
||||
fakereactor.run.assert_not_called()
|
||||
fakereactor.stop.assert_not_called()
|
||||
|
||||
@patch('twisted.internet.reactor')
|
||||
def test_runner_no_run_happypath(self, fakereactor):
|
||||
|
@ -92,18 +84,14 @@ if os.environ.get('USE_TWISTED', False):
|
|||
|
||||
# shouldn't have actually connected to anything
|
||||
# successfully, and the run() call shouldn't have inserted
|
||||
# any of its own call/errbacks.
|
||||
# any of its own call/errbacks. (except the cleanup handler)
|
||||
self.assertFalse(d.called)
|
||||
self.assertEqual(0, len(d.callbacks))
|
||||
self.assertEqual(1, len(d.callbacks))
|
||||
|
||||
# neither reactor.run() NOR reactor.stop() should have been called
|
||||
# (just connectTCP() will have been called)
|
||||
run_calls = list(filter(lambda mc: mc[0] == 'run',
|
||||
fakereactor.method_calls))
|
||||
stop_calls = list(filter(lambda mc: mc[0] == 'stop',
|
||||
fakereactor.method_calls))
|
||||
self.assertEqual(len(run_calls), 0)
|
||||
self.assertEqual(len(stop_calls), 0)
|
||||
fakereactor.run.assert_not_called()
|
||||
fakereactor.stop.assert_not_called()
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
|
|
@ -30,75 +30,35 @@ import sys
|
|||
import inspect
|
||||
|
||||
from twisted.python import log
|
||||
from twisted.application import service
|
||||
from twisted.internet.defer import Deferred, \
|
||||
maybeDeferred, \
|
||||
DeferredList, \
|
||||
inlineCallbacks, \
|
||||
succeed, \
|
||||
fail
|
||||
from twisted.internet.defer import inlineCallbacks
|
||||
|
||||
from autobahn.wamp import protocol
|
||||
from autobahn.wamp.types import ComponentConfig
|
||||
from autobahn.websocket.protocol import parseWsUrl
|
||||
from autobahn.twisted.websocket import WampWebSocketClientFactory
|
||||
|
||||
__all__ = (
|
||||
'FutureMixin',
|
||||
import six
|
||||
import txaio
|
||||
txaio.use_twisted()
|
||||
|
||||
|
||||
__all__ = [
|
||||
'ApplicationSession',
|
||||
'ApplicationSessionFactory',
|
||||
'ApplicationRunner',
|
||||
'Application',
|
||||
'Service'
|
||||
)
|
||||
]
|
||||
|
||||
try:
|
||||
from twisted.application import service
|
||||
except (ImportError, SyntaxError):
|
||||
# Not on PY3 yet
|
||||
service = None
|
||||
__all__.pop(__all__.index('Service'))
|
||||
|
||||
|
||||
class FutureMixin(object):
|
||||
"""
|
||||
Mixin for Twisted style Futures ("Deferreds").
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def _create_future():
|
||||
return Deferred()
|
||||
|
||||
@staticmethod
|
||||
def _create_future_success(result=None):
|
||||
return succeed(result)
|
||||
|
||||
@staticmethod
|
||||
def _create_future_error(error=None):
|
||||
return fail(error)
|
||||
|
||||
@staticmethod
|
||||
def _as_future(fun, *args, **kwargs):
|
||||
return maybeDeferred(fun, *args, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def _resolve_future(future, result=None):
|
||||
future.callback(result)
|
||||
|
||||
@staticmethod
|
||||
def _reject_future(future, error):
|
||||
future.errback(error)
|
||||
|
||||
@staticmethod
|
||||
def _add_future_callbacks(future, callback, errback):
|
||||
# callback and/or errback may be None
|
||||
if callback is None:
|
||||
assert errback is not None
|
||||
future.addErrback(errback)
|
||||
return future
|
||||
else:
|
||||
future.addCallbacks(callback, errback)
|
||||
return future
|
||||
|
||||
@staticmethod
|
||||
def _gather_futures(futures, consume_exceptions=True):
|
||||
return DeferredList(futures, consumeErrors=consume_exceptions)
|
||||
|
||||
|
||||
class ApplicationSession(FutureMixin, protocol.ApplicationSession):
|
||||
class ApplicationSession(protocol.ApplicationSession):
|
||||
"""
|
||||
WAMP application session for Twisted-based applications.
|
||||
"""
|
||||
|
@ -114,7 +74,7 @@ class ApplicationSession(FutureMixin, protocol.ApplicationSession):
|
|||
log.err(msg)
|
||||
|
||||
|
||||
class ApplicationSessionFactory(FutureMixin, protocol.ApplicationSessionFactory):
|
||||
class ApplicationSessionFactory(protocol.ApplicationSessionFactory):
|
||||
"""
|
||||
WAMP application session factory for Twisted-based applications.
|
||||
"""
|
||||
|
@ -134,21 +94,34 @@ class ApplicationRunner(object):
|
|||
connecting to a WAMP router.
|
||||
"""
|
||||
|
||||
def __init__(self, url, realm, extra=None, debug=False, debug_wamp=False, debug_app=False):
|
||||
def __init__(self, url, realm, extra=None, debug=False, debug_wamp=False, debug_app=False, ssl=None):
|
||||
"""
|
||||
|
||||
:param url: The WebSocket URL of the WAMP router to connect to (e.g. `ws://somehost.com:8090/somepath`)
|
||||
:type url: unicode
|
||||
|
||||
:param realm: The WAMP realm to join the application session to.
|
||||
:type realm: unicode
|
||||
|
||||
:param extra: Optional extra configuration to forward to the application component.
|
||||
:type extra: dict
|
||||
|
||||
:param debug: Turn on low-level debugging.
|
||||
:type debug: bool
|
||||
|
||||
:param debug_wamp: Turn on WAMP-level debugging.
|
||||
:type debug_wamp: bool
|
||||
|
||||
:param debug_app: Turn on app-level debugging.
|
||||
:type debug_app: bool
|
||||
|
||||
:param ssl: (Optional). If specified this should be an
|
||||
instance suitable to pass as ``sslContextFactory`` to
|
||||
:class:`twisted.internet.endpoints.SSL4ClientEndpoint`` such
|
||||
as :class:`twisted.internet.ssl.CertificateOptions`. Leaving
|
||||
it as ``None`` will use the result of calling Twisted's
|
||||
:meth:`twisted.internet.ssl.platformTrust` which tries to use
|
||||
your distribution's CA certificates.
|
||||
:type ssl: :class:`twisted.internet.ssl.CertificateOptions`
|
||||
"""
|
||||
self.url = url
|
||||
self.realm = realm
|
||||
|
@ -157,6 +130,7 @@ class ApplicationRunner(object):
|
|||
self.debug_wamp = debug_wamp
|
||||
self.debug_app = debug_app
|
||||
self.make = None
|
||||
self.ssl = ssl
|
||||
|
||||
def run(self, make, start_reactor=True):
|
||||
"""
|
||||
|
@ -179,6 +153,8 @@ class ApplicationRunner(object):
|
|||
of :class:`WampWebSocketClientProtocol`
|
||||
"""
|
||||
from twisted.internet import reactor
|
||||
txaio.use_twisted()
|
||||
txaio.config.loop = reactor
|
||||
|
||||
isSecure, host, port, resource, path, params = parseWsUrl(self.url)
|
||||
|
||||
|
@ -208,17 +184,40 @@ class ApplicationRunner(object):
|
|||
transport_factory = WampWebSocketClientFactory(create, url=self.url,
|
||||
debug=self.debug, debug_wamp=self.debug_wamp)
|
||||
|
||||
# start the client from a Twisted endpoint
|
||||
from twisted.internet.endpoints import clientFromString
|
||||
# if user passed ssl= but isn't using isSecure, we'll never
|
||||
# use the ssl argument which makes no sense.
|
||||
context_factory = None
|
||||
if self.ssl is not None:
|
||||
if not isSecure:
|
||||
raise RuntimeError(
|
||||
'ssl= argument value passed to %s conflicts with the "ws:" '
|
||||
'prefix of the url argument. Did you mean to use "wss:"?' %
|
||||
self.__class__.__name__)
|
||||
context_factory = self.ssl
|
||||
elif isSecure:
|
||||
from twisted.internet.ssl import optionsForClientTLS
|
||||
context_factory = optionsForClientTLS(six.u(host))
|
||||
|
||||
if isSecure:
|
||||
endpoint_descriptor = "ssl:{0}:{1}".format(host, port)
|
||||
from twisted.internet.endpoints import SSL4ClientEndpoint
|
||||
assert context_factory is not None
|
||||
client = SSL4ClientEndpoint(reactor, host, port, context_factory)
|
||||
else:
|
||||
endpoint_descriptor = "tcp:{0}:{1}".format(host, port)
|
||||
from twisted.internet.endpoints import TCP4ClientEndpoint
|
||||
client = TCP4ClientEndpoint(reactor, host, port)
|
||||
|
||||
client = clientFromString(reactor, endpoint_descriptor)
|
||||
d = client.connect(transport_factory)
|
||||
|
||||
# as the reactor shuts down, we wish to wait until we've sent
|
||||
# out our "Goodbye" message; leave() returns a Deferred that
|
||||
# fires when the transport gets to STATE_CLOSED
|
||||
def cleanup(proto):
|
||||
if hasattr(proto, '_session') and proto._session is not None:
|
||||
return proto._session.leave()
|
||||
# if we connect successfully, the arg is a WampWebSocketClientProtocol
|
||||
d.addCallback(lambda proto: reactor.addSystemEventTrigger(
|
||||
'before', 'shutdown', cleanup, proto))
|
||||
|
||||
# if the user didn't ask us to start the reactor, then they
|
||||
# get to deal with any connect errors themselves.
|
||||
if start_reactor:
|
||||
|
@ -516,78 +515,81 @@ class Application(object):
|
|||
log.msg("Warning: exception in signal handler swallowed", e)
|
||||
|
||||
|
||||
class Service(service.MultiService):
|
||||
"""
|
||||
A WAMP application as a twisted service.
|
||||
The application object provides a simple way of creating, debugging and running WAMP application
|
||||
components inside a traditional twisted application
|
||||
if service:
|
||||
# Don't define it if Twisted's service support isn't here
|
||||
|
||||
This manages application lifecycle of the wamp connection using startService and stopService
|
||||
Using services also allows to create integration tests that properly terminates their connections
|
||||
|
||||
It can host a WAMP application component in a WAMP-over-WebSocket client
|
||||
connecting to a WAMP router.
|
||||
"""
|
||||
factory = WampWebSocketClientFactory
|
||||
|
||||
def __init__(self, url, realm, make, extra=None,
|
||||
debug=False, debug_wamp=False, debug_app=False):
|
||||
class Service(service.MultiService):
|
||||
"""
|
||||
A WAMP application as a twisted service.
|
||||
The application object provides a simple way of creating, debugging and running WAMP application
|
||||
components inside a traditional twisted application
|
||||
|
||||
:param url: The WebSocket URL of the WAMP router to connect to (e.g. `ws://somehost.com:8090/somepath`)
|
||||
:type url: unicode
|
||||
:param realm: The WAMP realm to join the application session to.
|
||||
:type realm: unicode
|
||||
:param make: A factory that produces instances of :class:`autobahn.asyncio.wamp.ApplicationSession`
|
||||
when called with an instance of :class:`autobahn.wamp.types.ComponentConfig`.
|
||||
:type make: callable
|
||||
:param extra: Optional extra configuration to forward to the application component.
|
||||
:type extra: dict
|
||||
:param debug: Turn on low-level debugging.
|
||||
:type debug: bool
|
||||
:param debug_wamp: Turn on WAMP-level debugging.
|
||||
:type debug_wamp: bool
|
||||
:param debug_app: Turn on app-level debugging.
|
||||
:type debug_app: bool
|
||||
This manages application lifecycle of the wamp connection using startService and stopService
|
||||
Using services also allows to create integration tests that properly terminates their connections
|
||||
|
||||
You can replace the attribute factory in order to change connectionLost or connectionFailed behaviour.
|
||||
The factory attribute must return a WampWebSocketClientFactory object
|
||||
It can host a WAMP application component in a WAMP-over-WebSocket client
|
||||
connecting to a WAMP router.
|
||||
"""
|
||||
self.url = url
|
||||
self.realm = realm
|
||||
self.extra = extra or dict()
|
||||
self.debug = debug
|
||||
self.debug_wamp = debug_wamp
|
||||
self.debug_app = debug_app
|
||||
self.make = make
|
||||
service.MultiService.__init__(self)
|
||||
self.setupService()
|
||||
factory = WampWebSocketClientFactory
|
||||
|
||||
def setupService(self):
|
||||
"""
|
||||
Setup the application component.
|
||||
"""
|
||||
isSecure, host, port, resource, path, params = parseWsUrl(self.url)
|
||||
def __init__(self, url, realm, make, extra=None,
|
||||
debug=False, debug_wamp=False, debug_app=False):
|
||||
"""
|
||||
|
||||
# factory for use ApplicationSession
|
||||
def create():
|
||||
cfg = ComponentConfig(self.realm, self.extra)
|
||||
session = self.make(cfg)
|
||||
session.debug_app = self.debug_app
|
||||
return session
|
||||
:param url: The WebSocket URL of the WAMP router to connect to (e.g. `ws://somehost.com:8090/somepath`)
|
||||
:type url: unicode
|
||||
:param realm: The WAMP realm to join the application session to.
|
||||
:type realm: unicode
|
||||
:param make: A factory that produces instances of :class:`autobahn.asyncio.wamp.ApplicationSession`
|
||||
when called with an instance of :class:`autobahn.wamp.types.ComponentConfig`.
|
||||
:type make: callable
|
||||
:param extra: Optional extra configuration to forward to the application component.
|
||||
:type extra: dict
|
||||
:param debug: Turn on low-level debugging.
|
||||
:type debug: bool
|
||||
:param debug_wamp: Turn on WAMP-level debugging.
|
||||
:type debug_wamp: bool
|
||||
:param debug_app: Turn on app-level debugging.
|
||||
:type debug_app: bool
|
||||
|
||||
# create a WAMP-over-WebSocket transport client factory
|
||||
transport_factory = self.factory(create, url=self.url,
|
||||
debug=self.debug, debug_wamp=self.debug_wamp)
|
||||
You can replace the attribute factory in order to change connectionLost or connectionFailed behaviour.
|
||||
The factory attribute must return a WampWebSocketClientFactory object
|
||||
"""
|
||||
self.url = url
|
||||
self.realm = realm
|
||||
self.extra = extra or dict()
|
||||
self.debug = debug
|
||||
self.debug_wamp = debug_wamp
|
||||
self.debug_app = debug_app
|
||||
self.make = make
|
||||
service.MultiService.__init__(self)
|
||||
self.setupService()
|
||||
|
||||
# setup the client from a Twisted endpoint
|
||||
def setupService(self):
|
||||
"""
|
||||
Setup the application component.
|
||||
"""
|
||||
isSecure, host, port, resource, path, params = parseWsUrl(self.url)
|
||||
|
||||
if isSecure:
|
||||
from twisted.application.internet import SSLClient
|
||||
clientClass = SSLClient
|
||||
else:
|
||||
from twisted.application.internet import TCPClient
|
||||
clientClass = TCPClient
|
||||
# factory for use ApplicationSession
|
||||
def create():
|
||||
cfg = ComponentConfig(self.realm, self.extra)
|
||||
session = self.make(cfg)
|
||||
session.debug_app = self.debug_app
|
||||
return session
|
||||
|
||||
client = clientClass(host, port, transport_factory)
|
||||
client.setServiceParent(self)
|
||||
# create a WAMP-over-WebSocket transport client factory
|
||||
transport_factory = self.factory(create, url=self.url,
|
||||
debug=self.debug, debug_wamp=self.debug_wamp)
|
||||
|
||||
# setup the client from a Twisted endpoint
|
||||
|
||||
if isSecure:
|
||||
from twisted.application.internet import SSLClient
|
||||
clientClass = SSLClient
|
||||
else:
|
||||
from twisted.application.internet import TCPClient
|
||||
clientClass = TCPClient
|
||||
|
||||
client = clientClass(host, port, transport_factory)
|
||||
client.setServiceParent(self)
|
||||
|
|
|
@ -32,7 +32,6 @@ from zope.interface import implementer
|
|||
|
||||
import twisted.internet.protocol
|
||||
from twisted.internet.defer import maybeDeferred
|
||||
from twisted.python import log
|
||||
from twisted.internet.interfaces import ITransport
|
||||
|
||||
from autobahn.wamp import websocket
|
||||
|
@ -40,11 +39,14 @@ from autobahn.websocket import protocol
|
|||
from autobahn.websocket import http
|
||||
from autobahn.twisted.util import peer2str
|
||||
|
||||
from autobahn.logger import make_logger
|
||||
|
||||
from autobahn.websocket.compress import PerMessageDeflateOffer, \
|
||||
PerMessageDeflateOfferAccept, \
|
||||
PerMessageDeflateResponse, \
|
||||
PerMessageDeflateResponseAccept
|
||||
|
||||
|
||||
__all__ = (
|
||||
'WebSocketAdapterProtocol',
|
||||
'WebSocketServerProtocol',
|
||||
|
@ -189,12 +191,7 @@ class WebSocketAdapterFactory(object):
|
|||
"""
|
||||
Adapter class for Twisted-based WebSocket client and server factories.
|
||||
"""
|
||||
|
||||
def _log(self, msg):
|
||||
log.msg(msg)
|
||||
|
||||
def _callLater(self, delay, fun):
|
||||
return self.reactor.callLater(delay, fun)
|
||||
log = make_logger("twisted")
|
||||
|
||||
|
||||
class WebSocketServerFactory(WebSocketAdapterFactory, protocol.WebSocketServerFactory, twisted.internet.protocol.ServerFactory):
|
||||
|
|
|
@ -58,7 +58,7 @@ def utcnow():
|
|||
:rtype: unicode
|
||||
"""
|
||||
now = datetime.utcnow()
|
||||
return now.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z"
|
||||
return u"{0}Z".format(now.strftime(u"%Y-%m-%dT%H:%M:%S.%f")[:-3])
|
||||
|
||||
|
||||
def utcstr(ts):
|
||||
|
@ -72,7 +72,7 @@ def utcstr(ts):
|
|||
:rtype: unicode
|
||||
"""
|
||||
if ts:
|
||||
return ts.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z"
|
||||
return u"{0}Z".format(ts.strftime(u"%Y-%m-%dT%H:%M:%S.%f")[:-3])
|
||||
else:
|
||||
return ts
|
||||
|
||||
|
@ -92,11 +92,31 @@ def parseutc(datestr):
|
|||
:rtype: instance of :py:class:`datetime.datetime`
|
||||
"""
|
||||
try:
|
||||
return datetime.strptime(datestr, "%Y-%m-%dT%H:%M:%SZ")
|
||||
return datetime.strptime(datestr, u"%Y-%m-%dT%H:%M:%SZ")
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
|
||||
class IdGenerator(object):
|
||||
"""
|
||||
ID generator for WAMP request IDs.
|
||||
|
||||
WAMP request IDs are sequential per WAMP session, starting at 0 and
|
||||
wrapping around at 2**53 (both value are inclusive [0, 2**53]).
|
||||
|
||||
See https://github.com/tavendo/WAMP/blob/master/spec/basic.md#ids
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self._next = -1
|
||||
|
||||
def next(self):
|
||||
self._next += 1
|
||||
if self._next > 9007199254740992:
|
||||
self._next = 0
|
||||
return self._next
|
||||
|
||||
|
||||
# noinspection PyShadowingBuiltins
|
||||
def id():
|
||||
"""
|
||||
|
@ -111,8 +131,7 @@ def id():
|
|||
:returns: A random object ID.
|
||||
:rtype: int
|
||||
"""
|
||||
# return random.randint(0, 9007199254740992) # this is what the WAMP spec says
|
||||
return random.randint(0, 2147483647) # use a reduced ID space for now (2**31-1)
|
||||
return random.randint(0, 9007199254740992)
|
||||
|
||||
|
||||
def newid(length=16):
|
||||
|
|
|
@ -303,6 +303,8 @@ class ISession(object):
|
|||
:param message: An optional (human readable) closing message, intended for
|
||||
logging purposes.
|
||||
:type message: str
|
||||
|
||||
:return: may return a Future/Deferred that fires when we've disconnected
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
|
@ -320,6 +322,12 @@ class ISession(object):
|
|||
Close the underlying transport.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def is_connected(self):
|
||||
"""
|
||||
Check if the underlying transport is connected.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def onDisconnect(self):
|
||||
"""
|
||||
|
|
|
@ -76,6 +76,12 @@ _URI_PAT_STRICT_NON_EMPTY = re.compile(r"^([0-9a-z_]+\.)*([0-9a-z_]+)$")
|
|||
# loose URI check disallowing empty URI components
|
||||
_URI_PAT_LOOSE_NON_EMPTY = re.compile(r"^([^\s\.#]+\.)*([^\s\.#]+)$")
|
||||
|
||||
# strict URI check disallowing empty URI components in all but the last component
|
||||
_URI_PAT_STRICT_LAST_EMPTY = re.compile(r"^([0-9a-z_]+\.)*([0-9a-z_]*)$")
|
||||
|
||||
# loose URI check disallowing empty URI components in all but the last component
|
||||
_URI_PAT_LOOSE_LAST_EMPTY = re.compile(r"^([^\s\.#]+\.)*([^\s\.#]*)$")
|
||||
|
||||
|
||||
def check_or_raise_uri(value, message=u"WAMP message invalid", strict=False, allowEmptyComponents=False):
|
||||
"""
|
||||
|
@ -278,7 +284,6 @@ class Hello(Message):
|
|||
if u'features' in details_role:
|
||||
check_or_raise_extra(details_role[u'features'], "'features' in role '{0}' in 'roles' in 'details' in HELLO".format(role))
|
||||
|
||||
# FIXME: skip unknown attributes
|
||||
role_features = role_cls(**details_role[u'features'])
|
||||
|
||||
else:
|
||||
|
@ -431,7 +436,6 @@ class Welcome(Message):
|
|||
if u'features' in details_role:
|
||||
check_or_raise_extra(details_role[u'features'], "'features' in role '{0}' in 'roles' in 'details' in WELCOME".format(role))
|
||||
|
||||
# FIXME: skip unknown attributes
|
||||
role_features = role_cls(**details_roles[role][u'features'])
|
||||
|
||||
else:
|
||||
|
|
|
@ -40,7 +40,6 @@ from autobahn.wamp.interfaces import ISession, \
|
|||
IRegistration, \
|
||||
ITransportHandler
|
||||
|
||||
from autobahn import util
|
||||
from autobahn import wamp
|
||||
from autobahn.wamp import uri
|
||||
from autobahn.wamp import message
|
||||
|
@ -49,6 +48,9 @@ from autobahn.wamp import role
|
|||
from autobahn.wamp import exception
|
||||
from autobahn.wamp.exception import ApplicationError, ProtocolError, SessionNotReady, SerializationError
|
||||
from autobahn.wamp.types import SessionDetails
|
||||
from autobahn.util import IdGenerator
|
||||
|
||||
import txaio
|
||||
|
||||
|
||||
def is_method_or_function(f):
|
||||
|
@ -280,6 +282,9 @@ class BaseSession(object):
|
|||
self._authmethod = None
|
||||
self._authprovider = None
|
||||
|
||||
# generator for WAMP request IDs
|
||||
self._request_id_gen = IdGenerator()
|
||||
|
||||
def onConnect(self):
|
||||
"""
|
||||
Implements :func:`autobahn.wamp.interfaces.ISession.onConnect`
|
||||
|
@ -455,11 +460,11 @@ class ApplicationSession(BaseSession):
|
|||
Implements :func:`autobahn.wamp.interfaces.ITransportHandler.onOpen`
|
||||
"""
|
||||
self._transport = transport
|
||||
d = self._as_future(self.onConnect)
|
||||
d = txaio.as_future(self.onConnect)
|
||||
|
||||
def _error(e):
|
||||
return self._swallow_error(e, "While firing onConnect")
|
||||
self._add_future_callbacks(d, None, _error)
|
||||
txaio.add_callbacks(d, None, _error)
|
||||
|
||||
def onConnect(self):
|
||||
"""
|
||||
|
@ -491,9 +496,12 @@ class ApplicationSession(BaseSession):
|
|||
"""
|
||||
if self._transport:
|
||||
self._transport.close()
|
||||
else:
|
||||
# XXX or shall we just ignore this?
|
||||
raise RuntimeError("No transport, but disconnect() called.")
|
||||
|
||||
def is_connected(self):
|
||||
"""
|
||||
Implements :func:`autobahn.wamp.interfaces.ISession.is_connected`
|
||||
"""
|
||||
return self._transport is not None
|
||||
|
||||
def onUserError(self, e, msg):
|
||||
"""
|
||||
|
@ -544,26 +552,26 @@ class ApplicationSession(BaseSession):
|
|||
self._session_id = msg.session
|
||||
|
||||
details = SessionDetails(self._realm, self._session_id, msg.authid, msg.authrole, msg.authmethod)
|
||||
d = self._as_future(self.onJoin, details)
|
||||
d = txaio.as_future(self.onJoin, details)
|
||||
|
||||
def _error(e):
|
||||
return self._swallow_error(e, "While firing onJoin")
|
||||
self._add_future_callbacks(d, None, _error)
|
||||
txaio.add_callbacks(d, None, _error)
|
||||
|
||||
elif isinstance(msg, message.Abort):
|
||||
|
||||
# fire callback and close the transport
|
||||
details = types.CloseDetails(msg.reason, msg.message)
|
||||
d = self._as_future(self.onLeave, details)
|
||||
d = txaio.as_future(self.onLeave, details)
|
||||
|
||||
def _error(e):
|
||||
return self._swallow_error(e, "While firing onLeave")
|
||||
self._add_future_callbacks(d, None, _error)
|
||||
txaio.add_callbacks(d, None, _error)
|
||||
|
||||
elif isinstance(msg, message.Challenge):
|
||||
|
||||
challenge = types.Challenge(msg.method, msg.extra)
|
||||
d = self._as_future(self.onChallenge, challenge)
|
||||
d = txaio.as_future(self.onChallenge, challenge)
|
||||
|
||||
def success(signature):
|
||||
reply = message.Authenticate(signature)
|
||||
|
@ -574,16 +582,16 @@ class ApplicationSession(BaseSession):
|
|||
self._transport.send(reply)
|
||||
# fire callback and close the transport
|
||||
details = types.CloseDetails(reply.reason, reply.message)
|
||||
d = self._as_future(self.onLeave, details)
|
||||
d = txaio.as_future(self.onLeave, details)
|
||||
|
||||
def _error(e):
|
||||
return self._swallow_error(e, "While firing onLeave")
|
||||
self._add_future_callbacks(d, None, _error)
|
||||
txaio.add_callbacks(d, None, _error)
|
||||
# switching to the callback chain, effectively
|
||||
# cancelling error (which we've now handled)
|
||||
return d
|
||||
|
||||
self._add_future_callbacks(d, success, error)
|
||||
txaio.add_callbacks(d, success, error)
|
||||
|
||||
else:
|
||||
raise ProtocolError("Received {0} message, and session is not yet established".format(msg.__class__))
|
||||
|
@ -600,12 +608,12 @@ class ApplicationSession(BaseSession):
|
|||
|
||||
# fire callback and close the transport
|
||||
details = types.CloseDetails(msg.reason, msg.message)
|
||||
d = self._as_future(self.onLeave, details)
|
||||
d = txaio.as_future(self.onLeave, details)
|
||||
|
||||
def _error(e):
|
||||
errmsg = 'While firing onLeave for reason "{0}" and message "{1}"'.format(msg.reason, msg.message)
|
||||
return self._swallow_error(e, errmsg)
|
||||
self._add_future_callbacks(d, None, _error)
|
||||
txaio.add_callbacks(d, None, _error)
|
||||
|
||||
elif isinstance(msg, message.Event):
|
||||
|
||||
|
@ -624,15 +632,13 @@ class ApplicationSession(BaseSession):
|
|||
if handler.details_arg:
|
||||
invoke_kwargs[handler.details_arg] = types.EventDetails(publication=msg.publication, publisher=msg.publisher, topic=msg.topic)
|
||||
|
||||
try:
|
||||
handler.fn(*invoke_args, **invoke_kwargs)
|
||||
except Exception as e:
|
||||
msg = 'While firing {0} subscribed under {1}.'.format(
|
||||
def _error(e):
|
||||
errmsg = 'While firing {0} subscribed under {1}.'.format(
|
||||
handler.fn, msg.subscription)
|
||||
try:
|
||||
self.onUserError(e, msg)
|
||||
except:
|
||||
pass
|
||||
return self._swallow_error(e, errmsg)
|
||||
|
||||
future = txaio.as_future(handler.fn, *invoke_args, **invoke_kwargs)
|
||||
txaio.add_callbacks(future, None, _error)
|
||||
|
||||
else:
|
||||
raise ProtocolError("EVENT received for non-subscribed subscription ID {0}".format(msg.subscription))
|
||||
|
@ -648,7 +654,7 @@ class ApplicationSession(BaseSession):
|
|||
publication = Publication(msg.publication)
|
||||
|
||||
# resolve deferred/future for publishing successfully
|
||||
self._resolve_future(publish_request.on_reply, publication)
|
||||
txaio.resolve(publish_request.on_reply, publication)
|
||||
else:
|
||||
raise ProtocolError("PUBLISHED received for non-pending request ID {0}".format(msg.request))
|
||||
|
||||
|
@ -669,7 +675,7 @@ class ApplicationSession(BaseSession):
|
|||
self._subscriptions[msg.subscription].append(subscription)
|
||||
|
||||
# resolve deferred/future for subscribing successfully
|
||||
self._resolve_future(request.on_reply, subscription)
|
||||
txaio.resolve(request.on_reply, subscription)
|
||||
else:
|
||||
raise ProtocolError("SUBSCRIBED received for non-pending request ID {0}".format(msg.request))
|
||||
|
||||
|
@ -687,7 +693,7 @@ class ApplicationSession(BaseSession):
|
|||
del self._subscriptions[request.subscription_id]
|
||||
|
||||
# resolve deferred/future for unsubscribing successfully
|
||||
self._resolve_future(request.on_reply, 0)
|
||||
txaio.resolve(request.on_reply, 0)
|
||||
else:
|
||||
raise ProtocolError("UNSUBSCRIBED received for non-pending request ID {0}".format(msg.request))
|
||||
|
||||
|
@ -727,16 +733,16 @@ class ApplicationSession(BaseSession):
|
|||
res = types.CallResult(*msg.args, **msg.kwargs)
|
||||
else:
|
||||
res = types.CallResult(**msg.kwargs)
|
||||
self._resolve_future(on_reply, res)
|
||||
txaio.resolve(on_reply, res)
|
||||
else:
|
||||
if msg.args:
|
||||
if len(msg.args) > 1:
|
||||
res = types.CallResult(*msg.args)
|
||||
self._resolve_future(on_reply, res)
|
||||
txaio.resolve(on_reply, res)
|
||||
else:
|
||||
self._resolve_future(on_reply, msg.args[0])
|
||||
txaio.resolve(on_reply, msg.args[0])
|
||||
else:
|
||||
self._resolve_future(on_reply, None)
|
||||
txaio.resolve(on_reply, None)
|
||||
else:
|
||||
raise ProtocolError("RESULT received for non-pending request ID {0}".format(msg.request))
|
||||
|
||||
|
@ -754,10 +760,9 @@ class ApplicationSession(BaseSession):
|
|||
|
||||
else:
|
||||
registration = self._registrations[msg.registration]
|
||||
|
||||
endpoint = registration.endpoint
|
||||
|
||||
if endpoint.obj:
|
||||
if endpoint.obj is not None:
|
||||
invoke_args = (endpoint.obj,)
|
||||
else:
|
||||
invoke_args = tuple()
|
||||
|
@ -778,7 +783,7 @@ class ApplicationSession(BaseSession):
|
|||
|
||||
invoke_kwargs[endpoint.details_arg] = types.CallDetails(progress, caller=msg.caller, procedure=msg.procedure)
|
||||
|
||||
on_reply = self._as_future(endpoint.fn, *invoke_args, **invoke_kwargs)
|
||||
on_reply = txaio.as_future(endpoint.fn, *invoke_args, **invoke_kwargs)
|
||||
|
||||
def success(res):
|
||||
del self._invocations[msg.request]
|
||||
|
@ -831,7 +836,7 @@ class ApplicationSession(BaseSession):
|
|||
|
||||
self._invocations[msg.request] = InvocationRequest(msg.request, on_reply)
|
||||
|
||||
self._add_future_callbacks(on_reply, success, error)
|
||||
txaio.add_callbacks(on_reply, success, error)
|
||||
|
||||
elif isinstance(msg, message.Interrupt):
|
||||
|
||||
|
@ -864,7 +869,7 @@ class ApplicationSession(BaseSession):
|
|||
else:
|
||||
raise ProtocolError("REGISTERED received for already existing registration ID {0}".format(msg.registration))
|
||||
|
||||
self._resolve_future(request.on_reply, registration)
|
||||
txaio.resolve(request.on_reply, registration)
|
||||
else:
|
||||
raise ProtocolError("REGISTERED received for non-pending request ID {0}".format(msg.request))
|
||||
|
||||
|
@ -881,7 +886,7 @@ class ApplicationSession(BaseSession):
|
|||
del self._registrations[request.registration_id]
|
||||
|
||||
# resolve deferred/future for unregistering successfully
|
||||
self._resolve_future(request.on_reply)
|
||||
txaio.resolve(request.on_reply)
|
||||
else:
|
||||
raise ProtocolError("UNREGISTERED received for non-pending request ID {0}".format(msg.request))
|
||||
|
||||
|
@ -915,7 +920,7 @@ class ApplicationSession(BaseSession):
|
|||
on_reply = self._unregister_reqs.pop(msg.request).on_reply
|
||||
|
||||
if on_reply:
|
||||
self._reject_future(on_reply, self._exception_from_message(msg))
|
||||
txaio.reject(on_reply, self._exception_from_message(msg))
|
||||
else:
|
||||
raise ProtocolError("WampAppSession.onMessage(): ERROR received for non-pending request_type {0} and request ID {1}".format(msg.request_type, msg.request))
|
||||
|
||||
|
@ -932,19 +937,19 @@ class ApplicationSession(BaseSession):
|
|||
|
||||
if self._session_id:
|
||||
# fire callback and close the transport
|
||||
d = self._as_future(self.onLeave, types.CloseDetails(reason=types.CloseDetails.REASON_TRANSPORT_LOST, message="WAMP transport was lost without closing the session before"))
|
||||
d = txaio.as_future(self.onLeave, types.CloseDetails(reason=types.CloseDetails.REASON_TRANSPORT_LOST, message="WAMP transport was lost without closing the session before"))
|
||||
|
||||
def _error(e):
|
||||
return self._swallow_error(e, "While firing onLeave")
|
||||
self._add_future_callbacks(d, None, _error)
|
||||
txaio.add_callbacks(d, None, _error)
|
||||
|
||||
self._session_id = None
|
||||
|
||||
d = self._as_future(self.onDisconnect)
|
||||
d = txaio.as_future(self.onDisconnect)
|
||||
|
||||
def _error(e):
|
||||
return self._swallow_error(e, "While firing onDisconnect")
|
||||
self._add_future_callbacks(d, None, _error)
|
||||
txaio.add_callbacks(d, None, _error)
|
||||
|
||||
def onChallenge(self, challenge):
|
||||
"""
|
||||
|
@ -961,7 +966,9 @@ class ApplicationSession(BaseSession):
|
|||
"""
|
||||
Implements :func:`autobahn.wamp.interfaces.ISession.onLeave`
|
||||
"""
|
||||
self.disconnect()
|
||||
if self._transport:
|
||||
self.disconnect()
|
||||
# do we ever call onLeave with a valid transport?
|
||||
|
||||
def leave(self, reason=None, log_message=None):
|
||||
"""
|
||||
|
@ -976,6 +983,8 @@ class ApplicationSession(BaseSession):
|
|||
msg = wamp.message.Goodbye(reason=reason, message=log_message)
|
||||
self._transport.send(msg)
|
||||
self._goodbye_sent = True
|
||||
# deferred that fires when transport actually hits CLOSED
|
||||
return self._transport.is_closed
|
||||
else:
|
||||
raise SessionNotReady(u"Already requested to close the session")
|
||||
|
||||
|
@ -990,7 +999,7 @@ class ApplicationSession(BaseSession):
|
|||
if not self._transport:
|
||||
raise exception.TransportLost()
|
||||
|
||||
request_id = util.id()
|
||||
request_id = self._request_id_gen.next()
|
||||
|
||||
if 'options' in kwargs and isinstance(kwargs['options'], types.PublishOptions):
|
||||
options = kwargs.pop('options')
|
||||
|
@ -1001,7 +1010,7 @@ class ApplicationSession(BaseSession):
|
|||
|
||||
if options and options.acknowledge:
|
||||
# only acknowledged publications expect a reply ..
|
||||
on_reply = self._create_future()
|
||||
on_reply = txaio.create_future()
|
||||
self._publish_reqs[request_id] = PublishRequest(request_id, on_reply)
|
||||
else:
|
||||
on_reply = None
|
||||
|
@ -1037,8 +1046,8 @@ class ApplicationSession(BaseSession):
|
|||
raise exception.TransportLost()
|
||||
|
||||
def _subscribe(obj, fn, topic, options):
|
||||
request_id = util.id()
|
||||
on_reply = self._create_future()
|
||||
request_id = self._request_id_gen.next()
|
||||
on_reply = txaio.create_future()
|
||||
handler_obj = Handler(fn, obj, options.details_arg if options else None)
|
||||
self._subscribe_reqs[request_id] = SubscribeRequest(request_id, on_reply, handler_obj)
|
||||
|
||||
|
@ -1051,7 +1060,6 @@ class ApplicationSession(BaseSession):
|
|||
return on_reply
|
||||
|
||||
if callable(handler):
|
||||
|
||||
# subscribe a single handler
|
||||
return _subscribe(None, handler, topic, options)
|
||||
|
||||
|
@ -1062,13 +1070,14 @@ class ApplicationSession(BaseSession):
|
|||
for k in inspect.getmembers(handler.__class__, is_method_or_function):
|
||||
proc = k[1]
|
||||
if "_wampuris" in proc.__dict__:
|
||||
pat = proc.__dict__["_wampuris"][0]
|
||||
if pat.is_handler():
|
||||
uri = pat.uri()
|
||||
subopts = options or pat.subscribe_options()
|
||||
on_replies.append(_subscribe(handler, proc, uri, subopts))
|
||||
for pat in proc.__dict__["_wampuris"]:
|
||||
if pat.is_handler():
|
||||
uri = pat.uri()
|
||||
subopts = options or pat.subscribe_options()
|
||||
on_replies.append(_subscribe(handler, proc, uri, subopts))
|
||||
|
||||
return self._gather_futures(on_replies, consume_exceptions=True)
|
||||
# XXX needs coverage
|
||||
return txaio.gather(on_replies, consume_exceptions=True)
|
||||
|
||||
def _unsubscribe(self, subscription):
|
||||
"""
|
||||
|
@ -1091,9 +1100,9 @@ class ApplicationSession(BaseSession):
|
|||
|
||||
if scount == 0:
|
||||
# if the last handler was removed, unsubscribe from broker ..
|
||||
request_id = util.id()
|
||||
request_id = self._request_id_gen.next()
|
||||
|
||||
on_reply = self._create_future()
|
||||
on_reply = txaio.create_future()
|
||||
self._unsubscribe_reqs[request_id] = UnsubscribeRequest(request_id, on_reply, subscription.id)
|
||||
|
||||
msg = message.Unsubscribe(request_id, subscription.id)
|
||||
|
@ -1102,7 +1111,7 @@ class ApplicationSession(BaseSession):
|
|||
return on_reply
|
||||
else:
|
||||
# there are still handlers active on the subscription!
|
||||
return self._create_future_success(scount)
|
||||
return txaio.create_future_success(scount)
|
||||
|
||||
def call(self, procedure, *args, **kwargs):
|
||||
"""
|
||||
|
@ -1115,7 +1124,7 @@ class ApplicationSession(BaseSession):
|
|||
if not self._transport:
|
||||
raise exception.TransportLost()
|
||||
|
||||
request_id = util.id()
|
||||
request_id = self._request_id_gen.next()
|
||||
|
||||
if 'options' in kwargs and isinstance(kwargs['options'], types.CallOptions):
|
||||
options = kwargs.pop('options')
|
||||
|
@ -1130,7 +1139,7 @@ class ApplicationSession(BaseSession):
|
|||
# self._transport.send(cancel_msg)
|
||||
# d = Deferred(canceller)
|
||||
|
||||
on_reply = self._create_future()
|
||||
on_reply = txaio.create_future()
|
||||
self._call_reqs[request_id] = CallRequest(request_id, on_reply, options)
|
||||
|
||||
try:
|
||||
|
@ -1143,10 +1152,10 @@ class ApplicationSession(BaseSession):
|
|||
# will immediately lead on an incoming WAMP message in onMessage()
|
||||
#
|
||||
self._transport.send(msg)
|
||||
except Exception as e:
|
||||
except:
|
||||
if request_id in self._call_reqs:
|
||||
del self._call_reqs[request_id]
|
||||
raise e
|
||||
raise
|
||||
|
||||
return on_reply
|
||||
|
||||
|
@ -1164,8 +1173,8 @@ class ApplicationSession(BaseSession):
|
|||
raise exception.TransportLost()
|
||||
|
||||
def _register(obj, fn, procedure, options):
|
||||
request_id = util.id()
|
||||
on_reply = self._create_future()
|
||||
request_id = self._request_id_gen.next()
|
||||
on_reply = txaio.create_future()
|
||||
endpoint_obj = Endpoint(fn, obj, options.details_arg if options else None)
|
||||
self._register_reqs[request_id] = RegisterRequest(request_id, on_reply, procedure, endpoint_obj)
|
||||
|
||||
|
@ -1189,12 +1198,13 @@ class ApplicationSession(BaseSession):
|
|||
for k in inspect.getmembers(endpoint.__class__, is_method_or_function):
|
||||
proc = k[1]
|
||||
if "_wampuris" in proc.__dict__:
|
||||
pat = proc.__dict__["_wampuris"][0]
|
||||
if pat.is_endpoint():
|
||||
uri = pat.uri()
|
||||
on_replies.append(_register(endpoint, proc, uri, options))
|
||||
for pat in proc.__dict__["_wampuris"]:
|
||||
if pat.is_endpoint():
|
||||
uri = pat.uri()
|
||||
on_replies.append(_register(endpoint, proc, uri, options))
|
||||
|
||||
return self._gather_futures(on_replies, consume_exceptions=True)
|
||||
# XXX neds coverage
|
||||
return txaio.gather(on_replies, consume_exceptions=True)
|
||||
|
||||
def _unregister(self, registration):
|
||||
"""
|
||||
|
@ -1207,9 +1217,9 @@ class ApplicationSession(BaseSession):
|
|||
if not self._transport:
|
||||
raise exception.TransportLost()
|
||||
|
||||
request_id = util.id()
|
||||
request_id = self._request_id_gen.next()
|
||||
|
||||
on_reply = self._create_future()
|
||||
on_reply = txaio.create_future()
|
||||
self._unregister_reqs[request_id] = UnregisterRequest(request_id, on_reply, registration.id)
|
||||
|
||||
msg = message.Unregister(request_id, registration.id)
|
||||
|
|
|
@ -85,7 +85,8 @@ class RoleBrokerFeatures(RoleFeatures):
|
|||
subscriber_blackwhite_listing=None,
|
||||
publisher_exclusion=None,
|
||||
subscription_revocation=None,
|
||||
event_history=None):
|
||||
event_history=None,
|
||||
**kwargs):
|
||||
self.publisher_identification = publisher_identification
|
||||
self.publication_trustlevels = publication_trustlevels
|
||||
self.pattern_based_subscription = pattern_based_subscription
|
||||
|
@ -110,7 +111,8 @@ class RoleSubscriberFeatures(RoleFeatures):
|
|||
publication_trustlevels=None,
|
||||
pattern_based_subscription=None,
|
||||
subscription_revocation=None,
|
||||
event_history=None):
|
||||
event_history=None,
|
||||
**kwargs):
|
||||
self.publisher_identification = publisher_identification
|
||||
self.publication_trustlevels = publication_trustlevels
|
||||
self.pattern_based_subscription = pattern_based_subscription
|
||||
|
@ -130,7 +132,8 @@ class RolePublisherFeatures(RoleFeatures):
|
|||
def __init__(self,
|
||||
publisher_identification=None,
|
||||
subscriber_blackwhite_listing=None,
|
||||
publisher_exclusion=None):
|
||||
publisher_exclusion=None,
|
||||
**kwargs):
|
||||
self.publisher_identification = publisher_identification
|
||||
self.subscriber_blackwhite_listing = subscriber_blackwhite_listing
|
||||
self.publisher_exclusion = publisher_exclusion
|
||||
|
@ -154,7 +157,8 @@ class RoleDealerFeatures(RoleFeatures):
|
|||
call_timeout=None,
|
||||
call_canceling=None,
|
||||
progressive_call_results=None,
|
||||
registration_revocation=None):
|
||||
registration_revocation=None,
|
||||
**kwargs):
|
||||
self.caller_identification = caller_identification
|
||||
self.call_trustlevels = call_trustlevels
|
||||
self.pattern_based_registration = pattern_based_registration
|
||||
|
@ -179,7 +183,8 @@ class RoleCallerFeatures(RoleFeatures):
|
|||
caller_identification=None,
|
||||
call_timeout=None,
|
||||
call_canceling=None,
|
||||
progressive_call_results=None):
|
||||
progressive_call_results=None,
|
||||
**kwargs):
|
||||
self.caller_identification = caller_identification
|
||||
self.call_timeout = call_timeout
|
||||
self.call_canceling = call_canceling
|
||||
|
@ -203,7 +208,8 @@ class RoleCalleeFeatures(RoleFeatures):
|
|||
call_timeout=None,
|
||||
call_canceling=None,
|
||||
progressive_call_results=None,
|
||||
registration_revocation=None):
|
||||
registration_revocation=None,
|
||||
**kwargs):
|
||||
self.caller_identification = caller_identification
|
||||
self.call_trustlevels = call_trustlevels
|
||||
self.pattern_based_registration = pattern_based_registration
|
||||
|
|
|
@ -214,7 +214,22 @@ IObjectSerializer.register(JsonObjectSerializer)
|
|||
class JsonSerializer(Serializer):
|
||||
|
||||
SERIALIZER_ID = "json"
|
||||
"""
|
||||
ID used as part of the WebSocket subprotocol name to identify the
|
||||
serializer with WAMP-over-WebSocket.
|
||||
"""
|
||||
|
||||
RAWSOCKET_SERIALIZER_ID = 1
|
||||
"""
|
||||
ID used in lower four bits of second octet in RawSocket opening
|
||||
handshake identify the serializer with WAMP-over-RawSocket.
|
||||
"""
|
||||
|
||||
MIME_TYPE = "application/json"
|
||||
"""
|
||||
MIME type announced in HTTP request/response headers when running
|
||||
WAMP-over-Longpoll HTTP fallback.
|
||||
"""
|
||||
|
||||
def __init__(self, batched=False):
|
||||
"""
|
||||
|
@ -276,6 +291,23 @@ else:
|
|||
"""
|
||||
Implements :func:`autobahn.wamp.interfaces.IObjectSerializer.unserialize`
|
||||
"""
|
||||
|
||||
def ensure_string_keys(d):
|
||||
"""
|
||||
under python 2, with use_bin_type=True, most dict keys end up
|
||||
getting encoded as bytes (any syntax except {u"key":
|
||||
u"value"}) so instead of recursively looking through
|
||||
everything that's getting serialized, we fix them up
|
||||
on the way out using msgpack's `object_hook` as
|
||||
there's no corresponding hook for serialization...
|
||||
"""
|
||||
for (k, v) in six.iteritems(d):
|
||||
if not isinstance(k, six.text_type):
|
||||
newk = six.text_type(k, encoding='utf8')
|
||||
del d[k]
|
||||
d[newk] = v
|
||||
return d
|
||||
|
||||
if self._batched:
|
||||
msgs = []
|
||||
N = len(payload)
|
||||
|
@ -292,7 +324,13 @@ else:
|
|||
data = payload[i + 4:i + 4 + l]
|
||||
|
||||
# append parsed raw message
|
||||
msgs.append(msgpack.unpackb(data, encoding='utf-8'))
|
||||
msgs.append(
|
||||
msgpack.unpackb(
|
||||
data,
|
||||
encoding='utf-8',
|
||||
object_hook=ensure_string_keys,
|
||||
)
|
||||
)
|
||||
|
||||
# advance until everything consumed
|
||||
i = i + 4 + l
|
||||
|
@ -302,7 +340,12 @@ else:
|
|||
return msgs
|
||||
|
||||
else:
|
||||
return [msgpack.unpackb(payload, encoding='utf-8')]
|
||||
unpacked = msgpack.unpackb(
|
||||
payload,
|
||||
encoding='utf-8',
|
||||
object_hook=ensure_string_keys,
|
||||
)
|
||||
return [unpacked]
|
||||
|
||||
IObjectSerializer.register(MsgPackObjectSerializer)
|
||||
|
||||
|
@ -311,7 +354,22 @@ else:
|
|||
class MsgPackSerializer(Serializer):
|
||||
|
||||
SERIALIZER_ID = "msgpack"
|
||||
"""
|
||||
ID used as part of the WebSocket subprotocol name to identify the
|
||||
serializer with WAMP-over-WebSocket.
|
||||
"""
|
||||
|
||||
RAWSOCKET_SERIALIZER_ID = 2
|
||||
"""
|
||||
ID used in lower four bits of second octet in RawSocket opening
|
||||
handshake identify the serializer with WAMP-over-RawSocket.
|
||||
"""
|
||||
|
||||
MIME_TYPE = "application/x-msgpack"
|
||||
"""
|
||||
MIME type announced in HTTP request/response headers when running
|
||||
WAMP-over-Longpoll HTTP fallback.
|
||||
"""
|
||||
|
||||
def __init__(self, batched=False):
|
||||
"""
|
||||
|
|
|
@ -33,8 +33,9 @@ if os.environ.get('USE_TWISTED', False):
|
|||
from twisted.trial import unittest
|
||||
# import unittest
|
||||
|
||||
from twisted.internet.defer import inlineCallbacks, Deferred, returnValue, succeed
|
||||
from twisted.internet.defer import inlineCallbacks, Deferred, returnValue, succeed, DeferredList
|
||||
from twisted.python import log
|
||||
from six import PY3
|
||||
|
||||
from autobahn.wamp import message
|
||||
from autobahn.wamp import serializer
|
||||
|
@ -42,9 +43,11 @@ if os.environ.get('USE_TWISTED', False):
|
|||
from autobahn import util
|
||||
from autobahn.wamp.exception import ApplicationError, NotAuthorized, InvalidUri, ProtocolError
|
||||
from autobahn.wamp import types
|
||||
|
||||
from autobahn.twisted.wamp import ApplicationSession
|
||||
|
||||
if PY3:
|
||||
long = int
|
||||
|
||||
class MockTransport(object):
|
||||
|
||||
def __init__(self, handler):
|
||||
|
@ -64,6 +67,7 @@ if os.environ.get('USE_TWISTED', False):
|
|||
|
||||
msg = message.Welcome(self._my_session_id, roles)
|
||||
self._handler.onMessage(msg)
|
||||
self._fake_router_session = ApplicationSession()
|
||||
|
||||
def send(self, msg):
|
||||
if self._log:
|
||||
|
@ -75,7 +79,7 @@ if os.environ.get('USE_TWISTED', False):
|
|||
if isinstance(msg, message.Publish):
|
||||
if msg.topic.startswith(u'com.myapp'):
|
||||
if msg.acknowledge:
|
||||
reply = message.Published(msg.request, util.id())
|
||||
reply = message.Published(msg.request, self._fake_router_session._request_id_gen.next())
|
||||
elif len(msg.topic) == 0:
|
||||
reply = message.Error(message.Publish.MESSAGE_TYPE, msg.request, u'wamp.error.invalid_uri')
|
||||
else:
|
||||
|
@ -91,7 +95,9 @@ if os.environ.get('USE_TWISTED', False):
|
|||
|
||||
elif msg.procedure.startswith(u'com.myapp.myproc'):
|
||||
registration = self._registrations[msg.procedure]
|
||||
request = util.id()
|
||||
request = self._fake_router_session._request_id_gen.next()
|
||||
if request in self._invocations:
|
||||
raise ProtocolError("duplicate invocation")
|
||||
self._invocations[request] = msg.request
|
||||
reply = message.Invocation(
|
||||
request, registration,
|
||||
|
@ -112,7 +118,7 @@ if os.environ.get('USE_TWISTED', False):
|
|||
if topic in self._subscription_topics:
|
||||
reply_id = self._subscription_topics[topic]
|
||||
else:
|
||||
reply_id = util.id()
|
||||
reply_id = self._fake_router_session._request_id_gen.next()
|
||||
self._subscription_topics[topic] = reply_id
|
||||
reply = message.Subscribed(msg.request, reply_id)
|
||||
|
||||
|
@ -120,7 +126,7 @@ if os.environ.get('USE_TWISTED', False):
|
|||
reply = message.Unsubscribed(msg.request)
|
||||
|
||||
elif isinstance(msg, message.Register):
|
||||
registration = util.id()
|
||||
registration = self._fake_router_session._request_id_gen.next()
|
||||
self._registrations[msg.procedure] = registration
|
||||
reply = message.Registered(msg.request, registration)
|
||||
|
||||
|
@ -154,6 +160,15 @@ if os.environ.get('USE_TWISTED', False):
|
|||
def abort(self):
|
||||
pass
|
||||
|
||||
class TestClose(unittest.TestCase):
|
||||
def test_server_abort(self):
|
||||
handler = ApplicationSession()
|
||||
MockTransport(handler)
|
||||
|
||||
# this should not raise an exception, but did when this
|
||||
# test-case was written
|
||||
handler.onClose(False)
|
||||
|
||||
class TestPublisher(unittest.TestCase):
|
||||
|
||||
@inlineCallbacks
|
||||
|
@ -532,6 +547,55 @@ if os.environ.get('USE_TWISTED', False):
|
|||
res = yield handler.call(u'com.myapp.myproc1')
|
||||
self.assertEqual(res, 23)
|
||||
|
||||
@inlineCallbacks
|
||||
def test_invoke_twice(self):
|
||||
handler = ApplicationSession()
|
||||
MockTransport(handler)
|
||||
|
||||
def myproc1():
|
||||
return 23
|
||||
|
||||
yield handler.register(myproc1, u'com.myapp.myproc1')
|
||||
|
||||
d0 = handler.call(u'com.myapp.myproc1')
|
||||
d1 = handler.call(u'com.myapp.myproc1')
|
||||
res = yield DeferredList([d0, d1])
|
||||
self.assertEqual(res, [(True, 23), (True, 23)])
|
||||
|
||||
@inlineCallbacks
|
||||
def test_invoke_request_id_sequences(self):
|
||||
"""
|
||||
make sure each session independently generates sequential IDs
|
||||
"""
|
||||
handler0 = ApplicationSession()
|
||||
handler1 = ApplicationSession()
|
||||
trans0 = MockTransport(handler0)
|
||||
trans1 = MockTransport(handler1)
|
||||
|
||||
# the ID sequences for each session should both start at 0
|
||||
# (the register) and then increment for the call()
|
||||
def verify_seq_id(orig, msg):
|
||||
if isinstance(msg, message.Register):
|
||||
self.assertEqual(msg.request, 0)
|
||||
elif isinstance(msg, message.Call):
|
||||
self.assertEqual(msg.request, 1)
|
||||
return orig(msg)
|
||||
orig0 = trans0.send
|
||||
orig1 = trans1.send
|
||||
trans0.send = lambda msg: verify_seq_id(orig0, msg)
|
||||
trans1.send = lambda msg: verify_seq_id(orig1, msg)
|
||||
|
||||
def myproc1():
|
||||
return 23
|
||||
|
||||
yield handler0.register(myproc1, u'com.myapp.myproc1')
|
||||
yield handler1.register(myproc1, u'com.myapp.myproc1')
|
||||
|
||||
d0 = handler0.call(u'com.myapp.myproc1')
|
||||
d1 = handler1.call(u'com.myapp.myproc1')
|
||||
res = yield DeferredList([d0, d1])
|
||||
self.assertEqual(res, [(True, 23), (True, 23)])
|
||||
|
||||
@inlineCallbacks
|
||||
def test_invoke_user_raises(self):
|
||||
handler = ApplicationSession()
|
||||
|
@ -575,7 +639,7 @@ if os.environ.get('USE_TWISTED', False):
|
|||
yield succeed(i)
|
||||
returnValue(42)
|
||||
|
||||
progressive = map(lambda _: Deferred(), range(10))
|
||||
progressive = list(map(lambda _: Deferred(), range(10)))
|
||||
|
||||
def progress(arg):
|
||||
progressive[arg].callback(arg)
|
||||
|
|
|
@ -24,60 +24,177 @@
|
|||
#
|
||||
###############################################################################
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import absolute_import, print_function
|
||||
|
||||
import os
|
||||
try:
|
||||
import unittest2 as unittest
|
||||
except ImportError:
|
||||
import unittest
|
||||
from mock import patch
|
||||
|
||||
if os.environ.get('USE_TWISTED', False):
|
||||
from mock import patch
|
||||
from zope.interface import implementer
|
||||
from twisted.internet.interfaces import IReactorTime
|
||||
|
||||
class FakeReactor(object):
|
||||
'''
|
||||
This just fakes out enough reactor methods so .run() can work.
|
||||
'''
|
||||
stop_called = False
|
||||
|
||||
def __init__(self, to_raise):
|
||||
self.stop_called = False
|
||||
self.to_raise = to_raise
|
||||
|
||||
def run(self, *args, **kw):
|
||||
raise self.to_raise
|
||||
|
||||
def stop(self):
|
||||
self.stop_called = True
|
||||
|
||||
def connectTCP(self, *args, **kw):
|
||||
raise RuntimeError("ConnectTCP shouldn't get called")
|
||||
|
||||
|
||||
class TestWampTwistedRunner(unittest.TestCase):
|
||||
|
||||
def test_connect_error(self):
|
||||
@implementer(IReactorTime)
|
||||
class FakeReactor(object):
|
||||
'''
|
||||
Ensure the runner doesn't swallow errors and that it exits the
|
||||
reactor properly if there is one.
|
||||
This just fakes out enough reactor methods so .run() can work.
|
||||
'''
|
||||
try:
|
||||
from autobahn.twisted.wamp import ApplicationRunner
|
||||
from twisted.internet.error import ConnectionRefusedError
|
||||
# the 'reactor' member doesn't exist until we import it
|
||||
from twisted.internet import reactor # noqa: F401
|
||||
except ImportError:
|
||||
raise unittest.SkipTest('No twisted')
|
||||
stop_called = False
|
||||
|
||||
runner = ApplicationRunner('ws://localhost:1', 'realm')
|
||||
exception = ConnectionRefusedError("It's a trap!")
|
||||
def __init__(self, to_raise):
|
||||
self.stop_called = False
|
||||
self.to_raise = to_raise
|
||||
self.delayed = []
|
||||
|
||||
with patch('twisted.internet.reactor', FakeReactor(exception)) as mockreactor:
|
||||
self.assertRaises(
|
||||
ConnectionRefusedError,
|
||||
# pass a no-op session-creation method
|
||||
runner.run, lambda _: None, start_reactor=True
|
||||
)
|
||||
self.assertTrue(mockreactor.stop_called)
|
||||
def run(self, *args, **kw):
|
||||
raise self.to_raise
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
def stop(self):
|
||||
self.stop_called = True
|
||||
|
||||
def callLater(self, delay, func, *args, **kwargs):
|
||||
self.delayed.append((delay, func, args, kwargs))
|
||||
|
||||
def connectTCP(self, *args, **kw):
|
||||
raise RuntimeError("ConnectTCP shouldn't get called")
|
||||
|
||||
class TestWampTwistedRunner(unittest.TestCase):
|
||||
def test_connect_error(self):
|
||||
'''
|
||||
Ensure the runner doesn't swallow errors and that it exits the
|
||||
reactor properly if there is one.
|
||||
'''
|
||||
try:
|
||||
from autobahn.twisted.wamp import ApplicationRunner
|
||||
from twisted.internet.error import ConnectionRefusedError
|
||||
# the 'reactor' member doesn't exist until we import it
|
||||
from twisted.internet import reactor # noqa: F401
|
||||
except ImportError:
|
||||
raise unittest.SkipTest('No twisted')
|
||||
|
||||
runner = ApplicationRunner('ws://localhost:1', 'realm')
|
||||
exception = ConnectionRefusedError("It's a trap!")
|
||||
|
||||
with patch('twisted.internet.reactor', FakeReactor(exception)) as mockreactor:
|
||||
self.assertRaises(
|
||||
ConnectionRefusedError,
|
||||
# pass a no-op session-creation method
|
||||
runner.run, lambda _: None, start_reactor=True
|
||||
)
|
||||
self.assertTrue(mockreactor.stop_called)
|
||||
else:
|
||||
# Asyncio tests.
|
||||
try:
|
||||
import asyncio
|
||||
from unittest.mock import patch, Mock
|
||||
except ImportError:
|
||||
# Trollius >= 0.3 was renamed to asyncio
|
||||
# noinspection PyUnresolvedReferences
|
||||
import trollius as asyncio
|
||||
from mock import patch, Mock
|
||||
from autobahn.asyncio.wamp import ApplicationRunner
|
||||
|
||||
class TestApplicationRunner(unittest.TestCase):
|
||||
'''
|
||||
Test the autobahn.asyncio.wamp.ApplicationRunner class.
|
||||
'''
|
||||
def _assertRaisesRegex(self, exception, error, *args, **kw):
|
||||
try:
|
||||
self.assertRaisesRegex
|
||||
except AttributeError:
|
||||
f = self.assertRaisesRegexp
|
||||
else:
|
||||
f = self.assertRaisesRegex
|
||||
f(exception, error, *args, **kw)
|
||||
|
||||
def test_explicit_SSLContext(self):
|
||||
'''
|
||||
Ensure that loop.create_connection is called with the exact SSL
|
||||
context object that is passed (as ssl) to the __init__ method of
|
||||
ApplicationRunner.
|
||||
'''
|
||||
loop = Mock()
|
||||
loop.run_until_complete = Mock(return_value=(Mock(), Mock()))
|
||||
with patch.object(asyncio, 'get_event_loop', return_value=loop):
|
||||
ssl = {}
|
||||
runner = ApplicationRunner('ws://127.0.0.1:8080/ws', 'realm',
|
||||
ssl=ssl)
|
||||
runner.run('_unused_')
|
||||
self.assertIs(ssl, loop.create_connection.call_args[1]['ssl'])
|
||||
|
||||
def test_omitted_SSLContext_insecure(self):
|
||||
'''
|
||||
Ensure that loop.create_connection is called with ssl=False
|
||||
if no ssl argument is passed to the __init__ method of
|
||||
ApplicationRunner and the websocket URL starts with "ws:".
|
||||
'''
|
||||
loop = Mock()
|
||||
loop.run_until_complete = Mock(return_value=(Mock(), Mock()))
|
||||
with patch.object(asyncio, 'get_event_loop', return_value=loop):
|
||||
runner = ApplicationRunner('ws://127.0.0.1:8080/ws', 'realm')
|
||||
runner.run('_unused_')
|
||||
self.assertIs(False, loop.create_connection.call_args[1]['ssl'])
|
||||
|
||||
def test_omitted_SSLContext_secure(self):
|
||||
'''
|
||||
Ensure that loop.create_connection is called with ssl=True
|
||||
if no ssl argument is passed to the __init__ method of
|
||||
ApplicationRunner and the websocket URL starts with "wss:".
|
||||
'''
|
||||
loop = Mock()
|
||||
loop.run_until_complete = Mock(return_value=(Mock(), Mock()))
|
||||
with patch.object(asyncio, 'get_event_loop', return_value=loop):
|
||||
runner = ApplicationRunner('wss://127.0.0.1:8080/wss', 'realm')
|
||||
runner.run('_unused_')
|
||||
self.assertIs(True, loop.create_connection.call_args[1]['ssl'])
|
||||
|
||||
def test_conflict_SSL_True_with_ws_url(self):
|
||||
'''
|
||||
ApplicationRunner must raise an exception if given an ssl value of True
|
||||
but only a "ws:" URL.
|
||||
'''
|
||||
loop = Mock()
|
||||
loop.run_until_complete = Mock(return_value=(Mock(), Mock()))
|
||||
with patch.object(asyncio, 'get_event_loop', return_value=loop):
|
||||
runner = ApplicationRunner('ws://127.0.0.1:8080/wss', 'realm',
|
||||
ssl=True)
|
||||
error = ('^ssl argument value passed to ApplicationRunner '
|
||||
'conflicts with the "ws:" prefix of the url '
|
||||
'argument\. Did you mean to use "wss:"\?$')
|
||||
self._assertRaisesRegex(Exception, error, runner.run, '_unused_')
|
||||
|
||||
def test_conflict_SSLContext_with_ws_url(self):
|
||||
'''
|
||||
ApplicationRunner must raise an exception if given an ssl value that is
|
||||
an instance of SSLContext, but only a "ws:" URL.
|
||||
'''
|
||||
import ssl
|
||||
try:
|
||||
# Try to create an SSLContext, to be as rigorous as we can be
|
||||
# by avoiding making assumptions about the ApplicationRunner
|
||||
# implementation. If we happen to be on a Python that has no
|
||||
# SSLContext, we pass ssl=True, which will simply cause this
|
||||
# test to degenerate to the behavior of
|
||||
# test_conflict_SSL_True_with_ws_url (above). In fact, at the
|
||||
# moment (2015-05-10), none of this matters because the
|
||||
# ApplicationRunner implementation does not check to require
|
||||
# that its ssl argument is either a bool or an SSLContext. But
|
||||
# that may change, so we should be careful.
|
||||
ssl.create_default_context
|
||||
except AttributeError:
|
||||
context = True
|
||||
else:
|
||||
context = ssl.create_default_context()
|
||||
|
||||
loop = Mock()
|
||||
loop.run_until_complete = Mock(return_value=(Mock(), Mock()))
|
||||
with patch.object(asyncio, 'get_event_loop', return_value=loop):
|
||||
runner = ApplicationRunner('ws://127.0.0.1:8080/wss', 'realm',
|
||||
ssl=context)
|
||||
error = ('^ssl argument value passed to ApplicationRunner '
|
||||
'conflicts with the "ws:" prefix of the url '
|
||||
'argument\. Did you mean to use "wss:"\?$')
|
||||
self._assertRaisesRegex(Exception, error, runner.run, '_unused_')
|
||||
|
|
|
@ -28,6 +28,7 @@ from __future__ import absolute_import
|
|||
|
||||
# from twisted.trial import unittest
|
||||
import unittest
|
||||
import six
|
||||
|
||||
from autobahn.wamp import message
|
||||
from autobahn.wamp import role
|
||||
|
@ -93,6 +94,50 @@ class TestSerializer(unittest.TestCase):
|
|||
self.serializers.append(serializer.MsgPackSerializer())
|
||||
self.serializers.append(serializer.MsgPackSerializer(batched=True))
|
||||
|
||||
def test_dict_keys_msgpack(self):
|
||||
"""
|
||||
dict keys should always be strings. the data provided is from
|
||||
calling msgpack encode on a dict in python2 with
|
||||
`use_bin_type=True` and the following message:
|
||||
|
||||
print(ser.serialize(
|
||||
message.Call(
|
||||
123456, u"com.myapp.procedure1",
|
||||
args=(),
|
||||
kwargs={u'unicode': 23, 'str': 42}
|
||||
)
|
||||
))
|
||||
"""
|
||||
|
||||
if not hasattr(serializer, 'MsgPackSerializer'):
|
||||
self.skipTest("no msgpack")
|
||||
|
||||
ser = serializer.MsgPackSerializer()
|
||||
payload = b'\x960\xce\x00\x01\xe2@\x80\xb4com.myapp.procedure1\x90\x82\xc4\x03str*\xa7unicode\x17'
|
||||
msg_out = ser.unserialize(payload, True)[0]
|
||||
|
||||
for k in msg_out.kwargs.keys():
|
||||
self.assertEqual(type(k), six.text_type)
|
||||
self.assertTrue('str' in msg_out.kwargs)
|
||||
self.assertTrue('unicode' in msg_out.kwargs)
|
||||
|
||||
def test_dict_keys_msgpack_batched(self):
|
||||
"""
|
||||
dict keys should always be strings. the data provided is from
|
||||
calling msgpack encode on a dict in python2 with
|
||||
`use_bin_type=True`
|
||||
"""
|
||||
if not hasattr(serializer, 'MsgPackSerializer'):
|
||||
self.skipTest("no msgpack")
|
||||
|
||||
ser = serializer.MsgPackSerializer(batched=True)
|
||||
payload = b'\x00\x00\x00-\x960\xce\x00\x01\xe2@\x80\xb4com.myapp.procedure1\x90\x82\xa7unicode\x17\xa3str*'
|
||||
msg_out = ser.unserialize(payload, True)[0]
|
||||
for k in msg_out.kwargs.keys():
|
||||
self.assertEqual(type(k), six.text_type)
|
||||
self.assertTrue('str' in msg_out.kwargs)
|
||||
self.assertTrue('unicode' in msg_out.kwargs)
|
||||
|
||||
def test_roundtrip(self):
|
||||
for msg in generate_test_messages():
|
||||
for ser in self.serializers:
|
||||
|
|
|
@ -333,10 +333,8 @@ if os.environ.get('USE_TWISTED', False):
|
|||
# autobahn.wamp.websocket.WampWebSocketProtocol
|
||||
session.onClose(False)
|
||||
|
||||
self.assertEqual(2, len(session.errors))
|
||||
# might want to re-think this?
|
||||
self.assertEqual("No transport, but disconnect() called.", str(session.errors[0][0]))
|
||||
self.assertEqual(exception, session.errors[1][0])
|
||||
self.assertEqual(1, len(session.errors))
|
||||
self.assertEqual(exception, session.errors[0][0])
|
||||
|
||||
def test_on_disconnect_with_session_deferred(self):
|
||||
session = MockApplicationSession()
|
||||
|
@ -351,10 +349,8 @@ if os.environ.get('USE_TWISTED', False):
|
|||
# autobahn.wamp.websocket.WampWebSocketProtocol
|
||||
session.onClose(False)
|
||||
|
||||
self.assertEqual(2, len(session.errors))
|
||||
# might want to re-think this?
|
||||
self.assertEqual("No transport, but disconnect() called.", str(session.errors[0][0]))
|
||||
self.assertEqual(exception, session.errors[1][0])
|
||||
self.assertEqual(1, len(session.errors))
|
||||
self.assertEqual(exception, session.errors[0][0])
|
||||
|
||||
def test_on_connect(self):
|
||||
session = MockApplicationSession()
|
||||
|
|
|
@ -55,11 +55,13 @@ class ComponentConfig(object):
|
|||
|
||||
def __init__(self, realm=None, extra=None):
|
||||
"""
|
||||
|
||||
:param realm: The realm the session should join.
|
||||
:type realm: unicode
|
||||
:param extra: Optional dictionary with extra configuration.
|
||||
:type extra: dict
|
||||
|
||||
:param extra: Optional user-supplied object with extra
|
||||
configuration. This can be any object you like, and is
|
||||
accessible in your `ApplicationSession` subclass via
|
||||
`self.config.extra`. `dict` is a good default choice.
|
||||
"""
|
||||
if six.PY2 and type(realm) == str:
|
||||
realm = six.u(realm)
|
||||
|
@ -330,6 +332,9 @@ class PublishOptions(object):
|
|||
to subscribers.
|
||||
:type disclose_me: bool
|
||||
"""
|
||||
# filter out None entries from exclude list, so it's easier for callers
|
||||
if type(exclude) == list:
|
||||
exclude = [x for x in exclude if x is not None]
|
||||
assert(acknowledge is None or type(acknowledge) == bool)
|
||||
assert(exclude_me is None or type(exclude_me) == bool)
|
||||
assert(exclude is None or (type(exclude) == list and all(type(x) in six.integer_types for x in exclude)))
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
#
|
||||
###############################################################################
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import absolute_import, print_function
|
||||
|
||||
import traceback
|
||||
|
||||
|
@ -79,9 +79,8 @@ class WampWebSocketProtocol(object):
|
|||
print("WAMP-over-WebSocket transport lost: wasClean = {0}, code = {1}, reason = '{2}'".format(wasClean, code, reason))
|
||||
self._session.onClose(wasClean)
|
||||
except Exception:
|
||||
# silently ignore exceptions raised here ..
|
||||
if self.factory.debug_wamp:
|
||||
traceback.print_exc()
|
||||
print("Error invoking onClose():")
|
||||
traceback.print_exc()
|
||||
self._session = None
|
||||
|
||||
def onMessage(self, payload, isBinary):
|
||||
|
@ -95,6 +94,7 @@ class WampWebSocketProtocol(object):
|
|||
self._session.onMessage(msg)
|
||||
|
||||
except ProtocolError as e:
|
||||
print(e)
|
||||
if self.factory.debug_wamp:
|
||||
traceback.print_exc()
|
||||
reason = "WAMP Protocol Error ({0})".format(e)
|
||||
|
|
|
@ -26,8 +26,20 @@
|
|||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from autobahn.websocket.compress_base import * # noqa
|
||||
from autobahn.websocket.compress_deflate import * # noqa
|
||||
from autobahn.websocket.compress_base import \
|
||||
PerMessageCompressOffer, \
|
||||
PerMessageCompressOfferAccept, \
|
||||
PerMessageCompressResponse, \
|
||||
PerMessageCompressResponseAccept, \
|
||||
PerMessageCompress
|
||||
|
||||
from autobahn.websocket.compress_deflate import \
|
||||
PerMessageDeflateMixin, \
|
||||
PerMessageDeflateOffer, \
|
||||
PerMessageDeflateOfferAccept, \
|
||||
PerMessageDeflateResponse, \
|
||||
PerMessageDeflateResponseAccept, \
|
||||
PerMessageDeflate
|
||||
|
||||
# this must be a list (not tuple), since we dynamically
|
||||
# extend it ..
|
||||
|
@ -65,7 +77,13 @@ try:
|
|||
except ImportError:
|
||||
bz2 = None
|
||||
else:
|
||||
from autobahn.websocket.compress_bzip2 import * # noqa
|
||||
from autobahn.websocket.compress_bzip2 import \
|
||||
PerMessageBzip2Mixin, \
|
||||
PerMessageBzip2Offer, \
|
||||
PerMessageBzip2OfferAccept, \
|
||||
PerMessageBzip2Response, \
|
||||
PerMessageBzip2ResponseAccept, \
|
||||
PerMessageBzip2
|
||||
|
||||
PMCE = {
|
||||
'Offer': PerMessageBzip2Offer,
|
||||
|
@ -91,7 +109,13 @@ try:
|
|||
except ImportError:
|
||||
snappy = None
|
||||
else:
|
||||
from autobahn.websocket.compress_snappy import * # noqa
|
||||
from autobahn.websocket.compress_snappy import \
|
||||
PerMessageSnappyMixin, \
|
||||
PerMessageSnappyOffer, \
|
||||
PerMessageSnappyOfferAccept, \
|
||||
PerMessageSnappyResponse, \
|
||||
PerMessageSnappyResponseAccept, \
|
||||
PerMessageSnappy
|
||||
|
||||
PMCE = {
|
||||
'Offer': PerMessageSnappyOffer,
|
||||
|
|
|
@ -49,10 +49,11 @@ from autobahn.websocket.interfaces import IWebSocketChannel, \
|
|||
from autobahn.util import Stopwatch, newid, wildcards2patterns
|
||||
from autobahn.websocket.utf8validator import Utf8Validator
|
||||
from autobahn.websocket.xormasker import XorMaskerNull, createXorMasker
|
||||
from autobahn.websocket.compress import * # noqa
|
||||
from autobahn.websocket.compress import PERMESSAGE_COMPRESSION_EXTENSION
|
||||
from autobahn.websocket import http
|
||||
|
||||
from six.moves import urllib
|
||||
import txaio
|
||||
|
||||
if six.PY3:
|
||||
# Python 3
|
||||
|
@ -659,12 +660,16 @@ class WebSocketProtocol(object):
|
|||
Configuration attributes specific to clients.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
#: a Future/Deferred that fires when we hit STATE_CLOSED
|
||||
self.is_closed = txaio.create_future()
|
||||
|
||||
def onOpen(self):
|
||||
"""
|
||||
Implements :func:`autobahn.websocket.interfaces.IWebSocketChannel.onOpen`
|
||||
"""
|
||||
if self.debugCodePaths:
|
||||
self.factory._log("WebSocketProtocol.onOpen")
|
||||
self.factory.log.debug("WebSocketProtocol.onOpen")
|
||||
|
||||
def onMessageBegin(self, isBinary):
|
||||
"""
|
||||
|
@ -736,14 +741,14 @@ class WebSocketProtocol(object):
|
|||
Implements :func:`autobahn.websocket.interfaces.IWebSocketChannel.onMessage`
|
||||
"""
|
||||
if self.debug:
|
||||
self.factory._log("WebSocketProtocol.onMessage")
|
||||
self.factory.log.debug("WebSocketProtocol.onMessage")
|
||||
|
||||
def onPing(self, payload):
|
||||
"""
|
||||
Implements :func:`autobahn.websocket.interfaces.IWebSocketChannel.onPing`
|
||||
"""
|
||||
if self.debug:
|
||||
self.factory._log("WebSocketProtocol.onPing")
|
||||
self.factory.log.debug("WebSocketProtocol.onPing")
|
||||
if self.state == WebSocketProtocol.STATE_OPEN:
|
||||
self.sendPong(payload)
|
||||
|
||||
|
@ -752,7 +757,7 @@ class WebSocketProtocol(object):
|
|||
Implements :func:`autobahn.websocket.interfaces.IWebSocketChannel.onPong`
|
||||
"""
|
||||
if self.debug:
|
||||
self.factory._log("WebSocketProtocol.onPong")
|
||||
self.factory.log.debug("WebSocketProtocol.onPong")
|
||||
|
||||
def onClose(self, wasClean, code, reason):
|
||||
"""
|
||||
|
@ -772,7 +777,7 @@ class WebSocketProtocol(object):
|
|||
s += "self.localCloseReason=%s\n" % self.localCloseReason
|
||||
s += "self.remoteCloseCode=%s\n" % self.remoteCloseCode
|
||||
s += "self.remoteCloseReason=%s\n" % self.remoteCloseReason
|
||||
self.factory._log(s)
|
||||
self.factory.log.debug(s)
|
||||
|
||||
def onCloseFrame(self, code, reasonRaw):
|
||||
"""
|
||||
|
@ -789,11 +794,11 @@ class WebSocketProtocol(object):
|
|||
|
||||
:param code: Close status code, if there was one (:class:`WebSocketProtocol`.CLOSE_STATUS_CODE_*).
|
||||
:type code: int or None
|
||||
:param reason: Close reason (when present, a status code MUST have been also be present).
|
||||
:param reasonRaw: Close reason (when present, a status code MUST have been also be present).
|
||||
:type reason: str or None
|
||||
"""
|
||||
if self.debugCodePaths:
|
||||
self.factory._log("WebSocketProtocol.onCloseFrame")
|
||||
self.factory.log.debug("WebSocketProtocol.onCloseFrame")
|
||||
|
||||
self.remoteCloseCode = code
|
||||
|
||||
|
@ -825,7 +830,7 @@ class WebSocketProtocol(object):
|
|||
#
|
||||
if self.closeHandshakeTimeoutCall is not None:
|
||||
if self.debugCodePaths:
|
||||
self.factory._log("closeHandshakeTimeoutCall.cancel")
|
||||
self.factory.log.debug("closeHandshakeTimeoutCall.cancel")
|
||||
self.closeHandshakeTimeoutCall.cancel()
|
||||
self.closeHandshakeTimeoutCall = None
|
||||
|
||||
|
@ -838,7 +843,11 @@ class WebSocketProtocol(object):
|
|||
# When we are a client, the server should drop the TCP
|
||||
# If that doesn't happen, we do. And that will set wasClean = False.
|
||||
if self.serverConnectionDropTimeout > 0:
|
||||
self.serverConnectionDropTimeoutCall = self.factory._callLater(self.serverConnectionDropTimeout, self.onServerConnectionDropTimeout)
|
||||
call = txaio.call_later(
|
||||
self.serverConnectionDropTimeout,
|
||||
self.onServerConnectionDropTimeout,
|
||||
)
|
||||
self.serverConnectionDropTimeoutCall = call
|
||||
|
||||
elif self.state == WebSocketProtocol.STATE_OPEN:
|
||||
# The peer initiates a closing handshake, so we reply
|
||||
|
@ -851,7 +860,7 @@ class WebSocketProtocol(object):
|
|||
else:
|
||||
# Either reply with same code/reason, or code == NORMAL/reason=None
|
||||
if self.echoCloseCodeReason:
|
||||
self.sendCloseFrame(code=code, reasonUtf8=reason.encode("UTF-8"), isReply=True)
|
||||
self.sendCloseFrame(code=code, reasonUtf8=reasonRaw.encode("UTF-8"), isReply=True)
|
||||
else:
|
||||
self.sendCloseFrame(code=WebSocketProtocol.CLOSE_STATUS_CODE_NORMAL, isReply=True)
|
||||
|
||||
|
@ -883,14 +892,14 @@ class WebSocketProtocol(object):
|
|||
self.serverConnectionDropTimeoutCall = None
|
||||
if self.state != WebSocketProtocol.STATE_CLOSED:
|
||||
if self.debugCodePaths:
|
||||
self.factory._log("onServerConnectionDropTimeout")
|
||||
self.factory.log.debug("onServerConnectionDropTimeout")
|
||||
self.wasClean = False
|
||||
self.wasNotCleanReason = "server did not drop TCP connection (in time)"
|
||||
self.wasServerConnectionDropTimeout = True
|
||||
self.dropConnection(abort=True)
|
||||
else:
|
||||
if self.debugCodePaths:
|
||||
self.factory._log("skipping onServerConnectionDropTimeout since connection is already closed")
|
||||
self.factory.log.debug("skipping onServerConnectionDropTimeout since connection is already closed")
|
||||
|
||||
def onOpenHandshakeTimeout(self):
|
||||
"""
|
||||
|
@ -903,20 +912,20 @@ class WebSocketProtocol(object):
|
|||
self.openHandshakeTimeoutCall = None
|
||||
if self.state in [WebSocketProtocol.STATE_CONNECTING, WebSocketProtocol.STATE_PROXY_CONNECTING]:
|
||||
if self.debugCodePaths:
|
||||
self.factory._log("onOpenHandshakeTimeout fired")
|
||||
self.factory.log.debug("onOpenHandshakeTimeout fired")
|
||||
self.wasClean = False
|
||||
self.wasNotCleanReason = "peer did not finish (in time) the opening handshake"
|
||||
self.wasOpenHandshakeTimeout = True
|
||||
self.dropConnection(abort=True)
|
||||
elif self.state == WebSocketProtocol.STATE_OPEN:
|
||||
if self.debugCodePaths:
|
||||
self.factory._log("skipping onOpenHandshakeTimeout since WebSocket connection is open (opening handshake already finished)")
|
||||
self.factory.log.debug("skipping onOpenHandshakeTimeout since WebSocket connection is open (opening handshake already finished)")
|
||||
elif self.state == WebSocketProtocol.STATE_CLOSING:
|
||||
if self.debugCodePaths:
|
||||
self.factory._log("skipping onOpenHandshakeTimeout since WebSocket connection is closing")
|
||||
self.factory.log.debug("skipping onOpenHandshakeTimeout since WebSocket connection is closing")
|
||||
elif self.state == WebSocketProtocol.STATE_CLOSED:
|
||||
if self.debugCodePaths:
|
||||
self.factory._log("skipping onOpenHandshakeTimeout since WebSocket connection already closed")
|
||||
self.factory.log.debug("skipping onOpenHandshakeTimeout since WebSocket connection already closed")
|
||||
else:
|
||||
# should not arrive here
|
||||
raise Exception("logic error")
|
||||
|
@ -932,14 +941,14 @@ class WebSocketProtocol(object):
|
|||
self.closeHandshakeTimeoutCall = None
|
||||
if self.state != WebSocketProtocol.STATE_CLOSED:
|
||||
if self.debugCodePaths:
|
||||
self.factory._log("onCloseHandshakeTimeout fired")
|
||||
self.factory.log.debug("onCloseHandshakeTimeout fired")
|
||||
self.wasClean = False
|
||||
self.wasNotCleanReason = "peer did not respond (in time) in closing handshake"
|
||||
self.wasCloseHandshakeTimeout = True
|
||||
self.dropConnection(abort=True)
|
||||
else:
|
||||
if self.debugCodePaths:
|
||||
self.factory._log("skipping onCloseHandshakeTimeout since connection is already closed")
|
||||
self.factory.log.debug("skipping onCloseHandshakeTimeout since connection is already closed")
|
||||
|
||||
def onAutoPingTimeout(self):
|
||||
"""
|
||||
|
@ -947,7 +956,7 @@ class WebSocketProtocol(object):
|
|||
did not reply in time to our ping. We drop the connection.
|
||||
"""
|
||||
if self.debugCodePaths:
|
||||
self.factory._log("Auto ping/pong: onAutoPingTimeout fired")
|
||||
self.factory.log.debug("Auto ping/pong: onAutoPingTimeout fired")
|
||||
|
||||
self.autoPingTimeoutCall = None
|
||||
self.dropConnection(abort=True)
|
||||
|
@ -960,14 +969,19 @@ class WebSocketProtocol(object):
|
|||
"""
|
||||
if self.state != WebSocketProtocol.STATE_CLOSED:
|
||||
if self.debugCodePaths:
|
||||
self.factory._log("dropping connection")
|
||||
self.factory.log.debug("dropping connection")
|
||||
self.droppedByMe = True
|
||||
|
||||
# this code-path will be hit (*without* hitting
|
||||
# _connectionLost) in some timeout scenarios (unit-tests
|
||||
# cover these). However, sometimes we hit both.
|
||||
self.state = WebSocketProtocol.STATE_CLOSED
|
||||
txaio.resolve(self.is_closed, self)
|
||||
|
||||
self._closeConnection(abort)
|
||||
else:
|
||||
if self.debugCodePaths:
|
||||
self.factory._log("skipping dropConnection since connection is already closed")
|
||||
self.factory.log.debug("skipping dropConnection since connection is already closed")
|
||||
|
||||
def failConnection(self, code=CLOSE_STATUS_CODE_GOING_AWAY, reason="Going Away"):
|
||||
"""
|
||||
|
@ -980,7 +994,7 @@ class WebSocketProtocol(object):
|
|||
"""
|
||||
if self.state != WebSocketProtocol.STATE_CLOSED:
|
||||
if self.debugCodePaths:
|
||||
self.factory._log("Failing connection : %s - %s" % (code, reason))
|
||||
self.factory.log.debug("Failing connection : %s - %s" % (code, reason))
|
||||
|
||||
self.failedByMe = True
|
||||
|
||||
|
@ -1001,7 +1015,7 @@ class WebSocketProtocol(object):
|
|||
|
||||
else:
|
||||
if self.debugCodePaths:
|
||||
self.factory._log("skipping failConnection since connection is already closed")
|
||||
self.factory.log.debug("skipping failConnection since connection is already closed")
|
||||
|
||||
def protocolViolation(self, reason):
|
||||
"""
|
||||
|
@ -1018,7 +1032,7 @@ class WebSocketProtocol(object):
|
|||
:returns: bool -- True, when any further processing should be discontinued.
|
||||
"""
|
||||
if self.debugCodePaths:
|
||||
self.factory._log("Protocol violation : %s" % reason)
|
||||
self.factory.log.debug("Protocol violation : %s" % reason)
|
||||
self.failConnection(WebSocketProtocol.CLOSE_STATUS_CODE_PROTOCOL_ERROR, reason)
|
||||
if self.failByDrop:
|
||||
return True
|
||||
|
@ -1044,7 +1058,7 @@ class WebSocketProtocol(object):
|
|||
:returns: bool -- True, when any further processing should be discontinued.
|
||||
"""
|
||||
if self.debugCodePaths:
|
||||
self.factory._log("Invalid payload : %s" % reason)
|
||||
self.factory.log.debug("Invalid payload : %s" % reason)
|
||||
self.failConnection(WebSocketProtocol.CLOSE_STATUS_CODE_INVALID_PAYLOAD, reason)
|
||||
if self.failByDrop:
|
||||
return True
|
||||
|
@ -1089,8 +1103,8 @@ class WebSocketProtocol(object):
|
|||
configAttrLog.append((configAttr, getattr(self, configAttr), configAttrSource))
|
||||
|
||||
if self.debug:
|
||||
# self.factory._log(configAttrLog)
|
||||
self.factory._log("\n" + pformat(configAttrLog))
|
||||
# self.factory.log.debug(configAttrLog)
|
||||
self.factory.log.debug("\n" + pformat(configAttrLog))
|
||||
|
||||
# permessage-compress extension
|
||||
self._perMessageCompress = None
|
||||
|
@ -1179,7 +1193,7 @@ class WebSocketProtocol(object):
|
|||
|
||||
# set opening handshake timeout handler
|
||||
if self.openHandshakeTimeout > 0:
|
||||
self.openHandshakeTimeoutCall = self.factory._callLater(self.openHandshakeTimeout, self.onOpenHandshakeTimeout)
|
||||
self.openHandshakeTimeoutCall = txaio.call_later(self.openHandshakeTimeout, self.onOpenHandshakeTimeout)
|
||||
|
||||
self.autoPingTimeoutCall = None
|
||||
self.autoPingPending = None
|
||||
|
@ -1195,7 +1209,7 @@ class WebSocketProtocol(object):
|
|||
#
|
||||
if not self.factory.isServer and self.serverConnectionDropTimeoutCall is not None:
|
||||
if self.debugCodePaths:
|
||||
self.factory._log("serverConnectionDropTimeoutCall.cancel")
|
||||
self.factory.log.debug("serverConnectionDropTimeoutCall.cancel")
|
||||
self.serverConnectionDropTimeoutCall.cancel()
|
||||
self.serverConnectionDropTimeoutCall = None
|
||||
|
||||
|
@ -1203,20 +1217,25 @@ class WebSocketProtocol(object):
|
|||
#
|
||||
if self.autoPingPendingCall:
|
||||
if self.debugCodePaths:
|
||||
self.factory._log("Auto ping/pong: canceling autoPingPendingCall upon lost connection")
|
||||
self.factory.log.debug("Auto ping/pong: canceling autoPingPendingCall upon lost connection")
|
||||
self.autoPingPendingCall.cancel()
|
||||
self.autoPingPendingCall = None
|
||||
|
||||
if self.autoPingTimeoutCall:
|
||||
if self.debugCodePaths:
|
||||
self.factory._log("Auto ping/pong: canceling autoPingTimeoutCall upon lost connection")
|
||||
self.factory.log.debug("Auto ping/pong: canceling autoPingTimeoutCall upon lost connection")
|
||||
self.autoPingTimeoutCall.cancel()
|
||||
self.autoPingTimeoutCall = None
|
||||
|
||||
self.state = WebSocketProtocol.STATE_CLOSED
|
||||
# check required here because in some scenarios dropConnection
|
||||
# will already have resolved the Future/Deferred.
|
||||
if self.state != WebSocketProtocol.STATE_CLOSED:
|
||||
self.state = WebSocketProtocol.STATE_CLOSED
|
||||
txaio.resolve(self.is_closed, self)
|
||||
|
||||
if self.wasServingFlashSocketPolicyFile:
|
||||
if self.debug:
|
||||
self.factory._log("connection dropped after serving Flash Socket Policy File")
|
||||
self.factory.log.debug("connection dropped after serving Flash Socket Policy File")
|
||||
else:
|
||||
if not self.wasClean:
|
||||
if not self.droppedByMe and self.wasNotCleanReason is None:
|
||||
|
@ -1231,7 +1250,7 @@ class WebSocketProtocol(object):
|
|||
|
||||
Modes: Hybi, Hixie
|
||||
"""
|
||||
self.factory._log("RX Octets from %s : octets = %s" % (self.peer, binascii.b2a_hex(data)))
|
||||
self.factory.log.debug("RX Octets from %s : octets = %s" % (self.peer, binascii.b2a_hex(data)))
|
||||
|
||||
def logTxOctets(self, data, sync):
|
||||
"""
|
||||
|
@ -1239,7 +1258,7 @@ class WebSocketProtocol(object):
|
|||
|
||||
Modes: Hybi, Hixie
|
||||
"""
|
||||
self.factory._log("TX Octets to %s : sync = %s, octets = %s" % (self.peer, sync, binascii.b2a_hex(data)))
|
||||
self.factory.log.debug("TX Octets to %s : sync = %s, octets = %s" % (self.peer, sync, binascii.b2a_hex(data)))
|
||||
|
||||
def logRxFrame(self, frameHeader, payload):
|
||||
"""
|
||||
|
@ -1256,7 +1275,7 @@ class WebSocketProtocol(object):
|
|||
frameHeader.length,
|
||||
data if frameHeader.opcode == 1 else binascii.b2a_hex(data))
|
||||
|
||||
self.factory._log("RX Frame from %s : fin = %s, rsv = %s, opcode = %s, mask = %s, length = %s, payload = %s" % info)
|
||||
self.factory.log.debug("RX Frame from %s : fin = %s, rsv = %s, opcode = %s, mask = %s, length = %s, payload = %s" % info)
|
||||
|
||||
def logTxFrame(self, frameHeader, payload, repeatLength, chopsize, sync):
|
||||
"""
|
||||
|
@ -1275,7 +1294,7 @@ class WebSocketProtocol(object):
|
|||
sync,
|
||||
payload if frameHeader.opcode == 1 else binascii.b2a_hex(payload))
|
||||
|
||||
self.factory._log("TX Frame to %s : fin = %s, rsv = %s, opcode = %s, mask = %s, length = %s, repeat_length = %s, chopsize = %s, sync = %s, payload = %s" % info)
|
||||
self.factory.log.debug("TX Frame to %s : fin = %s, rsv = %s, opcode = %s, mask = %s, length = %s, repeat_length = %s, chopsize = %s, sync = %s, payload = %s" % info)
|
||||
|
||||
def _dataReceived(self, data):
|
||||
"""
|
||||
|
@ -1332,7 +1351,7 @@ class WebSocketProtocol(object):
|
|||
# ignore any data received after WS was closed
|
||||
#
|
||||
if self.debugCodePaths:
|
||||
self.factory._log("received data in STATE_CLOSED")
|
||||
self.factory.log.debug("received data in STATE_CLOSED")
|
||||
|
||||
# should not arrive here (invalid state)
|
||||
#
|
||||
|
@ -1388,13 +1407,13 @@ class WebSocketProtocol(object):
|
|||
self.logTxOctets(e[0], e[1])
|
||||
else:
|
||||
if self.debugCodePaths:
|
||||
self.factory._log("skipped delayed write, since connection is closed")
|
||||
self.factory.log.debug("skipped delayed write, since connection is closed")
|
||||
# we need to reenter the reactor to make the latter
|
||||
# reenter the OS network stack, so that octets
|
||||
# can get on the wire. Note: this is a "heuristic",
|
||||
# since there is no (easy) way to really force out
|
||||
# octets from the OS network stack to wire.
|
||||
self.factory._callLater(WebSocketProtocol._QUEUED_WRITE_DELAY, self._send)
|
||||
txaio.call_later(WebSocketProtocol._QUEUED_WRITE_DELAY, self._send)
|
||||
else:
|
||||
self.triggered = False
|
||||
|
||||
|
@ -1835,7 +1854,7 @@ class WebSocketProtocol(object):
|
|||
if self._isMessageCompressed:
|
||||
compressedLen = len(payload)
|
||||
if self.debug:
|
||||
self.factory._log("RX compressed [%d]: %s" % (compressedLen, binascii.b2a_hex(payload)))
|
||||
self.factory.log.debug("RX compressed [%d]: %s" % (compressedLen, binascii.b2a_hex(payload)))
|
||||
|
||||
payload = self._perMessageCompress.decompressMessageData(payload)
|
||||
uncompressedLen = len(payload)
|
||||
|
@ -1891,7 +1910,7 @@ class WebSocketProtocol(object):
|
|||
return False
|
||||
|
||||
# if self.debug:
|
||||
# self.factory._log("Traffic statistics:\n" + str(self.trafficStats))
|
||||
# self.factory.log.debug("Traffic statistics:\n" + str(self.trafficStats))
|
||||
|
||||
if self.state == WebSocketProtocol.STATE_OPEN:
|
||||
self.trafficStats.incomingWebSocketMessages += 1
|
||||
|
@ -1939,10 +1958,9 @@ class WebSocketProtocol(object):
|
|||
#
|
||||
if self.autoPingPending:
|
||||
try:
|
||||
p = payload.decode('utf8')
|
||||
if p == self.autoPingPending:
|
||||
if payload == self.autoPingPending:
|
||||
if self.debugCodePaths:
|
||||
self.factory._log("Auto ping/pong: received pending pong for auto-ping/pong")
|
||||
self.factory.log.debug("Auto ping/pong: received pending pong for auto-ping/pong")
|
||||
|
||||
if self.autoPingTimeoutCall:
|
||||
self.autoPingTimeoutCall.cancel()
|
||||
|
@ -1951,13 +1969,13 @@ class WebSocketProtocol(object):
|
|||
self.autoPingTimeoutCall = None
|
||||
|
||||
if self.autoPingInterval:
|
||||
self.autoPingPendingCall = self.factory._callLater(self.autoPingInterval, self._sendAutoPing)
|
||||
self.autoPingPendingCall = txaio.call_later(self.autoPingInterval, self._sendAutoPing)
|
||||
else:
|
||||
if self.debugCodePaths:
|
||||
self.factory._log("Auto ping/pong: received non-pending pong")
|
||||
self.factory.log.debug("Auto ping/pong: received non-pending pong")
|
||||
except:
|
||||
if self.debugCodePaths:
|
||||
self.factory._log("Auto ping/pong: received non-pending pong")
|
||||
self.factory.log.debug("Auto ping/pong: received non-pending pong")
|
||||
|
||||
# fire app-level callback
|
||||
#
|
||||
|
@ -2085,18 +2103,18 @@ class WebSocketProtocol(object):
|
|||
def _sendAutoPing(self):
|
||||
# Sends an automatic ping and sets up a timeout.
|
||||
if self.debugCodePaths:
|
||||
self.factory._log("Auto ping/pong: sending ping auto-ping/pong")
|
||||
self.factory.log.debug("Auto ping/pong: sending ping auto-ping/pong")
|
||||
|
||||
self.autoPingPendingCall = None
|
||||
|
||||
self.autoPingPending = newid(self.autoPingSize)
|
||||
self.autoPingPending = newid(self.autoPingSize).encode('utf8')
|
||||
|
||||
self.sendPing(self.autoPingPending.encode('utf8'))
|
||||
self.sendPing(self.autoPingPending)
|
||||
|
||||
if self.autoPingTimeout:
|
||||
if self.debugCodePaths:
|
||||
self.factory._log("Auto ping/pong: expecting ping in {0} seconds for auto-ping/pong".format(self.autoPingTimeout))
|
||||
self.autoPingTimeoutCall = self.factory._callLater(self.autoPingTimeout, self.onAutoPingTimeout)
|
||||
self.factory.log.debug("Auto ping/pong: expecting ping in {0} seconds for auto-ping/pong".format(self.autoPingTimeout))
|
||||
self.autoPingTimeoutCall = txaio.call_later(self.autoPingTimeout, self.onAutoPingTimeout)
|
||||
|
||||
def sendPong(self, payload=None):
|
||||
"""
|
||||
|
@ -2128,11 +2146,11 @@ class WebSocketProtocol(object):
|
|||
"""
|
||||
if self.state == WebSocketProtocol.STATE_CLOSING:
|
||||
if self.debugCodePaths:
|
||||
self.factory._log("ignoring sendCloseFrame since connection is closing")
|
||||
self.factory.log.debug("ignoring sendCloseFrame since connection is closing")
|
||||
|
||||
elif self.state == WebSocketProtocol.STATE_CLOSED:
|
||||
if self.debugCodePaths:
|
||||
self.factory._log("ignoring sendCloseFrame since connection already closed")
|
||||
self.factory.log.debug("ignoring sendCloseFrame since connection already closed")
|
||||
|
||||
elif self.state in [WebSocketProtocol.STATE_PROXY_CONNECTING, WebSocketProtocol.STATE_CONNECTING]:
|
||||
raise Exception("cannot close a connection not yet connected")
|
||||
|
@ -2160,7 +2178,7 @@ class WebSocketProtocol(object):
|
|||
|
||||
# drop connection when timeout on receiving close handshake reply
|
||||
if self.closedByMe and self.closeHandshakeTimeout > 0:
|
||||
self.closeHandshakeTimeoutCall = self.factory._callLater(self.closeHandshakeTimeout, self.onCloseHandshakeTimeout)
|
||||
self.closeHandshakeTimeoutCall = txaio.call_later(self.closeHandshakeTimeout, self.onCloseHandshakeTimeout)
|
||||
|
||||
else:
|
||||
raise Exception("logic error")
|
||||
|
@ -2516,7 +2534,7 @@ class WebSocketProtocol(object):
|
|||
i += pfs
|
||||
|
||||
# if self.debug:
|
||||
# self.factory._log("Traffic statistics:\n" + str(self.trafficStats))
|
||||
# self.factory.log.debug("Traffic statistics:\n" + str(self.trafficStats))
|
||||
|
||||
def _parseExtensionsHeader(self, header, removeQuotes=True):
|
||||
"""
|
||||
|
@ -2715,7 +2733,7 @@ class WebSocketServerProtocol(WebSocketProtocol):
|
|||
WebSocketProtocol._connectionMade(self)
|
||||
self.factory.countConnections += 1
|
||||
if self.debug:
|
||||
self.factory._log("connection accepted from peer %s" % self.peer)
|
||||
self.factory.log.debug("connection accepted from peer %s" % self.peer)
|
||||
|
||||
def _connectionLost(self, reason):
|
||||
"""
|
||||
|
@ -2727,7 +2745,7 @@ class WebSocketServerProtocol(WebSocketProtocol):
|
|||
WebSocketProtocol._connectionLost(self, reason)
|
||||
self.factory.countConnections -= 1
|
||||
if self.debug:
|
||||
self.factory._log("connection from %s lost" % self.peer)
|
||||
self.factory.log.debug("connection from %s lost" % self.peer)
|
||||
|
||||
def processProxyConnect(self):
|
||||
raise Exception("Autobahn isn't a proxy server")
|
||||
|
@ -2749,7 +2767,7 @@ class WebSocketServerProtocol(WebSocketProtocol):
|
|||
|
||||
self.http_request_data = self.data[:end_of_header + 4]
|
||||
if self.debug:
|
||||
self.factory._log("received HTTP request:\n\n%s\n\n" % self.http_request_data)
|
||||
self.factory.log.debug("received HTTP request:\n\n%s\n\n" % self.http_request_data)
|
||||
|
||||
# extract HTTP status line and headers
|
||||
#
|
||||
|
@ -2758,8 +2776,8 @@ class WebSocketServerProtocol(WebSocketProtocol):
|
|||
# validate WebSocket opening handshake client request
|
||||
#
|
||||
if self.debug:
|
||||
self.factory._log("received HTTP status line in opening handshake : %s" % str(self.http_status_line))
|
||||
self.factory._log("received HTTP headers in opening handshake : %s" % str(self.http_headers))
|
||||
self.factory.log.debug("received HTTP status line in opening handshake : %s" % str(self.http_status_line))
|
||||
self.factory.log.debug("received HTTP headers in opening handshake : %s" % str(self.http_headers))
|
||||
|
||||
# HTTP Request line : METHOD, VERSION
|
||||
#
|
||||
|
@ -2818,7 +2836,7 @@ class WebSocketServerProtocol(WebSocketProtocol):
|
|||
return self.failHandshake("port %d in HTTP Host header '%s' does not match server listening port %s" % (port, str(self.http_request_host), self.factory.externalPort))
|
||||
else:
|
||||
if self.debugCodePaths:
|
||||
self.factory._log("skipping opening handshake port checking - neither WS URL nor external port set")
|
||||
self.factory.log.debug("skipping opening handshake port checking - neither WS URL nor external port set")
|
||||
|
||||
self.http_request_host = h
|
||||
|
||||
|
@ -2829,7 +2847,7 @@ class WebSocketServerProtocol(WebSocketProtocol):
|
|||
return self.failHandshake("missing port in HTTP Host header '%s' and server runs on non-standard port %d (wss = %s)" % (str(self.http_request_host), self.factory.externalPort, self.factory.isSecure))
|
||||
else:
|
||||
if self.debugCodePaths:
|
||||
self.factory._log("skipping opening handshake port checking - neither WS URL nor external port set")
|
||||
self.factory.log.debug("skipping opening handshake port checking - neither WS URL nor external port set")
|
||||
|
||||
# Upgrade
|
||||
#
|
||||
|
@ -2857,15 +2875,15 @@ class WebSocketServerProtocol(WebSocketProtocol):
|
|||
if 'after' in self.http_request_params and len(self.http_request_params['after']) > 0:
|
||||
after = int(self.http_request_params['after'][0])
|
||||
if self.debugCodePaths:
|
||||
self.factory._log("HTTP Upgrade header missing : render server status page and meta-refresh-redirecting to %s after %d seconds" % (url, after))
|
||||
self.factory.log.debug("HTTP Upgrade header missing : render server status page and meta-refresh-redirecting to %s after %d seconds" % (url, after))
|
||||
self.sendServerStatus(url, after)
|
||||
else:
|
||||
if self.debugCodePaths:
|
||||
self.factory._log("HTTP Upgrade header missing : 303-redirecting to %s" % url)
|
||||
self.factory.log.debug("HTTP Upgrade header missing : 303-redirecting to %s" % url)
|
||||
self.sendRedirect(url)
|
||||
else:
|
||||
if self.debugCodePaths:
|
||||
self.factory._log("HTTP Upgrade header missing : render server status page")
|
||||
self.factory.log.debug("HTTP Upgrade header missing : render server status page")
|
||||
self.sendServerStatus()
|
||||
self.dropConnection(abort=False)
|
||||
return
|
||||
|
@ -2895,14 +2913,14 @@ class WebSocketServerProtocol(WebSocketProtocol):
|
|||
#
|
||||
if 'sec-websocket-version' not in self.http_headers:
|
||||
if self.debugCodePaths:
|
||||
self.factory._log("Hixie76 protocol detected")
|
||||
self.factory.log.debug("Hixie76 protocol detected")
|
||||
if self.allowHixie76:
|
||||
version = 0
|
||||
else:
|
||||
return self.failHandshake("WebSocket connection denied - Hixie76 protocol mode disabled.")
|
||||
else:
|
||||
if self.debugCodePaths:
|
||||
self.factory._log("Hybi protocol detected")
|
||||
self.factory.log.debug("Hybi protocol detected")
|
||||
if http_headers_cnt["sec-websocket-version"] > 1:
|
||||
return self.failHandshake("HTTP Sec-WebSocket-Version header appears more than once in opening handshake request")
|
||||
try:
|
||||
|
@ -3020,7 +3038,7 @@ class WebSocketServerProtocol(WebSocketProtocol):
|
|||
else:
|
||||
key3 = self.data[end_of_header + 4:end_of_header + 4 + 8]
|
||||
if self.debug:
|
||||
self.factory._log("received HTTP request body containing key3 for Hixie-76: %s" % key3)
|
||||
self.factory.log.debug("received HTTP request body containing key3 for Hixie-76: %s" % key3)
|
||||
|
||||
# Ok, got complete HS input, remember rest (if any)
|
||||
#
|
||||
|
@ -3067,11 +3085,11 @@ class WebSocketServerProtocol(WebSocketProtocol):
|
|||
flash_policy_file_request = self.data.find(b"<policy-file-request/>\x00")
|
||||
if flash_policy_file_request >= 0:
|
||||
if self.debug:
|
||||
self.factory._log("received Flash Socket Policy File request")
|
||||
self.factory.log.debug("received Flash Socket Policy File request")
|
||||
|
||||
if self.serveFlashSocketPolicy:
|
||||
if self.debug:
|
||||
self.factory._log("sending Flash Socket Policy File :\n%s" % self.flashSocketPolicy)
|
||||
self.factory.log.debug("sending Flash Socket Policy File :\n%s" % self.flashSocketPolicy)
|
||||
|
||||
self.sendData(self.flashSocketPolicy.encode('utf8'))
|
||||
|
||||
|
@ -3080,7 +3098,7 @@ class WebSocketServerProtocol(WebSocketProtocol):
|
|||
self.dropConnection()
|
||||
else:
|
||||
if self.debug:
|
||||
self.factory._log("No Flash Policy File served. You might want to serve a Flask Socket Policy file on the destination port since you received a request for it. See WebSocketServerFactory.serveFlashSocketPolicy and WebSocketServerFactory.flashSocketPolicy")
|
||||
self.factory.log.debug("No Flash Policy File served. You might want to serve a Flask Socket Policy file on the destination port since you received a request for it. See WebSocketServerFactory.serveFlashSocketPolicy and WebSocketServerFactory.flashSocketPolicy")
|
||||
|
||||
def succeedHandshake(self, res):
|
||||
"""
|
||||
|
@ -3121,7 +3139,7 @@ class WebSocketServerProtocol(WebSocketProtocol):
|
|||
for (extension, params) in self.websocket_extensions:
|
||||
|
||||
if self.debug:
|
||||
self.factory._log("parsed WebSocket extension '%s' with params '%s'" % (extension, params))
|
||||
self.factory.log.debug("parsed WebSocket extension '%s' with params '%s'" % (extension, params))
|
||||
|
||||
# process permessage-compress extension
|
||||
#
|
||||
|
@ -3137,7 +3155,7 @@ class WebSocketServerProtocol(WebSocketProtocol):
|
|||
|
||||
else:
|
||||
if self.debug:
|
||||
self.factory._log("client requested '%s' extension we don't support or which is not activated" % extension)
|
||||
self.factory.log.debug("client requested '%s' extension we don't support or which is not activated" % extension)
|
||||
|
||||
# handle permessage-compress offers by the client
|
||||
#
|
||||
|
@ -3150,7 +3168,7 @@ class WebSocketServerProtocol(WebSocketProtocol):
|
|||
extensionResponse.append(accept.getExtensionString())
|
||||
else:
|
||||
if self.debug:
|
||||
self.factory._log("client request permessage-compress extension, but we did not accept any offer [%s]" % pmceOffers)
|
||||
self.factory.log.debug("client request permessage-compress extension, but we did not accept any offer [%s]" % pmceOffers)
|
||||
|
||||
# build response to complete WebSocket handshake
|
||||
#
|
||||
|
@ -3190,15 +3208,15 @@ class WebSocketServerProtocol(WebSocketProtocol):
|
|||
response += "Sec-WebSocket-Origin: %s\x0d\x0a" % str(self.websocket_origin)
|
||||
|
||||
if self.debugCodePaths:
|
||||
self.factory._log('factory isSecure = %s port = %s' % (self.factory.isSecure, self.factory.externalPort))
|
||||
self.factory.log.debug('factory isSecure = %s port = %s' % (self.factory.isSecure, self.factory.externalPort))
|
||||
|
||||
if self.factory.externalPort and ((self.factory.isSecure and self.factory.externalPort != 443) or ((not self.factory.isSecure) and self.factory.externalPort != 80)):
|
||||
if self.debugCodePaths:
|
||||
self.factory._log('factory running on non-default port')
|
||||
self.factory.log.debug('factory running on non-default port')
|
||||
response_port = ':' + str(self.factory.externalPort)
|
||||
else:
|
||||
if self.debugCodePaths:
|
||||
self.factory._log('factory running on default port')
|
||||
self.factory.log.debug('factory running on default port')
|
||||
response_port = ''
|
||||
|
||||
# FIXME: check this! But see below ..
|
||||
|
@ -3245,12 +3263,12 @@ class WebSocketServerProtocol(WebSocketProtocol):
|
|||
# send out opening handshake response
|
||||
#
|
||||
if self.debug:
|
||||
self.factory._log("sending HTTP response:\n\n%s" % response)
|
||||
self.factory.log.debug("sending HTTP response:\n\n%s" % response)
|
||||
self.sendData(response.encode('utf8'))
|
||||
|
||||
if response_body:
|
||||
if self.debug:
|
||||
self.factory._log("sending HTTP response body:\n\n%s" % binascii.b2a_hex(response_body))
|
||||
self.factory.log.debug("sending HTTP response body:\n\n%s" % binascii.b2a_hex(response_body))
|
||||
self.sendData(response_body)
|
||||
|
||||
# save response for testsuite
|
||||
|
@ -3265,7 +3283,7 @@ class WebSocketServerProtocol(WebSocketProtocol):
|
|||
#
|
||||
if self.openHandshakeTimeoutCall is not None:
|
||||
if self.debugCodePaths:
|
||||
self.factory._log("openHandshakeTimeoutCall.cancel")
|
||||
self.factory.log.debug("openHandshakeTimeoutCall.cancel")
|
||||
self.openHandshakeTimeoutCall.cancel()
|
||||
self.openHandshakeTimeoutCall = None
|
||||
|
||||
|
@ -3278,7 +3296,7 @@ class WebSocketServerProtocol(WebSocketProtocol):
|
|||
# automatic ping/pong
|
||||
#
|
||||
if self.autoPingInterval:
|
||||
self.autoPingPendingCall = self.factory._callLater(self.autoPingInterval, self._sendAutoPing)
|
||||
self.autoPingPendingCall = txaio.call_later(self.autoPingInterval, self._sendAutoPing)
|
||||
|
||||
# fire handler on derived class
|
||||
#
|
||||
|
@ -3297,7 +3315,7 @@ class WebSocketServerProtocol(WebSocketProtocol):
|
|||
error response and then drop the connection.
|
||||
"""
|
||||
if self.debug:
|
||||
self.factory._log("failing WebSocket opening handshake ('%s')" % reason)
|
||||
self.factory.log.debug("failing WebSocket opening handshake ('%s')" % reason)
|
||||
self.sendHttpErrorResponse(code, reason, responseHeaders)
|
||||
self.dropConnection(abort=False)
|
||||
|
||||
|
@ -3728,7 +3746,7 @@ class WebSocketClientProtocol(WebSocketProtocol):
|
|||
"""
|
||||
WebSocketProtocol._connectionMade(self)
|
||||
if self.debug:
|
||||
self.factory._log("connection to %s established" % self.peer)
|
||||
self.factory.log.debug("connection to %s established" % self.peer)
|
||||
|
||||
if not self.factory.isServer and self.factory.proxy is not None:
|
||||
# start by doing a HTTP/CONNECT for explicit proxies
|
||||
|
@ -3746,7 +3764,7 @@ class WebSocketClientProtocol(WebSocketProtocol):
|
|||
"""
|
||||
WebSocketProtocol._connectionLost(self, reason)
|
||||
if self.debug:
|
||||
self.factory._log("connection to %s lost" % self.peer)
|
||||
self.factory.log.debug("connection to %s lost" % self.peer)
|
||||
|
||||
def startProxyConnect(self):
|
||||
"""
|
||||
|
@ -3760,7 +3778,7 @@ class WebSocketClientProtocol(WebSocketProtocol):
|
|||
request += "\x0d\x0a"
|
||||
|
||||
if self.debug:
|
||||
self.factory._log(request)
|
||||
self.factory.log.debug(request)
|
||||
|
||||
self.sendData(request)
|
||||
|
||||
|
@ -3775,7 +3793,7 @@ class WebSocketClientProtocol(WebSocketProtocol):
|
|||
|
||||
http_response_data = self.data[:end_of_header + 4]
|
||||
if self.debug:
|
||||
self.factory._log("received HTTP response:\n\n%s\n\n" % http_response_data)
|
||||
self.factory.log.debug("received HTTP response:\n\n%s\n\n" % http_response_data)
|
||||
|
||||
# extract HTTP status line and headers
|
||||
#
|
||||
|
@ -3784,8 +3802,8 @@ class WebSocketClientProtocol(WebSocketProtocol):
|
|||
# validate proxy connect response
|
||||
#
|
||||
if self.debug:
|
||||
self.factory._log("received HTTP status line for proxy connect request : %s" % str(http_status_line))
|
||||
self.factory._log("received HTTP headers for proxy connect request : %s" % str(http_headers))
|
||||
self.factory.log.debug("received HTTP status line for proxy connect request : %s" % str(http_status_line))
|
||||
self.factory.log.debug("received HTTP headers for proxy connect request : %s" % str(http_headers))
|
||||
|
||||
# Response Line
|
||||
#
|
||||
|
@ -3840,7 +3858,7 @@ class WebSocketClientProtocol(WebSocketProtocol):
|
|||
connection.
|
||||
"""
|
||||
if self.debug:
|
||||
self.factory._log("failing proxy connect ('%s')" % reason)
|
||||
self.factory.log.debug("failing proxy connect ('%s')" % reason)
|
||||
self.dropConnection(abort=True)
|
||||
|
||||
def createHixieKey(self):
|
||||
|
@ -3958,7 +3976,7 @@ class WebSocketClientProtocol(WebSocketProtocol):
|
|||
self.sendData(request_body)
|
||||
|
||||
if self.debug:
|
||||
self.factory._log(request)
|
||||
self.factory.log.debug(request)
|
||||
|
||||
def processHandshake(self):
|
||||
"""
|
||||
|
@ -3971,7 +3989,7 @@ class WebSocketClientProtocol(WebSocketProtocol):
|
|||
|
||||
self.http_response_data = self.data[:end_of_header + 4]
|
||||
if self.debug:
|
||||
self.factory._log("received HTTP response:\n\n%s\n\n" % self.http_response_data)
|
||||
self.factory.log.debug("received HTTP response:\n\n%s\n\n" % self.http_response_data)
|
||||
|
||||
# extract HTTP status line and headers
|
||||
#
|
||||
|
@ -3980,8 +3998,8 @@ class WebSocketClientProtocol(WebSocketProtocol):
|
|||
# validate WebSocket opening handshake server response
|
||||
#
|
||||
if self.debug:
|
||||
self.factory._log("received HTTP status line in opening handshake : %s" % str(self.http_status_line))
|
||||
self.factory._log("received HTTP headers in opening handshake : %s" % str(self.http_headers))
|
||||
self.factory.log.debug("received HTTP status line in opening handshake : %s" % str(self.http_status_line))
|
||||
self.factory.log.debug("received HTTP headers in opening handshake : %s" % str(self.http_headers))
|
||||
|
||||
# Response Line
|
||||
#
|
||||
|
@ -4072,7 +4090,7 @@ class WebSocketClientProtocol(WebSocketProtocol):
|
|||
for (extension, params) in websocket_extensions:
|
||||
|
||||
if self.debug:
|
||||
self.factory._log("parsed WebSocket extension '%s' with params '%s'" % (extension, params))
|
||||
self.factory.log.debug("parsed WebSocket extension '%s' with params '%s'" % (extension, params))
|
||||
|
||||
# process permessage-compress extension
|
||||
#
|
||||
|
@ -4142,7 +4160,7 @@ class WebSocketClientProtocol(WebSocketProtocol):
|
|||
#
|
||||
if self.openHandshakeTimeoutCall is not None:
|
||||
if self.debugCodePaths:
|
||||
self.factory._log("openHandshakeTimeoutCall.cancel")
|
||||
self.factory.log.debug("openHandshakeTimeoutCall.cancel")
|
||||
self.openHandshakeTimeoutCall.cancel()
|
||||
self.openHandshakeTimeoutCall = None
|
||||
|
||||
|
@ -4187,7 +4205,7 @@ class WebSocketClientProtocol(WebSocketProtocol):
|
|||
connection.
|
||||
"""
|
||||
if self.debug:
|
||||
self.factory._log("failing WebSocket opening handshake ('%s')" % reason)
|
||||
self.factory.log.debug("failing WebSocket opening handshake ('%s')" % reason)
|
||||
self.dropConnection(abort=True)
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,295 @@
|
|||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) Tavendo GmbH
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
from __future__ import absolute_import, print_function
|
||||
import os
|
||||
import struct
|
||||
|
||||
if os.environ.get('USE_TWISTED', False):
|
||||
from twisted.trial import unittest
|
||||
from twisted.internet.address import IPv4Address
|
||||
from twisted.internet.task import Clock
|
||||
from six import PY3
|
||||
|
||||
from autobahn.twisted.websocket import WebSocketServerProtocol
|
||||
from autobahn.twisted.websocket import WebSocketServerFactory
|
||||
from autobahn.twisted.websocket import WebSocketClientProtocol
|
||||
from autobahn.twisted.websocket import WebSocketClientFactory
|
||||
|
||||
from mock import MagicMock, patch
|
||||
from txaio.testutil import replace_loop
|
||||
|
||||
from base64 import b64decode
|
||||
|
||||
@patch('base64.b64encode')
|
||||
def create_client_frame(b64patch, **kwargs):
|
||||
"""
|
||||
Kind-of hack-y; maybe better to re-factor the Protocol to have a
|
||||
frame-encoder method-call? Anyway, makes a throwaway protocol
|
||||
encode a frame for us, collects the .sendData call and returns
|
||||
the data that would have gone out. Accepts all the kwargs that
|
||||
WebSocketClientProtocol.sendFrame() accepts.
|
||||
"""
|
||||
|
||||
# only real way to inject a "known" secret-key for the headers
|
||||
# to line up... :/
|
||||
b64patch.return_value = b'QIatSt9QkZPyS4QQfdufO8TgkL0='
|
||||
|
||||
factory = WebSocketClientFactory(protocols=['wamp.2.json'])
|
||||
factory.protocol = WebSocketClientProtocol
|
||||
factory.doStart()
|
||||
proto = factory.buildProtocol(IPv4Address('TCP', '127.0.0.9', 65534))
|
||||
proto.transport = MagicMock()
|
||||
proto.connectionMade()
|
||||
proto.data = mock_handshake_server
|
||||
proto.processHandshake()
|
||||
|
||||
data = []
|
||||
|
||||
def collect(d, *args):
|
||||
data.append(d)
|
||||
proto.sendData = collect
|
||||
|
||||
proto.sendFrame(**kwargs)
|
||||
return b''.join(data)
|
||||
|
||||
# beware the evils of line-endings...
|
||||
mock_handshake_client = b'GET / HTTP/1.1\r\nUser-Agent: AutobahnPython/0.10.2\r\nHost: localhost:80\r\nUpgrade: WebSocket\r\nConnection: Upgrade\r\nPragma: no-cache\r\nCache-Control: no-cache\r\nSec-WebSocket-Key: 6Jid6RgXpH0RVegaNSs/4g==\r\nSec-WebSocket-Protocol: wamp.2.json\r\nSec-WebSocket-Version: 13\r\n\r\n'
|
||||
|
||||
mock_handshake_server = b'HTTP/1.1 101 Switching Protocols\r\nServer: AutobahnPython/0.10.2\r\nX-Powered-By: AutobahnPython/0.10.2\r\nUpgrade: WebSocket\r\nConnection: Upgrade\r\nSec-WebSocket-Protocol: wamp.2.json\r\nSec-WebSocket-Accept: QIatSt9QkZPyS4QQfdufO8TgkL0=\r\n\r\n\x81~\x02\x19[1,"crossbar",{"roles":{"subscriber":{"features":{"publisher_identification":true,"pattern_based_subscription":true,"subscription_revocation":true}},"publisher":{"features":{"publisher_identification":true,"publisher_exclusion":true,"subscriber_blackwhite_listing":true}},"caller":{"features":{"caller_identification":true,"progressive_call_results":true}},"callee":{"features":{"progressive_call_results":true,"pattern_based_registration":true,"registration_revocation":true,"shared_registration":true,"caller_identification":true}}}}]\x18'
|
||||
|
||||
class TestClient(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.factory = WebSocketClientFactory(protocols=['wamp.2.json'])
|
||||
self.factory.protocol = WebSocketClientProtocol
|
||||
self.factory.doStart()
|
||||
|
||||
self.proto = self.factory.buildProtocol(IPv4Address('TCP', '127.0.0.1', 65534))
|
||||
self.transport = MagicMock()
|
||||
self.proto.transport = self.transport
|
||||
self.proto.connectionMade()
|
||||
|
||||
def tearDown(self):
|
||||
self.factory.doStop()
|
||||
# not really necessary, but ...
|
||||
del self.factory
|
||||
del self.proto
|
||||
|
||||
def test_unclean_timeout_client(self):
|
||||
"""
|
||||
make a delayed call to drop the connection (client-side)
|
||||
"""
|
||||
|
||||
if False:
|
||||
self.proto.debug = True
|
||||
self.proto.factory._log = print
|
||||
|
||||
# get to STATE_OPEN
|
||||
self.proto.websocket_key = b64decode('6Jid6RgXpH0RVegaNSs/4g==')
|
||||
self.proto.data = mock_handshake_server
|
||||
self.proto.processHandshake()
|
||||
self.assertEqual(self.proto.state, WebSocketServerProtocol.STATE_OPEN)
|
||||
self.assertTrue(self.proto.serverConnectionDropTimeout > 0)
|
||||
|
||||
with replace_loop(Clock()) as reactor:
|
||||
# now 'do the test' and transition to CLOSING
|
||||
self.proto.sendCloseFrame()
|
||||
self.proto.onCloseFrame(1000, b"raw reason")
|
||||
|
||||
# check we scheduled a call
|
||||
self.assertEqual(len(reactor.calls), 1)
|
||||
self.assertEqual(reactor.calls[0].func, self.proto.onServerConnectionDropTimeout)
|
||||
self.assertEqual(reactor.calls[0].getTime(), self.proto.serverConnectionDropTimeout)
|
||||
|
||||
# now, advance the clock past the call (and thereby
|
||||
# execute it)
|
||||
reactor.advance(self.proto.closeHandshakeTimeout + 1)
|
||||
|
||||
# we should have called abortConnection
|
||||
self.assertEqual("call.abortConnection()", str(self.proto.transport.method_calls[-1]))
|
||||
self.assertTrue(self.proto.transport.abortConnection.called)
|
||||
# ...too "internal" for an assert?
|
||||
self.assertEqual(self.proto.state, WebSocketServerProtocol.STATE_CLOSED)
|
||||
|
||||
class TestPing(unittest.TestCase):
|
||||
def setUp(self):
|
||||
if False:
|
||||
# debug leftover reactor events
|
||||
import twisted.internet.base
|
||||
twisted.internet.base.DelayedCall.debug = True
|
||||
|
||||
self.factory = WebSocketServerFactory(protocols=['wamp.2.json'])
|
||||
self.factory.protocol = WebSocketServerProtocol
|
||||
self.factory.doStart()
|
||||
|
||||
self.proto = self.factory.buildProtocol(IPv4Address('TCP', '127.0.0.1', 65534))
|
||||
self.transport = MagicMock()
|
||||
self.proto.transport = self.transport
|
||||
self.proto.connectionMade()
|
||||
|
||||
def tearDown(self):
|
||||
self.factory.doStop()
|
||||
# not really necessary, but ...
|
||||
del self.factory
|
||||
del self.proto
|
||||
|
||||
def test_unclean_timeout(self):
|
||||
"""
|
||||
make a delayed call to drop the connection
|
||||
"""
|
||||
# first we have to drive the protocol to STATE_CLOSING
|
||||
# ... which we achieve by sendCloseFrame after we're in
|
||||
# STATE_OPEN
|
||||
# XXX double-check this is the correct code-path to get here
|
||||
# "normally"?
|
||||
|
||||
if False:
|
||||
self.proto.debug = True
|
||||
self.proto.factory._log = print
|
||||
|
||||
# get to STATE_OPEN
|
||||
self.proto.data = mock_handshake_client
|
||||
self.proto.processHandshake()
|
||||
self.assertTrue(self.proto.state == WebSocketServerProtocol.STATE_OPEN)
|
||||
|
||||
with replace_loop(Clock()) as reactor:
|
||||
# now 'do the test' and transition to CLOSING
|
||||
self.proto.sendCloseFrame()
|
||||
|
||||
# check we scheduled a call
|
||||
self.assertEqual(len(reactor.calls), 1)
|
||||
self.assertEqual(reactor.calls[0].func, self.proto.onCloseHandshakeTimeout)
|
||||
self.assertEqual(reactor.calls[0].getTime(), self.proto.closeHandshakeTimeout)
|
||||
|
||||
# now, advance the clock past the call (and thereby
|
||||
# execute it)
|
||||
reactor.advance(self.proto.closeHandshakeTimeout + 1)
|
||||
|
||||
# we should have called abortConnection
|
||||
self.assertEqual("call.abortConnection()", str(self.proto.transport.method_calls[-1]))
|
||||
self.assertTrue(self.proto.transport.abortConnection.called)
|
||||
# ...too "internal" for an assert?
|
||||
self.assertEqual(self.proto.state, WebSocketServerProtocol.STATE_CLOSED)
|
||||
|
||||
def test_auto_pingpong_timeout(self):
|
||||
"""
|
||||
autoping and autoping-timeout timing
|
||||
"""
|
||||
if False:
|
||||
self.proto.debug = True
|
||||
self.proto.factory._log = print
|
||||
self.proto.debugCodePaths = True
|
||||
|
||||
# options are evaluated in succeedHandshake, called below
|
||||
self.proto.autoPingInterval = 5
|
||||
self.proto.autoPingTimeout = 2
|
||||
|
||||
with replace_loop(Clock()) as reactor:
|
||||
# get to STATE_OPEN
|
||||
self.proto.data = mock_handshake_client
|
||||
self.proto.processHandshake()
|
||||
self.assertTrue(self.proto.state == WebSocketServerProtocol.STATE_OPEN)
|
||||
|
||||
# we should have scheduled an autoPing
|
||||
self.assertEqual(1, len(reactor.calls))
|
||||
self.assertEqual(self.proto._sendAutoPing, reactor.calls[0].func)
|
||||
# ^^ un-unit-testy to assert on internal method?
|
||||
|
||||
# advance past first auto-ping timeout
|
||||
reactor.advance(5)
|
||||
|
||||
# first element from args tuple from transport.write()
|
||||
# call is our data
|
||||
self.assertTrue(self.transport.write.called)
|
||||
data = self.transport.write.call_args[0][0]
|
||||
|
||||
if PY3:
|
||||
_data = bytes([data[0]])
|
||||
else:
|
||||
_data = data[0]
|
||||
|
||||
# the opcode is the lower 7 bits of the first byte.
|
||||
(opcode,) = struct.unpack("B", _data)
|
||||
opcode = opcode & (~0x80)
|
||||
|
||||
# ... and should be "9" for ping
|
||||
self.assertEqual(9, opcode)
|
||||
|
||||
# Because we have autoPingTimeout there should be
|
||||
# another delayed-called created now
|
||||
self.assertEqual(1, len(reactor.calls))
|
||||
self.assertEqual(self.proto.onAutoPingTimeout, reactor.calls[0].func)
|
||||
self.assertNotEqual(self.proto.state, self.proto.STATE_CLOSED)
|
||||
|
||||
# ...which we'll now cause to trigger, aborting the connection
|
||||
reactor.advance(3)
|
||||
self.assertEqual(self.proto.state, self.proto.STATE_CLOSED)
|
||||
|
||||
def test_auto_ping_got_pong(self):
|
||||
"""
|
||||
auto-ping with correct reply cancels timeout
|
||||
"""
|
||||
if False:
|
||||
self.proto.debug = True
|
||||
self.proto.factory._log = print
|
||||
self.proto.debugCodePaths = True
|
||||
|
||||
# options are evaluated in succeedHandshake, called below
|
||||
self.proto.autoPingInterval = 5
|
||||
self.proto.autoPingTimeout = 2
|
||||
|
||||
with replace_loop(Clock()) as reactor:
|
||||
# get to STATE_OPEN
|
||||
self.proto.data = mock_handshake_client
|
||||
self.proto.processHandshake()
|
||||
self.assertTrue(self.proto.state == WebSocketServerProtocol.STATE_OPEN)
|
||||
|
||||
# we should have scheduled an autoPing
|
||||
self.assertEqual(1, len(reactor.calls))
|
||||
self.assertEqual(self.proto._sendAutoPing, reactor.calls[0].func)
|
||||
# ^^ un-unit-testy to assert on internal method?
|
||||
|
||||
# advance past first auto-ping timeout
|
||||
reactor.advance(5)
|
||||
|
||||
# should have an auto-ping timeout scheduled, and we
|
||||
# save it for later (to check it got cancelled)
|
||||
self.assertEqual(1, len(reactor.calls))
|
||||
self.assertEqual(self.proto.onAutoPingTimeout, reactor.calls[0].func)
|
||||
timeout_call = reactor.calls[0]
|
||||
|
||||
# elsewhere we check that we actually send an opcode-9
|
||||
# message; now we just blindly inject our own reply
|
||||
# with a PONG frame
|
||||
|
||||
frame = create_client_frame(opcode=10, payload=self.proto.autoPingPending)
|
||||
self.proto.data = frame
|
||||
# really needed twice; does header first, then rest
|
||||
self.proto.processData()
|
||||
self.proto.processData()
|
||||
|
||||
# which should have cancelled the call
|
||||
self.assertTrue(timeout_call.cancelled)
|
|
@ -12,10 +12,12 @@ all:
|
|||
@echo ""
|
||||
|
||||
build:
|
||||
scons
|
||||
#scons
|
||||
sphinx-build -A cstatic="//tavendo-common-static.s3-eu-west-1.amazonaws.com" -b html . _build
|
||||
|
||||
build_no_network:
|
||||
scons --no_network
|
||||
#scons --no_network
|
||||
sphinx-build -A no_network=1 -D no_network=1 -A cstatic="http://127.0.0.1:8888" -b html . _build
|
||||
|
||||
test: build
|
||||
python serve.py --root ./_build --silence
|
||||
|
|
|
@ -10,6 +10,8 @@ You will need to have Python and [SCons](http://www.scons.org/) installed. To in
|
|||
make install_deps
|
||||
```
|
||||
|
||||
**Note:** If you want to use this in a virtualenv, you'll have to install the SCons package for your distribution and use ``virtualenv --system-site-packages`` to build the venv. Then, activate it and install dependencies as above. To run SCons you'll have to do ``python `which scons` `` so that it uses the interpreter from your virtualenv.
|
||||
|
||||
Then, to get help on available build targets, just type
|
||||
|
||||
```sh
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
#
|
||||
# Copyright (c) Tavendo GmbH
|
||||
#
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 130 KiB |
|
@ -11,30 +11,30 @@ The asynchronous programming approach
|
|||
|
||||
|Ab| is written according to a programming paradigm called *asynchronous programming* (or *event driven programming*) and implemented using *non-blocking* execution - and both go hand in hand.
|
||||
|
||||
A very good technical introduction to these concepts can be found in `this chapter <http://krondo.com/?p=1209>`__ of an "Introduction to Asynchronous Programming and Twisted".
|
||||
A very good technical introduction to these concepts can be found in `this chapter <http://krondo.com/?p=1209>`_ of an "Introduction to Asynchronous Programming and Twisted".
|
||||
|
||||
Here are two more presentations that introduce event-driven programming in Python
|
||||
|
||||
* `Alex Martelli - Don't call us, we'll call you: callback patterns and idioms <https://www.youtube.com/watch?v=LCZRJStwkKM>`__
|
||||
* `Glyph Lefkowitz - So Easy You Can Even Do It in JavaScript: Event-Driven Architecture for Regular Programmers <http://www.pyvideo.org/video/1681/so-easy-you-can-even-do-it-in-javascript-event-d>`__
|
||||
* `Alex Martelli - Don't call us, we'll call you: callback patterns and idioms <https://www.youtube.com/watch?v=LCZRJStwkKM>`_
|
||||
* `Glyph Lefkowitz - So Easy You Can Even Do It in JavaScript: Event-Driven Architecture for Regular Programmers <http://www.pyvideo.org/video/1681/so-easy-you-can-even-do-it-in-javascript-event-d>`_
|
||||
|
||||
Another highly recommended reading is `The Reactive Manifesto <http://www.reactivemanifesto.org>`__ which describes guiding principles, motivations and connects the dots
|
||||
Another highly recommended reading is `The Reactive Manifesto <http://www.reactivemanifesto.org>`_ which describes guiding principles, motivations and connects the dots
|
||||
|
||||
.. epigraph::
|
||||
|
||||
Non-blocking means the ability to make continuous progress in order to for the application to be responsive at all times, even under failure and burst scenarios. For this all resources needed for a response—for example CPU, memory and network—must not be monopolized. As such it can enable both lower latency, higher throughput and better scalability.
|
||||
|
||||
-- `The Reactive Manifesto <http://www.reactivemanifesto.org>`__
|
||||
-- `The Reactive Manifesto <http://www.reactivemanifesto.org>`_
|
||||
|
||||
The fact that |Ab| is implemented using asynchronous programming and non-blocking execution shouldn't come as a surprise, since both `Twisted <https://twistedmatrix.com/trac/>`__ and `asyncio <https://docs.python.org/3/library/asyncio.html>`__ - the foundations upon which |ab| runs - are *asynchronous network programming frameworks*.
|
||||
|
||||
On the other hand, the principles of asynchronous programming are independent of Twisted and asyncio. For example, other frameworks that fall into the same category are:
|
||||
|
||||
* `NodeJS <http://nodejs.org/>`__
|
||||
* `Boost/ASIO <http://think-async.com/>`__
|
||||
* `Netty <http://netty.io/>`__
|
||||
* `Tornado <http://www.tornadoweb.org/>`__
|
||||
* `React <http://reactphp.org/>`__
|
||||
* `NodeJS <http://nodejs.org/>`_
|
||||
* `Boost/ASIO <http://think-async.com/>`_
|
||||
* `Netty <http://netty.io/>`_
|
||||
* `Tornado <http://www.tornadoweb.org/>`_
|
||||
* `React <http://reactphp.org/>`_
|
||||
|
||||
.. tip::
|
||||
While getting accustomed to the asynchronous way of thinking takes some time and effort, the knowledge and experience acquired can be translated more or less directly to other frameworks in the asynchronous category.
|
||||
|
@ -45,27 +45,27 @@ Other forms of Concurrency
|
|||
|
||||
Asynchronous programming is not the only approach to concurrency. Other styles of concurrency include
|
||||
|
||||
1. `OS Threads <http://en.wikipedia.org/wiki/Thread_%28computing%29>`__
|
||||
2. `Green Threads <http://en.wikipedia.org/wiki/Green_threads>`__
|
||||
3. `Actors <http://en.wikipedia.org/wiki/Actor_model>`__
|
||||
4. `Software Transactional Memory (STM) <http://en.wikipedia.org/wiki/Software_transactional_memory>`__
|
||||
1. `OS Threads <http://en.wikipedia.org/wiki/Thread_%28computing%29>`_
|
||||
2. `Green Threads <http://en.wikipedia.org/wiki/Green_threads>`_
|
||||
3. `Actors <http://en.wikipedia.org/wiki/Actor_model>`_
|
||||
4. `Software Transactional Memory (STM) <http://en.wikipedia.org/wiki/Software_transactional_memory>`_
|
||||
|
||||
Obviously, we cannot go into much detail with all of above. But here are some pointers for further reading if you want to compare and contrast asynchronous programming with other approaches.
|
||||
|
||||
With the **Actor model** a system is composed of a set of *actors* which are independently running, executing sequentially and communicate strictly by message passing. There is no shared state at all. This approach is used in systems like
|
||||
|
||||
* `Erlang <http://www.erlang.org/>`__
|
||||
* `Akka <http://akka.io/>`__
|
||||
* `Rust <http://www.rust-lang.org/>`__
|
||||
* `C++ Actor Framework <http://actor-framework.org/>`__
|
||||
* `Erlang <http://www.erlang.org/>`_
|
||||
* `Akka <http://akka.io/>`_
|
||||
* `Rust <http://www.rust-lang.org/>`_
|
||||
* `C++ Actor Framework <http://actor-framework.org/>`_
|
||||
|
||||
**Software Transactional Memory (STM)** applies the concept of `Optimistic Concurrency Control <http://en.wikipedia.org/wiki/Optimistic_concurrency_control>`__ from the persistent database world to (transient) program memory. Instead of lettings programs directly modify memory, all operations are first logged (inside a transaction), and then applied atomically - but only if no conflicting transaction has committed in the meantime. Hence, it's "optimistic" in that it assumes to be able to commit "normally", but needs to handle the failing at commit time.
|
||||
**Software Transactional Memory (STM)** applies the concept of `Optimistic Concurrency Control <http://en.wikipedia.org/wiki/Optimistic_concurrency_control>`_ from the persistent database world to (transient) program memory. Instead of lettings programs directly modify memory, all operations are first logged (inside a transaction), and then applied atomically - but only if no conflicting transaction has committed in the meantime. Hence, it's "optimistic" in that it assumes to be able to commit "normally", but needs to handle the failing at commit time.
|
||||
|
||||
**Green Threads** is using light-weight, run-time level threads and thread scheduling instead of OS threads. Other than that, systems are implemented similar: green threads still block, and still do share state. Python has multiple efforts in this category:
|
||||
|
||||
* `Eventlet <http://eventlet.net/>`__
|
||||
* `Gevent <http://gevent.org/>`__
|
||||
* `Stackless <http://www.stackless.com/>`__
|
||||
* `Eventlet <http://eventlet.net/>`_
|
||||
* `Gevent <http://gevent.org/>`_
|
||||
* `Stackless <http://www.stackless.com/>`_
|
||||
|
||||
|
||||
Twisted or asyncio?
|
||||
|
@ -89,9 +89,9 @@ Even more so, as the core of Twisted and asyncio is very similar and relies on t
|
|||
| Protocol Factory | Protocol Factory | responsible for creating protocol instances |
|
||||
+------------------+------------------+-------------------------------------------------------------+
|
||||
|
||||
In fact, I'd say the biggest difference between Twisted and asyncio is Deferred vs Future. Those are similar on surface, but their semantics is different.
|
||||
In fact, I'd say the biggest difference between Twisted and asyncio is ``Deferred`` vs ``Future``. Although similar on surface, their semantics are different. ``Deferred`` supports the concept of chainable callbacks (which can mutate the return values), and separate error-backs (which can cancel errors). ``Future`` has just a callback, that always gets a single argument: the Future.
|
||||
|
||||
Also, asyncio is opinionated towards co-routines. Means, idiomatic user code for asyncio is expected to use co-routines, and not plain Futures (which are considered too low-level for application code).
|
||||
Also, asyncio is opinionated towards co-routines. This means idiomatic user code for asyncio is expected to use co-routines, and not plain Futures (which are considered too low-level for application code).
|
||||
|
||||
But anyway, with asyncio being part of the language standard library (since Python 3.4), wouldn't you just *always* use asyncio? At least if you don't have a need to support already existing Twisted based code.
|
||||
|
||||
|
@ -117,14 +117,14 @@ Twisted Resources
|
|||
|
||||
We cannot give an introduction to asynchronous programming with Twisted here. And there is no need to, since there is lots of great stuff on the Web. In particular we'd like to recommend the following resources.
|
||||
|
||||
If you have limited time and nevertheless want to have an in-depth view of Twisted, Jessica McKellar has a great presentation recording with `Architecting an event-driven networking engine: Twisted Python <https://www.youtube.com/watch?v=3R4gP6Egh5M>`__. That's 45 minutes. Highly recommended.
|
||||
If you have limited time and nevertheless want to have an in-depth view of Twisted, Jessica McKellar has a great presentation recording with `Architecting an event-driven networking engine: Twisted Python <https://www.youtube.com/watch?v=3R4gP6Egh5M>`_. That's 45 minutes. Highly recommended.
|
||||
|
||||
If you really want to get it, Dave Peticolas has written an awesome `Introduction to Asynchronous Programming and Twisted <http://krondo.com/?page_id=1327>`__. This is a detailed, hands-on tutorial with lots of code examples that will take some time to work through - but you actually *learn* how to program with Twisted.
|
||||
If you really want to get it, Dave Peticolas has written an awesome `Introduction to Asynchronous Programming and Twisted <http://krondo.com/?page_id=1327>`_. This is a detailed, hands-on tutorial with lots of code examples that will take some time to work through - but you actually *learn* how to program with Twisted.
|
||||
|
||||
Then of course there is
|
||||
|
||||
* `The Twisted Documentation <https://twisted.readthedocs.org/>`__
|
||||
* `The Twisted API Reference <https://twistedmatrix.com/documents/current/api/>`__
|
||||
* `The Twisted Documentation <https://twisted.readthedocs.org/>`_
|
||||
* `The Twisted API Reference <https://twistedmatrix.com/documents/current/api/>`_
|
||||
|
||||
and lots and lots of awesome `Twisted talks <http://www.pyvideo.org/search?models=videos.video&q=twisted>`__ on PyVideo.
|
||||
|
||||
|
@ -134,10 +134,10 @@ Asyncio Resources
|
|||
|
||||
asyncio is very new (August 2014). So the amount of material on the Web is still limited. Here are some resources you may find useful:
|
||||
|
||||
* `Guido van Rossum's Keynote at PyCon US 2013 <http://pyvideo.org/video/1667/keynote-1>`__
|
||||
* `Tulip: Async I/O for Python 3 <http://www.youtube.com/watch?v=1coLC-MUCJc>`__
|
||||
* `Python 3.4 docs - asyncio <http://docs.python.org/3.4/library/asyncio.html>`__
|
||||
* `PEP-3156 - Asynchronous IO Support Rebooted <http://www.python.org/dev/peps/pep-3156/>`__
|
||||
* `Guido van Rossum's Keynote at PyCon US 2013 <http://pyvideo.org/video/1667/keynote-1>`_
|
||||
* `Tulip: Async I/O for Python 3 <http://www.youtube.com/watch?v=1coLC-MUCJc>`_
|
||||
* `Python 3.4 docs - asyncio <http://docs.python.org/3.4/library/asyncio.html>`_
|
||||
* `PEP-3156 - Asynchronous IO Support Rebooted <http://www.python.org/dev/peps/pep-3156/>`_
|
||||
|
||||
However, we quickly introduce core asynchronous programming primitives provided by `Twisted <https://twistedmatrix.com/>`__ and `asyncio <https://docs.python.org/3.4/library/asyncio.html>`__:
|
||||
|
||||
|
@ -293,11 +293,11 @@ Asyncio Futures and Coroutines
|
|||
..............................
|
||||
|
||||
|
||||
`Asyncio Futures <http://docs.python.org/3.4/library/asyncio-task.html#future>`_ like Twisted Deferreds encapsulate the result of a future computation. At the time of creation, the result is (usually) not yet available, and will only be available eventually.
|
||||
`Asyncio Futures <http://docs.python.org/3.4/library/asyncio-task.html#future>`__ like Twisted Deferreds encapsulate the result of a future computation. At the time of creation, the result is (usually) not yet available, and will only be available eventually.
|
||||
|
||||
On the other hand, asyncio futures are quite different from Twisted Deferreds. One difference is that they have no built-in machinery for chaining.
|
||||
|
||||
`Asyncio Coroutines <http://docs.python.org/3.4/library/asyncio-task.html#coroutines>`_ are (on a certain level) quite similar to Twisted inline callbacks. Here is the code corresponding to our example above:
|
||||
`Asyncio Coroutines <http://docs.python.org/3.4/library/asyncio-task.html#coroutines>`__ are (on a certain level) quite similar to Twisted inline callbacks. Here is the code corresponding to our example above:
|
||||
|
||||
|
||||
-------
|
||||
|
|
|
@ -5,6 +5,29 @@
|
|||
Changelog
|
||||
=========
|
||||
|
||||
0.10.5
|
||||
------
|
||||
|
||||
`Published 2015-08-06 <https://pypi.python.org/pypi/autobahn/0.10.5>`__
|
||||
|
||||
* maintenance release with lots of smaller bug fixes
|
||||
|
||||
0.10.4
|
||||
------
|
||||
|
||||
`Published 2015-05-08 <https://pypi.python.org/pypi/autobahn/0.10.4>`__
|
||||
|
||||
* maintenance release with some smaller bug fixes
|
||||
|
||||
0.10.3
|
||||
------
|
||||
|
||||
`Published 2015-04-14 <https://pypi.python.org/pypi/autobahn/0.10.3>`__
|
||||
|
||||
* new: using txaio package
|
||||
* new: revised WAMP-over-RawSocket specification implemented
|
||||
* fix: ignore unknown attributes in WAMP Options/Details
|
||||
|
||||
0.10.2
|
||||
------
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ master_doc = 'index'
|
|||
|
||||
# General information about the project.
|
||||
project = u'AutobahnPython'
|
||||
copyright = None
|
||||
copyright = u'Tavendo GmbH'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
|
|
|
@ -17,3 +17,13 @@ This means that you fork the repo, make changes to your fork, and then make a pu
|
|||
This `article on GitHub <https://help.github.com/articles/using-pull-requests>`_ gives more detailed information on how the process works.
|
||||
|
||||
|
||||
Running the Tests
|
||||
-----------------
|
||||
|
||||
In order to run the unit-tests, we use `Tox <http://tox.readthedocs.org/en/latest/>`_ to build the various test-environments. To run them all, simply run ``tox`` from the top-level directory of the clone.
|
||||
|
||||
For test-coverage, see the Makefile target ``test_coverage``, which deletes the coverage data and then runs the test suite with various tox test-environments before outputting HTML annotated coverage to ``./htmlcov/index.html`` and a coverage report to the terminal.
|
||||
|
||||
There are two environment variables the tests use: ``USE_TWISTED=1`` or ``USE_ASYNCIO=1`` control whether to run unit-tests that are specific to one framework or the other.
|
||||
|
||||
See ``tox.ini`` for details on how to run in the different environments
|
||||
|
|
123
doc/index.rst
123
doc/index.rst
|
@ -1,7 +1,7 @@
|
|||
|AbL|
|
||||
=====
|
||||
|
||||
*Open-source real-time framework for Web, Mobile & Internet of Things.*
|
||||
*Open-source (MIT) real-time framework for Web, Mobile & Internet of Things.*
|
||||
|
||||
Latest release: v\ |version| (:ref:`Changelog`)
|
||||
|
||||
|
@ -25,13 +25,36 @@ Latest release: v\ |version| (:ref:`Changelog`)
|
|||
* `The WebSocket Protocol <http://tools.ietf.org/html/rfc6455>`_
|
||||
* `The Web Application Messaging Protocol (WAMP) <http://wamp.ws/>`_
|
||||
|
||||
in Python 2 and 3, running on `Twisted`_ and `asyncio`_.
|
||||
in Python 2 and 3, running on `Twisted`_ **or** `asyncio`_.
|
||||
|
||||
WebSocket allows `bidirectional real-time messaging <http://tavendo.com/blog/post/websocket-why-what-can-i-use-it/>`_ on the Web while `WAMP <http://wamp.ws/>`_ provides applications with `high-level communication abstractions <http://wamp.ws/why/>`_ in an open standard WebSocket based protocol.
|
||||
Documentation Overview
|
||||
----------------------
|
||||
|
||||
|AbL| features
|
||||
See :ref:`site_contents` for a full site-map. Top-level pages available:
|
||||
|
||||
* framework for `WebSocket`_ / `WAMP`_ clients
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
installation
|
||||
asynchronous-programming
|
||||
wamp/programming
|
||||
wamp/examples
|
||||
websocket/programming
|
||||
websocket/examples
|
||||
reference/autobahn
|
||||
contribute
|
||||
changelog
|
||||
|
||||
-----
|
||||
|
||||
Autobahn Features
|
||||
-----------------
|
||||
|
||||
WebSocket allows `bidirectional real-time messaging <http://tavendo.com/blog/post/websocket-why-what-can-i-use-it/>`_ on the Web while `WAMP <http://wamp.ws/>`_ provides applications with `high-level communication abstractions <http://wamp.ws/why/>`_ (remote procedure calling and publish/subscribe) in an open standard WebSocket-based protocol.
|
||||
|
||||
|AbL| features:
|
||||
|
||||
* framework for `WebSocket`_ and `WAMP`_ clients
|
||||
* compatible with Python 2.6, 2.7, 3.3 and 3.4
|
||||
* runs on `CPython`_, `PyPy`_ and `Jython`_
|
||||
* runs under `Twisted`_ and `asyncio`_
|
||||
|
@ -41,14 +64,14 @@ WebSocket allows `bidirectional real-time messaging <http://tavendo.com/blog/pos
|
|||
* supports TLS (secure WebSocket) and proxies
|
||||
* Open-source (`MIT license <https://github.com/tavendo/AutobahnPython/blob/master/LICENSE>`_)
|
||||
|
||||
and much more.
|
||||
...and much more.
|
||||
|
||||
Further, |AbL| is written with these goals
|
||||
Further, |AbL| is written with these goals:
|
||||
|
||||
1. high-performance, fully asynchronous and scalable code
|
||||
2. best-in-class standards conformance and security
|
||||
|
||||
We do take those design and implementation goals quite serious. For example, |AbL| has 100% strict passes with `AutobahnTestsuite`_, the quasi industry standard of WebSocket protocol test suites we originally created only to test |AbL|;)
|
||||
We do take those design and implementation goals quite serious. For example, |AbL| has 100% strict passes with `AutobahnTestsuite`_, the quasi industry standard of WebSocket protocol test suites we originally created only to test |AbL| ;)
|
||||
|
||||
.. note::
|
||||
In the following, we will just refer to |Ab| instead of the
|
||||
|
@ -56,10 +79,18 @@ We do take those design and implementation goals quite serious. For example, |Ab
|
|||
ambiguity.
|
||||
|
||||
|
||||
What can I do with this stuff?
|
||||
------------------------------
|
||||
What can I do with Autobahn?
|
||||
----------------------------
|
||||
|
||||
WebSocket is great for apps like **chat**, **trading**, **multi-player games** or **real-time charts**. It allows you to **actively push information** to clients as it happens.
|
||||
WebSocket is great for apps like **chat**, **trading**, **multi-player games** or **real-time charts**. It allows you to **actively push information** to clients as it happens. (See also :ref:`run_all_examples`)
|
||||
|
||||
.. image:: _static/wamp-demos.png
|
||||
:alt: ascii-cast of all WAMP demos running
|
||||
:height: 443
|
||||
:width: 768
|
||||
:target: wamp/examples.html#run-all-examples
|
||||
:scale: 40%
|
||||
:align: right
|
||||
|
||||
Further, WebSocket allows you to real-time enable your Web user interfaces: **always current information** without reloads or polling. UIs no longer need to be a boring, static thing. Looking for the right communication technology for your next-generation Web apps? Enter WebSocket.
|
||||
|
||||
|
@ -84,25 +115,28 @@ A sample **WebSocket server**:
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
class MyServerProtocol(WebSocketServerProtocol):
|
||||
from autobahn.twisted.websocket import WebSocketServerProtocol
|
||||
# or: from autobahn.asyncio.websocket import WebSocketServerProtocol
|
||||
|
||||
def onConnect(self, request):
|
||||
print("Client connecting: {}".format(request.peer))
|
||||
class MyServerProtocol(WebSocketServerProtocol):
|
||||
|
||||
def onOpen(self):
|
||||
print("WebSocket connection open.")
|
||||
def onConnect(self, request):
|
||||
print("Client connecting: {}".format(request.peer))
|
||||
|
||||
def onMessage(self, payload, isBinary):
|
||||
if isBinary:
|
||||
print("Binary message received: {} bytes".format(len(payload)))
|
||||
else:
|
||||
print("Text message received: {}".format(payload.decode('utf8')))
|
||||
def onOpen(self):
|
||||
print("WebSocket connection open.")
|
||||
|
||||
## echo back message verbatim
|
||||
self.sendMessage(payload, isBinary)
|
||||
def onMessage(self, payload, isBinary):
|
||||
if isBinary:
|
||||
print("Binary message received: {} bytes".format(len(payload)))
|
||||
else:
|
||||
print("Text message received: {}".format(payload.decode('utf8')))
|
||||
|
||||
def onClose(self, wasClean, code, reason):
|
||||
print("WebSocket connection closed: {}".format(reason))
|
||||
## echo back message verbatim
|
||||
self.sendMessage(payload, isBinary)
|
||||
|
||||
def onClose(self, wasClean, code, reason):
|
||||
print("WebSocket connection closed: {}".format(reason))
|
||||
|
||||
Complete example code:
|
||||
|
||||
|
@ -119,34 +153,35 @@ A sample **WAMP application component** implementing all client roles:
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
class MyComponent(ApplicationSession):
|
||||
from autobahn.twisted.wamp import ApplicationSession
|
||||
# or: from autobahn.asyncio.wamp import ApplicationSession
|
||||
class MyComponent(ApplicationSession):
|
||||
|
||||
@inlineCallbacks
|
||||
def onJoin(self, details):
|
||||
@inlineCallbacks
|
||||
def onJoin(self, details):
|
||||
|
||||
# 1) subscribe to a topic
|
||||
def onevent(msg):
|
||||
print("Got event: {}".format(msg))
|
||||
# 1) subscribe to a topic
|
||||
def onevent(msg):
|
||||
print("Got event: {}".format(msg))
|
||||
yield self.subscribe(onevent, 'com.myapp.hello')
|
||||
|
||||
yield self.subscribe(onevent, 'com.myapp.hello')
|
||||
# 2) publish an event
|
||||
self.publish('com.myapp.hello', 'Hello, world!')
|
||||
|
||||
# 2) publish an event
|
||||
self.publish('com.myapp.hello', 'Hello, world!')
|
||||
# 3) register a procedure for remoting
|
||||
def add2(x, y):
|
||||
return x + y
|
||||
self.register(add2, 'com.myapp.add2');
|
||||
|
||||
# 3) register a procedure for remoting
|
||||
def add2(x, y):
|
||||
return x + y
|
||||
|
||||
self.register(add2, 'com.myapp.add2');
|
||||
|
||||
# 4) call a remote procedure
|
||||
res = yield self.call('com.myapp.add2', 2, 3)
|
||||
print("Got result: {}".format(res))
|
||||
# 4) call a remote procedure
|
||||
res = yield self.call('com.myapp.add2', 2, 3)
|
||||
print("Got result: {}".format(res))
|
||||
|
||||
|
||||
Complete example code:
|
||||
|
||||
* `Twisted <https://github.com/tavendo/AutobahnPython/blob/master/examples/twisted/wamp/beginner/client.py>`__ - * `asyncio <https://github.com/tavendo/AutobahnPython/blob/master/examples/asyncio/wamp/beginner/client.py>`__
|
||||
* `Twisted Example <https://github.com/tavendo/AutobahnPython/blob/master/examples/twisted/wamp/overview/>`__
|
||||
* `asyncio Example <https://github.com/tavendo/AutobahnPython/blob/master/examples/asyncio/wamp/overview/>`__
|
||||
|
||||
Introduction to WAMP Programming with |ab|:
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ You will need at least one of those.
|
|||
|
||||
For Twisted installation, please see `here <http://twistedmatrix.com/>`__. Asyncio comes bundled with Python 3.4+. For Python 3.3, install it from `here <https://pypi.python.org/pypi/asyncio>`__. For Python 2, `trollius`_ will work.
|
||||
|
||||
|
||||
Supported Configurations
|
||||
........................
|
||||
|
||||
|
@ -42,6 +43,7 @@ Here are the configurations supported by |ab|:
|
|||
.. _1: http://twistedmatrix.com/trac/ticket/3413
|
||||
.. _2: http://twistedmatrix.com/trac/ticket/6746
|
||||
|
||||
|
||||
Performance Note
|
||||
................
|
||||
|
||||
|
@ -53,6 +55,7 @@ Performance Note
|
|||
To give you an idea of the performance you can expect, here is a `blog post <http://tavendo.com/blog/post/autobahn-pi-benchmark/>`_ benchmarking |ab| running on the `RaspberryPi <http://www.raspberrypi.org/>`_ (a tiny embedded computer) under `PyPy <http://pypy.org/>`_.
|
||||
|
||||
|
||||
|
||||
Installing Autobahn
|
||||
-------------------
|
||||
|
||||
|
@ -81,13 +84,13 @@ And to install asyncio backports automatically when required
|
|||
Install from Sources
|
||||
....................
|
||||
|
||||
To install from sources, clone the repository
|
||||
To install from sources, clone the repository:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
git clone git@github.com:tavendo/AutobahnPython.git
|
||||
|
||||
checkout a tagged release
|
||||
checkout a tagged release:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
|
@ -95,16 +98,16 @@ checkout a tagged release
|
|||
git checkout v0.9.1
|
||||
|
||||
.. warning::
|
||||
You should only use *tagged* releases, not *trunk*. The latest code from *trunk* might be broken, unfinished and untested. So you have been warned;)
|
||||
You should only use *tagged* releases, not *master*. The latest code from *master* might be broken, unfinished and untested. So you have been warned ;)
|
||||
|
||||
Then do
|
||||
Then do:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
cd autobahn
|
||||
python setup.py install
|
||||
|
||||
You can also use Pip for the last step, which allows to specify install variants (see below)
|
||||
You can also use ``pip`` for the last step, which allows to specify install variants (see below)
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
|
|
|
@ -3,31 +3,84 @@
|
|||
WAMP Examples
|
||||
=============
|
||||
|
||||
**NOTE** that for all examples you will **need to run a router**. We develop `Crossbar.io <http://crossbar.io/docs>`_ and there are `other routers <http://wamp.ws/implementations/#routers>`_ available as well. We include a working `Crossbar.io <http://crossbar.io/docs>`_ configuration in the `examples/router/ subdirectory <https://github.com/tavendo/AutobahnPython/tree/master/examples/router>`_ as well as `instructions on how to run it <https://github.com/tavendo/AutobahnPython/blob/master/examples/running-the-examples.md>`_.
|
||||
|
||||
Overview of Examples
|
||||
++++++++++++++++++++
|
||||
|
||||
The examples are organized between `asycio <https://docs.python.org/3.4/library/asyncio.html>`__ and `Twisted <https://www.twistedmatrix.com>`__ at the top-level, with similarly-named examples demonstrating the same functionality with the respective framework.
|
||||
|
||||
Each example typically includes four things:
|
||||
|
||||
- ``frontend.py``: the Caller or Subscriber, in Python
|
||||
- ``backend.py``: the Callee or Publisher, in Python
|
||||
- ``frontend.js``: JavaScript version of the frontend
|
||||
- ``backend.js``: JavaScript version of the backend
|
||||
- ``*.html``: boilerplate so a browser can run the JavaScript
|
||||
|
||||
So for each example, you start *one* backend and *one* frontend component (your choice). You can usually start multiple frontend components with no problem, but will get errors if you start two backends trying to register at the same procedure URI (for example).
|
||||
|
||||
Still, you are encouraged to try playing with mixing and matching the frontend and backend components, starting multiple front-ends, etc. to explore Crossbar and Autobahn's behavior. Often the different examples use similar URIs for procedures and published events, so you can even try mixing between the examples.
|
||||
|
||||
The provided `Crossbar.io <http://crossbar.io/docs>`__ configuration will run a Web server that you can visit at `http://localhost:8080` and includes links to the frontend/backend HTML for the javascript versions. Usually these just use ``console.log()`` so you'll have to open up the JavaScript console in your browser to see it working.
|
||||
|
||||
.. _run_all_examples:
|
||||
|
||||
Automatically Run All Examples
|
||||
++++++++++++++++++++++++++++++
|
||||
|
||||
There is a script (``./examples/run-all-examples.py``) which runs all the WAMP examples for 5 seconds each, `this asciicast
|
||||
<https://asciinema.org/a/9cnar155zalie80c9725nvyk7>`__ shows you how (see comments for how to run it yourself):
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<script type="text/javascript" src="https://asciinema.org/a/9cnar155zalie80c9725nvyk7.js" id="asciicast-21588" async></script>
|
||||
|
||||
|
||||
|
||||
Publish & Subscribe (PubSub)
|
||||
++++++++++++++++++++++++++++
|
||||
|
||||
* Basic `Twisted <https://github.com/tavendo/AutobahnPython/tree/master/examples/twisted/wamp/basic/pubsub/basic>`__ - `asyncio <https://github.com/tavendo/AutobahnPython/tree/master/examples/asyncio/wamp/basic/pubsub/basic>`__ - Demonstrates basic publish and subscribe.
|
||||
* Basic `Twisted <https://github.com/tavendo/AutobahnPython/tree/master/examples/twisted/wamp/pubsub/basic>`__ - `asyncio <https://github.com/tavendo/AutobahnPython/tree/master/examples/asyncio/wamp/pubsub/basic>`__ - Demonstrates basic publish and subscribe.
|
||||
|
||||
* Complex `Twisted <https://github.com/tavendo/AutobahnPython/tree/master/examples/twisted/wamp/basic/pubsub/complex>`__ - `asyncio <https://github.com/tavendo/AutobahnPython/tree/master/examples/asyncio/wamp/basic/pubsub/complex>`__ - Demonstrates publish and subscribe with complex events.
|
||||
* Complex `Twisted <https://github.com/tavendo/AutobahnPython/tree/master/examples/twisted/wamp/pubsub/complex>`__ - `asyncio <https://github.com/tavendo/AutobahnPython/tree/master/examples/asyncio/wamp/pubsub/complex>`__ - Demonstrates publish and subscribe with complex events.
|
||||
|
||||
* Options `Twisted <https://github.com/tavendo/AutobahnPython/tree/master/examples/twisted/wamp/basic/pubsub/options>`__ - `asyncio <https://github.com/tavendo/AutobahnPython/tree/master/examples/asyncio/wamp/basic/pubsub/options>`__ - Using options with PubSub.
|
||||
* Options `Twisted <https://github.com/tavendo/AutobahnPython/tree/master/examples/twisted/wamp/pubsub/options>`__ - `asyncio <https://github.com/tavendo/AutobahnPython/tree/master/examples/asyncio/wamp/pubsub/options>`__ - Using options with PubSub.
|
||||
|
||||
* Unsubscribe `Twisted <https://github.com/tavendo/AutobahnPython/tree/master/examples/twisted/wamp/basic/pubsub/unsubscribe>`__ - `asyncio <https://github.com/tavendo/AutobahnPython/tree/master/examples/asyncio/wamp/basic/pubsub/unsubscribe>`__ - Cancel a subscription to a topic.
|
||||
* Unsubscribe `Twisted <https://github.com/tavendo/AutobahnPython/tree/master/examples/twisted/wamp/pubsub/unsubscribe>`__ - `asyncio <https://github.com/tavendo/AutobahnPython/tree/master/examples/asyncio/wamp/pubsub/unsubscribe>`__ - Cancel a subscription to a topic.
|
||||
|
||||
|
||||
Remote Procedure Calls (RPC)
|
||||
++++++++++++++++++++++++++++
|
||||
|
||||
* Time Service `Twisted <https://github.com/tavendo/AutobahnPython/tree/master/examples/twisted/wamp/basic/rpc/timeservice>`__ - `asyncio <https://github.com/tavendo/AutobahnPython/tree/master/examples/asyncio/wamp/basic/rpc/timeservice>`__ - A trivial time service - demonstrates basic remote procedure feature.
|
||||
* Time Service `Twisted <https://github.com/tavendo/AutobahnPython/tree/master/examples/twisted/wamp/rpc/timeservice>`__ - `asyncio <https://github.com/tavendo/AutobahnPython/tree/master/examples/asyncio/wamp/rpc/timeservice>`__ - A trivial time service - demonstrates basic remote procedure feature.
|
||||
|
||||
* Slow Square `Twisted <https://github.com/tavendo/AutobahnPython/tree/master/examples/twisted/wamp/basic/rpc/slowsquare>`__ - `asyncio <https://github.com/tavendo/AutobahnPython/tree/master/examples/asyncio/wamp/basic/rpc/slowsquare>`__ - Demonstrates procedures which return promises and return asynchronously.
|
||||
* Slow Square `Twisted <https://github.com/tavendo/AutobahnPython/tree/master/examples/twisted/wamp/rpc/slowsquare>`__ - `asyncio <https://github.com/tavendo/AutobahnPython/tree/master/examples/asyncio/wamp/rpc/slowsquare>`__ - Demonstrates procedures which return promises and return asynchronously.
|
||||
|
||||
* Arguments `Twisted <https://github.com/tavendo/AutobahnPython/tree/master/examples/twisted/wamp/basic/rpc/arguments>`__ - `asyncio <https://github.com/tavendo/AutobahnPython/tree/master/examples/asyncio/wamp/basic/rpc/arguments>`__ - Demonstrates all variants of call arguments.
|
||||
* Arguments `Twisted <https://github.com/tavendo/AutobahnPython/tree/master/examples/twisted/wamp/rpc/arguments>`__ - `asyncio <https://github.com/tavendo/AutobahnPython/tree/master/examples/asyncio/wamp/rpc/arguments>`__ - Demonstrates all variants of call arguments.
|
||||
|
||||
* Complex Result `Twisted <https://github.com/tavendo/AutobahnPython/tree/master/examples/twisted/wamp/basic/rpc/complex>`__ - `asyncio <https://github.com/tavendo/AutobahnPython/tree/master/examples/asyncio/wamp/basic/rpc/complex>`__ - Demonstrates complex call results (call results with more than one positional or keyword results).
|
||||
* Complex Result `Twisted <https://github.com/tavendo/AutobahnPython/tree/master/examples/twisted/wamp/rpc/complex>`__ - `asyncio <https://github.com/tavendo/AutobahnPython/tree/master/examples/asyncio/wamp/rpc/complex>`__ - Demonstrates complex call results (call results with more than one positional or keyword results).
|
||||
|
||||
* Errors `Twisted <https://github.com/tavendo/AutobahnPython/tree/master/examples/twisted/wamp/basic/rpc/errors>`__ - `asyncio <https://github.com/tavendo/AutobahnPython/tree/master/examples/asyncio/wamp/basic/rpc/errors>`__ - Demonstrates error raising and catching over remote procedures.
|
||||
* Errors `Twisted <https://github.com/tavendo/AutobahnPython/tree/master/examples/twisted/wamp/rpc/errors>`__ - `asyncio <https://github.com/tavendo/AutobahnPython/tree/master/examples/asyncio/wamp/rpc/errors>`__ - Demonstrates error raising and catching over remote procedures.
|
||||
|
||||
* Progressive Results `Twisted <https://github.com/tavendo/AutobahnPython/tree/master/examples/twisted/wamp/basic/rpc/progress>`__ - `asyncio <https://github.com/tavendo/AutobahnPython/tree/master/examples/asyncio/wamp/basic/rpc/progress>`__ - Demonstrates calling remote procedures that produce progressive results.
|
||||
* Progressive Results `Twisted <https://github.com/tavendo/AutobahnPython/tree/master/examples/twisted/wamp/rpc/progress>`__ - `asyncio <https://github.com/tavendo/AutobahnPython/tree/master/examples/asyncio/wamp/rpc/progress>`__ - Demonstrates calling remote procedures that produce progressive results.
|
||||
|
||||
* Options `Twisted <https://github.com/tavendo/AutobahnPython/tree/master/examples/twisted/wamp/basic/rpc/options>`__ - `asyncio <https://github.com/tavendo/AutobahnPython/tree/master/examples/asyncio/wamp/basic/rpc/options>`__ - Using options with RPC.
|
||||
* Options `Twisted <https://github.com/tavendo/AutobahnPython/tree/master/examples/twisted/wamp/rpc/options>`__ - `asyncio <https://github.com/tavendo/AutobahnPython/tree/master/examples/asyncio/wamp/rpc/options>`__ - Using options with RPC.
|
||||
|
||||
|
||||
I'm Confused, Just Tell Me What To Run
|
||||
++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
If all that is too many options to consider, you want to do this:
|
||||
|
||||
1. Open 3 terminals
|
||||
2. In terminal 1, `setup and run a local Crossbar <https://github.com/tavendo/AutobahnPython/blob/master/examples/running-the-examples.md>`_ in the root of your Autobahn checkout.
|
||||
3. In terminals 2 and 3, go to the root of your Autobahn checkout and activate the virtualenv from step 2 (``source venv-autobahn/bin/activate``)
|
||||
4. In terminal 2 run ``python ./examples/twisted/wamp/rpc/arguments/backend.py``
|
||||
5. In terminal 3 run ``python ./examples/twisted/wamp/rpc/arguments/frontend.py``
|
||||
|
||||
The above procedure is gone over in this `this asciicast <https://asciinema.org/a/2vl1eahfaxptoen9bnevd06lq.png)](https://asciinema.org/a/2vl1eahfaxptoen9bnevd06lq>`_:
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<script type="text/javascript" src="https://asciinema.org/a/2vl1eahfaxptoen9bnevd06lq.js" id="asciicast-21580" async></script>
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
WAMP Programming
|
||||
================
|
||||
==================
|
||||
WAMP Programming
|
||||
==================
|
||||
|
||||
This guide gives an introduction to programming with `WAMP <http://wamp.ws>`__ in Python using |Ab|.
|
||||
This guide gives an introduction to programming with `WAMP <http://wamp.ws>`__ in Python using |Ab|. (Go straight to :ref:`wamp_examples`)
|
||||
|
||||
WAMP provides two communication patterns for application components to talk to each other
|
||||
|
||||
|
@ -15,28 +16,38 @@ and we will cover all four interactions involved in above patterns
|
|||
3. :ref:`subscribing-to-topics` for receiving events
|
||||
4. :ref:`publishing-events` to topics
|
||||
|
||||
Note that WAMP is a "routed" protocol, and defines a Dealer and Broker role. Practically speaking, this means that any WAMP client needs a WAMP Router to talk to. We provide an open-source one called `Crossbar <http://crossbar.io>`_ (there are `other routers <http://wamp.ws/implementations/#routers>`_ available). See also `the WAMP specification <http://wamp.ws/spec/>`_ for more details
|
||||
|
||||
.. tip::
|
||||
If you are new to WAMP or want to learn more about the design principles behind WAMP, we have a longer text `here <http://wamp.ws/why/>`__.
|
||||
|
||||
------
|
||||
|
||||
Application Components
|
||||
----------------------
|
||||
======================
|
||||
|
||||
WAMP is all about creating systems from loosely coupled *application components*. It's application components where your application specific code runs.
|
||||
WAMP is all about creating systems from loosely coupled *application components*. These application components are where your application-specific code runs.
|
||||
|
||||
A WAMP based system consists of potentially many application components, which all connect to a WAMP router. The router is *generic*, which means, it does *not* run any application code, but only provides routing of events and calls.
|
||||
A WAMP-based system consists of potentially many application components, which all connect to a WAMP router. The router is *generic*, which means, it does *not* run any application code, but only provides routing of events and calls.
|
||||
|
||||
Hence, to create a WAMP application, you
|
||||
These components use either Remote Procedure Calls (RPC) or Publish/Subscribe (PubSub) to communicate. Each component can do any mix of: register, call, subscribe or publish.
|
||||
|
||||
For RPC, an application component registers a callable method at a URI ("endpoint"), and other components call it via that endpoint.
|
||||
|
||||
In the Publish/Subscribe model, interested components subscribe to an event URI and when a publish to that URI happens, the event payload is routerd to all subscribers:
|
||||
|
||||
Hence, to create a WAMP application, you:
|
||||
|
||||
1. write application components
|
||||
2. connect the components to a router
|
||||
|
||||
Note that each component can do any mix of registering, calling, subscribing and publishing -- it is entirely up to you to logically group functionality as suits your problem space.
|
||||
|
||||
|
||||
.. _creating-components:
|
||||
|
||||
Creating Components
|
||||
...................
|
||||
-------------------
|
||||
|
||||
You create an application component by deriving from a base class provided by |ab|.
|
||||
|
||||
|
@ -48,7 +59,6 @@ When using **Twisted**, you derive from :class:`autobahn.twisted.wamp.Applicatio
|
|||
from autobahn.twisted.wamp import ApplicationSession
|
||||
|
||||
class MyComponent(ApplicationSession):
|
||||
|
||||
def onJoin(self, details):
|
||||
print("session ready")
|
||||
|
||||
|
@ -60,19 +70,18 @@ whereas when you are using **asyncio**, you derive from :class:`autobahn.asyncio
|
|||
from autobahn.asyncio.wamp import ApplicationSession
|
||||
|
||||
class MyComponent(ApplicationSession):
|
||||
|
||||
def onJoin(self, details):
|
||||
print("session ready")
|
||||
|
||||
As can be seen, the only difference between Twisted and asyncio is the import (line 1). The rest of the code is identical.
|
||||
|
||||
Also, |ab| will invoke callbacks on your application component when certain events happen. For example, :func:`autobahn.wamp.interfaces.ISession.onJoin` is triggered when the WAMP session has connected to a router and joined a realm. We'll come back to this topic later.
|
||||
Also, |ab| will invoke callbacks on your application component when certain events happen. For example, :func:`ISession.onJoin <autobahn.wamp.interfaces.ISession.onJoin>` is triggered when the WAMP session has connected to a router and joined a realm. We'll come back to this topic later.
|
||||
|
||||
|
||||
.. _running-components:
|
||||
|
||||
Running Components
|
||||
..................
|
||||
------------------
|
||||
|
||||
To actually make use of an application components, the component needs to connect to a WAMP router.
|
||||
|Ab| includes a *runner* that does the heavy lifting for you.
|
||||
|
@ -84,7 +93,7 @@ Here is how you use :class:`autobahn.twisted.wamp.ApplicationRunner` with **Twis
|
|||
|
||||
from autobahn.twisted.wamp import ApplicationRunner
|
||||
|
||||
runner = ApplicationRunner(url = u"ws://localhost:8080/ws", realm = u"realm1")
|
||||
runner = ApplicationRunner(url=u"ws://localhost:8080/ws", realm=u"realm1")
|
||||
runner.run(MyComponent)
|
||||
|
||||
and here is how you use :class:`autobahn.asyncio.wamp.ApplicationRunner` with **asyncio**
|
||||
|
@ -94,7 +103,7 @@ and here is how you use :class:`autobahn.asyncio.wamp.ApplicationRunner` with **
|
|||
|
||||
from autobahn.asyncio.wamp import ApplicationRunner
|
||||
|
||||
runner = ApplicationRunner(url = u"ws://localhost:8080/ws", realm = u"realm1")
|
||||
runner = ApplicationRunner(url=u"ws://localhost:8080/ws", realm=u"realm1")
|
||||
runner.run(MyComponent)
|
||||
|
||||
As can be seen, the only difference between Twisted and asyncio is the import (line 1). The rest of the code is identical.
|
||||
|
@ -113,46 +122,52 @@ Here are quick templates for you to copy/paste for creating and running a WAMP c
|
|||
**Twisted**:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 1
|
||||
:emphasize-lines: 2
|
||||
|
||||
from autobahn.twisted.wamp import ApplicationSession, ApplicationRunner
|
||||
from twisted.internet.defer import inlineCallbacks
|
||||
from autobahn.twisted.wamp import ApplicationSession, ApplicationRunner
|
||||
|
||||
class MyComponent(ApplicationSession):
|
||||
class MyComponent(ApplicationSession):
|
||||
|
||||
def onJoin(self, details):
|
||||
print("session joined")
|
||||
@inlineCallbacks
|
||||
def onJoin(self, details):
|
||||
print("session joined")
|
||||
# can do subscribes, registers here e.g.:
|
||||
# yield self.subscribe(...)
|
||||
# yield self.register(...)
|
||||
|
||||
if __name__ == '__main__':
|
||||
runner = ApplicationRunner(url=u"ws://localhost:8080/ws", realm=u"realm1")
|
||||
runner.run(MyComponent)
|
||||
|
||||
if __name__ == '__main__':
|
||||
runner = ApplicationRunner(url = u"ws://localhost:8080/ws", realm = u"realm1")
|
||||
runner.run(MyComponent)
|
||||
|
||||
**asyncio**:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 1
|
||||
:emphasize-lines: 2
|
||||
|
||||
from autobahn.asyncio.wamp import ApplicationSession, ApplicationRunner
|
||||
from asyncio import coroutine
|
||||
from autobahn.asyncio.wamp import ApplicationSession, ApplicationRunner
|
||||
|
||||
class MyComponent(ApplicationSession):
|
||||
class MyComponent(ApplicationSession):
|
||||
@coroutine
|
||||
def onJoin(self, details):
|
||||
print("session joined")
|
||||
# can do subscribes, registers here e.g.:
|
||||
# yield from self.subscribe(...)
|
||||
# yield from self.register(...)
|
||||
|
||||
def onJoin(self, details):
|
||||
print("session joined")
|
||||
|
||||
if __name__ == '__main__':
|
||||
runner = ApplicationRunner(url = u"ws://localhost:8080/ws", realm = u"realm1")
|
||||
runner.run(MyComponent)
|
||||
if __name__ == '__main__':
|
||||
runner = ApplicationRunner(url=u"ws://localhost:8080/ws", realm=u"realm1")
|
||||
runner.run(MyComponent)
|
||||
|
||||
|
||||
Running a WAMP Router
|
||||
---------------------
|
||||
=====================
|
||||
|
||||
The component we've created attempts to connect to a **WAMP router** running locally which accepts connections on port ``8080``, and for a realm ``realm1``.
|
||||
|
||||
Our suggested way is to use `Crossbar.io <http://crossbar.io>`_ as your WAMP router.
|
||||
|
||||
.. tip::
|
||||
|
||||
There are other WAMP routers besides Crossbar.io as well. Please see this `list <http://wamp.ws/implementations#routers>`__.
|
||||
Our suggested way is to use `Crossbar.io <http://crossbar.io>`_ as your WAMP router. There are `other WAMP routers <http://wamp.ws/implementations#routers>`_ besides Crossbar.io as well.
|
||||
|
||||
Once you've `installed Crossbar.io <http://crossbar.io/docs/Quick-Start/>`_, initialize an instance of it with the default settings, which will accept WAMP (over WebSocket) connections on ``ws://<hostname>:8080/ws`` and has a ``realm1`` pre-configured.
|
||||
|
||||
|
@ -162,7 +177,7 @@ To do this, do
|
|||
|
||||
crossbar init
|
||||
|
||||
This will create the default Crossbar.io node configuration ``.crossbar/config.json``. You can then start Crossbar.io by doing
|
||||
This will create the default Crossbar.io node configuration ``./.crossbar/config.json``. You can then start Crossbar.io by doing:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
|
@ -172,7 +187,7 @@ This will create the default Crossbar.io node configuration ``.crossbar/config.j
|
|||
.. _remote-procedure-calls:
|
||||
|
||||
Remote Procedure Calls
|
||||
----------------------
|
||||
======================
|
||||
|
||||
**Remote Procedure Call (RPC)** is a messaging pattern involving peers of three roles:
|
||||
|
||||
|
@ -184,40 +199,40 @@ A *Caller* issues calls to remote procedures by providing the procedure URI and
|
|||
|
||||
*Callees* register procedures they provide with *Dealers*. *Callers* initiate procedure calls first to *Dealers*. *Dealers* route calls incoming from *Callers* to *Callees* implementing the procedure called, and route call results back from *Callees* to *Callers*.
|
||||
|
||||
The *Caller* and *Callee* will usually run application code, while the *Dealer* works as a generic router for remote procedure calls decoupling *Callers* and *Callees*.
|
||||
The *Caller* and *Callee* will usually run application code, while the *Dealer* works as a generic router for remote procedure calls decoupling *Callers* and *Callees*. Thus, the *Caller* can be in a separate process (even a separate implementation language) from the *Callee*.
|
||||
|
||||
|
||||
.. _registering-procedures:
|
||||
|
||||
Registering Procedures
|
||||
......................
|
||||
|
||||
To make a procedure available for remote calling, the procedure needs to be *registered*. Registering a procedure is done by calling :func:`autobahn.wamp.interfaces.ICallee.register` from a session.
|
||||
Registering Procedures
|
||||
----------------------
|
||||
|
||||
To make a procedure available for remote calling, the procedure needs to be *registered*. Registering a procedure is done by calling :meth:`ICallee.register <autobahn.wamp.interfaces.ICallee.register>` from a session.
|
||||
|
||||
Here is an example using **Twisted**
|
||||
|
||||
.. code-block:: python
|
||||
:linenos:
|
||||
:emphasize-lines: 15
|
||||
:linenos:
|
||||
:emphasize-lines: 14
|
||||
|
||||
from autobahn.twisted.wamp import ApplicationSession
|
||||
from twisted.internet.defer import inlineCallbacks
|
||||
from autobahn.twisted.wamp import ApplicationSession
|
||||
from twisted.internet.defer import inlineCallbacks
|
||||
|
||||
|
||||
class MyComponent(ApplicationSession):
|
||||
|
||||
@inlineCallbacks
|
||||
def onJoin(self, details):
|
||||
print("session ready")
|
||||
class MyComponent(ApplicationSession):
|
||||
@inlineCallbacks
|
||||
def onJoin(self, details):
|
||||
print("session ready")
|
||||
|
||||
def add2(x, y):
|
||||
return x + y
|
||||
def add2(x, y):
|
||||
return x + y
|
||||
|
||||
try:
|
||||
yield self.register(add2, u'com.myapp.add2')
|
||||
print("procedure registered")
|
||||
except Exception as e:
|
||||
print("could not register procedure: {0}".format(e))
|
||||
try:
|
||||
yield self.register(add2, u'com.myapp.add2')
|
||||
print("procedure registered")
|
||||
except Exception as e:
|
||||
print("could not register procedure: {0}".format(e))
|
||||
|
||||
The procedure ``add2`` is registered (line 14) under the URI ``u"com.myapp.add2"`` immediately in the ``onJoin`` callback which fires when the session has connected to a *Router* and joined a *Realm*.
|
||||
|
||||
|
@ -229,29 +244,28 @@ When the registration succeeds, authorized callers will immediately be able to c
|
|||
|
||||
A registration may also fail, e.g. when a procedure is already registered under the given URI or when the session is not authorized to register procedures.
|
||||
|
||||
Using **asyncio**, the example looks like this
|
||||
Using **asyncio**, the example looks like this:
|
||||
|
||||
.. code-block:: python
|
||||
:linenos:
|
||||
:emphasize-lines: 14
|
||||
:linenos:
|
||||
:emphasize-lines: 13
|
||||
|
||||
from autobahn.asyncio.wamp import ApplicationSession
|
||||
from asyncio import coroutine
|
||||
from autobahn.asyncio.wamp import ApplicationSession
|
||||
from asyncio import coroutine
|
||||
|
||||
class MyComponent(ApplicationSession):
|
||||
class MyComponent(ApplicationSession):
|
||||
@coroutine
|
||||
def onJoin(self, details):
|
||||
print("session ready")
|
||||
|
||||
@coroutine
|
||||
def onJoin(self, details):
|
||||
print("session ready")
|
||||
def add2(x, y):
|
||||
return x + y
|
||||
|
||||
def add2(x, y):
|
||||
return x + y
|
||||
|
||||
try:
|
||||
yield from self.register(add2, u'com.myapp.add2')
|
||||
print("procedure registered")
|
||||
except Exception as e:
|
||||
print("could not register procedure: {0}".format(e))
|
||||
try:
|
||||
yield from self.register(add2, u'com.myapp.add2')
|
||||
print("procedure registered")
|
||||
except Exception as e:
|
||||
print("could not register procedure: {0}".format(e))
|
||||
|
||||
The differences compared with the Twisted variant are:
|
||||
|
||||
|
@ -263,59 +277,57 @@ The differences compared with the Twisted variant are:
|
|||
.. _calling-procedures:
|
||||
|
||||
Calling Procedures
|
||||
..................
|
||||
------------------
|
||||
|
||||
Calling a procedure (that has been previously registered) is done using :func:`autobahn.wamp.interfaces.ICaller.call`.
|
||||
|
||||
Here is how you would call the procedure ``add2`` that we registered in :ref:`registering-procedures` under URI ``com.myapp.add2`` in **Twisted**
|
||||
|
||||
.. code-block:: python
|
||||
:linenos:
|
||||
:emphasize-lines: 12
|
||||
:linenos:
|
||||
:emphasize-lines: 11
|
||||
|
||||
from autobahn.twisted.wamp import ApplicationSession
|
||||
from twisted.internet.defer import inlineCallbacks
|
||||
from autobahn.twisted.wamp import ApplicationSession
|
||||
from twisted.internet.defer import inlineCallbacks
|
||||
|
||||
|
||||
class MyComponent(ApplicationSession):
|
||||
class MyComponent(ApplicationSession):
|
||||
@inlineCallbacks
|
||||
def onJoin(self, details):
|
||||
print("session ready")
|
||||
|
||||
@inlineCallbacks
|
||||
def onJoin(self, details):
|
||||
print("session ready")
|
||||
|
||||
try:
|
||||
res = yield self.call(u'com.myapp.add2', 2, 3)
|
||||
print("call result: {}".format(res))
|
||||
except Exception as e:
|
||||
print("call error: {0}".format(e))
|
||||
try:
|
||||
res = yield self.call(u'com.myapp.add2', 2, 3)
|
||||
print("call result: {}".format(res))
|
||||
except Exception as e:
|
||||
print("call error: {0}".format(e))
|
||||
|
||||
And here is the same done on **asyncio**
|
||||
|
||||
.. code-block:: python
|
||||
:linenos:
|
||||
:emphasize-lines: 12
|
||||
:linenos:
|
||||
:emphasize-lines: 11
|
||||
|
||||
from autobahn.asyncio.wamp import ApplicationSession
|
||||
from asyncio import coroutine
|
||||
from autobahn.asyncio.wamp import ApplicationSession
|
||||
from asyncio import coroutine
|
||||
|
||||
|
||||
class MyComponent(ApplicationSession):
|
||||
class MyComponent(ApplicationSession):
|
||||
@coroutine
|
||||
def onJoin(self, details):
|
||||
print("session ready")
|
||||
|
||||
@coroutine
|
||||
def onJoin(self, details):
|
||||
print("session ready")
|
||||
|
||||
try:
|
||||
res = yield from self.call(u'com.myapp.add2', 2, 3)
|
||||
print("call result: {}".format(res))
|
||||
except Exception as e:
|
||||
print("call error: {0}".format(e))
|
||||
try:
|
||||
res = yield from self.call(u'com.myapp.add2', 2, 3)
|
||||
print("call result: {}".format(res))
|
||||
except Exception as e:
|
||||
print("call error: {0}".format(e))
|
||||
|
||||
|
||||
.. _publish-and-subscribe:
|
||||
|
||||
Publish & Subscribe
|
||||
-------------------
|
||||
===================
|
||||
|
||||
**Publish & Subscribe (PubSub)** is a messaging pattern involving peers of three roles:
|
||||
|
||||
|
@ -323,46 +335,43 @@ Publish & Subscribe
|
|||
* *Subscriber*
|
||||
* *Broker*
|
||||
|
||||
A *Publishers* publishes events to topics by providing the topic URI and any payload for the event. Subscribers of the topic will receive the event together with the event payload.
|
||||
A *Publisher* publishes events to topics by providing the topic URI and any payload for the event. Subscribers of the topic will receive the event together with the event payload.
|
||||
|
||||
*Subscribers* subscribe to topics they are interested in with *Brokers*. *Publishers* initiate publication first at *Brokers*. *Brokers* route events incoming from *Publishers* to *Subscribers* that are subscribed to respective topics.
|
||||
*Subscribers* subscribe to topics they are interested in with *Brokers*. *Publishers* initiate publication first at a *Broker*. *Brokers* route events incoming from *Publishers* to *Subscribers* that are subscribed to respective topics.
|
||||
|
||||
The *Publisher* and *Subscriber* will usually run application code, while the *Broker* works as a generic router for events decoupling *Publishers* from *Subscribers*.
|
||||
The *Publisher* and *Subscriber* will usually run application code, while the *Broker* works as a generic router for events thus decoupling *Publishers* from *Subscribers*. That is, there can be many *Subscribers* written in different languages on different machines which can all receive a single event published by an independant *Publisher*.
|
||||
|
||||
|
||||
.. _subscribing-to-topics:
|
||||
|
||||
Subscribing to Topics
|
||||
.....................
|
||||
---------------------
|
||||
|
||||
To receive events published to a topic, a session needs to first subscribe to the topic.
|
||||
To receive events published to a topic, a session needs to first subscribe to the topic. Subscribing to a topic is done by calling :func:`autobahn.wamp.interfaces.ISubscriber.subscribe`.
|
||||
|
||||
Subscribing to a topic is done by calling :func:`autobahn.wamp.interfaces.ISubscriber.subscribe`.
|
||||
|
||||
Here is a **Twisted** example
|
||||
Here is a **Twisted** example:
|
||||
|
||||
.. code-block:: python
|
||||
:linenos:
|
||||
:emphasize-lines: 15
|
||||
:linenos:
|
||||
:emphasize-lines: 14
|
||||
|
||||
from autobahn.twisted.wamp import ApplicationSession
|
||||
from twisted.internet.defer import inlineCallbacks
|
||||
from autobahn.twisted.wamp import ApplicationSession
|
||||
from twisted.internet.defer import inlineCallbacks
|
||||
|
||||
|
||||
class MyComponent(ApplicationSession):
|
||||
class MyComponent(ApplicationSession):
|
||||
@inlineCallbacks
|
||||
def onJoin(self, details):
|
||||
print("session ready")
|
||||
|
||||
@inlineCallbacks
|
||||
def onJoin(self, details):
|
||||
print("session ready")
|
||||
def oncounter(count):
|
||||
print("event received: {0}", count)
|
||||
|
||||
def oncounter(count):
|
||||
print("event received: {0}", count)
|
||||
|
||||
try:
|
||||
yield self.subscribe(oncounter, u'com.myapp.oncounter')
|
||||
print("subscribed to topic")
|
||||
except Exception as e:
|
||||
print("could not subscribe to topic: {0}".format(e))
|
||||
try:
|
||||
yield self.subscribe(oncounter, u'com.myapp.oncounter')
|
||||
print("subscribed to topic")
|
||||
except Exception as e:
|
||||
print("could not subscribe to topic: {0}".format(e))
|
||||
|
||||
We create an event handler function ``oncounter`` (you can name that as you like) which will get called whenever an event for the topic is received.
|
||||
|
||||
|
@ -373,27 +382,26 @@ When the subscription succeeds, we will receive any events published to ``u'com.
|
|||
The corresponding **asyncio** code looks like this
|
||||
|
||||
.. code-block:: python
|
||||
:linenos:
|
||||
:emphasize-lines: 15
|
||||
:linenos:
|
||||
:emphasize-lines: 14
|
||||
|
||||
from autobahn.asyncio.wamp import ApplicationSession
|
||||
from asyncio import coroutine
|
||||
from autobahn.asyncio.wamp import ApplicationSession
|
||||
from asyncio import coroutine
|
||||
|
||||
|
||||
class MyComponent(ApplicationSession):
|
||||
class MyComponent(ApplicationSession):
|
||||
@coroutine
|
||||
def onJoin(self, details):
|
||||
print("session ready")
|
||||
|
||||
@coroutine
|
||||
def onJoin(self, details):
|
||||
print("session ready")
|
||||
def oncounter(count):
|
||||
print("event received: {0}", count)
|
||||
|
||||
def oncounter(count):
|
||||
print("event received: {0}", count)
|
||||
|
||||
try:
|
||||
yield from self.subscribe(oncounter, u'com.myapp.oncounter')
|
||||
print("subscribed to topic")
|
||||
except Exception as e:
|
||||
print("could not subscribe to topic: {0}".format(e))
|
||||
try:
|
||||
yield from self.subscribe(oncounter, u'com.myapp.oncounter')
|
||||
print("subscribed to topic")
|
||||
except Exception as e:
|
||||
print("could not subscribe to topic: {0}".format(e))
|
||||
|
||||
Again, nearly identical to Twisted.
|
||||
|
||||
|
@ -401,112 +409,109 @@ Again, nearly identical to Twisted.
|
|||
.. _publishing-events:
|
||||
|
||||
Publishing Events
|
||||
.................
|
||||
-----------------
|
||||
|
||||
Publishing an event to a topic is done by calling :func:`autobahn.wamp.interfaces.IPublisher.publish`.
|
||||
|
||||
Events can carry arbitrary positional and keyword based payload - as long as the payload is serializable in JSON.
|
||||
Events can carry arbitrary positional and keyword based payload -- as long as the payload is serializable in JSON.
|
||||
|
||||
Here is a **Twisted** example that will publish an event to topic ``u'com.myapp.oncounter'`` with a single (positional) payload being a counter that is incremented for each publish
|
||||
Here is a **Twisted** example that will publish an event to topic ``u'com.myapp.oncounter'`` with a single (positional) payload being a counter that is incremented for each publish:
|
||||
|
||||
.. code-block:: python
|
||||
:linenos:
|
||||
:emphasize-lines: 14
|
||||
:linenos:
|
||||
:emphasize-lines: 13
|
||||
|
||||
from autobahn.twisted.wamp import ApplicationSession
|
||||
from autobahn.twisted.util import sleep
|
||||
from twisted.internet.defer import inlineCallbacks
|
||||
from autobahn.twisted.wamp import ApplicationSession
|
||||
from autobahn.twisted.util import sleep
|
||||
from twisted.internet.defer import inlineCallbacks
|
||||
|
||||
|
||||
class MyComponent(ApplicationSession):
|
||||
class MyComponent(ApplicationSession):
|
||||
@inlineCallbacks
|
||||
def onJoin(self, details):
|
||||
print("session ready")
|
||||
|
||||
@inlineCallbacks
|
||||
def onJoin(self, details):
|
||||
print("session ready")
|
||||
|
||||
counter = 0
|
||||
while True:
|
||||
self.publish(u'com.myapp.oncounter', counter)
|
||||
counter += 1
|
||||
yield sleep(1)
|
||||
counter = 0
|
||||
while True:
|
||||
self.publish(u'com.myapp.oncounter', counter)
|
||||
counter += 1
|
||||
yield sleep(1)
|
||||
|
||||
The corresponding **asyncio** code looks like this
|
||||
|
||||
.. code-block:: python
|
||||
:linenos:
|
||||
:emphasize-lines: 14
|
||||
:linenos:
|
||||
:emphasize-lines: 13
|
||||
|
||||
from autobahn.asyncio.wamp import ApplicationSession
|
||||
from asyncio import sleep
|
||||
from asyncio import coroutine
|
||||
from autobahn.asyncio.wamp import ApplicationSession
|
||||
from asyncio import sleep
|
||||
from asyncio import coroutine
|
||||
|
||||
|
||||
class MyComponent(ApplicationSession):
|
||||
class MyComponent(ApplicationSession):
|
||||
@coroutine
|
||||
def onJoin(self, details):
|
||||
print("session ready")
|
||||
|
||||
@coroutine
|
||||
def onJoin(self, details):
|
||||
print("session ready")
|
||||
|
||||
counter = 0
|
||||
while True:
|
||||
self.publish(u'com.myapp.oncounter', counter)
|
||||
counter += 1
|
||||
yield from sleep(1)
|
||||
counter = 0
|
||||
while True:
|
||||
self.publish(u'com.myapp.oncounter', counter)
|
||||
counter += 1
|
||||
yield from sleep(1)
|
||||
|
||||
|
||||
.. tip::
|
||||
By default, a publisher will not receive an event it publishes even when the publisher is *itself* subscribed to the topic subscribed to. This behavior can be overridden.
|
||||
By default, a publisher will not receive an event it publishes even when the publisher is *itself* subscribed to the topic subscribed to. This behavior can be overridden; see :class:`PublishOptions <autobahn.wamp.types.PublishOptions>` and ``exclude_me=False``.
|
||||
|
||||
.. tip::
|
||||
By default, publications are *unacknowledged*. This means, a ``publish()`` may fail *silently* (like when the session is not authorized to publish to the given topic). This behavior can be overridden.
|
||||
By default, publications are *unacknowledged*. This means, a ``publish()`` may fail *silently* (like when the session is not authorized to publish to the given topic). This behavior can be overridden; see :class:`PublishOptions <autobahn.wamp.types.PublishOptions>` and ``acknowledge=True``.
|
||||
|
||||
|
||||
.. _session_lifecycle:
|
||||
|
||||
Session Lifecycle
|
||||
-----------------
|
||||
=================
|
||||
|
||||
A WAMP application component has this lifecycle:
|
||||
|
||||
1. component created
|
||||
2. transport connected
|
||||
3. authentication challenge received (only for authenticated WAMP sessions)
|
||||
4. session established (realm joined)
|
||||
5. session closed (realm left)
|
||||
6. transport disconnected
|
||||
2. transport connected (:meth:`ISession.onConnect <autobahn.wamp.interfaces.ISession.onConnect>` called)
|
||||
3. authentication challenge received (only for authenticated WAMP sessions, :meth:`ISession.onChallenge <autobahn.wamp.interfaces.ISession.onChallenge>` called)
|
||||
4. session established (realm joined, :meth:`ISession.onJoin <autobahn.wamp.interfaces.ISession.onJoin>` called)
|
||||
5. session closed (realm left, :meth:`ISession.onLeave <autobahn.wamp.interfaces.ISession.onLeave>` called)
|
||||
6. transport disconnected (:meth:`ISession.onDisconnect <autobahn.wamp.interfaces.ISession.onDisconnect>` called)
|
||||
|
||||
The `ApplicationSession` will fire the following events which you can handle by overriding the respective method (see :class:`autobahn.wamp.interfaces.ISession` for more information):
|
||||
The :class:`ApplicationSession <autobahn.twisted.wamp.ApplicationSession>` will fire the following events which you can handle by overriding the respective method (see :class:`ISession <autobahn.wamp.interfaces.ISession>` for more information):
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class MyComponent(ApplicationSession):
|
||||
class MyComponent(ApplicationSession):
|
||||
def __init__(self, config=None):
|
||||
ApplicationSession.__init__(self, config)
|
||||
print("component created")
|
||||
|
||||
def __init__(self, config = None):
|
||||
ApplicationSession.__init__(self, config)
|
||||
print("component created")
|
||||
def onConnect(self):
|
||||
print("transport connected")
|
||||
self.join(self.config.realm)
|
||||
|
||||
def onConnect(self):
|
||||
print("transport connected")
|
||||
self.join(self.config.realm)
|
||||
def onChallenge(self, challenge):
|
||||
print("authentication challenge received")
|
||||
|
||||
def onChallenge(self, challenge):
|
||||
print("authentication challenge received")
|
||||
def onJoin(self, details):
|
||||
print("session joined")
|
||||
|
||||
def onJoin(self, details):
|
||||
print("session joined")
|
||||
def onLeave(self, details):
|
||||
print("session left")
|
||||
|
||||
def onLeave(self, details):
|
||||
print("session left")
|
||||
|
||||
def onDisconnect(self):
|
||||
print("transport disconnected")
|
||||
def onDisconnect(self):
|
||||
print("transport disconnected")
|
||||
|
||||
|
||||
Upgrading
|
||||
---------
|
||||
=========
|
||||
|
||||
From < 0.8.0
|
||||
............
|
||||
------------
|
||||
|
||||
Starting with release 0.8.0, |Ab| now supports WAMP v2, and also support both Twisted and asyncio. This required changing module naming for WAMP v1 (which is Twisted only).
|
||||
|
||||
|
@ -526,6 +531,6 @@ should be modified for |ab| **>= 0.8.0** for (using Twisted)
|
|||
|
||||
|
||||
From < 0.9.4
|
||||
............
|
||||
------------
|
||||
|
||||
Starting with release 0.9.4, all WAMP router code in |Ab| has been split out and moved to `Crossbar.io <http://crossbar.io>`_. Please see the announcement `here <https://groups.google.com/d/msg/autobahnws/bCj7O2G2sxA/6-pioJZ_S_MJ>`__.
|
||||
Starting with release 0.9.4, all WAMP router code in |Ab| has been split out and moved to `Crossbar.io <http://crossbar.io>`_. Please see the announcement `here <https://groups.google.com/d/msg/autobahnws/bCj7O2G2sxA/6-pioJZ_S_MJ>`__.
|
||||
|
|
|
@ -14,3 +14,6 @@ flake8:
|
|||
|
||||
pylint:
|
||||
pylint -d line-too-long,invalid-name .
|
||||
|
||||
examples:
|
||||
python run-all-examples.py
|
||||
|
|
|
@ -1,15 +1,25 @@
|
|||
# Autobahn|Python Examples
|
||||
|
||||
This folder contains complete working code examples that demonstrate various
|
||||
features of **Autobahn**|Python:
|
||||
This folder contains complete working code examples that demonstrate various features of **Autobahn**|Python:
|
||||
|
||||
1. **Twisted**-based Examples
|
||||
* [WebSocket](twisted/websocket)
|
||||
* [WAMP](twisted/wamp)
|
||||
* [WebSocket](twisted/websocket/README.md)
|
||||
* [WAMP](twisted/wamp/README.md)
|
||||
|
||||
2. **asyncio**-based Examples
|
||||
* [WebSocket](asyncio/websocket)
|
||||
* [WAMP](asyncio/wamp)
|
||||
* [WebSocket](asyncio/websocket/README.md)
|
||||
* [WAMP](asyncio/wamp/README.md)
|
||||
|
||||
> Note: old Twisted / WAMP v1 examples are [here](twisted/wamp1)
|
||||
|
||||
If you are new to Autobahn and WAMP, you should start with the following if you're going to use Twisted:
|
||||
|
||||
* twisted/wamp/pubsub/basic/
|
||||
* twisted/wamp/rpc/arguments/
|
||||
|
||||
...whereas if you prefer asyncio:
|
||||
|
||||
* asyncio/wamp/pubsub/basic/
|
||||
* asycnio/wamp/rpc/arguments/
|
||||
|
||||
Note that many of the examples use the same URIs for topics or RPC endpoints, so you can mix and match which `backend` or `frontend` script (whether Python or JavaScript) you use. For example, a Web browser tab could load a `backend.html` page that does publishes while you run a Python `frontend.py` that subscribes to those topics.
|
||||
|
||||
[Set up locally to run the examples](running-the-examples.md).
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
# scripted demo for https://asciinema.org/
|
||||
# to use:
|
||||
# 1. create virtualenv with autobahn, ansicolors and asciinema installed:
|
||||
# pip install autobahn asciinema ansicolors
|
||||
# 2. change to root of fresh AutobahnPython checkout
|
||||
# 3. a) to record and upload, run:
|
||||
#
|
||||
# asciinema -c ./examples/asciinema-autobahn-demo.py rec
|
||||
#
|
||||
# 3. b) to just test this (e.g. without recording anything):
|
||||
#
|
||||
# python asciinema-autobahn-demo0.py
|
||||
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import random
|
||||
import colors
|
||||
import subprocess
|
||||
|
||||
prompt = 'user@machine:~/autobahn-python$ '
|
||||
|
||||
def interkey_interval():
|
||||
"""in milliseconds"""
|
||||
# return 0 # makes testing faster
|
||||
return (random.lognormvariate(0.0, 0.5) * 30.0) / 1000.0
|
||||
return float(random.randrange(10,50)) / 1000.0
|
||||
|
||||
|
||||
def type_it_out(line):
|
||||
for c in line:
|
||||
sys.stdout.write(c)
|
||||
sys.stdout.flush()
|
||||
time.sleep(interkey_interval())
|
||||
|
||||
|
||||
def do_commands(lines):
|
||||
for line in lines:
|
||||
sys.stdout.write(colors.blue(prompt))
|
||||
type_it_out(line)
|
||||
time.sleep(0.5)
|
||||
print
|
||||
os.system(colors.strip_color(line))
|
||||
|
||||
commands = [
|
||||
"clear",
|
||||
colors.red('# Welcome! Here we set up and run one basic'),
|
||||
colors.red('# http://autobahn.ws example'),
|
||||
colors.red('# (Note there are many other examples to try)'),
|
||||
colors.red('#'),
|
||||
colors.red("# I presume you've got a clone of https://github.com/tavendo/AutobahnPython"),
|
||||
colors.red("# in ~/autobahn-python"),
|
||||
"sleep 5",
|
||||
"clear",
|
||||
colors.red("# first, we create a virtualenv:"),
|
||||
"virtualenv venv-autobahn",
|
||||
"./venv-autobahn/bin/" + colors.bold("pip install -q --editable ."),
|
||||
colors.red("# we also need a WAMP router"),
|
||||
colors.red("# so we will use http://crossbar.io"),
|
||||
"./venv-autobahn/bin/" + colors.bold("pip install -q crossbar"),
|
||||
"clear",
|
||||
colors.red("# we have installed the AutobahnPython checkout, and crossbar."),
|
||||
colors.red("# the examples have a suitable crossbar configuration"),
|
||||
"./venv-autobahn/bin/" + colors.bold("crossbar start --cbdir examples/router/.crossbar &"),
|
||||
"sleep 2",
|
||||
colors.red('# now we run a simple "backend" which registers some callable methods'),
|
||||
"./venv-autobahn/bin/" + colors.bold("python examples/twisted/wamp/rpc/arguments/backend.py &"),
|
||||
"sleep 2",
|
||||
colors.red('# ...and a frontend that calls those methods'),
|
||||
"./venv-autobahn/bin/" + colors.bold("python examples/twisted/wamp/rpc/arguments/frontend.py"),
|
||||
colors.red('# Thanks for watching!'),
|
||||
colors.red('# http://autobahn.ws/python/wamp/examples.html'),
|
||||
"sleep 5",
|
||||
]
|
||||
|
||||
if __name__ == '__main__':
|
||||
do_commands(commands)
|
|
@ -1 +0,0 @@
|
|||
node_modules
|
|
@ -1,150 +0,0 @@
|
|||
all:
|
||||
@echo "Targets: s1-s14, f1-f14, b1-b14"
|
||||
|
||||
|
||||
s:
|
||||
PYTHONPATH=../../../../autobahn python3 server.py
|
||||
|
||||
|
||||
s1:
|
||||
PYTHONPATH=../../../../autobahn python3 server.py --component "rpc.timeservice.backend.Component"
|
||||
|
||||
f1:
|
||||
PYTHONPATH=../../../../autobahn python3 client.py --component "rpc.timeservice.frontend.Component"
|
||||
|
||||
b1:
|
||||
PYTHONPATH=../../../../autobahn python3 client.py --component "rpc.timeservice.backend.Component"
|
||||
|
||||
|
||||
s2:
|
||||
PYTHONPATH=../../../../autobahn python3 server.py --component "rpc.slowsquare.backend.Component"
|
||||
|
||||
f2:
|
||||
PYTHONPATH=../../../../autobahn python3 client.py --component "rpc.slowsquare.frontend.Component"
|
||||
|
||||
b2:
|
||||
PYTHONPATH=../../../../autobahn python3 client.py --component "rpc.slowsquare.backend.Component"
|
||||
|
||||
|
||||
s3:
|
||||
PYTHONPATH=../../../../autobahn python3 server.py --component "rpc.arguments.backend.Component"
|
||||
|
||||
f3:
|
||||
PYTHONPATH=../../../../autobahn python3 client.py --component "rpc.arguments.frontend.Component"
|
||||
|
||||
b3:
|
||||
PYTHONPATH=../../../../autobahn python3 client.py --component "rpc.arguments.backend.Component"
|
||||
|
||||
|
||||
s4:
|
||||
PYTHONPATH=../../../../autobahn python3 server.py --component "rpc.options.backend.Component"
|
||||
|
||||
f4:
|
||||
PYTHONPATH=../../../../autobahn python3 client.py --component "rpc.options.frontend.Component"
|
||||
|
||||
b4:
|
||||
PYTHONPATH=../../../../autobahn python3 client.py --component "rpc.options.backend.Component"
|
||||
|
||||
|
||||
s5:
|
||||
PYTHONPATH=../../../../autobahn python3 server.py --component "rpc.errors.backend.Component"
|
||||
|
||||
f5:
|
||||
PYTHONPATH=../../../../autobahn python3 client.py --component "rpc.errors.frontend.Component"
|
||||
|
||||
b5:
|
||||
PYTHONPATH=../../../../autobahn python3 client.py --component "rpc.errors.backend.Component"
|
||||
|
||||
|
||||
s6:
|
||||
PYTHONPATH=../../../../autobahn python3 server.py --component "rpc.complex.backend.Component"
|
||||
|
||||
f6:
|
||||
PYTHONPATH=../../../../autobahn python3 client.py --component "rpc.complex.frontend.Component"
|
||||
|
||||
b6:
|
||||
PYTHONPATH=../../../../autobahn python3 client.py --component "rpc.complex.backend.Component"
|
||||
|
||||
|
||||
s7:
|
||||
PYTHONPATH=../../../../autobahn python3 server.py --component "rpc.progress.backend.Component"
|
||||
|
||||
f7:
|
||||
PYTHONPATH=../../../../autobahn python3 client.py --component "rpc.progress.frontend.Component"
|
||||
|
||||
b7:
|
||||
PYTHONPATH=../../../../autobahn python3 client.py --component "rpc.progress.backend.Component"
|
||||
|
||||
|
||||
s8:
|
||||
PYTHONPATH=../../../../autobahn python3 server.py --component "rpc.decorators.backend.Component"
|
||||
|
||||
f8:
|
||||
PYTHONPATH=../../../../autobahn python3 client.py --component "rpc.decorators.frontend.Component"
|
||||
|
||||
b8:
|
||||
PYTHONPATH=../../../../autobahn python3 client.py --component "rpc.decorators.backend.Component"
|
||||
|
||||
|
||||
s9:
|
||||
PYTHONPATH=../../../../autobahn python3 server.py --component "pubsub.basic.backend.Component"
|
||||
|
||||
f9:
|
||||
PYTHONPATH=../../../../autobahn python3 client.py --component "pubsub.basic.frontend.Component"
|
||||
|
||||
b9:
|
||||
PYTHONPATH=../../../../autobahn python3 client.py --component "pubsub.basic.backend.Component"
|
||||
|
||||
|
||||
s10:
|
||||
PYTHONPATH=../../../../autobahn python3 server.py --component "pubsub.complex.backend.Component"
|
||||
|
||||
f10:
|
||||
PYTHONPATH=../../../../autobahn python3 client.py --component "pubsub.complex.frontend.Component"
|
||||
|
||||
b10:
|
||||
PYTHONPATH=../../../../autobahn python3 client.py --component "pubsub.complex.backend.Component"
|
||||
|
||||
|
||||
s11:
|
||||
PYTHONPATH=../../../../autobahn python3 server.py --component "pubsub.options.backend.Component"
|
||||
|
||||
f11:
|
||||
PYTHONPATH=../../../../autobahn python3 client.py --component "pubsub.options.frontend.Component"
|
||||
|
||||
b11:
|
||||
PYTHONPATH=../../../../autobahn python3 client.py --component "pubsub.options.backend.Component"
|
||||
|
||||
|
||||
s12:
|
||||
PYTHONPATH=../../../../autobahn python3 server.py --component "pubsub.unsubscribe.backend.Component"
|
||||
|
||||
f12:
|
||||
PYTHONPATH=../../../../autobahn python3 client.py --component "pubsub.unsubscribe.frontend.Component"
|
||||
|
||||
b12:
|
||||
PYTHONPATH=../../../../autobahn python3 client.py --component "pubsub.unsubscribe.backend.Component"
|
||||
|
||||
|
||||
s13:
|
||||
PYTHONPATH=../../../../autobahn python3 server.py --component "pubsub.decorators.backend.Component"
|
||||
|
||||
f13:
|
||||
PYTHONPATH=../../../../autobahn python3 client.py --component "pubsub.decorators.frontend.Component"
|
||||
|
||||
b13:
|
||||
PYTHONPATH=../../../../autobahn python3 client.py --component "pubsub.decorators.backend.Component"
|
||||
|
||||
|
||||
s14:
|
||||
PYTHONPATH=../../../../autobahn python3 server.py --component "session.series.backend.Component"
|
||||
|
||||
f14:
|
||||
PYTHONPATH=../../../../autobahn python3 client.py --component "session.series.frontend.Component"
|
||||
|
||||
b14:
|
||||
PYTHONPATH=../../../../autobahn python3 client.py --component "session.series.backend.Component"
|
||||
|
||||
|
||||
client_session_fromoutside_backend:
|
||||
PYTHONPATH=../../../../autobahn python3 session/fromoutside/backend.py
|
|
@ -1,116 +0,0 @@
|
|||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) Tavendo GmbH
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
import sys
|
||||
import argparse
|
||||
|
||||
try:
|
||||
import asyncio
|
||||
except ImportError:
|
||||
# Trollius >= 0.3 was renamed
|
||||
import trollius as asyncio
|
||||
|
||||
# parse command line arguments
|
||||
##
|
||||
parser = argparse.ArgumentParser()
|
||||
|
||||
parser.add_argument("-d", "--debug", action="store_true",
|
||||
help="Enable debug output.")
|
||||
|
||||
parser.add_argument("-c", "--component", type=str,
|
||||
help="Start WAMP client with this application component, e.g. 'timeservice.TimeServiceFrontend'")
|
||||
|
||||
parser.add_argument("-r", "--realm", type=str, default="realm1",
|
||||
help="The WAMP realm to start the component in (if any).")
|
||||
|
||||
parser.add_argument("--host", type=str, default="127.0.0.1",
|
||||
help='IP or hostname to connect to.')
|
||||
|
||||
parser.add_argument("--port", type=int, default=8080,
|
||||
help='TCP port to connect to.')
|
||||
|
||||
parser.add_argument("--transport", choices=['websocket', 'rawsocket-json', 'rawsocket-msgpack'], default="websocket",
|
||||
help='WAMP transport type')
|
||||
|
||||
parser.add_argument("--url", type=str, default=None,
|
||||
help='The WebSocket URL to connect to, e.g. ws://127.0.0.1:8080/ws.')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# create a WAMP application session factory
|
||||
##
|
||||
from autobahn.asyncio.wamp import ApplicationSessionFactory
|
||||
from autobahn.wamp import types
|
||||
session_factory = ApplicationSessionFactory(types.ComponentConfig(realm=args.realm))
|
||||
|
||||
# dynamically load the application component ..
|
||||
##
|
||||
import importlib
|
||||
c = args.component.split('.')
|
||||
mod, klass = '.'.join(c[:-1]), c[-1]
|
||||
app = importlib.import_module(mod)
|
||||
|
||||
# .. and set the session class on the factory
|
||||
##
|
||||
session_factory.session = getattr(app, klass)
|
||||
|
||||
if args.transport == "websocket":
|
||||
|
||||
# create a WAMP-over-WebSocket transport client factory
|
||||
##
|
||||
from autobahn.asyncio.websocket import WampWebSocketClientFactory
|
||||
transport_factory = WampWebSocketClientFactory(session_factory, url=args.url, debug_wamp=args.debug)
|
||||
transport_factory.setProtocolOptions(failByDrop=False)
|
||||
|
||||
elif args.transport in ['rawsocket-json', 'rawsocket-msgpack']:
|
||||
|
||||
# create a WAMP-over-RawSocket transport client factory
|
||||
##
|
||||
if args.transport == 'rawsocket-msgpack':
|
||||
from autobahn.wamp.serializer import MsgPackSerializer
|
||||
serializer = MsgPackSerializer()
|
||||
elif args.transport == 'rawsocket-json':
|
||||
from autobahn.wamp.serializer import JsonSerializer
|
||||
serializer = JsonSerializer()
|
||||
else:
|
||||
raise Exception("should not arrive here")
|
||||
|
||||
from autobahn.asyncio.rawsocket import WampRawSocketClientFactory
|
||||
transport_factory = WampRawSocketClientFactory(session_factory, serializer, debug=args.debug)
|
||||
|
||||
else:
|
||||
raise Exception("logic error")
|
||||
|
||||
# start the client
|
||||
loop = asyncio.get_event_loop()
|
||||
coro = loop.create_connection(transport_factory, args.host, args.port)
|
||||
loop.run_until_complete(coro)
|
||||
|
||||
# now enter the asyncio event loop
|
||||
loop.run_forever()
|
||||
loop.close()
|
|
@ -1,25 +0,0 @@
|
|||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) Tavendo GmbH
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
###############################################################################
|
|
@ -1,25 +0,0 @@
|
|||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) Tavendo GmbH
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
###############################################################################
|
|
@ -1,25 +0,0 @@
|
|||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) Tavendo GmbH
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
###############################################################################
|
|
@ -1,25 +0,0 @@
|
|||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) Tavendo GmbH
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
###############################################################################
|
|
@ -1,25 +0,0 @@
|
|||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) Tavendo GmbH
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
###############################################################################
|
|
@ -1,25 +0,0 @@
|
|||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) Tavendo GmbH
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
###############################################################################
|
|
@ -1,25 +0,0 @@
|
|||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) Tavendo GmbH
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
###############################################################################
|
|
@ -1,25 +0,0 @@
|
|||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) Tavendo GmbH
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
###############################################################################
|
|
@ -1,25 +0,0 @@
|
|||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) Tavendo GmbH
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
###############################################################################
|
|
@ -1,25 +0,0 @@
|
|||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) Tavendo GmbH
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
###############################################################################
|
|
@ -1,25 +0,0 @@
|
|||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) Tavendo GmbH
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
###############################################################################
|
|
@ -1,25 +0,0 @@
|
|||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) Tavendo GmbH
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
###############################################################################
|
|
@ -1,25 +0,0 @@
|
|||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) Tavendo GmbH
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
###############################################################################
|
|
@ -1,25 +0,0 @@
|
|||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) Tavendo GmbH
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
###############################################################################
|
|
@ -1,25 +0,0 @@
|
|||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) Tavendo GmbH
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
###############################################################################
|
|
@ -1,129 +0,0 @@
|
|||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) Tavendo GmbH
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
import sys
|
||||
import argparse
|
||||
|
||||
try:
|
||||
import asyncio
|
||||
except ImportError:
|
||||
# Trollius >= 0.3 was renamed
|
||||
import trollius as asyncio
|
||||
|
||||
# parse command line arguments
|
||||
##
|
||||
parser = argparse.ArgumentParser()
|
||||
|
||||
parser.add_argument("-d", "--debug", action="store_true",
|
||||
help="Enable debug output.")
|
||||
|
||||
parser.add_argument("-c", "--component", type=str, default=None,
|
||||
help="Start WAMP server with this application component, e.g. 'timeservice.TimeServiceBackend', or None.")
|
||||
|
||||
parser.add_argument("-r", "--realm", type=str, default="realm1",
|
||||
help="The WAMP realm to start the component in (if any).")
|
||||
|
||||
parser.add_argument("--interface", type=str, default="127.0.0.1",
|
||||
help='IP of interface to listen on.')
|
||||
|
||||
parser.add_argument("--port", type=int, default=8080,
|
||||
help='TCP port to listen on.')
|
||||
|
||||
parser.add_argument("--transport", choices=['websocket', 'rawsocket-json', 'rawsocket-msgpack'], default="websocket",
|
||||
help='WAMP transport type')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# create a WAMP router factory
|
||||
##
|
||||
from autobahn.asyncio.wamp import RouterFactory
|
||||
router_factory = RouterFactory()
|
||||
|
||||
# create a WAMP router session factory
|
||||
##
|
||||
from autobahn.asyncio.wamp import RouterSessionFactory
|
||||
session_factory = RouterSessionFactory(router_factory)
|
||||
|
||||
# if asked to start an embedded application component ..
|
||||
##
|
||||
if args.component:
|
||||
# dynamically load the application component ..
|
||||
##
|
||||
import importlib
|
||||
c = args.component.split('.')
|
||||
mod, klass = '.'.join(c[:-1]), c[-1]
|
||||
app = importlib.import_module(mod)
|
||||
SessionKlass = getattr(app, klass)
|
||||
|
||||
# .. and create and add an WAMP application session to
|
||||
# run next to the router
|
||||
##
|
||||
from autobahn.wamp import types
|
||||
session_factory.add(SessionKlass(types.ComponentConfig(realm=args.realm)))
|
||||
|
||||
if args.transport == "websocket":
|
||||
|
||||
# create a WAMP-over-WebSocket transport server factory
|
||||
##
|
||||
from autobahn.asyncio.websocket import WampWebSocketServerFactory
|
||||
transport_factory = WampWebSocketServerFactory(session_factory, debug_wamp=args.debug)
|
||||
transport_factory.setProtocolOptions(failByDrop=False)
|
||||
|
||||
elif args.transport in ['rawsocket-json', 'rawsocket-msgpack']:
|
||||
|
||||
# create a WAMP-over-RawSocket transport server factory
|
||||
##
|
||||
if args.transport == 'rawsocket-msgpack':
|
||||
from autobahn.wamp.serializer import MsgPackSerializer
|
||||
serializer = MsgPackSerializer()
|
||||
elif args.transport == 'rawsocket-json':
|
||||
from autobahn.wamp.serializer import JsonSerializer
|
||||
serializer = JsonSerializer()
|
||||
else:
|
||||
raise Exception("should not arrive here")
|
||||
|
||||
from autobahn.asyncio.rawsocket import WampRawSocketServerFactory
|
||||
transport_factory = WampRawSocketServerFactory(session_factory, serializer, debug=args.debug)
|
||||
|
||||
else:
|
||||
raise Exception("should not arrive here")
|
||||
|
||||
# start the server from an endpoint
|
||||
##
|
||||
loop = asyncio.get_event_loop()
|
||||
coro = loop.create_server(transport_factory, args.interface, args.port)
|
||||
server = loop.run_until_complete(coro)
|
||||
|
||||
try:
|
||||
# now enter the asyncio event loop
|
||||
loop.run_forever()
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
finally:
|
||||
server.close()
|
||||
loop.close()
|
|
@ -1,25 +0,0 @@
|
|||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) Tavendo GmbH
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
###############################################################################
|
|
@ -1,25 +0,0 @@
|
|||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) Tavendo GmbH
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
###############################################################################
|
|
@ -1,44 +0,0 @@
|
|||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) Tavendo GmbH
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
import datetime
|
||||
|
||||
from autobahn.asyncio.wamp import ApplicationSession
|
||||
|
||||
|
||||
class Component(ApplicationSession):
|
||||
|
||||
"""
|
||||
A simple time service application component.
|
||||
"""
|
||||
|
||||
def onJoin(self, details):
|
||||
|
||||
def utcnow():
|
||||
now = datetime.datetime.utcnow()
|
||||
return now.strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
|
||||
self.register(utcnow, 'com.timeservice.now')
|
|
@ -1,5 +0,0 @@
|
|||
test_server:
|
||||
PYTHONPATH="../../../../autobahn" ~/python34/bin/python3 server.py
|
||||
|
||||
test_client:
|
||||
PYTHONPATH="../../../../autobahn" ~/python34/bin/python3 client.py
|
|
@ -1,99 +0,0 @@
|
|||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) Tavendo GmbH
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
import sys
|
||||
|
||||
try:
|
||||
import asyncio
|
||||
except ImportError:
|
||||
# Trollius >= 0.3 was renamed
|
||||
import trollius as asyncio
|
||||
|
||||
from autobahn.asyncio import wamp, websocket
|
||||
|
||||
|
||||
class MyFrontendComponent(wamp.ApplicationSession):
|
||||
|
||||
"""
|
||||
Application code goes here. This is an example component that calls
|
||||
a remote procedure on a WAMP peer, subscribes to a topic to receive
|
||||
events, and then stops the world after some events.
|
||||
"""
|
||||
|
||||
def onConnect(self):
|
||||
self.join(u"realm1")
|
||||
|
||||
@asyncio.coroutine
|
||||
def onJoin(self, details):
|
||||
|
||||
# call a remote procedure
|
||||
##
|
||||
try:
|
||||
now = yield from self.call(u'com.timeservice.now')
|
||||
except Exception as e:
|
||||
print("Error: {}".format(e))
|
||||
else:
|
||||
print("Current time from time service: {}".format(now))
|
||||
|
||||
# subscribe to a topic
|
||||
##
|
||||
self.received = 0
|
||||
|
||||
def on_event(i):
|
||||
print("Got event: {}".format(i))
|
||||
self.received += 1
|
||||
if self.received > 5:
|
||||
self.leave()
|
||||
|
||||
sub = yield from self.subscribe(on_event, u'com.myapp.topic1')
|
||||
print("Subscribed with subscription ID {}".format(sub.id))
|
||||
|
||||
def onLeave(self, details):
|
||||
self.disconnect()
|
||||
|
||||
def onDisconnect(self):
|
||||
asyncio.get_event_loop().stop()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
# 1) create a WAMP application session factory
|
||||
session_factory = wamp.ApplicationSessionFactory()
|
||||
session_factory.session = MyFrontendComponent
|
||||
|
||||
# 2) create a WAMP-over-WebSocket transport client factory
|
||||
transport_factory = websocket.WampWebSocketClientFactory(session_factory,
|
||||
debug=False,
|
||||
debug_wamp=False)
|
||||
|
||||
# 3) start the client
|
||||
loop = asyncio.get_event_loop()
|
||||
coro = loop.create_connection(transport_factory, '127.0.0.1', 8080)
|
||||
loop.run_until_complete(coro)
|
||||
|
||||
# 4) now enter the asyncio event loop
|
||||
loop.run_forever()
|
||||
loop.close()
|
|
@ -1,101 +0,0 @@
|
|||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) Tavendo GmbH
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
import sys
|
||||
import six
|
||||
import datetime
|
||||
|
||||
try:
|
||||
import asyncio
|
||||
except ImportError:
|
||||
# Trollius >= 0.3 was renamed
|
||||
import trollius as asyncio
|
||||
|
||||
from autobahn.asyncio import wamp, websocket
|
||||
|
||||
|
||||
class MyBackendComponent(wamp.ApplicationSession):
|
||||
|
||||
"""
|
||||
Application code goes here. This is an example component that provides
|
||||
a simple procedure which can be called remotely from any WAMP peer.
|
||||
It also publishes an event every second to some topic.
|
||||
"""
|
||||
|
||||
def onConnect(self):
|
||||
self.join(u"realm1")
|
||||
|
||||
@asyncio.coroutine
|
||||
def onJoin(self, details):
|
||||
# register a procedure for remote calling
|
||||
##
|
||||
def utcnow():
|
||||
print("Someone is calling me;)")
|
||||
now = datetime.datetime.utcnow()
|
||||
return six.u(now.strftime("%Y-%m-%dT%H:%M:%SZ"))
|
||||
|
||||
reg = yield from self.register(utcnow, u'com.timeservice.now')
|
||||
print("Registered procedure with ID {}".format(reg.id))
|
||||
|
||||
# publish events to a topic
|
||||
##
|
||||
counter = 0
|
||||
while True:
|
||||
self.publish(u'com.myapp.topic1', counter)
|
||||
print("Published event.")
|
||||
counter += 1
|
||||
yield from asyncio.sleep(1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
# 1) create a WAMP router factory
|
||||
router_factory = wamp.RouterFactory()
|
||||
|
||||
# 2) create a WAMP router session factory
|
||||
session_factory = wamp.RouterSessionFactory(router_factory)
|
||||
|
||||
# 3) Optionally, add embedded WAMP application sessions to the router
|
||||
session_factory.add(MyBackendComponent())
|
||||
|
||||
# 4) create a WAMP-over-WebSocket transport server factory
|
||||
transport_factory = websocket.WampWebSocketServerFactory(session_factory,
|
||||
debug=False,
|
||||
debug_wamp=False)
|
||||
|
||||
# 5) start the server
|
||||
loop = asyncio.get_event_loop()
|
||||
coro = loop.create_server(transport_factory, '127.0.0.1', 8080)
|
||||
server = loop.run_until_complete(coro)
|
||||
|
||||
try:
|
||||
# 6) now enter the asyncio event loop
|
||||
loop.run_forever()
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
finally:
|
||||
server.close()
|
||||
loop.close()
|
|
@ -0,0 +1,29 @@
|
|||
from os import environ
|
||||
import asyncio
|
||||
from autobahn.asyncio.wamp import ApplicationSession, ApplicationRunner
|
||||
|
||||
class MyComponent(ApplicationSession):
|
||||
@asyncio.coroutine
|
||||
def onJoin(self, details):
|
||||
# a remote procedure; see frontend.py for a Python front-end
|
||||
# that calls this. Any language with WAMP bindings can now call
|
||||
# this procedure if its connected to the same router and realm.
|
||||
def add2(x, y):
|
||||
return x + y
|
||||
yield from self.register(add2, 'com.myapp.add2');
|
||||
|
||||
# publish an event every second. The event payloads can be
|
||||
# anything JSON- and msgpack- serializable
|
||||
while True:
|
||||
self.publish('com.myapp.hello', 'Hello, world!')
|
||||
yield from asyncio.sleep(1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
runner = ApplicationRunner(
|
||||
environ.get("AUTOBAHN_DEMO_ROUTER", "ws://127.0.0.1:8080/ws"),
|
||||
u"crossbardemo",
|
||||
debug_wamp=False, # optional; log many WAMP details
|
||||
debug=False, # optional; log even more details
|
||||
)
|
||||
runner.run(MyComponent)
|
|
@ -0,0 +1,26 @@
|
|||
from os import environ
|
||||
import asyncio
|
||||
from autobahn.asyncio.wamp import ApplicationSession, ApplicationRunner
|
||||
|
||||
class MyComponent(ApplicationSession):
|
||||
@asyncio.coroutine
|
||||
def onJoin(self, details):
|
||||
# listening for the corresponding message from the "backend"
|
||||
# (any session that .publish()es to this topic).
|
||||
def onevent(msg):
|
||||
print("Got event: {}".format(msg))
|
||||
yield from self.subscribe(onevent, 'com.myapp.hello')
|
||||
|
||||
# call a remote procedure.
|
||||
res = yield from self.call('com.myapp.add2', 2, 3)
|
||||
print("Got result: {}".format(res))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
runner = ApplicationRunner(
|
||||
environ.get("AUTOBAHN_DEMO_ROUTER", "ws://127.0.0.1:8080/ws"),
|
||||
u"crossbardemo",
|
||||
debug_wamp=False, # optional; log many WAMP details
|
||||
debug=False, # optional; log even more details
|
||||
)
|
||||
runner.run(MyComponent)
|
|
@ -30,11 +30,11 @@ except ImportError:
|
|||
# Trollius >= 0.3 was renamed
|
||||
import trollius as asyncio
|
||||
|
||||
from autobahn.asyncio.wamp import ApplicationSession
|
||||
from os import environ
|
||||
from autobahn.asyncio.wamp import ApplicationSession, ApplicationRunner
|
||||
|
||||
|
||||
class Component(ApplicationSession):
|
||||
|
||||
"""
|
||||
An application component that publishes an event every second.
|
||||
"""
|
||||
|
@ -43,6 +43,17 @@ class Component(ApplicationSession):
|
|||
def onJoin(self, details):
|
||||
counter = 0
|
||||
while True:
|
||||
print("publish: com.myapp.topic1", counter)
|
||||
self.publish('com.myapp.topic1', counter)
|
||||
counter += 1
|
||||
yield from asyncio.sleep(1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
runner = ApplicationRunner(
|
||||
environ.get("AUTOBAHN_DEMO_ROUTER", "ws://127.0.0.1:8080/ws"),
|
||||
u"crossbardemo",
|
||||
debug_wamp=False, # optional; log many WAMP details
|
||||
debug=False, # optional; log even more details
|
||||
)
|
||||
runner.run(Component)
|
|
@ -30,14 +30,14 @@ except ImportError:
|
|||
# Trollius >= 0.3 was renamed
|
||||
import trollius as asyncio
|
||||
|
||||
from autobahn.asyncio.wamp import ApplicationSession
|
||||
from os import environ
|
||||
from autobahn.asyncio.wamp import ApplicationSession, ApplicationRunner
|
||||
|
||||
|
||||
class Component(ApplicationSession):
|
||||
|
||||
"""
|
||||
An application component that subscribes and receives events,
|
||||
and stop after having received 5 events.
|
||||
An application component that subscribes and receives events, and
|
||||
stop after having received 5 events.
|
||||
"""
|
||||
|
||||
@asyncio.coroutine
|
||||
|
@ -55,3 +55,13 @@ class Component(ApplicationSession):
|
|||
|
||||
def onDisconnect(self):
|
||||
asyncio.get_event_loop().stop()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
runner = ApplicationRunner(
|
||||
environ.get("AUTOBAHN_DEMO_ROUTER", "ws://127.0.0.1:8080/ws"),
|
||||
u"crossbardemo",
|
||||
debug_wamp=False, # optional; log many WAMP details
|
||||
debug=False, # optional; log even more details
|
||||
)
|
||||
runner.run(Component)
|
|
@ -25,6 +25,7 @@
|
|||
###############################################################################
|
||||
|
||||
import random
|
||||
from os import environ
|
||||
|
||||
try:
|
||||
import asyncio
|
||||
|
@ -33,25 +34,35 @@ except ImportError:
|
|||
import trollius as asyncio
|
||||
|
||||
from autobahn.wamp.types import SubscribeOptions
|
||||
from autobahn.asyncio.wamp import ApplicationSession
|
||||
from autobahn.asyncio.wamp import ApplicationSession, ApplicationRunner
|
||||
|
||||
|
||||
class Component(ApplicationSession):
|
||||
|
||||
"""
|
||||
An application component that publishes events with no payload
|
||||
and with complex payloads every second.
|
||||
An application component that publishes events with no payload and
|
||||
with complex payloads every second.
|
||||
"""
|
||||
|
||||
@asyncio.coroutine
|
||||
def onJoin(self, details):
|
||||
|
||||
counter = 0
|
||||
while True:
|
||||
print("publish: com.myapp.heartbeat")
|
||||
self.publish('com.myapp.heartbeat')
|
||||
|
||||
obj = {'counter': counter, 'foo': [1, 2, 3]}
|
||||
print("publish: com.myapp.topic2")
|
||||
self.publish('com.myapp.topic2', random.randint(0, 100), 23, c="Hello", d=obj)
|
||||
|
||||
counter += 1
|
||||
yield from asyncio.sleep(1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
runner = ApplicationRunner(
|
||||
environ.get("AUTOBAHN_DEMO_ROUTER", "ws://127.0.0.1:8080/ws"),
|
||||
u"crossbardemo",
|
||||
debug_wamp=False, # optional; log many WAMP details
|
||||
debug=False, # optional; log even more details
|
||||
)
|
||||
runner.run(Component)
|
|
@ -25,6 +25,7 @@
|
|||
###############################################################################
|
||||
|
||||
import random
|
||||
from os import environ
|
||||
|
||||
try:
|
||||
import asyncio
|
||||
|
@ -33,19 +34,17 @@ except ImportError:
|
|||
import trollius as asyncio
|
||||
|
||||
from autobahn.wamp.types import SubscribeOptions
|
||||
from autobahn.asyncio.wamp import ApplicationSession
|
||||
from autobahn.asyncio.wamp import ApplicationSession, ApplicationRunner
|
||||
|
||||
|
||||
class Component(ApplicationSession):
|
||||
|
||||
"""
|
||||
An application component that subscribes and receives events
|
||||
of no payload and of complex payload, and stops after 5 seconds.
|
||||
An application component that subscribes and receives events of no
|
||||
payload and of complex payload, and stops after 5 seconds.
|
||||
"""
|
||||
|
||||
@asyncio.coroutine
|
||||
def onJoin(self, details):
|
||||
|
||||
self.received = 0
|
||||
|
||||
def on_heartbeat(details=None):
|
||||
|
@ -57,8 +56,17 @@ class Component(ApplicationSession):
|
|||
print("Got event: {} {} {} {}".format(a, b, c, d))
|
||||
|
||||
yield from self.subscribe(on_topic2, 'com.myapp.topic2')
|
||||
|
||||
asyncio.get_event_loop().call_later(5, self.leave)
|
||||
|
||||
def onDisconnect(self):
|
||||
asyncio.get_event_loop().stop()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
runner = ApplicationRunner(
|
||||
environ.get("AUTOBAHN_DEMO_ROUTER", "ws://127.0.0.1:8080/ws"),
|
||||
u"crossbardemo",
|
||||
debug_wamp=False, # optional; log many WAMP details
|
||||
debug=False, # optional; log even more details
|
||||
)
|
||||
runner.run(Component)
|
|
@ -24,17 +24,18 @@
|
|||
#
|
||||
###############################################################################
|
||||
|
||||
from os import environ
|
||||
|
||||
try:
|
||||
import asyncio
|
||||
except ImportError:
|
||||
# Trollius >= 0.3 was renamed
|
||||
import trollius as asyncio
|
||||
|
||||
from autobahn.asyncio.wamp import ApplicationSession
|
||||
from autobahn.asyncio.wamp import ApplicationSession, ApplicationRunner
|
||||
|
||||
|
||||
class Component(ApplicationSession):
|
||||
|
||||
"""
|
||||
An application component that publishes an event every second.
|
||||
"""
|
||||
|
@ -43,7 +44,20 @@ class Component(ApplicationSession):
|
|||
def onJoin(self, details):
|
||||
counter = 0
|
||||
while True:
|
||||
print("publish: com.myapp.topic1", counter)
|
||||
self.publish('com.myapp.topic1', counter)
|
||||
|
||||
print("publish: com.myapp.topic2 'Hello world.'")
|
||||
self.publish('com.myapp.topic2', "Hello world.")
|
||||
counter += 1
|
||||
yield from asyncio.sleep(1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
runner = ApplicationRunner(
|
||||
environ.get("AUTOBAHN_DEMO_ROUTER", "ws://127.0.0.1:8080/ws"),
|
||||
u"crossbardemo",
|
||||
debug_wamp=False, # optional; log many WAMP details
|
||||
debug=False, # optional; log even more details
|
||||
)
|
||||
runner.run(Component)
|
|
@ -24,6 +24,8 @@
|
|||
#
|
||||
###############################################################################
|
||||
|
||||
from os import environ
|
||||
|
||||
try:
|
||||
import asyncio
|
||||
except ImportError:
|
||||
|
@ -31,24 +33,21 @@ except ImportError:
|
|||
import trollius as asyncio
|
||||
|
||||
from autobahn import wamp
|
||||
from autobahn.asyncio.wamp import ApplicationSession
|
||||
from autobahn.asyncio.wamp import ApplicationSession, ApplicationRunner
|
||||
|
||||
|
||||
class Component(ApplicationSession):
|
||||
|
||||
"""
|
||||
An application component that subscribes and receives events,
|
||||
and stop after having received 5 events.
|
||||
An application component that subscribes and receives events, and
|
||||
stop after having received 5 events.
|
||||
"""
|
||||
|
||||
@asyncio.coroutine
|
||||
def onJoin(self, details):
|
||||
|
||||
self.received = 0
|
||||
|
||||
# subscribe all methods on this object decorated with "@wamp.subscribe"
|
||||
# as PubSub event handlers
|
||||
##
|
||||
results = yield from self.subscribe(self)
|
||||
for res in results:
|
||||
if isinstance(res, wamp.protocol.Subscription):
|
||||
|
@ -71,3 +70,13 @@ class Component(ApplicationSession):
|
|||
|
||||
def onDisconnect(self):
|
||||
asyncio.get_event_loop().stop()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
runner = ApplicationRunner(
|
||||
environ.get("AUTOBAHN_DEMO_ROUTER", "ws://127.0.0.1:8080/ws"),
|
||||
u"crossbardemo",
|
||||
debug_wamp=False, # optional; log many WAMP details
|
||||
debug=False, # optional; log even more details
|
||||
)
|
||||
runner.run(Component)
|
|
@ -24,6 +24,8 @@
|
|||
#
|
||||
###############################################################################
|
||||
|
||||
from os import environ
|
||||
|
||||
try:
|
||||
import asyncio
|
||||
except ImportError:
|
||||
|
@ -31,11 +33,10 @@ except ImportError:
|
|||
import trollius as asyncio
|
||||
|
||||
from autobahn.wamp.types import PublishOptions, EventDetails, SubscribeOptions
|
||||
from autobahn.asyncio.wamp import ApplicationSession
|
||||
from autobahn.asyncio.wamp import ApplicationSession, ApplicationRunner
|
||||
|
||||
|
||||
class Component(ApplicationSession):
|
||||
|
||||
"""
|
||||
An application component that publishes an event every second.
|
||||
"""
|
||||
|
@ -45,13 +46,24 @@ class Component(ApplicationSession):
|
|||
|
||||
def on_event(i):
|
||||
print("Got event: {}".format(i))
|
||||
|
||||
yield from self.subscribe(on_event, 'com.myapp.topic1')
|
||||
|
||||
counter = 0
|
||||
while True:
|
||||
publication = yield from self.publish('com.myapp.topic1', counter,
|
||||
options=PublishOptions(acknowledge=True, discloseMe=True, excludeMe=False))
|
||||
publication = yield from self.publish(
|
||||
'com.myapp.topic1', counter,
|
||||
options=PublishOptions(acknowledge=True, disclose_me=True, exclude_me=False)
|
||||
)
|
||||
print("Event published with publication ID {}".format(publication.id))
|
||||
counter += 1
|
||||
yield from asyncio.sleep(1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
runner = ApplicationRunner(
|
||||
environ.get("AUTOBAHN_DEMO_ROUTER", "ws://127.0.0.1:8080/ws"),
|
||||
u"crossbardemo",
|
||||
debug_wamp=False, # optional; log many WAMP details
|
||||
debug=False, # optional; log even more details
|
||||
)
|
||||
runner.run(Component)
|
|
@ -24,6 +24,8 @@
|
|||
#
|
||||
###############################################################################
|
||||
|
||||
from os import environ
|
||||
|
||||
try:
|
||||
import asyncio
|
||||
except ImportError:
|
||||
|
@ -31,19 +33,17 @@ except ImportError:
|
|||
import trollius as asyncio
|
||||
|
||||
from autobahn.wamp.types import PublishOptions, EventDetails, SubscribeOptions
|
||||
from autobahn.asyncio.wamp import ApplicationSession
|
||||
from autobahn.asyncio.wamp import ApplicationSession, ApplicationRunner
|
||||
|
||||
|
||||
class Component(ApplicationSession):
|
||||
|
||||
"""
|
||||
An application component that subscribes and receives events,
|
||||
and stop after having received 5 events.
|
||||
An application component that subscribes and receives events, and
|
||||
stop after having received 5 events.
|
||||
"""
|
||||
|
||||
@asyncio.coroutine
|
||||
def onJoin(self, details):
|
||||
|
||||
self.received = 0
|
||||
|
||||
def on_event(i, details=None):
|
||||
|
@ -57,3 +57,13 @@ class Component(ApplicationSession):
|
|||
|
||||
def onDisconnect(self):
|
||||
asyncio.get_event_loop().stop()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
runner = ApplicationRunner(
|
||||
environ.get("AUTOBAHN_DEMO_ROUTER", "ws://127.0.0.1:8080/ws"),
|
||||
u"crossbardemo",
|
||||
debug_wamp=False, # optional; log many WAMP details
|
||||
debug=False, # optional; log even more details
|
||||
)
|
||||
runner.run(Component)
|
|
@ -0,0 +1,3 @@
|
|||
This corresponds to the Twisted version with similar path; see the
|
||||
README there for how to configure crossbar and create a self-signed
|
||||
certificate.
|
|
@ -0,0 +1,69 @@
|
|||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) Tavendo GmbH
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
try:
|
||||
import asyncio
|
||||
except ImportError:
|
||||
# Trollius >= 0.3 was renamed
|
||||
import trollius as asyncio
|
||||
|
||||
from os import environ
|
||||
from autobahn.asyncio.wamp import ApplicationSession, ApplicationRunner
|
||||
import ssl
|
||||
|
||||
|
||||
class Component(ApplicationSession):
|
||||
"""
|
||||
An application component that publishes an event every second.
|
||||
"""
|
||||
|
||||
@asyncio.coroutine
|
||||
def onJoin(self, details):
|
||||
counter = 0
|
||||
while True:
|
||||
print("publish: com.myapp.topic1", counter)
|
||||
self.publish('com.myapp.topic1', counter)
|
||||
counter += 1
|
||||
yield from asyncio.sleep(1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# see README; this way everything accesses same cert-files
|
||||
cert_path = '../../../../twisted/wamp/pubsub/tls/server.crt'
|
||||
print(cert_path)
|
||||
# create an ssl.Context using just our self-signed cert as the CA certificates
|
||||
options = ssl.create_default_context(cadata=open(cert_path, 'r').read())
|
||||
# ...which we pass as "ssl=" to ApplicationRunner (passed to loop.create_connection)
|
||||
runner = ApplicationRunner(
|
||||
environ.get("AUTOBAHN_DEMO_ROUTER", "wss://127.0.0.1:8083/ws"),
|
||||
u"crossbardemo",
|
||||
ssl=options, # try removing this, but still use self-signed cert
|
||||
debug_wamp=False, # optional; log many WAMP details
|
||||
debug=False, # optional; log even more details
|
||||
)
|
||||
runner.run(Component)
|
|
@ -24,17 +24,18 @@
|
|||
#
|
||||
###############################################################################
|
||||
|
||||
from os import environ
|
||||
|
||||
try:
|
||||
import asyncio
|
||||
except ImportError:
|
||||
# Trollius >= 0.3 was renamed
|
||||
import trollius as asyncio
|
||||
|
||||
from autobahn.asyncio.wamp import ApplicationSession
|
||||
from autobahn.asyncio.wamp import ApplicationSession, ApplicationRunner
|
||||
|
||||
|
||||
class Component(ApplicationSession):
|
||||
|
||||
"""
|
||||
An application component that subscribes and receives events.
|
||||
After receiving 5 events, it unsubscribes, sleeps and then
|
||||
|
@ -43,10 +44,9 @@ class Component(ApplicationSession):
|
|||
|
||||
@asyncio.coroutine
|
||||
def test(self):
|
||||
|
||||
self.received = 0
|
||||
|
||||
# @asyncio.coroutine
|
||||
@asyncio.coroutine
|
||||
def on_event(i):
|
||||
print("Got event: {}".format(i))
|
||||
self.received += 1
|
||||
|
@ -55,21 +55,30 @@ class Component(ApplicationSession):
|
|||
if self.runs > 1:
|
||||
self.leave()
|
||||
else:
|
||||
self.subscription.unsubscribe()
|
||||
# yield from self.subscription.unsubscribe()
|
||||
print("Unsubscribed .. continue in 2s ..")
|
||||
yield from self.subscription.unsubscribe()
|
||||
|
||||
# FIXME
|
||||
asyncio.get_event_loop().call_later(2, self.test)
|
||||
print("Unsubscribed .. continue in 5s ..")
|
||||
# can't use loop.call_later() with a coroutine for some reason
|
||||
yield from asyncio.sleep(5)
|
||||
yield from self.test()
|
||||
|
||||
self.subscription = yield from self.subscribe(on_event, 'com.myapp.topic1')
|
||||
print("Subscribed with subscription ID {}".format(self.subscription.id))
|
||||
|
||||
@asyncio.coroutine
|
||||
def onJoin(self, details):
|
||||
|
||||
self.runs = 0
|
||||
yield from self.test()
|
||||
|
||||
def onDisconnect(self):
|
||||
asyncio.get_event_loop().stop()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
runner = ApplicationRunner(
|
||||
environ.get("AUTOBAHN_DEMO_ROUTER", "ws://127.0.0.1:8080/ws"),
|
||||
u"crossbardemo",
|
||||
debug_wamp=False, # optional; log many WAMP details
|
||||
debug=False, # optional; log even more details
|
||||
)
|
||||
runner.run(Component)
|
|
@ -24,15 +24,23 @@
|
|||
#
|
||||
###############################################################################
|
||||
|
||||
from autobahn.asyncio.wamp import ApplicationSession
|
||||
try:
|
||||
import asyncio
|
||||
except ImportError:
|
||||
# Trollius >= 0.3 was renamed
|
||||
import trollius as asyncio
|
||||
|
||||
from os import environ
|
||||
from autobahn.asyncio.wamp import ApplicationSession, ApplicationRunner
|
||||
|
||||
|
||||
class Component(ApplicationSession):
|
||||
|
||||
"""
|
||||
An application component providing procedures with different kinds of arguments.
|
||||
An application component providing procedures with different kinds
|
||||
of arguments.
|
||||
"""
|
||||
|
||||
@asyncio.coroutine
|
||||
def onJoin(self, details):
|
||||
|
||||
def ping():
|
||||
|
@ -51,8 +59,19 @@ class Component(ApplicationSession):
|
|||
def arglen(*args, **kwargs):
|
||||
return [len(args), len(kwargs)]
|
||||
|
||||
self.register(ping, u'com.arguments.ping')
|
||||
self.register(add2, u'com.arguments.add2')
|
||||
self.register(stars, u'com.arguments.stars')
|
||||
self.register(orders, u'com.arguments.orders')
|
||||
self.register(arglen, u'com.arguments.arglen')
|
||||
yield from self.register(ping, u'com.arguments.ping')
|
||||
yield from self.register(add2, u'com.arguments.add2')
|
||||
yield from self.register(stars, u'com.arguments.stars')
|
||||
yield from self.register(orders, u'com.arguments.orders')
|
||||
yield from self.register(arglen, u'com.arguments.arglen')
|
||||
print("Registered methods; ready for frontend.")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
runner = ApplicationRunner(
|
||||
environ.get("AUTOBAHN_DEMO_ROUTER", "ws://127.0.0.1:8080/ws"),
|
||||
u"crossbardemo",
|
||||
debug_wamp=False, # optional; log many WAMP details
|
||||
debug=False, # optional; log even more details
|
||||
)
|
||||
runner.run(Component)
|
|
@ -30,11 +30,11 @@ except ImportError:
|
|||
# Trollius >= 0.3 was renamed
|
||||
import trollius as asyncio
|
||||
|
||||
from autobahn.asyncio.wamp import ApplicationSession
|
||||
from os import environ
|
||||
from autobahn.asyncio.wamp import ApplicationSession, ApplicationRunner
|
||||
|
||||
|
||||
class Component(ApplicationSession):
|
||||
|
||||
"""
|
||||
An application component calling the different backend procedures.
|
||||
"""
|
||||
|
@ -82,3 +82,13 @@ class Component(ApplicationSession):
|
|||
|
||||
def onDisconnect(self):
|
||||
asyncio.get_event_loop().stop()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
runner = ApplicationRunner(
|
||||
environ.get("AUTOBAHN_DEMO_ROUTER", "ws://127.0.0.1:8080/ws"),
|
||||
u"crossbardemo",
|
||||
debug_wamp=False, # optional; log many WAMP details
|
||||
debug=False, # optional; log even more details
|
||||
)
|
||||
runner.run(Component)
|
|
@ -30,26 +30,37 @@ except ImportError:
|
|||
# Trollius >= 0.3 was renamed
|
||||
import trollius as asyncio
|
||||
|
||||
from os import environ
|
||||
from autobahn.wamp.types import CallResult
|
||||
from autobahn.asyncio.wamp import ApplicationSession
|
||||
from autobahn.asyncio.wamp import ApplicationSession, ApplicationRunner
|
||||
|
||||
|
||||
class Component(ApplicationSession):
|
||||
|
||||
"""
|
||||
Application component that provides procedures which
|
||||
return complex results.
|
||||
"""
|
||||
|
||||
@asyncio.coroutine
|
||||
def onJoin(self, details):
|
||||
|
||||
def add_complex(a, ai, b, bi):
|
||||
return CallResult(c=a + b, ci=ai + bi)
|
||||
|
||||
self.register(add_complex, 'com.myapp.add_complex')
|
||||
yield from self.register(add_complex, 'com.myapp.add_complex')
|
||||
|
||||
def split_name(fullname):
|
||||
forename, surname = fullname.split()
|
||||
return CallResult(forename, surname)
|
||||
|
||||
self.register(split_name, 'com.myapp.split_name')
|
||||
yield from self.register(split_name, 'com.myapp.split_name')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
runner = ApplicationRunner(
|
||||
environ.get("AUTOBAHN_DEMO_ROUTER", "ws://127.0.0.1:8080/ws"),
|
||||
u"crossbardemo",
|
||||
debug_wamp=False, # optional; log many WAMP details
|
||||
debug=False, # optional; log even more details
|
||||
)
|
||||
runner.run(Component)
|
|
@ -30,12 +30,12 @@ except ImportError:
|
|||
# Trollius >= 0.3 was renamed
|
||||
import trollius as asyncio
|
||||
|
||||
from os import environ
|
||||
from autobahn.wamp.types import CallResult
|
||||
from autobahn.asyncio.wamp import ApplicationSession
|
||||
from autobahn.asyncio.wamp import ApplicationSession, ApplicationRunner
|
||||
|
||||
|
||||
class Component(ApplicationSession):
|
||||
|
||||
"""
|
||||
Application component that calls procedures which
|
||||
produce complex results and showing how to access those.
|
||||
|
@ -54,3 +54,13 @@ class Component(ApplicationSession):
|
|||
|
||||
def onDisconnect(self):
|
||||
asyncio.get_event_loop().stop()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
runner = ApplicationRunner(
|
||||
environ.get("AUTOBAHN_DEMO_ROUTER", "ws://127.0.0.1:8080/ws"),
|
||||
u"crossbardemo",
|
||||
debug_wamp=False, # optional; log many WAMP details
|
||||
debug=False, # optional; log even more details
|
||||
)
|
||||
runner.run(Component)
|
|
@ -24,6 +24,7 @@
|
|||
#
|
||||
###############################################################################
|
||||
|
||||
from os import environ
|
||||
import datetime
|
||||
|
||||
try:
|
||||
|
@ -33,11 +34,10 @@ except ImportError:
|
|||
import trollius as asyncio
|
||||
|
||||
from autobahn import wamp
|
||||
from autobahn.asyncio.wamp import ApplicationSession
|
||||
from autobahn.asyncio.wamp import ApplicationSession, ApplicationRunner
|
||||
|
||||
|
||||
class Component(ApplicationSession):
|
||||
|
||||
"""
|
||||
An application component registering RPC endpoints using decorators.
|
||||
"""
|
||||
|
@ -71,3 +71,13 @@ class Component(ApplicationSession):
|
|||
return float(x) / float(y)
|
||||
else:
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
runner = ApplicationRunner(
|
||||
environ.get("AUTOBAHN_DEMO_ROUTER", "ws://127.0.0.1:8080/ws"),
|
||||
u"crossbardemo",
|
||||
debug_wamp=False, # optional; log many WAMP details
|
||||
debug=False, # optional; log even more details
|
||||
)
|
||||
runner.run(Component)
|
|
@ -30,11 +30,11 @@ except ImportError:
|
|||
# Trollius >= 0.3 was renamed
|
||||
import trollius as asyncio
|
||||
|
||||
from autobahn.asyncio.wamp import ApplicationSession
|
||||
from os import environ
|
||||
from autobahn.asyncio.wamp import ApplicationSession, ApplicationRunner
|
||||
|
||||
|
||||
class Component(ApplicationSession):
|
||||
|
||||
"""
|
||||
An application component calling the different backend procedures.
|
||||
"""
|
||||
|
@ -57,3 +57,13 @@ class Component(ApplicationSession):
|
|||
|
||||
def onDisconnect(self):
|
||||
asyncio.get_event_loop().stop()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
runner = ApplicationRunner(
|
||||
environ.get("AUTOBAHN_DEMO_ROUTER", "ws://127.0.0.1:8080/ws"),
|
||||
u"crossbardemo",
|
||||
debug_wamp=False, # optional; log many WAMP details
|
||||
debug=False, # optional; log even more details
|
||||
)
|
||||
runner.run(Component)
|
|
@ -24,6 +24,7 @@
|
|||
#
|
||||
###############################################################################
|
||||
|
||||
from os import environ
|
||||
import math
|
||||
|
||||
try:
|
||||
|
@ -34,12 +35,11 @@ except ImportError:
|
|||
|
||||
from autobahn import wamp
|
||||
from autobahn.wamp.exception import ApplicationError
|
||||
from autobahn.asyncio.wamp import ApplicationSession
|
||||
from autobahn.asyncio.wamp import ApplicationSession, ApplicationRunner
|
||||
|
||||
|
||||
@wamp.error("com.myapp.error1")
|
||||
class AppError1(Exception):
|
||||
|
||||
"""
|
||||
An application specific exception that is decorated with a WAMP URI,
|
||||
and hence can be automapped by Autobahn.
|
||||
|
@ -47,11 +47,11 @@ class AppError1(Exception):
|
|||
|
||||
|
||||
class Component(ApplicationSession):
|
||||
|
||||
"""
|
||||
Example WAMP application backend that raised exceptions.
|
||||
"""
|
||||
|
||||
@asyncio.coroutine
|
||||
def onJoin(self, details):
|
||||
|
||||
# raising standard exceptions
|
||||
|
@ -63,7 +63,7 @@ class Component(ApplicationSession):
|
|||
# this also will raise, if x < 0
|
||||
return math.sqrt(x)
|
||||
|
||||
self.register(sqrt, 'com.myapp.sqrt')
|
||||
yield from self.register(sqrt, 'com.myapp.sqrt')
|
||||
|
||||
# raising WAMP application exceptions
|
||||
##
|
||||
|
@ -79,7 +79,7 @@ class Component(ApplicationSession):
|
|||
# forward keyword arguments in exceptions
|
||||
raise ApplicationError("com.myapp.error.invalid_length", min=3, max=10)
|
||||
|
||||
self.register(checkname, 'com.myapp.checkname')
|
||||
yield from self.register(checkname, 'com.myapp.checkname')
|
||||
|
||||
# defining and automapping WAMP application exceptions
|
||||
##
|
||||
|
@ -89,4 +89,14 @@ class Component(ApplicationSession):
|
|||
if a < b:
|
||||
raise AppError1(b - a)
|
||||
|
||||
self.register(compare, 'com.myapp.compare')
|
||||
yield from self.register(compare, 'com.myapp.compare')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
runner = ApplicationRunner(
|
||||
environ.get("AUTOBAHN_DEMO_ROUTER", "ws://127.0.0.1:8080/ws"),
|
||||
u"crossbardemo",
|
||||
debug_wamp=False, # optional; log many WAMP details
|
||||
debug=False, # optional; log even more details
|
||||
)
|
||||
runner.run(Component)
|
|
@ -24,6 +24,7 @@
|
|||
#
|
||||
###############################################################################
|
||||
|
||||
from os import environ
|
||||
import math
|
||||
|
||||
try:
|
||||
|
@ -34,12 +35,11 @@ except ImportError:
|
|||
|
||||
from autobahn import wamp
|
||||
from autobahn.wamp.exception import ApplicationError
|
||||
from autobahn.asyncio.wamp import ApplicationSession
|
||||
from autobahn.asyncio.wamp import ApplicationSession, ApplicationRunner
|
||||
|
||||
|
||||
@wamp.error("com.myapp.error1")
|
||||
class AppError1(Exception):
|
||||
|
||||
"""
|
||||
An application specific exception that is decorated with a WAMP URI,
|
||||
and hence can be automapped by Autobahn.
|
||||
|
@ -47,7 +47,6 @@ class AppError1(Exception):
|
|||
|
||||
|
||||
class Component(ApplicationSession):
|
||||
|
||||
"""
|
||||
Example WAMP application frontend that catches exceptions.
|
||||
"""
|
||||
|
@ -84,7 +83,17 @@ class Component(ApplicationSession):
|
|||
except AppError1 as e:
|
||||
print("Compare Error: {}".format(e))
|
||||
|
||||
self.leave()
|
||||
yield from self.leave()
|
||||
|
||||
def onDisconnect(self):
|
||||
asyncio.get_event_loop().stop()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
runner = ApplicationRunner(
|
||||
environ.get("AUTOBAHN_DEMO_ROUTER", "ws://127.0.0.1:8080/ws"),
|
||||
u"crossbardemo",
|
||||
debug_wamp=False, # optional; log many WAMP details
|
||||
debug=False, # optional; log even more details
|
||||
)
|
||||
runner.run(Component)
|
|
@ -30,17 +30,18 @@ except ImportError:
|
|||
# Trollius >= 0.3 was renamed
|
||||
import trollius as asyncio
|
||||
|
||||
from os import environ
|
||||
from autobahn.wamp.types import CallOptions, RegisterOptions, PublishOptions
|
||||
from autobahn.asyncio.wamp import ApplicationSession
|
||||
from autobahn.asyncio.wamp import ApplicationSession, ApplicationRunner
|
||||
|
||||
|
||||
class Component(ApplicationSession):
|
||||
|
||||
"""
|
||||
An application component providing procedures with
|
||||
different kinds of arguments.
|
||||
"""
|
||||
|
||||
@asyncio.coroutine
|
||||
def onJoin(self, details):
|
||||
|
||||
def square(val, details=None):
|
||||
|
@ -56,4 +57,14 @@ class Component(ApplicationSession):
|
|||
self.publish('com.myapp.square_on_nonpositive', val, options=options)
|
||||
return val * val
|
||||
|
||||
self.register(square, 'com.myapp.square', RegisterOptions(details_arg='details'))
|
||||
yield from self.register(square, 'com.myapp.square', RegisterOptions(details_arg='details'))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
runner = ApplicationRunner(
|
||||
environ.get("AUTOBAHN_DEMO_ROUTER", "ws://127.0.0.1:8080/ws"),
|
||||
u"crossbardemo",
|
||||
debug_wamp=False, # optional; log many WAMP details
|
||||
debug=False, # optional; log even more details
|
||||
)
|
||||
runner.run(Component)
|
|
@ -30,12 +30,12 @@ except ImportError:
|
|||
# Trollius >= 0.3 was renamed
|
||||
import trollius as asyncio
|
||||
|
||||
from os import environ
|
||||
from autobahn.wamp.types import CallOptions, RegisterOptions, PublishOptions
|
||||
from autobahn.asyncio.wamp import ApplicationSession
|
||||
from autobahn.asyncio.wamp import ApplicationSession, ApplicationRunner
|
||||
|
||||
|
||||
class Component(ApplicationSession):
|
||||
|
||||
"""
|
||||
An application component calling the different backend procedures.
|
||||
"""
|
||||
|
@ -49,10 +49,20 @@ class Component(ApplicationSession):
|
|||
yield from self.subscribe(on_event, 'com.myapp.square_on_nonpositive')
|
||||
|
||||
for val in [2, 0, -2]:
|
||||
res = yield from self.call('com.myapp.square', val, options=CallOptions(discloseMe=True))
|
||||
res = yield from self.call('com.myapp.square', val, options=CallOptions(disclose_me=True))
|
||||
print("Squared {} = {}".format(val, res))
|
||||
|
||||
self.leave()
|
||||
yield from self.leave()
|
||||
|
||||
def onDisconnect(self):
|
||||
asyncio.get_event_loop().stop()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
runner = ApplicationRunner(
|
||||
environ.get("AUTOBAHN_DEMO_ROUTER", "ws://127.0.0.1:8080/ws"),
|
||||
u"crossbardemo",
|
||||
debug_wamp=False, # optional; log many WAMP details
|
||||
debug=False, # optional; log even more details
|
||||
)
|
||||
runner.run(Component)
|
|
@ -30,16 +30,17 @@ except ImportError:
|
|||
# Trollius >= 0.3 was renamed
|
||||
import trollius as asyncio
|
||||
|
||||
from os import environ
|
||||
from autobahn.wamp.types import CallOptions, RegisterOptions
|
||||
from autobahn.asyncio.wamp import ApplicationSession
|
||||
from autobahn.asyncio.wamp import ApplicationSession, ApplicationRunner
|
||||
|
||||
|
||||
class Component(ApplicationSession):
|
||||
|
||||
"""
|
||||
Application component that produces progressive results.
|
||||
"""
|
||||
|
||||
@asyncio.coroutine
|
||||
def onJoin(self, details):
|
||||
|
||||
@asyncio.coroutine
|
||||
|
@ -52,4 +53,14 @@ class Component(ApplicationSession):
|
|||
yield from asyncio.sleep(1 * n)
|
||||
return n
|
||||
|
||||
self.register(longop, 'com.myapp.longop', RegisterOptions(details_arg='details'))
|
||||
yield from self.register(longop, 'com.myapp.longop', RegisterOptions(details_arg='details'))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
runner = ApplicationRunner(
|
||||
environ.get("AUTOBAHN_DEMO_ROUTER", "ws://127.0.0.1:8080/ws"),
|
||||
u"crossbardemo",
|
||||
debug_wamp=False, # optional; log many WAMP details
|
||||
debug=False, # optional; log even more details
|
||||
)
|
||||
runner.run(Component)
|
|
@ -30,12 +30,12 @@ except ImportError:
|
|||
# Trollius >= 0.3 was renamed
|
||||
import trollius as asyncio
|
||||
|
||||
from os import environ
|
||||
from autobahn.wamp.types import CallOptions, RegisterOptions
|
||||
from autobahn.asyncio.wamp import ApplicationSession
|
||||
from autobahn.asyncio.wamp import ApplicationSession, ApplicationRunner
|
||||
|
||||
|
||||
class Component(ApplicationSession):
|
||||
|
||||
"""
|
||||
Application component that consumes progressive results.
|
||||
"""
|
||||
|
@ -54,3 +54,13 @@ class Component(ApplicationSession):
|
|||
|
||||
def onDisconnect(self):
|
||||
asyncio.get_event_loop().stop()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
runner = ApplicationRunner(
|
||||
environ.get("AUTOBAHN_DEMO_ROUTER", "ws://127.0.0.1:8080/ws"),
|
||||
u"crossbardemo",
|
||||
debug_wamp=False, # optional; log many WAMP details
|
||||
debug=False, # optional; log even more details
|
||||
)
|
||||
runner.run(Component)
|
|
@ -30,25 +30,37 @@ except ImportError:
|
|||
# Trollius >= 0.3 was renamed
|
||||
import trollius as asyncio
|
||||
|
||||
from autobahn.asyncio.wamp import ApplicationSession
|
||||
from os import environ
|
||||
from autobahn.asyncio.wamp import ApplicationSession, ApplicationRunner
|
||||
|
||||
|
||||
class Component(ApplicationSession):
|
||||
|
||||
"""
|
||||
A math service application component.
|
||||
"""
|
||||
|
||||
@asyncio.coroutine
|
||||
def onJoin(self, details):
|
||||
|
||||
def square(x):
|
||||
return x * x
|
||||
|
||||
self.register(square, 'com.math.square')
|
||||
yield from self.register(square, 'com.math.square')
|
||||
|
||||
@asyncio.coroutine
|
||||
def slowsquare(x, delay=1):
|
||||
yield from asyncio.sleep(delay)
|
||||
return x * x
|
||||
|
||||
self.register(slowsquare, 'com.math.slowsquare')
|
||||
yield from self.register(slowsquare, 'com.math.slowsquare')
|
||||
print("Registered 'com.math.slowsquare'")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
runner = ApplicationRunner(
|
||||
environ.get("AUTOBAHN_DEMO_ROUTER", "ws://127.0.0.1:8080/ws"),
|
||||
u"crossbardemo",
|
||||
debug_wamp=False, # optional; log many WAMP details
|
||||
debug=False, # optional; log even more details
|
||||
)
|
||||
runner.run(Component)
|
|
@ -24,6 +24,7 @@
|
|||
#
|
||||
###############################################################################
|
||||
|
||||
from os import environ
|
||||
import time
|
||||
|
||||
try:
|
||||
|
@ -34,11 +35,10 @@ except ImportError:
|
|||
|
||||
from functools import partial
|
||||
|
||||
from autobahn.asyncio.wamp import ApplicationSession
|
||||
from autobahn.asyncio.wamp import ApplicationSession, ApplicationRunner
|
||||
|
||||
|
||||
class Component(ApplicationSession):
|
||||
|
||||
"""
|
||||
An application component using the time service.
|
||||
"""
|
||||
|
@ -65,3 +65,13 @@ class Component(ApplicationSession):
|
|||
|
||||
def onDisconnect(self):
|
||||
asyncio.get_event_loop().stop()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
runner = ApplicationRunner(
|
||||
environ.get("AUTOBAHN_DEMO_ROUTER", "ws://127.0.0.1:8080/ws"),
|
||||
u"crossbardemo",
|
||||
debug_wamp=False, # optional; log many WAMP details
|
||||
debug=False, # optional; log even more details
|
||||
)
|
||||
runner.run(Component)
|
|
@ -24,30 +24,38 @@
|
|||
#
|
||||
###############################################################################
|
||||
|
||||
try:
|
||||
import asyncio
|
||||
except ImportError:
|
||||
# Trollius >= 0.3 was renamed
|
||||
import trollius as asyncio
|
||||
|
||||
from os import environ
|
||||
import datetime
|
||||
|
||||
from twisted.internet import reactor
|
||||
from twisted.internet.defer import inlineCallbacks
|
||||
|
||||
from autobahn.twisted.wamp import ApplicationSession
|
||||
from autobahn.asyncio.wamp import ApplicationSession, ApplicationRunner
|
||||
|
||||
|
||||
class Component(ApplicationSession):
|
||||
|
||||
"""
|
||||
A simple time service application component.
|
||||
"""
|
||||
|
||||
@asyncio.coroutine
|
||||
def onJoin(self, details):
|
||||
|
||||
def utcnow():
|
||||
now = datetime.datetime.utcnow()
|
||||
return now.strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
|
||||
self.register(utcnow, 'com.timeservice.now')
|
||||
yield from self.register(utcnow, 'com.timeservice.now')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from autobahn.twisted.wamp import ApplicationRunner
|
||||
runner = ApplicationRunner("ws://127.0.0.1:8080/ws", "realm1")
|
||||
runner = ApplicationRunner(
|
||||
environ.get("AUTOBAHN_DEMO_ROUTER", "ws://127.0.0.1:8080/ws"),
|
||||
u"crossbardemo",
|
||||
debug_wamp=False, # optional; log many WAMP details
|
||||
debug=False, # optional; log even more details
|
||||
)
|
||||
runner.run(Component)
|
|
@ -24,6 +24,7 @@
|
|||
#
|
||||
###############################################################################
|
||||
|
||||
from os import environ
|
||||
import datetime
|
||||
|
||||
try:
|
||||
|
@ -32,11 +33,10 @@ except ImportError:
|
|||
# Trollius >= 0.3 was renamed
|
||||
import trollius as asyncio
|
||||
|
||||
from autobahn.asyncio.wamp import ApplicationSession
|
||||
from autobahn.asyncio.wamp import ApplicationSession, ApplicationRunner
|
||||
|
||||
|
||||
class Component(ApplicationSession):
|
||||
|
||||
"""
|
||||
An application component using the time service.
|
||||
"""
|
||||
|
@ -54,3 +54,13 @@ class Component(ApplicationSession):
|
|||
|
||||
def onDisconnect(self):
|
||||
asyncio.get_event_loop().stop()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
runner = ApplicationRunner(
|
||||
environ.get("AUTOBAHN_DEMO_ROUTER", "ws://127.0.0.1:8080/ws"),
|
||||
u"crossbardemo",
|
||||
debug_wamp=False, # optional; log many WAMP details
|
||||
debug=False, # optional; log even more details
|
||||
)
|
||||
runner.run(Component)
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue