Merge remote-tracking branch 'upstream/master' into more-twisted-tox-versions

This commit is contained in:
HawkOwl 2015-08-14 11:30:52 +08:00
commit 8f253822d6
328 changed files with 3943 additions and 6556 deletions

57
.gitignore vendored
View File

@ -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

View File

@ -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

View File

@ -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):

View File

@ -24,5 +24,5 @@
#
###############################################################################
__version__ = "0.10.2"
__version__ = "0.10.5-2"
version = __version__ # backward compat.

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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

View File

@ -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:

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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)

View File

@ -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):

View File

@ -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):

View File

@ -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):
"""

View File

@ -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:

View File

@ -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)

View File

@ -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

View File

@ -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):
"""

View File

@ -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)

View File

@ -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_')

View File

@ -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:

View File

@ -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()

View File

@ -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)))

View File

@ -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)

View File

@ -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,

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

BIN
doc/_static/wamp-demos.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

View File

@ -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:
-------

View File

@ -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
------

View File

@ -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

View File

@ -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

View File

@ -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|:

View File

@ -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

View File

@ -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>

View File

@ -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>`__.

View File

@ -14,3 +14,6 @@ flake8:
pylint:
pylint -d line-too-long,invalid-name .
examples:
python run-all-examples.py

View File

@ -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).

View File

@ -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)

View File

@ -1 +0,0 @@
node_modules

View File

@ -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

View File

@ -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()

View File

@ -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.
#
###############################################################################

View File

@ -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.
#
###############################################################################

View File

@ -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.
#
###############################################################################

View File

@ -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.
#
###############################################################################

View File

@ -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.
#
###############################################################################

View File

@ -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.
#
###############################################################################

View File

@ -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.
#
###############################################################################

View File

@ -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.
#
###############################################################################

View File

@ -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.
#
###############################################################################

View File

@ -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.
#
###############################################################################

View File

@ -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.
#
###############################################################################

View File

@ -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.
#
###############################################################################

View File

@ -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.
#
###############################################################################

View File

@ -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.
#
###############################################################################

View File

@ -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.
#
###############################################################################

View File

@ -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()

View File

@ -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.
#
###############################################################################

View File

@ -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.
#
###############################################################################

View File

@ -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')

View File

@ -1,5 +0,0 @@
test_server:
PYTHONPATH="../../../../autobahn" ~/python34/bin/python3 server.py
test_client:
PYTHONPATH="../../../../autobahn" ~/python34/bin/python3 client.py

View File

@ -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()

View File

@ -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()

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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.

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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