diff --git a/.gitignore b/.gitignore index 2e8057d2..6b5e006f 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/Makefile b/Makefile index 8ecbca45..37095d7d 100644 --- a/Makefile +++ b/Makefile @@ -55,6 +55,12 @@ test_twisted_coverage: coverage html coverage report --show-missing +test_coverage: + -rm .coverage + tox -e py27twisted,py27asyncio,py34asyncio + coverage html + coverage report --show-missing + # test under asyncio test_asyncio: USE_ASYNCIO=1 python -m pytest -rsx diff --git a/README.md b/README.md index 65d0f5d8..fb803ab7 100644 --- a/README.md +++ b/README.md @@ -19,13 +19,19 @@ WAMP provides asynchronous **Remote Procedure Calls** and **Publish & Subscribe* It is ideal for distributed, multi-client and server applications, such as multi-user database-drive business applications, sensor networks (IoT), instant messaging or MMOGs (massively multi-player online games) . -WAMP enables application architectures with application code distributed freely across processes and devices according to functional aspects. Since WAMP implementations exist for multiple languages, WAMP applications can be polyglott. Application components can be implemented in a language and run on a device which best fit the particular use case. +WAMP enables application architectures with application code distributed freely across processes and devices according to functional aspects. Since WAMP implementations exist for multiple languages, WAMP applications can be polyglot. Application components can be implemented in a language and run on a device which best fit the particular use case. + +**Note** that WAMP is a *routed* protocol, so you need to run something that plays the Broker and Dealer roles from the [WAMP Specification](http://wamp.ws/spec/). We provide [Crossbar.io](http://crossbar.io) but there are [other options](http://wamp.ws/implementations/#routers) as well. + ## Show me some code A simple WebSocket echo server: ```python +from autobahn.twisted.websocket import WebSocketServerProtocol +# or: from autobahn.asyncio.websocket import WebSocketServerProtocol + class MyServerProtocol(WebSocketServerProtocol): def onConnect(self, request): @@ -50,6 +56,8 @@ class MyServerProtocol(WebSocketServerProtocol): ... and a sample WAMP application component: ```python +from autobahn.twisted.wamp import ApplicationSession +# or: from autobahn.asyncio.wamp import ApplicationSession class MyComponent(ApplicationSession): diff --git a/autobahn/__init__.py b/autobahn/__init__.py index ebe97e9a..73e3f9f2 100644 --- a/autobahn/__init__.py +++ b/autobahn/__init__.py @@ -24,5 +24,5 @@ # ############################################################################### -__version__ = "0.10.2" +__version__ = "0.10.5-2" version = __version__ # backward compat. diff --git a/autobahn/asyncio/wamp.py b/autobahn/asyncio/wamp.py index 7e58082f..092847ea 100644 --- a/autobahn/asyncio/wamp.py +++ b/autobahn/asyncio/wamp.py @@ -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() diff --git a/autobahn/asyncio/websocket.py b/autobahn/asyncio/websocket.py index dd4163ca..4e33c8ec 100644 --- a/autobahn/asyncio/websocket.py +++ b/autobahn/asyncio/websocket.py @@ -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() diff --git a/examples/twisted/wamp/longpoll/test.py b/autobahn/logger.py similarity index 76% rename from examples/twisted/wamp/longpoll/test.py rename to autobahn/logger.py index 9e63e28c..224a073b 100644 --- a/examples/twisted/wamp/longpoll/test.py +++ b/autobahn/logger.py @@ -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() diff --git a/autobahn/twisted/choosereactor.py b/autobahn/twisted/choosereactor.py index 932a3ce0..6fa813d4 100644 --- a/autobahn/twisted/choosereactor.py +++ b/autobahn/twisted/choosereactor.py @@ -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 diff --git a/autobahn/twisted/longpoll.py b/autobahn/twisted/longpoll.py index 4d9b31ea..33ac314f 100644 --- a/autobahn/twisted/longpoll.py +++ b/autobahn/twisted/longpoll.py @@ -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 = {} # /open - ## + # self.putChild("open", WampLongPollResourceOpen(self)) if self._debug: diff --git a/autobahn/twisted/rawsocket.py b/autobahn/twisted/rawsocket.py index a5c895ee..56458d08 100644 --- a/autobahn/twisted/rawsocket.py +++ b/autobahn/twisted/rawsocket.py @@ -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 diff --git a/autobahn/twisted/resource.py b/autobahn/twisted/resource.py index 42106068..a52d3a7a 100644 --- a/autobahn/twisted/resource.py +++ b/autobahn/twisted/resource.py @@ -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 diff --git a/autobahn/twisted/test/test_application_runner.py b/autobahn/twisted/test/test_application_runner.py index 62ac4c30..42f8c17c 100644 --- a/autobahn/twisted/test/test_application_runner.py +++ b/autobahn/twisted/test/test_application_runner.py @@ -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() diff --git a/autobahn/twisted/wamp.py b/autobahn/twisted/wamp.py index c88ab625..7a242328 100644 --- a/autobahn/twisted/wamp.py +++ b/autobahn/twisted/wamp.py @@ -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) diff --git a/autobahn/twisted/websocket.py b/autobahn/twisted/websocket.py index 11379aeb..7a84e5bc 100644 --- a/autobahn/twisted/websocket.py +++ b/autobahn/twisted/websocket.py @@ -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): diff --git a/autobahn/util.py b/autobahn/util.py index dde10bca..a366f8f0 100644 --- a/autobahn/util.py +++ b/autobahn/util.py @@ -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): diff --git a/autobahn/wamp/interfaces.py b/autobahn/wamp/interfaces.py index ccfceadf..edb87543 100644 --- a/autobahn/wamp/interfaces.py +++ b/autobahn/wamp/interfaces.py @@ -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): """ diff --git a/autobahn/wamp/message.py b/autobahn/wamp/message.py index a63d54b5..e8ac6e67 100644 --- a/autobahn/wamp/message.py +++ b/autobahn/wamp/message.py @@ -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: diff --git a/autobahn/wamp/protocol.py b/autobahn/wamp/protocol.py index 96ebc3ad..0f57e112 100644 --- a/autobahn/wamp/protocol.py +++ b/autobahn/wamp/protocol.py @@ -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) diff --git a/autobahn/wamp/role.py b/autobahn/wamp/role.py index 64102500..688a9fac 100644 --- a/autobahn/wamp/role.py +++ b/autobahn/wamp/role.py @@ -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 diff --git a/autobahn/wamp/serializer.py b/autobahn/wamp/serializer.py index 6721c831..37372571 100644 --- a/autobahn/wamp/serializer.py +++ b/autobahn/wamp/serializer.py @@ -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): """ diff --git a/autobahn/wamp/test/test_protocol.py b/autobahn/wamp/test/test_protocol.py index 4f1bd70c..d96f3d9c 100644 --- a/autobahn/wamp/test/test_protocol.py +++ b/autobahn/wamp/test/test_protocol.py @@ -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) diff --git a/autobahn/wamp/test/test_runner.py b/autobahn/wamp/test/test_runner.py index 807779cc..adf2ba96 100644 --- a/autobahn/wamp/test/test_runner.py +++ b/autobahn/wamp/test/test_runner.py @@ -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_') diff --git a/autobahn/wamp/test/test_serializer.py b/autobahn/wamp/test/test_serializer.py index 04046b14..f34f18a8 100644 --- a/autobahn/wamp/test/test_serializer.py +++ b/autobahn/wamp/test/test_serializer.py @@ -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: diff --git a/autobahn/wamp/test/test_user_handler_errors.py b/autobahn/wamp/test/test_user_handler_errors.py index 2165578e..2a64157c 100644 --- a/autobahn/wamp/test/test_user_handler_errors.py +++ b/autobahn/wamp/test/test_user_handler_errors.py @@ -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() diff --git a/autobahn/wamp/types.py b/autobahn/wamp/types.py index 5837dc0e..91f59eca 100644 --- a/autobahn/wamp/types.py +++ b/autobahn/wamp/types.py @@ -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))) diff --git a/autobahn/wamp/websocket.py b/autobahn/wamp/websocket.py index ed069d6e..a2a9d2c7 100644 --- a/autobahn/wamp/websocket.py +++ b/autobahn/wamp/websocket.py @@ -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) diff --git a/autobahn/websocket/compress.py b/autobahn/websocket/compress.py index 83f4a45f..8d7b58cc 100644 --- a/autobahn/websocket/compress.py +++ b/autobahn/websocket/compress.py @@ -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, diff --git a/autobahn/websocket/protocol.py b/autobahn/websocket/protocol.py index 6cf23373..64e9b582 100755 --- a/autobahn/websocket/protocol.py +++ b/autobahn/websocket/protocol.py @@ -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"\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) diff --git a/autobahn/websocket/test/test_websocket.py b/autobahn/websocket/test/test_websocket.py new file mode 100644 index 00000000..e86ffb8a --- /dev/null +++ b/autobahn/websocket/test/test_websocket.py @@ -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) diff --git a/doc/Makefile b/doc/Makefile index 503af674..4e924772 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -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 diff --git a/doc/README.md b/doc/README.md index 6b17632c..50868086 100644 --- a/doc/README.md +++ b/doc/README.md @@ -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 diff --git a/doc/SConstruct b/doc/SConstruct index ff1165be..357300d6 100644 --- a/doc/SConstruct +++ b/doc/SConstruct @@ -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 diff --git a/doc/_static/wamp-demos.png b/doc/_static/wamp-demos.png new file mode 100644 index 00000000..95bb20ac Binary files /dev/null and b/doc/_static/wamp-demos.png differ diff --git a/doc/asynchronous-programming.rst b/doc/asynchronous-programming.rst index eb26d4eb..22b90b97 100644 --- a/doc/asynchronous-programming.rst +++ b/doc/asynchronous-programming.rst @@ -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 `__ of an "Introduction to Asynchronous Programming and Twisted". +A very good technical introduction to these concepts can be found in `this chapter `_ 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 `__ -* `Glyph Lefkowitz - So Easy You Can Even Do It in JavaScript: Event-Driven Architecture for Regular Programmers `__ +* `Alex Martelli - Don't call us, we'll call you: callback patterns and idioms `_ +* `Glyph Lefkowitz - So Easy You Can Even Do It in JavaScript: Event-Driven Architecture for Regular Programmers `_ -Another highly recommended reading is `The Reactive Manifesto `__ which describes guiding principles, motivations and connects the dots +Another highly recommended reading is `The Reactive Manifesto `_ 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 `__ + -- `The Reactive Manifesto `_ The fact that |Ab| is implemented using asynchronous programming and non-blocking execution shouldn't come as a surprise, since both `Twisted `__ and `asyncio `__ - 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 `__ -* `Boost/ASIO `__ -* `Netty `__ -* `Tornado `__ -* `React `__ +* `NodeJS `_ +* `Boost/ASIO `_ +* `Netty `_ +* `Tornado `_ +* `React `_ .. 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 `__ -2. `Green Threads `__ -3. `Actors `__ -4. `Software Transactional Memory (STM) `__ +1. `OS Threads `_ +2. `Green Threads `_ +3. `Actors `_ +4. `Software Transactional Memory (STM) `_ 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 `__ -* `Akka `__ -* `Rust `__ -* `C++ Actor Framework `__ +* `Erlang `_ +* `Akka `_ +* `Rust `_ +* `C++ Actor Framework `_ -**Software Transactional Memory (STM)** applies the concept of `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 `_ 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 `__ -* `Gevent `__ -* `Stackless `__ +* `Eventlet `_ +* `Gevent `_ +* `Stackless `_ 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 `__. 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 `_. 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 `__. 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 `_. 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 `__ -* `The Twisted API Reference `__ +* `The Twisted Documentation `_ +* `The Twisted API Reference `_ and lots and lots of awesome `Twisted talks `__ 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 `__ -* `Tulip: Async I/O for Python 3 `__ -* `Python 3.4 docs - asyncio `__ -* `PEP-3156 - Asynchronous IO Support Rebooted `__ +* `Guido van Rossum's Keynote at PyCon US 2013 `_ +* `Tulip: Async I/O for Python 3 `_ +* `Python 3.4 docs - asyncio `_ +* `PEP-3156 - Asynchronous IO Support Rebooted `_ However, we quickly introduce core asynchronous programming primitives provided by `Twisted `__ and `asyncio `__: @@ -293,11 +293,11 @@ Asyncio Futures and Coroutines .............................. -`Asyncio Futures `_ 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 `__ 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 `_ are (on a certain level) quite similar to Twisted inline callbacks. Here is the code corresponding to our example above: +`Asyncio Coroutines `__ are (on a certain level) quite similar to Twisted inline callbacks. Here is the code corresponding to our example above: ------- diff --git a/doc/changelog.rst b/doc/changelog.rst index 95c93c7b..eabedd98 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -5,6 +5,29 @@ Changelog ========= +0.10.5 +------ + +`Published 2015-08-06 `__ + +* maintenance release with lots of smaller bug fixes + +0.10.4 +------ + +`Published 2015-05-08 `__ + +* maintenance release with some smaller bug fixes + +0.10.3 +------ + +`Published 2015-04-14 `__ + +* new: using txaio package +* new: revised WAMP-over-RawSocket specification implemented +* fix: ignore unknown attributes in WAMP Options/Details + 0.10.2 ------ diff --git a/doc/conf.py b/doc/conf.py index 28dcbd14..ad19b33d 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -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 diff --git a/doc/contribute.rst b/doc/contribute.rst index 5d3e1699..f5c37845 100644 --- a/doc/contribute.rst +++ b/doc/contribute.rst @@ -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 `_ gives more detailed information on how the process works. +Running the Tests +----------------- + +In order to run the unit-tests, we use `Tox `_ 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 diff --git a/doc/index.rst b/doc/index.rst index 00518d06..a4fb0086 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -1,7 +1,7 @@ |AbL| ===== -*Open-source real-time framework for Web, Mobile & Internet of Things.* +*Open-source (MIT) real-time framework for Web, Mobile & Internet of Things.* Latest release: v\ |version| (:ref:`Changelog`) @@ -25,13 +25,36 @@ Latest release: v\ |version| (:ref:`Changelog`) * `The WebSocket Protocol `_ * `The Web Application Messaging Protocol (WAMP) `_ -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 `_ on the Web while `WAMP `_ provides applications with `high-level communication abstractions `_ 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 `_ on the Web while `WAMP `_ provides applications with `high-level communication abstractions `_ (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 `_) -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 `__ - * `asyncio `__ +* `Twisted Example `__ +* `asyncio Example `__ Introduction to WAMP Programming with |ab|: diff --git a/doc/installation.rst b/doc/installation.rst index a8788e6e..2000d89f 100644 --- a/doc/installation.rst +++ b/doc/installation.rst @@ -18,6 +18,7 @@ You will need at least one of those. For Twisted installation, please see `here `__. Asyncio comes bundled with Python 3.4+. For Python 3.3, install it from `here `__. 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 `_ benchmarking |ab| running on the `RaspberryPi `_ (a tiny embedded computer) under `PyPy `_. + 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 diff --git a/doc/wamp/examples.rst b/doc/wamp/examples.rst index 9a857528..87c8f93d 100644 --- a/doc/wamp/examples.rst +++ b/doc/wamp/examples.rst @@ -3,31 +3,84 @@ WAMP Examples ============= +**NOTE** that for all examples you will **need to run a router**. We develop `Crossbar.io `_ and there are `other routers `_ available as well. We include a working `Crossbar.io `_ configuration in the `examples/router/ subdirectory `_ as well as `instructions on how to run it `_. + +Overview of Examples +++++++++++++++++++++ + +The examples are organized between `asycio `__ and `Twisted `__ 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 `__ 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 +`__ shows you how (see comments for how to run it yourself): + +.. raw:: html + + + + + Publish & Subscribe (PubSub) ++++++++++++++++++++++++++++ -* Basic `Twisted `__ - `asyncio `__ - Demonstrates basic publish and subscribe. +* Basic `Twisted `__ - `asyncio `__ - Demonstrates basic publish and subscribe. -* Complex `Twisted `__ - `asyncio `__ - Demonstrates publish and subscribe with complex events. +* Complex `Twisted `__ - `asyncio `__ - Demonstrates publish and subscribe with complex events. -* Options `Twisted `__ - `asyncio `__ - Using options with PubSub. +* Options `Twisted `__ - `asyncio `__ - Using options with PubSub. -* Unsubscribe `Twisted `__ - `asyncio `__ - Cancel a subscription to a topic. +* Unsubscribe `Twisted `__ - `asyncio `__ - Cancel a subscription to a topic. Remote Procedure Calls (RPC) ++++++++++++++++++++++++++++ -* Time Service `Twisted `__ - `asyncio `__ - A trivial time service - demonstrates basic remote procedure feature. +* Time Service `Twisted `__ - `asyncio `__ - A trivial time service - demonstrates basic remote procedure feature. -* Slow Square `Twisted `__ - `asyncio `__ - Demonstrates procedures which return promises and return asynchronously. +* Slow Square `Twisted `__ - `asyncio `__ - Demonstrates procedures which return promises and return asynchronously. -* Arguments `Twisted `__ - `asyncio `__ - Demonstrates all variants of call arguments. +* Arguments `Twisted `__ - `asyncio `__ - Demonstrates all variants of call arguments. -* Complex Result `Twisted `__ - `asyncio `__ - Demonstrates complex call results (call results with more than one positional or keyword results). +* Complex Result `Twisted `__ - `asyncio `__ - Demonstrates complex call results (call results with more than one positional or keyword results). -* Errors `Twisted `__ - `asyncio `__ - Demonstrates error raising and catching over remote procedures. +* Errors `Twisted `__ - `asyncio `__ - Demonstrates error raising and catching over remote procedures. -* Progressive Results `Twisted `__ - `asyncio `__ - Demonstrates calling remote procedures that produce progressive results. +* Progressive Results `Twisted `__ - `asyncio `__ - Demonstrates calling remote procedures that produce progressive results. -* Options `Twisted `__ - `asyncio `__ - Using options with RPC. +* Options `Twisted `__ - `asyncio `__ - 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 `_ 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 `_: + +.. raw:: html + + diff --git a/doc/wamp/programming.rst b/doc/wamp/programming.rst index ec96a3cb..3cd20895 100644 --- a/doc/wamp/programming.rst +++ b/doc/wamp/programming.rst @@ -1,7 +1,8 @@ -WAMP Programming -================ +================== + WAMP Programming +================== -This guide gives an introduction to programming with `WAMP `__ in Python using |Ab|. +This guide gives an introduction to programming with `WAMP `__ 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 `_ (there are `other routers `_ available). See also `the WAMP specification `_ 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 `__. ------ 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 ` 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 `_ as your WAMP router. - -.. tip:: - - There are other WAMP routers besides Crossbar.io as well. Please see this `list `__. +Our suggested way is to use `Crossbar.io `_ as your WAMP router. There are `other WAMP routers `_ besides Crossbar.io as well. Once you've `installed Crossbar.io `_, initialize an instance of it with the default settings, which will accept WAMP (over WebSocket) connections on ``ws://: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 ` 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 ` 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 ` 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 ` called) +3. authentication challenge received (only for authenticated WAMP sessions, :meth:`ISession.onChallenge ` called) +4. session established (realm joined, :meth:`ISession.onJoin ` called) +5. session closed (realm left, :meth:`ISession.onLeave ` called) +6. transport disconnected (:meth:`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 ` will fire the following events which you can handle by overriding the respective method (see :class:`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 `_. Please see the announcement `here `__. \ No newline at end of file +Starting with release 0.9.4, all WAMP router code in |Ab| has been split out and moved to `Crossbar.io `_. Please see the announcement `here `__. diff --git a/examples/Makefile b/examples/Makefile index a513e180..02d0d5e3 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -14,3 +14,6 @@ flake8: pylint: pylint -d line-too-long,invalid-name . + +examples: + python run-all-examples.py diff --git a/examples/README.md b/examples/README.md index 16816a59..06c251ce 100644 --- a/examples/README.md +++ b/examples/README.md @@ -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) - \ No newline at end of file +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). diff --git a/examples/asciinema-autobahn-demo.py b/examples/asciinema-autobahn-demo.py new file mode 100755 index 00000000..0681c429 --- /dev/null +++ b/examples/asciinema-autobahn-demo.py @@ -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) diff --git a/examples/asyncio/wamp/basic/.gitignore b/examples/asyncio/wamp/basic/.gitignore deleted file mode 100644 index 3c3629e6..00000000 --- a/examples/asyncio/wamp/basic/.gitignore +++ /dev/null @@ -1 +0,0 @@ -node_modules diff --git a/examples/asyncio/wamp/basic/Makefile b/examples/asyncio/wamp/basic/Makefile deleted file mode 100644 index 611bdaa7..00000000 --- a/examples/asyncio/wamp/basic/Makefile +++ /dev/null @@ -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 diff --git a/examples/asyncio/wamp/basic/client.py b/examples/asyncio/wamp/basic/client.py deleted file mode 100644 index 53837f73..00000000 --- a/examples/asyncio/wamp/basic/client.py +++ /dev/null @@ -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() diff --git a/examples/asyncio/wamp/basic/pubsub/__init__.py b/examples/asyncio/wamp/basic/pubsub/__init__.py deleted file mode 100644 index 8a4c67bf..00000000 --- a/examples/asyncio/wamp/basic/pubsub/__init__.py +++ /dev/null @@ -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. -# -############################################################################### diff --git a/examples/asyncio/wamp/basic/pubsub/basic/__init__.py b/examples/asyncio/wamp/basic/pubsub/basic/__init__.py deleted file mode 100644 index 8a4c67bf..00000000 --- a/examples/asyncio/wamp/basic/pubsub/basic/__init__.py +++ /dev/null @@ -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. -# -############################################################################### diff --git a/examples/asyncio/wamp/basic/pubsub/complex/__init__.py b/examples/asyncio/wamp/basic/pubsub/complex/__init__.py deleted file mode 100644 index 8a4c67bf..00000000 --- a/examples/asyncio/wamp/basic/pubsub/complex/__init__.py +++ /dev/null @@ -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. -# -############################################################################### diff --git a/examples/asyncio/wamp/basic/pubsub/decorators/__init__.py b/examples/asyncio/wamp/basic/pubsub/decorators/__init__.py deleted file mode 100644 index 8a4c67bf..00000000 --- a/examples/asyncio/wamp/basic/pubsub/decorators/__init__.py +++ /dev/null @@ -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. -# -############################################################################### diff --git a/examples/asyncio/wamp/basic/pubsub/options/__init__.py b/examples/asyncio/wamp/basic/pubsub/options/__init__.py deleted file mode 100644 index 8a4c67bf..00000000 --- a/examples/asyncio/wamp/basic/pubsub/options/__init__.py +++ /dev/null @@ -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. -# -############################################################################### diff --git a/examples/asyncio/wamp/basic/pubsub/unsubscribe/__init__.py b/examples/asyncio/wamp/basic/pubsub/unsubscribe/__init__.py deleted file mode 100644 index 8a4c67bf..00000000 --- a/examples/asyncio/wamp/basic/pubsub/unsubscribe/__init__.py +++ /dev/null @@ -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. -# -############################################################################### diff --git a/examples/asyncio/wamp/basic/rpc/__init__.py b/examples/asyncio/wamp/basic/rpc/__init__.py deleted file mode 100644 index 8a4c67bf..00000000 --- a/examples/asyncio/wamp/basic/rpc/__init__.py +++ /dev/null @@ -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. -# -############################################################################### diff --git a/examples/asyncio/wamp/basic/rpc/arguments/__init__.py b/examples/asyncio/wamp/basic/rpc/arguments/__init__.py deleted file mode 100644 index 8a4c67bf..00000000 --- a/examples/asyncio/wamp/basic/rpc/arguments/__init__.py +++ /dev/null @@ -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. -# -############################################################################### diff --git a/examples/asyncio/wamp/basic/rpc/complex/__init__.py b/examples/asyncio/wamp/basic/rpc/complex/__init__.py deleted file mode 100644 index 8a4c67bf..00000000 --- a/examples/asyncio/wamp/basic/rpc/complex/__init__.py +++ /dev/null @@ -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. -# -############################################################################### diff --git a/examples/asyncio/wamp/basic/rpc/decorators/__init__.py b/examples/asyncio/wamp/basic/rpc/decorators/__init__.py deleted file mode 100644 index 8a4c67bf..00000000 --- a/examples/asyncio/wamp/basic/rpc/decorators/__init__.py +++ /dev/null @@ -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. -# -############################################################################### diff --git a/examples/asyncio/wamp/basic/rpc/errors/__init__.py b/examples/asyncio/wamp/basic/rpc/errors/__init__.py deleted file mode 100644 index 8a4c67bf..00000000 --- a/examples/asyncio/wamp/basic/rpc/errors/__init__.py +++ /dev/null @@ -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. -# -############################################################################### diff --git a/examples/asyncio/wamp/basic/rpc/options/__init__.py b/examples/asyncio/wamp/basic/rpc/options/__init__.py deleted file mode 100644 index 8a4c67bf..00000000 --- a/examples/asyncio/wamp/basic/rpc/options/__init__.py +++ /dev/null @@ -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. -# -############################################################################### diff --git a/examples/asyncio/wamp/basic/rpc/progress/__init__.py b/examples/asyncio/wamp/basic/rpc/progress/__init__.py deleted file mode 100644 index 8a4c67bf..00000000 --- a/examples/asyncio/wamp/basic/rpc/progress/__init__.py +++ /dev/null @@ -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. -# -############################################################################### diff --git a/examples/asyncio/wamp/basic/rpc/slowsquare/__init__.py b/examples/asyncio/wamp/basic/rpc/slowsquare/__init__.py deleted file mode 100644 index 8a4c67bf..00000000 --- a/examples/asyncio/wamp/basic/rpc/slowsquare/__init__.py +++ /dev/null @@ -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. -# -############################################################################### diff --git a/examples/asyncio/wamp/basic/rpc/timeservice/__init__.py b/examples/asyncio/wamp/basic/rpc/timeservice/__init__.py deleted file mode 100644 index 8a4c67bf..00000000 --- a/examples/asyncio/wamp/basic/rpc/timeservice/__init__.py +++ /dev/null @@ -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. -# -############################################################################### diff --git a/examples/asyncio/wamp/basic/server.py b/examples/asyncio/wamp/basic/server.py deleted file mode 100644 index f2a02be2..00000000 --- a/examples/asyncio/wamp/basic/server.py +++ /dev/null @@ -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() diff --git a/examples/asyncio/wamp/basic/session/__init__.py b/examples/asyncio/wamp/basic/session/__init__.py deleted file mode 100644 index 8a4c67bf..00000000 --- a/examples/asyncio/wamp/basic/session/__init__.py +++ /dev/null @@ -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. -# -############################################################################### diff --git a/examples/asyncio/wamp/basic/session/series/__init__.py b/examples/asyncio/wamp/basic/session/series/__init__.py deleted file mode 100644 index 8a4c67bf..00000000 --- a/examples/asyncio/wamp/basic/session/series/__init__.py +++ /dev/null @@ -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. -# -############################################################################### diff --git a/examples/asyncio/wamp/basic/session/series/backend.py b/examples/asyncio/wamp/basic/session/series/backend.py deleted file mode 100644 index 17d3a3f9..00000000 --- a/examples/asyncio/wamp/basic/session/series/backend.py +++ /dev/null @@ -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') diff --git a/examples/asyncio/wamp/beginner/Makefile b/examples/asyncio/wamp/beginner/Makefile deleted file mode 100644 index 2b6faae3..00000000 --- a/examples/asyncio/wamp/beginner/Makefile +++ /dev/null @@ -1,5 +0,0 @@ -test_server: - PYTHONPATH="../../../../autobahn" ~/python34/bin/python3 server.py - -test_client: - PYTHONPATH="../../../../autobahn" ~/python34/bin/python3 client.py diff --git a/examples/asyncio/wamp/beginner/client.py b/examples/asyncio/wamp/beginner/client.py deleted file mode 100644 index c3f35f42..00000000 --- a/examples/asyncio/wamp/beginner/client.py +++ /dev/null @@ -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() diff --git a/examples/asyncio/wamp/beginner/server.py b/examples/asyncio/wamp/beginner/server.py deleted file mode 100644 index 4b4292b3..00000000 --- a/examples/asyncio/wamp/beginner/server.py +++ /dev/null @@ -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() diff --git a/examples/asyncio/wamp/overview/backend.py b/examples/asyncio/wamp/overview/backend.py new file mode 100644 index 00000000..000cfb78 --- /dev/null +++ b/examples/asyncio/wamp/overview/backend.py @@ -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) diff --git a/examples/asyncio/wamp/overview/frontend.py b/examples/asyncio/wamp/overview/frontend.py new file mode 100644 index 00000000..dbc9b4b1 --- /dev/null +++ b/examples/asyncio/wamp/overview/frontend.py @@ -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) diff --git a/examples/asyncio/wamp/basic/pubsub/basic/backend.py b/examples/asyncio/wamp/pubsub/basic/backend.py similarity index 79% rename from examples/asyncio/wamp/basic/pubsub/basic/backend.py rename to examples/asyncio/wamp/pubsub/basic/backend.py index eae99ee0..25979452 100644 --- a/examples/asyncio/wamp/basic/pubsub/basic/backend.py +++ b/examples/asyncio/wamp/pubsub/basic/backend.py @@ -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) diff --git a/examples/asyncio/wamp/basic/pubsub/basic/frontend.py b/examples/asyncio/wamp/pubsub/basic/frontend.py similarity index 80% rename from examples/asyncio/wamp/basic/pubsub/basic/frontend.py rename to examples/asyncio/wamp/pubsub/basic/frontend.py index cfdd6f2d..96e8e45f 100644 --- a/examples/asyncio/wamp/basic/pubsub/basic/frontend.py +++ b/examples/asyncio/wamp/pubsub/basic/frontend.py @@ -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) diff --git a/examples/asyncio/wamp/basic/pubsub/complex/backend.py b/examples/asyncio/wamp/pubsub/complex/backend.py similarity index 78% rename from examples/asyncio/wamp/basic/pubsub/complex/backend.py rename to examples/asyncio/wamp/pubsub/complex/backend.py index 772574be..2a698597 100644 --- a/examples/asyncio/wamp/basic/pubsub/complex/backend.py +++ b/examples/asyncio/wamp/pubsub/complex/backend.py @@ -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) diff --git a/examples/asyncio/wamp/basic/pubsub/complex/frontend.py b/examples/asyncio/wamp/pubsub/complex/frontend.py similarity index 82% rename from examples/asyncio/wamp/basic/pubsub/complex/frontend.py rename to examples/asyncio/wamp/pubsub/complex/frontend.py index ad1799b0..bc3eddfe 100644 --- a/examples/asyncio/wamp/basic/pubsub/complex/frontend.py +++ b/examples/asyncio/wamp/pubsub/complex/frontend.py @@ -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) diff --git a/examples/asyncio/wamp/basic/pubsub/decorators/backend.py b/examples/asyncio/wamp/pubsub/decorators/backend.py similarity index 77% rename from examples/asyncio/wamp/basic/pubsub/decorators/backend.py rename to examples/asyncio/wamp/pubsub/decorators/backend.py index 4259ec1a..e8aa3621 100644 --- a/examples/asyncio/wamp/basic/pubsub/decorators/backend.py +++ b/examples/asyncio/wamp/pubsub/decorators/backend.py @@ -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) diff --git a/examples/asyncio/wamp/basic/pubsub/decorators/frontend.py b/examples/asyncio/wamp/pubsub/decorators/frontend.py similarity index 84% rename from examples/asyncio/wamp/basic/pubsub/decorators/frontend.py rename to examples/asyncio/wamp/pubsub/decorators/frontend.py index de415537..1823cfff 100644 --- a/examples/asyncio/wamp/basic/pubsub/decorators/frontend.py +++ b/examples/asyncio/wamp/pubsub/decorators/frontend.py @@ -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) diff --git a/examples/asyncio/wamp/basic/pubsub/options/backend.py b/examples/asyncio/wamp/pubsub/options/backend.py similarity index 76% rename from examples/asyncio/wamp/basic/pubsub/options/backend.py rename to examples/asyncio/wamp/pubsub/options/backend.py index 0199687e..312f3d41 100644 --- a/examples/asyncio/wamp/basic/pubsub/options/backend.py +++ b/examples/asyncio/wamp/pubsub/options/backend.py @@ -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) diff --git a/examples/asyncio/wamp/basic/pubsub/options/frontend.py b/examples/asyncio/wamp/pubsub/options/frontend.py similarity index 82% rename from examples/asyncio/wamp/basic/pubsub/options/frontend.py rename to examples/asyncio/wamp/pubsub/options/frontend.py index 02107c96..24c660b9 100644 --- a/examples/asyncio/wamp/basic/pubsub/options/frontend.py +++ b/examples/asyncio/wamp/pubsub/options/frontend.py @@ -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) diff --git a/examples/asyncio/wamp/pubsub/tls/README b/examples/asyncio/wamp/pubsub/tls/README new file mode 100644 index 00000000..28a84df4 --- /dev/null +++ b/examples/asyncio/wamp/pubsub/tls/README @@ -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. diff --git a/examples/asyncio/wamp/pubsub/tls/backend_selfsigned.py b/examples/asyncio/wamp/pubsub/tls/backend_selfsigned.py new file mode 100644 index 00000000..b587ed37 --- /dev/null +++ b/examples/asyncio/wamp/pubsub/tls/backend_selfsigned.py @@ -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) diff --git a/examples/asyncio/wamp/basic/pubsub/unsubscribe/backend.py b/examples/asyncio/wamp/pubsub/unsubscribe/backend.py similarity index 100% rename from examples/asyncio/wamp/basic/pubsub/unsubscribe/backend.py rename to examples/asyncio/wamp/pubsub/unsubscribe/backend.py diff --git a/examples/asyncio/wamp/basic/pubsub/unsubscribe/frontend.py b/examples/asyncio/wamp/pubsub/unsubscribe/frontend.py similarity index 75% rename from examples/asyncio/wamp/basic/pubsub/unsubscribe/frontend.py rename to examples/asyncio/wamp/pubsub/unsubscribe/frontend.py index 0d6cdf30..f360570d 100644 --- a/examples/asyncio/wamp/basic/pubsub/unsubscribe/frontend.py +++ b/examples/asyncio/wamp/pubsub/unsubscribe/frontend.py @@ -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) diff --git a/examples/asyncio/wamp/basic/rpc/arguments/backend.py b/examples/asyncio/wamp/rpc/arguments/backend.py similarity index 66% rename from examples/asyncio/wamp/basic/rpc/arguments/backend.py rename to examples/asyncio/wamp/rpc/arguments/backend.py index 667bc24f..60ade969 100644 --- a/examples/asyncio/wamp/basic/rpc/arguments/backend.py +++ b/examples/asyncio/wamp/rpc/arguments/backend.py @@ -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) diff --git a/examples/asyncio/wamp/basic/rpc/arguments/frontend.py b/examples/asyncio/wamp/rpc/arguments/frontend.py similarity index 88% rename from examples/asyncio/wamp/basic/rpc/arguments/frontend.py rename to examples/asyncio/wamp/rpc/arguments/frontend.py index fc91090b..a3f7e33b 100644 --- a/examples/asyncio/wamp/basic/rpc/arguments/frontend.py +++ b/examples/asyncio/wamp/rpc/arguments/frontend.py @@ -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) diff --git a/examples/asyncio/wamp/basic/rpc/complex/backend.py b/examples/asyncio/wamp/rpc/complex/backend.py similarity index 76% rename from examples/asyncio/wamp/basic/rpc/complex/backend.py rename to examples/asyncio/wamp/rpc/complex/backend.py index 2c4c5e8b..2bda828c 100644 --- a/examples/asyncio/wamp/basic/rpc/complex/backend.py +++ b/examples/asyncio/wamp/rpc/complex/backend.py @@ -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) diff --git a/examples/asyncio/wamp/basic/rpc/complex/frontend.py b/examples/asyncio/wamp/rpc/complex/frontend.py similarity index 83% rename from examples/asyncio/wamp/basic/rpc/complex/frontend.py rename to examples/asyncio/wamp/rpc/complex/frontend.py index 2b0c8d2c..50809e9a 100644 --- a/examples/asyncio/wamp/basic/rpc/complex/frontend.py +++ b/examples/asyncio/wamp/rpc/complex/frontend.py @@ -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) diff --git a/examples/asyncio/wamp/basic/rpc/decorators/backend.py b/examples/asyncio/wamp/rpc/decorators/backend.py similarity index 86% rename from examples/asyncio/wamp/basic/rpc/decorators/backend.py rename to examples/asyncio/wamp/rpc/decorators/backend.py index 5556c5d8..f02df5b0 100644 --- a/examples/asyncio/wamp/basic/rpc/decorators/backend.py +++ b/examples/asyncio/wamp/rpc/decorators/backend.py @@ -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) diff --git a/examples/asyncio/wamp/basic/rpc/decorators/frontend.py b/examples/asyncio/wamp/rpc/decorators/frontend.py similarity index 83% rename from examples/asyncio/wamp/basic/rpc/decorators/frontend.py rename to examples/asyncio/wamp/rpc/decorators/frontend.py index fe5f318c..5ece0d58 100644 --- a/examples/asyncio/wamp/basic/rpc/decorators/frontend.py +++ b/examples/asyncio/wamp/rpc/decorators/frontend.py @@ -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) diff --git a/examples/asyncio/wamp/basic/rpc/errors/backend.py b/examples/asyncio/wamp/rpc/errors/backend.py similarity index 82% rename from examples/asyncio/wamp/basic/rpc/errors/backend.py rename to examples/asyncio/wamp/rpc/errors/backend.py index bca2c699..fe5316bc 100644 --- a/examples/asyncio/wamp/basic/rpc/errors/backend.py +++ b/examples/asyncio/wamp/rpc/errors/backend.py @@ -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) diff --git a/examples/asyncio/wamp/basic/rpc/errors/frontend.py b/examples/asyncio/wamp/rpc/errors/frontend.py similarity index 87% rename from examples/asyncio/wamp/basic/rpc/errors/frontend.py rename to examples/asyncio/wamp/rpc/errors/frontend.py index 6ab24429..e536b293 100644 --- a/examples/asyncio/wamp/basic/rpc/errors/frontend.py +++ b/examples/asyncio/wamp/rpc/errors/frontend.py @@ -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) diff --git a/examples/asyncio/wamp/basic/rpc/options/backend.py b/examples/asyncio/wamp/rpc/options/backend.py similarity index 80% rename from examples/asyncio/wamp/basic/rpc/options/backend.py rename to examples/asyncio/wamp/rpc/options/backend.py index 79e79dd2..22813174 100644 --- a/examples/asyncio/wamp/basic/rpc/options/backend.py +++ b/examples/asyncio/wamp/rpc/options/backend.py @@ -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) diff --git a/examples/asyncio/wamp/basic/rpc/options/frontend.py b/examples/asyncio/wamp/rpc/options/frontend.py similarity index 81% rename from examples/asyncio/wamp/basic/rpc/options/frontend.py rename to examples/asyncio/wamp/rpc/options/frontend.py index c3df9596..54492813 100644 --- a/examples/asyncio/wamp/basic/rpc/options/frontend.py +++ b/examples/asyncio/wamp/rpc/options/frontend.py @@ -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) diff --git a/examples/asyncio/wamp/basic/rpc/progress/backend.py b/examples/asyncio/wamp/rpc/progress/backend.py similarity index 78% rename from examples/asyncio/wamp/basic/rpc/progress/backend.py rename to examples/asyncio/wamp/rpc/progress/backend.py index b0aa0364..4a5bec42 100644 --- a/examples/asyncio/wamp/basic/rpc/progress/backend.py +++ b/examples/asyncio/wamp/rpc/progress/backend.py @@ -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) diff --git a/examples/asyncio/wamp/basic/rpc/progress/frontend.py b/examples/asyncio/wamp/rpc/progress/frontend.py similarity index 82% rename from examples/asyncio/wamp/basic/rpc/progress/frontend.py rename to examples/asyncio/wamp/rpc/progress/frontend.py index a23e267e..43e87a50 100644 --- a/examples/asyncio/wamp/basic/rpc/progress/frontend.py +++ b/examples/asyncio/wamp/rpc/progress/frontend.py @@ -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) diff --git a/examples/asyncio/wamp/basic/rpc/slowsquare/backend.py b/examples/asyncio/wamp/rpc/slowsquare/backend.py similarity index 73% rename from examples/asyncio/wamp/basic/rpc/slowsquare/backend.py rename to examples/asyncio/wamp/rpc/slowsquare/backend.py index 7d4b635d..6c633d59 100644 --- a/examples/asyncio/wamp/basic/rpc/slowsquare/backend.py +++ b/examples/asyncio/wamp/rpc/slowsquare/backend.py @@ -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) diff --git a/examples/asyncio/wamp/basic/rpc/slowsquare/frontend.py b/examples/asyncio/wamp/rpc/slowsquare/frontend.py similarity index 84% rename from examples/asyncio/wamp/basic/rpc/slowsquare/frontend.py rename to examples/asyncio/wamp/rpc/slowsquare/frontend.py index 3e9391d6..53cf7ca1 100644 --- a/examples/asyncio/wamp/basic/rpc/slowsquare/frontend.py +++ b/examples/asyncio/wamp/rpc/slowsquare/frontend.py @@ -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) diff --git a/examples/twisted/wamp/session/series/backend.py b/examples/asyncio/wamp/rpc/timeservice/backend.py similarity index 74% rename from examples/twisted/wamp/session/series/backend.py rename to examples/asyncio/wamp/rpc/timeservice/backend.py index 6a31a3a7..ecb70d08 100644 --- a/examples/twisted/wamp/session/series/backend.py +++ b/examples/asyncio/wamp/rpc/timeservice/backend.py @@ -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) diff --git a/examples/asyncio/wamp/basic/rpc/timeservice/frontend.py b/examples/asyncio/wamp/rpc/timeservice/frontend.py similarity index 82% rename from examples/asyncio/wamp/basic/rpc/timeservice/frontend.py rename to examples/asyncio/wamp/rpc/timeservice/frontend.py index 50013e7f..be2254a3 100644 --- a/examples/asyncio/wamp/basic/rpc/timeservice/frontend.py +++ b/examples/asyncio/wamp/rpc/timeservice/frontend.py @@ -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) diff --git a/examples/asyncio/wamp/basic/rpc/timeservice/backend.py b/examples/asyncio/wamp/session/series/backend.py similarity index 100% rename from examples/asyncio/wamp/basic/rpc/timeservice/backend.py rename to examples/asyncio/wamp/session/series/backend.py diff --git a/examples/asyncio/wamp/basic/session/series/frontend.py b/examples/asyncio/wamp/session/series/frontend.py similarity index 100% rename from examples/asyncio/wamp/basic/session/series/frontend.py rename to examples/asyncio/wamp/session/series/frontend.py diff --git a/examples/asyncio/wamp/wamplet/wamplet1/wamplet1/__init__.py b/examples/asyncio/wamp/wamplet/wamplet1/wamplet1/__init__.py deleted file mode 100644 index 8a4c67bf..00000000 --- a/examples/asyncio/wamp/wamplet/wamplet1/wamplet1/__init__.py +++ /dev/null @@ -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. -# -############################################################################### diff --git a/examples/asyncio/websocket/echo/client.py b/examples/asyncio/websocket/echo/client.py index 2fa95f6a..6acc73b5 100644 --- a/examples/asyncio/websocket/echo/client.py +++ b/examples/asyncio/websocket/echo/client.py @@ -62,7 +62,7 @@ if __name__ == '__main__': # Trollius >= 0.3 was renamed import trollius as asyncio - factory = WebSocketClientFactory("ws://localhost:9000", debug=False) + factory = WebSocketClientFactory("ws://127.0.0.1:9000", debug=False) factory.protocol = MyClientProtocol loop = asyncio.get_event_loop() diff --git a/examples/asyncio/websocket/echo/client_coroutines.py b/examples/asyncio/websocket/echo/client_coroutines.py index 8afb5cbc..68e881bf 100644 --- a/examples/asyncio/websocket/echo/client_coroutines.py +++ b/examples/asyncio/websocket/echo/client_coroutines.py @@ -60,7 +60,7 @@ class MyClientProtocol(WebSocketClientProtocol): if __name__ == '__main__': - factory = WebSocketClientFactory("ws://localhost:9000", debug=False) + factory = WebSocketClientFactory("ws://127.0.0.1:9000", debug=False) factory.protocol = MyClientProtocol loop = asyncio.get_event_loop() diff --git a/examples/asyncio/websocket/echo/client_coroutines_py2.py b/examples/asyncio/websocket/echo/client_coroutines_py2.py index ad572cb8..ebbbc7ee 100644 --- a/examples/asyncio/websocket/echo/client_coroutines_py2.py +++ b/examples/asyncio/websocket/echo/client_coroutines_py2.py @@ -57,7 +57,7 @@ class MyClientProtocol(WebSocketClientProtocol): if __name__ == '__main__': - factory = WebSocketClientFactory("ws://localhost:9000", debug=False) + factory = WebSocketClientFactory("ws://127.0.0.1:9000", debug=False) factory.protocol = MyClientProtocol loop = trollius.get_event_loop() diff --git a/examples/asyncio/websocket/echo/server.py b/examples/asyncio/websocket/echo/server.py index 1f71a57c..d9ef4d15 100644 --- a/examples/asyncio/websocket/echo/server.py +++ b/examples/asyncio/websocket/echo/server.py @@ -57,11 +57,11 @@ if __name__ == '__main__': # Trollius >= 0.3 was renamed import trollius as asyncio - factory = WebSocketServerFactory("ws://localhost:9000", debug=False) + factory = WebSocketServerFactory("ws://127.0.0.1:9000", debug=False) factory.protocol = MyServerProtocol loop = asyncio.get_event_loop() - coro = loop.create_server(factory, '127.0.0.1', 9000) + coro = loop.create_server(factory, '0.0.0.0', 9000) server = loop.run_until_complete(coro) try: diff --git a/examples/asyncio/websocket/slowsquare/client.py b/examples/asyncio/websocket/slowsquare/client.py index 0891033a..4eb13125 100644 --- a/examples/asyncio/websocket/slowsquare/client.py +++ b/examples/asyncio/websocket/slowsquare/client.py @@ -58,7 +58,7 @@ if __name__ == '__main__': # Trollius >= 0.3 was renamed import trollius as asyncio - factory = WebSocketClientFactory("ws://localhost:9000", debug=False) + factory = WebSocketClientFactory("ws://127.0.0.1:9000", debug=False) factory.protocol = SlowSquareClientProtocol loop = asyncio.get_event_loop() diff --git a/examples/asyncio/websocket/slowsquare/server.py b/examples/asyncio/websocket/slowsquare/server.py index 1bb9f287..046945e1 100644 --- a/examples/asyncio/websocket/slowsquare/server.py +++ b/examples/asyncio/websocket/slowsquare/server.py @@ -58,7 +58,7 @@ class SlowSquareServerProtocol(WebSocketServerProtocol): if __name__ == '__main__': - factory = WebSocketServerFactory("ws://localhost:9000", debug=False) + factory = WebSocketServerFactory("ws://127.0.0.1:9000", debug=False) factory.protocol = SlowSquareServerProtocol loop = asyncio.get_event_loop() diff --git a/examples/asyncio/websocket/slowsquare/server_py2.py b/examples/asyncio/websocket/slowsquare/server_py2.py index 39f0c92f..c89978db 100644 --- a/examples/asyncio/websocket/slowsquare/server_py2.py +++ b/examples/asyncio/websocket/slowsquare/server_py2.py @@ -55,7 +55,7 @@ class SlowSquareServerProtocol(WebSocketServerProtocol): if __name__ == '__main__': - factory = WebSocketServerFactory("ws://localhost:9000", debug=False) + factory = WebSocketServerFactory("ws://127.0.0.1:9000", debug=False) factory.protocol = SlowSquareServerProtocol loop = trollius.get_event_loop() diff --git a/examples/asyncio/websocket/testee/testee_server.py b/examples/asyncio/websocket/testee/testee_server.py index 3f69875d..36a3182f 100644 --- a/examples/asyncio/websocket/testee/testee_server.py +++ b/examples/asyncio/websocket/testee/testee_server.py @@ -91,7 +91,7 @@ if __name__ == '__main__': # Trollius >= 0.3 was renamed import trollius as asyncio - factory = TesteeServerFactory("ws://localhost:9002", debug=False) + factory = TesteeServerFactory("ws://127.0.0.1:9002", debug=False) loop = asyncio.get_event_loop() coro = loop.create_server(factory, port=9002) diff --git a/examples/index.html b/examples/index.html new file mode 100644 index 00000000..e13a29f1 --- /dev/null +++ b/examples/index.html @@ -0,0 +1,210 @@ + + + + + + + + Crossbar.io Examples + + + + +
+ + + +

Examples Index

+ +

+ Please see the README.md or + running-the-examples.md; + this provides convenience links to various demos' HTML and + JavaScript assets but is intended to be used via a router + and corresponding frontend or backend Python (or other + language) scripts. +

+ +

+ So, for example, it's sufficient to open the frontend.html and + backend.html scripts in different tabs, or run + the corresponding Python backend.py + with frontend.html (or vice-versa). +

+ +

+ For examples using RPC, you need to run the backend first, + so that procedures are registered and available to call. +

+ + + +
+ + diff --git a/examples/router/.crossbar/config-with-tls.json b/examples/router/.crossbar/config-with-tls.json new file mode 100644 index 00000000..34616707 --- /dev/null +++ b/examples/router/.crossbar/config-with-tls.json @@ -0,0 +1,66 @@ + +{ + "controller": { + }, + "workers": [ + { + "type": "router", + "realms": [ + { + "name": "crossbardemo", + "roles": [ + { + "name": "anonymous", + "permissions": [ + { + "uri": "*", + "publish": true, + "subscribe": true, + "call": true, + "register": true + } + ] + } + ] + } + ], + "transports": [ + { + "type": "web", + "endpoint": { + "type": "tcp", + "port": 8080 + }, + "paths": { + "/": { + "type": "static", + "directory": "../..", + "options": { + "enable_directory_listing": true, + "mime_types": { + ".md": "text/plain", + ".py": "text/plain" + } + } + }, + "ws": { + "type": "websocket" + } + } + }, + { + "type": "websocket", + "endpoint": { + "type": "tcp", + "interface": "127.0.0.1", + "port": 8083, + "tls": { + "key": "../../twisted/wamp/pubsub/tls/server.key", + "certificate": "../../twisted/wamp/pubsub/tls/server.crt" + } + } + } + ] + } + ] +} diff --git a/examples/router/.crossbar/config.json b/examples/router/.crossbar/config.json new file mode 100644 index 00000000..9a82c12f --- /dev/null +++ b/examples/router/.crossbar/config.json @@ -0,0 +1,54 @@ + +{ + "controller": { + }, + "workers": [ + { + "type": "router", + "realms": [ + { + "name": "crossbardemo", + "roles": [ + { + "name": "anonymous", + "permissions": [ + { + "uri": "*", + "publish": true, + "subscribe": true, + "call": true, + "register": true + } + ] + } + ] + } + ], + "transports": [ + { + "type": "web", + "endpoint": { + "type": "tcp", + "port": 8080 + }, + "paths": { + "/": { + "type": "static", + "directory": "../..", + "options": { + "enable_directory_listing": true, + "mime_types": { + ".md": "text/plain", + ".py": "text/plain" + } + } + }, + "ws": { + "type": "websocket" + } + } + } + ] + } + ] +} diff --git a/examples/router/README b/examples/router/README new file mode 100644 index 00000000..555382be --- /dev/null +++ b/examples/router/README @@ -0,0 +1,3 @@ +This contains a configuration file in `.crossbar/config.json` to run a local Crossbar instance and serve some of the example content via a Web server. + +See [../running-the-examples.md] for more information. diff --git a/examples/run-all-examples.py b/examples/run-all-examples.py new file mode 100755 index 00000000..229d8439 --- /dev/null +++ b/examples/run-all-examples.py @@ -0,0 +1,238 @@ +#!/usr/bin/env python + +from __future__ import print_function + +# this is mostly for testing that the examples run without errors +# +# To use this script: +# +# 0. change to this directory +# 1. be in an activated Python2 virtual-env with crossbar installed +# 2. pip install colorama +# 3. have a virtualenv called ./venv-py3 using a Python3 +# interpreter with crossbar (or just Autobahn) installed. +# +# ...then just run this script; it colors the output and runs each +# frontend/backend pair for a few seconds. + +import sys +from os import environ +from os.path import join, exists + +import colorama +from colorama import Fore + +from twisted.internet.protocol import ProcessProtocol +from twisted.internet.defer import inlineCallbacks, Deferred, returnValue +from twisted.internet.error import ProcessExitedAlready +from twisted.python.failure import Failure +from twisted.internet import reactor + +from autobahn.twisted.util import sleep + + +class CrossbarProcessProtocol(ProcessProtocol): + """ + A helper to talk to a crossbar instance we've launched. + """ + + def __init__(self, all_done, launched, color=None, prefix=''): + """ + :param all_done: Deferred that gets callback() when our process exits (.errback if it exits non-zero) + :param launched: Deferred that gets callback() when our process starts. + """ + self.all_done = all_done + self.launched = launched + self.color = color or '' + self.prefix = prefix + self._out = '' + self._err = '' + + def connectionMade(self): + """ProcessProtocol override""" + if not self.launched.called: + self.launched.callback(self) + + def outReceived(self, data): + """ProcessProtocol override""" + self._out += data.decode('utf8') + while '\n' in self._out: + idx = self._out.find('\n') + line = self._out[:idx] + self._out = self._out[idx + 1:] + sys.stdout.write(self.prefix + self.color + line + Fore.RESET + '\n') + + def errReceived(self, data): + """ProcessProtocol override""" + self._err += data.decode('utf8') + while '\n' in self._err: + idx = self._err.find('\n') + line = self._err[:idx] + self._err = self._err[idx+1:] + sys.stderr.write(self.prefix + self.color + line + Fore.RESET + '\n') + + def processEnded(self, reason): + """IProcessProtocol API""" + # reason.value should always be a ProcessTerminated instance + fail = reason.value + # print('processEnded', fail) + + if fail.exitCode != 0 and fail.exitCode is not None: + msg = 'Process exited with code "{}".'.format(fail.exitCode) + err = RuntimeError(msg) + self.all_done.errback(err) + if not self.launched.called: + self.launched.errback(err) + else: + self.all_done.callback(fail) + if not self.launched.called: + print("FIXME: _launched should have been callbacked by now.") + self.launched.callback(self) + + +@inlineCallbacks +def start_crossbar(): + finished = Deferred() + launched = Deferred() + protocol = CrossbarProcessProtocol(finished, launched, Fore.RED) + exe = 'crossbar' + args = [exe, 'start', '--cbdir', './router/.crossbar'] + + env = environ.copy() + env["PYTHONUNBUFFERED"] = "1" + + transport = reactor.spawnProcess( + protocol, exe, args, path='.', env=env) + + yield launched + returnValue(protocol) + + +@inlineCallbacks +def start_example(py_fname, color, prefix='', exe=sys.executable): + finished = Deferred() + launched = Deferred() + protocol = CrossbarProcessProtocol(finished, launched, color, prefix) + args = [exe, py_fname] + + env = environ.copy() + env["PYTHONUNBUFFERED"] = "1" + + transport = reactor.spawnProcess(protocol, exe, args, path='.', env=env) + + yield launched + returnValue(protocol) + + +def print_banner(title): + print('-' * 80) + print(title) + print('-' * 80) + print() + + +@inlineCallbacks +def main(reactor): + colorama.init() + examples = [ + './twisted/wamp/overview', + + './twisted/wamp/pubsub/basic/', + './twisted/wamp/pubsub/complex/', + './twisted/wamp/pubsub/decorators/', + './twisted/wamp/pubsub/options/', + './twisted/wamp/pubsub/tls/', + './twisted/wamp/pubsub/unsubscribe/', + + './twisted/wamp/rpc/timeservice/', + './twisted/wamp/rpc/slowsquare/', + './twisted/wamp/rpc/progress/', + './twisted/wamp/rpc/options/', + './twisted/wamp/rpc/errors/', + './twisted/wamp/rpc/decorators/', + './twisted/wamp/rpc/complex/', + './twisted/wamp/rpc/arguments/', + + 'py3 ./asyncio/wamp/overview', + + 'py3 ./asyncio/wamp/pubsub/unsubscribe/', + 'py3 ./asyncio/wamp/pubsub/tls/', + 'py3 ./asyncio/wamp/pubsub/options/', + 'py3 ./asyncio/wamp/pubsub/decorators/', + 'py3 ./asyncio/wamp/pubsub/complex/', + 'py3 ./asyncio/wamp/pubsub/basic/', + + 'py3 ./asyncio/wamp/rpc/timeservice/', + 'py3 ./asyncio/wamp/rpc/slowsquare/', + 'py3 ./asyncio/wamp/rpc/progress/', + 'py3 ./asyncio/wamp/rpc/options/', + 'py3 ./asyncio/wamp/rpc/errors/', + 'py3 ./asyncio/wamp/rpc/decorators/', + 'py3 ./asyncio/wamp/rpc/complex/', + 'py3 ./asyncio/wamp/rpc/arguments/', + + ] + + print_banner("Running crossbar.io instance") + cb_proto = yield start_crossbar() + yield sleep(2) # wait for sockets to be listening + if cb_proto.all_done.called: + raise RuntimeError("crossbar exited already") + + success = True + for exdir in examples: + py = sys.executable + if exdir.startswith('py3 '): + exdir = exdir[4:] + if sys.version_info.major < 3: + print("don't have python3, skipping:", exdir) + continue + frontend = join(exdir, 'frontend.py') + backend = join(exdir, 'backend.py') + if not exists(frontend) or not exists(backend): + print("skipping:", exdir, exists(frontend), exists(backend)) + continue + + print_banner("Running example: " + exdir) + print(" starting backend") + back_proto = yield start_example(backend, Fore.BLUE, ' backend: ', exe=py) + yield sleep(1) + print(" starting frontend") + front_proto = yield start_example(frontend, Fore.YELLOW, 'frontend: ', exe=py) + yield sleep(3) + + for p in [back_proto, front_proto]: + try: + if p.all_done.called: + yield p.all_done + except Exception as e: + print("FAILED:", e) + success = False + + for p in [front_proto, back_proto]: + try: + p.transport.signalProcess('KILL') + except ProcessExitedAlready: + pass + + if not success: + break + yield sleep(1) + + print("Killing crossbar") + try: + cb_proto.transport.signalProcess('KILL') + yield cb_proto.all_done + except: + pass + if success: + print() + print("Success!") + print(" ...all the examples neither crashed nor burned...") + returnValue(0) + returnValue(5) + + +from twisted.internet.task import react +if __name__ == '__main__': + sys.exit(react(main)) diff --git a/examples/running-the-examples.md b/examples/running-the-examples.md new file mode 100644 index 00000000..c3a5bdd3 --- /dev/null +++ b/examples/running-the-examples.md @@ -0,0 +1,90 @@ +# Running the Examples + +## Setting up a Router + +To run the following examples, you need a WAMP router. + +By default, **all examples are set up to use a local Crossbar instance**. You can change the URI used with the environment variable AUTOBAHN_DEMO_ROUTER (by default it is `ws://localhost:8080/ws`). Please see [Running Crossbar Locally] below. + + +## Creating a virtualenv + +If you do not yet have a `virtualenv` to run the examples with, you can do something like: + +```shell +git clone https://github.com/tavendo/AutobahnPython.git +cd ./AutobahnPython/ +virtualenv venv-autobahn +source venv-autobahn/bin/activate +pip install -e ./ +``` + +For all the examples, we presume that you are in the `./examples` directory of your autobahn clone, and that the virtualenv in which you've installed Autobahn is activated. If you're running your own Crossbar, it runs from `./examples/router` in its own virtualenv. + +The examples usually contain two components: + + * frontend + * backend + +Each component is (usually) provided in two languages: + + * Python + * JavaScript + +The JavaScript version can run on the browser or in NodeJS. + +To run an example, you can have two (or three) terminal sessions open with: + + 1. frontend + 2. backend + 3. the router (e.g. `crossbar`) + +You can also run the frontend/backend in the same shell by putting one in the background. This makes tbe examples less clear, however: + +```shell +python twisted/wamp/pubsub/basic/frontend.py & +python twisted/wamp/pubsub/basic/backend.py +``` + +Some **things to try**: open a new terminal and run a second frontend; leave the backend running for a while and then run the frontend; disconnect a frontend and reconnect (re-run) it; mix and match the examples (e.g. twisted/wamp/pubsub/basic/backend.py with twisted/wamp/pubsub/decorators/frontend.py) to see how the topic URIs interact. + + +## Running Crossbar Locally + +If you want to use your own local [Crossbar](http://crossbar.io) instance you must have a Python2-based virtualenv and `pip install crossbar` in it. See also [crossbar.io's platform-specific installation instructions](http://crossbar.io/docs/Local-Installation/) as you may need to install some native libraries as well. + +Once you have crossbar installed, use the provided router configuration in `examples/router/.crossbar/config.json`. Starting your router is then: + +```shell +cd ./examples/router +crossbar start +``` + +There should now be a router listening on `localhost:8080` so you can change the URI in all the demos to `ws://localhost:8080/ws` or set the environment variable `AUTOBAHN_DEMO_ROUTER=ws://localhost:8080/ws` Obviously, this environment variable isn't used by in-browser JavaScript so you'll have to change .js files by hand. + +If you are running the router successfully, you should see a Crossbar page at `http://localhost:8080/`. We've added enough configuration to serve the HTML, JavaScript and README files from all the examples; you should see a list of links at the page. + + +## Hosting + +Crossbar.io is a WAMP router that can also act as a host for WAMP application components. So, for example, to let Crossbar.io host one of the examples as a backend application component, you can add a `"components"` section to `examples/router/.crossbar/config.json` at the same level as `"realms"`: + +```javascript + +{ + ... + "options": { + "pythonpath": ["../../twisted/wamp/"] + }, + "components": [ + { + "type": "class", + "classname": "pubsub.complex.backend.Component", + "realm": "crossbardemo" + } + ], + ... +} +``` + +For the above exact configuration to work you'll need the `./examples/twisted/wamp/` directory in your PYTHONPATH (that configuration is provided in the `"options"` above). \ No newline at end of file diff --git a/examples/twisted/wamp/README.md b/examples/twisted/wamp/README.md index ea3a524f..30a0588d 100644 --- a/examples/twisted/wamp/README.md +++ b/examples/twisted/wamp/README.md @@ -1,5 +1,63 @@ -# WAMP programming with Autobahn on Twisted +# WAMP Programming Examples -This folder contains complete working code examples that demonstrate [WAMP v2](http://wamp.ws) programming with **Autobahn**|Python on [Twisted](http://www.twistedmatrix.com/): +There are several very-similar examples that each follow a similar form and demonstrate several different ways of using WAMP under Autobahn. Each of these examples usually includes: - * [Basic Publish & Subscribe and Remote Procedure Calls](basic) +- backend.py: the logical "backend", in Python (registers procedures, publishes events) +- frontend.py: the logical "frontend", in Python (calls endpoints, receives events) +- frontend.js: similar version in JavaScript +- backend.js: similar version in JavaScript +- *.html: boilerplate to hold the .js + +Note that any WAMP component can "do" all the roles (so a "backend" component can easily also call endpoints or listen for events) but we needed to separate things somehow. However, you can organize your components however you see fit. + +For examples using RPC, you need to run the backend first, so that procedures are registered and available to call. + +## The Examples + +### Overview Examples + * [LoopingCall](overview): shows an alternative way of publishing periodically + +### RPC Examples + + * [Arguments](rpc/arguments): different types of argument-passing + * [Complex](rpc/complex): complex return types + * [Decorators](rpc/decorators): register RPC methods using decorators + * [Errors](rpc/errors): map custom error classes to WAMP URIs + * [Options](rpc/options): show some RegistrationOptions and CallOptions use + * [Progress](rpc/progress): progressive results for long-running oprations + * [Slow Square](rpc/slowsquare): an RPC call that takes some time + * [Time Service](rpc/timeservice): XXX delete? + +### PubSub Examples + + * [Basic](pubsub/basic): publish to a topic once per second + * [Complex](pubsub/complex): demonstrates different payload arguments + * [Decorators](pubsub/decorators): doing subscriptions with decorators + * [Options](pubsub/options): use of PublishOptions and SubscribeOptions + * [Unsubscribe](pubsub/unsubscribe): listen to events for a limited time + +### WAMPlet Examples + +These are some larger examples, implemented as pluggable "WAMPlets". These can also serve as skeletons to base your own WAMPlets from, should you wish to package components like this. + + * [votegame](wamplet/votegame): a collaborative voting "game" to decide the most amazing fruit. Updates votes amongst all clients in realtime + * [wampirc](wamplet/wampirc): shows some simple bridging between IRC and WAMP, exporting private messages to the bot as WAMP publish()-es + * [wamplet1](wamplet/wamplet1): just a minimal skeleton + + +### App Examples + +_We still need to explain these. For starters, here's the list:_ + + * [Calculator](app/calculator): _to be explained..._ + * [Crochet](app/crochet): _to be explained..._ + * [DBus](app/dbus): _to be explained..._ + * [Hello](app/hello): _to be explained..._ + * [Keyvalue](app/keyvalue): _to be explained..._ + * [Klein](app/klein): _to be explained..._ + * [Serial2ws](app/serial2ws): _to be explained..._ + * [Subscribe upon call](app/subscribe_uipon_call): _to be explained..._ + +## How to run + +See [Running the Examples](../../running-the-examples.md) diff --git a/examples/twisted/wamp/app/calculator/calculator.py b/examples/twisted/wamp/app/calculator/calculator.py index 380e6cf5..3aaad395 100644 --- a/examples/twisted/wamp/app/calculator/calculator.py +++ b/examples/twisted/wamp/app/calculator/calculator.py @@ -24,14 +24,14 @@ # ############################################################################### +from os import environ import sys import decimal from twisted.internet.defer import inlineCallbacks from autobahn import wamp -from autobahn.twisted.wamp import ApplicationSession - +from autobahn.twisted.wamp import ApplicationSession, ApplicationRunner class Calculator(ApplicationSession): @@ -97,29 +97,14 @@ if __name__ == '__main__': from twisted.python import log log.startLogging(sys.stdout) - # import Twisted reactor - ## - from twisted.internet import reactor - print("Using Twisted reactor {0}".format(reactor.__class__)) - - # create embedded web server for static files - ## - if args.web: - from twisted.web.server import Site - from twisted.web.static import File - reactor.listenTCP(args.web, Site(File("."))) - # run WAMP application component ## from autobahn.twisted.wamp import ApplicationRunner - router = args.router or 'ws://localhost:9000' - runner = ApplicationRunner(router, u"realm1", standalone=not args.router, - debug=False, # low-level logging - debug_wamp=args.debug, # WAMP level logging - debug_app=args.debug # app-level logging - ) - - # start the component and the Twisted reactor .. - ## + 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(Calculator) diff --git a/examples/twisted/wamp/app/calculator/index.html b/examples/twisted/wamp/app/calculator/index.html index bded8f7f..de5bd09f 100644 --- a/examples/twisted/wamp/app/calculator/index.html +++ b/examples/twisted/wamp/app/calculator/index.html @@ -48,16 +48,16 @@ // var wsuri; if (document.location.origin == "file://") { - wsuri = "ws://localhost:9000"; + wsuri = "ws://localhost:8080/ws"; } else { - wsuri = "ws://" + document.location.hostname + ":9000"; + wsuri = "ws://" + document.location.hostname + ":8080/ws"; } // connect to WAMP server // var connection = new autobahn.Connection({ url: wsuri, - realm: 'realm1' + realm: 'crossbardemo' }); connection.onopen = function (session) { diff --git a/examples/twisted/wamp/app/crochet/example2/server.py b/examples/twisted/wamp/app/crochet/example2/server.py index bc4c59f5..3631eab6 100644 --- a/examples/twisted/wamp/app/crochet/example2/server.py +++ b/examples/twisted/wamp/app/crochet/example2/server.py @@ -94,7 +94,7 @@ if __name__ == '__main__': # @run_in_reactor def start_wamp(): - wapp.run("ws://localhost:9000", "realm1", standalone=True, start_reactor=False) + wapp.run("ws://127.0.0.1:9000", "realm1", standalone=True, start_reactor=False) start_wamp() diff --git a/examples/twisted/wamp/app/dbus/bridge.py b/examples/twisted/wamp/app/dbus/bridge.py index 11a1a080..4dab02ab 100644 --- a/examples/twisted/wamp/app/dbus/bridge.py +++ b/examples/twisted/wamp/app/dbus/bridge.py @@ -116,7 +116,7 @@ if __name__ == '__main__': # run WAMP application component ## from autobahn.twisted.wamp import ApplicationRunner - router = args.router or 'ws://localhost:9000' + router = args.router or 'ws://127.0.0.1:9000' runner = ApplicationRunner(router, u"realm1", standalone=not args.router, debug=False, # low-level logging diff --git a/examples/twisted/wamp/app/hello/hello.py b/examples/twisted/wamp/app/hello/hello.py index 1422059a..eb9d6bdb 100644 --- a/examples/twisted/wamp/app/hello/hello.py +++ b/examples/twisted/wamp/app/hello/hello.py @@ -50,4 +50,4 @@ def onjoined(): if __name__ == "__main__": - app.run("ws://localhost:8080/ws", "realm1", standalone=True) + app.run("ws://127.0.0.1:8080/ws", "realm1", standalone=True) diff --git a/examples/twisted/wamp/app/keyvalue/store.py b/examples/twisted/wamp/app/keyvalue/store.py index 665ca196..b37fdcb5 100644 --- a/examples/twisted/wamp/app/keyvalue/store.py +++ b/examples/twisted/wamp/app/keyvalue/store.py @@ -111,7 +111,7 @@ if __name__ == '__main__': # run WAMP application component ## from autobahn.twisted.wamp import ApplicationRunner - router = args.router or 'ws://localhost:9000' + router = args.router or 'ws://127.0.0.1:9000' runner = ApplicationRunner(router, u"realm1", standalone=not args.router, debug=False, # low-level logging diff --git a/examples/twisted/wamp/app/klein/example1/server_wamp.py b/examples/twisted/wamp/app/klein/example1/server_wamp.py index 7a8acff9..cccf9bc5 100644 --- a/examples/twisted/wamp/app/klein/example1/server_wamp.py +++ b/examples/twisted/wamp/app/klein/example1/server_wamp.py @@ -36,4 +36,4 @@ def square(x): if __name__ == "__main__": - app.run("ws://localhost:9000", "realm1", standalone=True) + app.run("ws://127.0.0.1:9000", "realm1", standalone=True) diff --git a/examples/twisted/wamp/app/klein/example1/server_web.py b/examples/twisted/wamp/app/klein/example1/server_web.py index 678b16d2..f29b7eb4 100644 --- a/examples/twisted/wamp/app/klein/example1/server_web.py +++ b/examples/twisted/wamp/app/klein/example1/server_web.py @@ -48,4 +48,4 @@ if __name__ == "__main__": log.startLogging(sys.stdout) reactor.listenTCP(8080, Site(app.resource())) - wampapp.run("ws://localhost:9000", "realm1", standalone=False) + wampapp.run("ws://127.0.0.1:9000", "realm1", standalone=False) diff --git a/examples/twisted/wamp/app/klein/example2/server.py b/examples/twisted/wamp/app/klein/example2/server.py index 392e3f69..46bbbbf8 100644 --- a/examples/twisted/wamp/app/klein/example2/server.py +++ b/examples/twisted/wamp/app/klein/example2/server.py @@ -80,4 +80,4 @@ if __name__ == "__main__": log.startLogging(sys.stdout) reactor.listenTCP(8080, Site(webapp.resource())) - wampapp.run("ws://localhost:9000", "realm1", standalone=True) + wampapp.run("ws://127.0.0.1:9000", "realm1", standalone=True) diff --git a/examples/twisted/wamp/app/serial2ws/serial2ws.py b/examples/twisted/wamp/app/serial2ws/serial2ws.py index 9e05daab..0a105d1c 100644 --- a/examples/twisted/wamp/app/serial2ws/serial2ws.py +++ b/examples/twisted/wamp/app/serial2ws/serial2ws.py @@ -161,7 +161,7 @@ if __name__ == '__main__': # run WAMP application component ## from autobahn.twisted.wamp import ApplicationRunner - router = args.router or 'ws://localhost:8080' + router = args.router or 'ws://127.0.0.1:8080' runner = ApplicationRunner(router, u"realm1", extra={'port': args.port, 'baudrate': args.baudrate, 'debug': args.debug}, diff --git a/examples/twisted/wamp/app/subscribe_upon_call/subscribe_upon_call.py b/examples/twisted/wamp/app/subscribe_upon_call/subscribe_upon_call.py index 02a8c4fc..e96ab5b5 100644 --- a/examples/twisted/wamp/app/subscribe_upon_call/subscribe_upon_call.py +++ b/examples/twisted/wamp/app/subscribe_upon_call/subscribe_upon_call.py @@ -46,4 +46,4 @@ def onjoined(): if __name__ == "__main__": - app.run("ws://localhost:8080/ws", "realm1", standalone=True) + app.run("ws://127.0.0.1:8080/ws", "realm1", standalone=True) diff --git a/examples/twisted/wamp/authentication/README.md b/examples/twisted/wamp/authentication/README.md deleted file mode 100644 index 53048f8c..00000000 --- a/examples/twisted/wamp/authentication/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# WAMP Authentication - -Here you find a couple of examples for *authenticating* WAMP sessions. diff --git a/examples/twisted/wamp/authentication/otp/Makefile b/examples/twisted/wamp/authentication/otp/Makefile deleted file mode 100644 index 84a692af..00000000 --- a/examples/twisted/wamp/authentication/otp/Makefile +++ /dev/null @@ -1,5 +0,0 @@ -server: - PYTHONPATH="../../../../../autobahn" python server.py - -client: - PYTHONPATH="../../../../../autobahn" python client.py diff --git a/examples/twisted/wamp/authentication/otp/README.md b/examples/twisted/wamp/authentication/otp/README.md deleted file mode 100644 index 16d37b8c..00000000 --- a/examples/twisted/wamp/authentication/otp/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# WAMP One-Time-Password Authentication - -Run this demo by starting the server - - python server.py - -and opening `http://127.0.0.1:8080` in your browser. Open the JavaScript console to watch output. - -When asked to enter the "current code", start a [TOTP](http://en.wikipedia.org/wiki/Time-based_One-time_Password_Algorithm) application like [Google Authenticator](http://en.wikipedia.org/wiki/Google_Authenticator), create an account with secret - - MFRGGZDFMZTWQ2LK - -and enter the code currently shown for that account. - -> The secret as well as the username are hard-coded into the example program. - diff --git a/examples/twisted/wamp/authentication/otp/autobahn.min.js b/examples/twisted/wamp/authentication/otp/autobahn.min.js deleted file mode 100644 index 7500d3c2..00000000 --- a/examples/twisted/wamp/authentication/otp/autobahn.min.js +++ /dev/null @@ -1,271 +0,0 @@ -/* - - Counter block mode compatible with Dr Brian Gladman fileenc.c - derived from CryptoJS.mode.CTR - Jan Hruby jhruby.web@gmail.com - - (c) 2012 by C?dric Mesnil. All rights reserved. - - Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - - - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - MIT License (c) copyright 2013-2014 original author or authors MIT License (c) copyright 2010-2014 original author or authors MIT License (c) copyright 2010-2014 original author or authors MIT License (c) copyright 2010-2014 original author or authors MIT License (c) copyright 2010-2014 original author or authors MIT License (c) copyright 2010-2014 original author or authors MIT License (c) copyright 2010-2014 original author or authors MIT License (c) copyright 2010-2014 original author or authors MIT License (c) copyright 2010-2014 original author or authors MIT License (c) copyright 2010-2014 original author or authors MIT License (c) copyright 2010-2014 original author or authors MIT License (c) copyright 2010-2014 original author or authors MIT License (c) copyright 2010-2014 original author or authors MIT License (c) copyright 2010-2014 original author or authors MIT License (c) copyright 2010-2014 original author or authors MIT License (c) copyright 2010-2014 original author or authors MIT License (c) copyright 2010-2014 original author or authors MIT License (c) copyright 2010-2014 original author or authors MIT License (c) copyright 2010-2014 original author or authors MIT License (c) copyright 2010-2014 original author or authors MIT License (c) copyright 2010-2014 original author or authors MIT License (c) copyright 2010-2014 original author or authors MIT License (c) copyright 2010-2014 original author or authors MIT License (c) copyright 2010-2014 original author or authors */ -!function(la){if("object"==typeof exports)module.exports=la();else if("function"==typeof define&&define.amd)define(la);else{var h;"undefined"!=typeof window?h=window:"undefined"!=typeof global?h=global:"undefined"!=typeof self&&(h=self);h.autobahn=la()}}(function(){return function h(p,k,b){function a(d,e){if(!k[d]){if(!p[d]){var q="function"==typeof require&&require;if(!e&&q)return q(d,!0);if(c)return c(d,!0);throw Error("Cannot find module '"+d+"'");}q=k[d]={exports:{}};p[d][0].call(q.exports,function(c){var n= -p[d][1][c];return a(n?n:c)},q,q.exports,h,p,k,b)}return k[d].exports}for(var c="function"==typeof require&&require,e=0;ethis._max_retry_delay&&(this._retry_delay= -this._max_retry_delay);this._retry_count+=1;var a;a=this._retry&&this._retry_count<=this._max_retries?{count:this._retry_count,delay:this._retry_delay,will_retry:!0}:{count:null,delay:null,will_retry:!1};this._retry_delay_growth&&(this._retry_delay*=this._retry_delay_growth);return a};q.prototype.open=function(){function a(){b._transport=b._create_transport();if(b._transport)b._session=new c.Session(b._transport,b._defer,b._options.onchallenge),b._session_close_reason=null,b._session_close_message= -null,b._transport.onopen=function(){b._autoreconnect_reset();b._connect_successes+=1;b._session.join(b._options.realm,b._options.authmethods,b._options.authid)},b._session.onjoin=function(a){if(b.onopen)try{b.onopen(b._session,a)}catch(c){d.debug("Exception raised from app code while firing Connection.onopen()",c)}},b._session.onleave=function(a,c){b._session_close_reason=a;b._session_close_message=c.message||"";b._retry=!1;b._transport.close(1E3)},b._transport.onclose=function(c){b._autoreconnect_reset_timer(); -var e=b._transport=null;0===b._connect_successes?(e="unreachable",b._retry_if_unreachable||(b._retry=!1)):e=c.wasClean?"closed":"lost";c=b._autoreconnect_advance();if(b.onclose){var m={reason:b._session_close_reason,message:b._session_close_message,retry_delay:c.delay,retry_count:c.count,will_retry:c.will_retry};try{var q=b.onclose(e,m)}catch(v){d.debug("Exception raised from app code while firing Connection.onclose()",v)}}b._session&&(b._session._id=null,b._session=null,b._session_close_reason=null, -b._session_close_message=null);b._retry&&!q&&(c.will_retry?(b._is_retrying=!0,d.debug("retrying in "+c.delay+" s"),b._retry_timer=setTimeout(a,1E3*c.delay)):d.debug("giving up trying to reconnect"))};else if(b._retry=!1,b.onclose)b.onclose("unsupported",{reason:null,message:null,retry_delay:null,retry_count:null,will_retry:!1})}var b=this;if(b._transport)throw"connection already open (or opening)";b._autoreconnect_reset();b._retry=!0;a()};q.prototype.close=function(a,c){if(!this._transport&&!this._is_retrying)throw"connection already closed"; -this._retry=!1;this._session&&this._session.isOpen?this._session.leave(a,c):this._transport&&this._transport.close(1E3)};Object.defineProperty(q.prototype,"defer",{get:function(){return this._defer}});Object.defineProperty(q.prototype,"session",{get:function(){return this._session}});Object.defineProperty(q.prototype,"isOpen",{get:function(){return this._session&&this._session.isOpen?!0:!1}});Object.defineProperty(q.prototype,"isConnected",{get:function(){return this._transport?!0:!1}});Object.defineProperty(q.prototype, -"transport",{get:function(){return this._transport?this._transport:{info:{type:"none",url:null,protocol:null}}}});Object.defineProperty(q.prototype,"isRetrying",{get:function(){return this._is_retrying}});k.Connection=q}).call(this,"undefined"!==typeof self?self:"undefined"!==typeof window?window:{})},{"./autobahn.js":4,"./log.js":7,"./session.js":16,"./util.js":19,when:77}],7:[function(h,p,k){(function(b){var a=function(){};"AUTOBAHN_DEBUG"in b&&AUTOBAHN_DEBUG&&"console"in b&&(a=function(){console.log.apply(console, -arguments)});k.debug=a}).call(this,"undefined"!==typeof self?self:"undefined"!==typeof window?window:{})},{}],8:[function(h,p,k){h("./polyfill/object");h("./polyfill/array");h("./polyfill/string");h("./polyfill/function");h("./polyfill/console");h("./polyfill/typedarray");h("./polyfill/json")},{"./polyfill/array":9,"./polyfill/console":10,"./polyfill/function":11,"./polyfill/json":12,"./polyfill/object":13,"./polyfill/string":14,"./polyfill/typedarray":15}],9:[function(h,p,k){"function"!==typeof Array.prototype.reduce&& -(Array.prototype.reduce=function(b){var a,c,e,d;if(null===this||"undefined"===typeof this)throw new TypeError("Array.prototype.reduce called on null or undefined");if("function"!==typeof b)throw new TypeError(b+" is not a function");c=Object(this);a=c.length>>>0;d=0;if(2<=arguments.length)e=arguments[1];else{for(;d=a)throw new TypeError("Reduce of empty array with no initial value");e=c[d++]}for(;da&&(a+=this.length);0>a&&(a=0);for(var c=this.length;aa&&(a+=this.length);a>this.length-1&&(a=this.length-1);for(a++;0>>0)-1,e;if(2<=arguments.length)e=arguments[1];else{for(;0<= -c&&!c in a;)c--;if(0>c)throw new TypeError("Reduce of empty array with no initial value");e=a[c--]}for(;0<=c;c--)c in a&&(e=b(e,a[c],c,a));return e})},{}],10:[function(h,p,k){(function(b){(function(a){a||(a=window.console={log:function(a,b,d,m,q){},info:function(a,b,d,m,q){},warn:function(a,b,d,m,q){},error:function(a,b,d,m,q){},assert:function(a,b){}});"object"===typeof a.log&&(a.log=Function.prototype.call.bind(a.log,a),a.info=Function.prototype.call.bind(a.info,a),a.warn=Function.prototype.call.bind(a.warn, -a),a.error=Function.prototype.call.bind(a.error,a),a.debug=Function.prototype.call.bind(a.info,a));"group"in a||(a.group=function(b){a.info("\n--- "+b+" ---\n")});"groupEnd"in a||(a.groupEnd=function(){a.log("\n")});"assert"in a||(a.assert=function(a,b){if(!a)try{throw Error("assertion failed: "+b);}catch(d){setTimeout(function(){throw d;},0)}});"time"in a||function(){var b={};a.time=function(a){b[a]=(new Date).getTime()};a.timeEnd=function(e){var d=(new Date).getTime();a.info(e+": "+(e in b?d-b[e]: -0)+"ms")}}()})(b.console)}).call(this,"undefined"!==typeof self?self:"undefined"!==typeof window?window:{})},{}],11:[function(h,p,k){Function.prototype.bind||(Function.prototype.bind=function(b){var a=this,c=Array.prototype.slice.call(arguments,1);return function(){return a.apply(b,Array.prototype.concat.apply(c,arguments))}})},{}],12:[function(h,p,k){"object"!==typeof JSON&&(JSON={});(function(){function b(a){return 10>a?"0"+a:a}function a(a){d.lastIndex=0;return d.test(a)?'"'+a.replace(d,function(a){var b= -g[a];return"string"===typeof b?b:"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+a+'"'}function c(b,g){var d,e,v,y,H=m,f,x=g[b];x&&"object"===typeof x&&"function"===typeof x.toJSON&&(x=x.toJSON(b));"function"===typeof n&&(x=n.call(g,b,x));switch(typeof x){case "string":return a(x);case "number":return isFinite(x)?String(x):"null";case "boolean":case "null":return String(x);case "object":if(!x)return"null";m+=q;f=[];if("[object Array]"===Object.prototype.toString.apply(x)){y=x.length; -for(d=0;dt)throw RangeError("Array too large for polyfill"); -var f;for(f=0;f>f}function l(a,b){var f=32-b;return a<>>f}function z(a){return[a&255]}function h(a){return n(a[0],8)}function w(a){return[a&255]}function v(a){return l(a[0],8)}function y(a){a=aa(Number(a));return[0>a?0:255>8&255,a&255]}function f(a){return n(a[0]<<8|a[1],16)}function x(a){return[a>>8&255,a&255]}function J(a){return l(a[0]<<8|a[1],16)}function A(a){return[a>>24&255,a>>16&255,a>>8&255, -a&255]}function k(a){return n(a[0]<<24|a[1]<<16|a[2]<<8|a[3],32)}function p(a){return[a>>24&255,a>>16&255,a>>8&255,a&255]}function B(a){return l(a[0]<<24|a[1]<<16|a[2]<<8|a[3],32)}function E(a,b,f){function c(a){var b=V(a);a-=b;return 0.5>a?b:0.5a?1:0):0===a?(e=l=0,d=-Infinity===1/a?1:0):(d=0>a,a=u(a),a>=O(2,1-g)?(l=R(V(S(a)/r),1023),e=c(a/O(2,l)*O(2,f)),2<=e/O(2,f)&&(l+=1,e=1), -l>g?(l=(1<>=1;c.reverse();d=c.join("");a=(1<c?-0:0}function Q(a){return I(a,11,52)}function N(a){return E(a,11,52)}function F(a){return I(a,8,23)}function G(a){return E(a,8,23)}var s=void 0,t=1E5,r=Math.LN2,u=Math.abs,V=Math.floor,S=Math.log,M=Math.max,R=Math.min,O=Math.pow,aa=Math.round;(function(){var a=Object.defineProperty,b;try{b=Object.defineProperty({},"x",{})}catch(f){b=!1}a&&b||(Object.defineProperty=function(b,f,c){if(a)try{return a(b, -f,c)}catch(g){}if(b!==Object(b))throw TypeError("Object.defineProperty called on non-object");Object.prototype.__defineGetter__&&"get"in c&&Object.prototype.__defineGetter__.call(b,f,c.get);Object.prototype.__defineSetter__&&"set"in c&&Object.prototype.__defineSetter__.call(b,f,c.set);"value"in c&&(b[f]=c.value);return b})})();(function(){function l(a){a>>=0;if(0>a)throw RangeError("ArrayBuffer size is not a small enough positive integer.");Object.defineProperty(this,"byteLength",{value:a});Object.defineProperty(this, -"_bytes",{value:Array(a)});for(var b=0;b>=0;if(0>a)throw RangeError("length is not a small enough positive integer.");Object.defineProperty(this,"length",{value:a});Object.defineProperty(this,"byteLength",{value:a*this.BYTES_PER_ELEMENT});Object.defineProperty(this,"buffer",{value:new l(this.byteLength)});Object.defineProperty(this,"byteOffset",{value:0})}.apply(this,arguments);if(1<=arguments.length&& -"object"===e(arguments[0])&&arguments[0]instanceof n)return function(a){if(this.constructor!==a.constructor)throw TypeError();var b=a.length*this.BYTES_PER_ELEMENT;Object.defineProperty(this,"buffer",{value:new l(b)});Object.defineProperty(this,"byteLength",{value:b});Object.defineProperty(this,"byteOffset",{value:0});Object.defineProperty(this,"length",{value:a.length});for(b=0;b>>=0;if(b>a.byteLength)throw RangeError("byteOffset out of range");if(b%this.BYTES_PER_ELEMENT)throw RangeError("buffer length minus the byteOffset is not a multiple of the element size.");if(f===s){var c=a.byteLength-b;if(c%this.BYTES_PER_ELEMENT)throw RangeError("length of buffer minus byteOffset not a multiple of the element size");f=c/this.BYTES_PER_ELEMENT}else f>>>=0,c=f*this.BYTES_PER_ELEMENT;if(b+c>a.byteLength)throw RangeError("byteOffset and length reference an area beyond the end of the buffer"); -Object.defineProperty(this,"buffer",{value:a});Object.defineProperty(this,"byteLength",{value:c});Object.defineProperty(this,"byteOffset",{value:b});Object.defineProperty(this,"length",{value:f})}.apply(this,arguments);throw TypeError();}function r(a,b,f){var c=function(){Object.defineProperty(this,"constructor",{value:c});n.apply(this,arguments);g(this)};"__proto__"in c?c.__proto__=n:(c.from=n.from,c.of=n.of);c.BYTES_PER_ELEMENT=a;var d=function(){};d.prototype=t;c.prototype=new d;Object.defineProperty(c.prototype, -"BYTES_PER_ELEMENT",{value:a});Object.defineProperty(c.prototype,"_pack",{value:b});Object.defineProperty(c.prototype,"_unpack",{value:f});return c}a.ArrayBuffer=a.ArrayBuffer||l;Object.defineProperty(n,"from",{value:function(a){return new this(a)}});Object.defineProperty(n,"of",{value:function(){return new this(arguments)}});var t={};n.prototype=t;Object.defineProperty(n.prototype,"_getter",{value:function(a){if(1>arguments.length)throw SyntaxError("Not enough arguments");a>>>=0;if(a>=this.length)return s; -var b=[],f,c;f=0;for(c=this.byteOffset+a*this.BYTES_PER_ELEMENT;farguments.length)throw SyntaxError("Not enough arguments");a>>>=0;if(!(a>=this.length)){var f=this._pack(b),c,d;c=0;for(d=this.byteOffset+a*this.BYTES_PER_ELEMENT;c>>0,d=M(d,0);a>>=0;a=0>a?M(d+a,0):R(a,d);b>>=0;b=0>b?M(d+b,0):R(b,d);f=f===s?d:f>>0;f=0>f?M(d+f,0):R(f,d);d=R(f-b,d-a);from>>0;if(!m(a))throw TypeError();for(var d=0;d>>0,d=M(d,0);b>>=0;b=0>b?M(d+b,0):R(b,d);f=f===s?d:f>>0;for(d=0>f?M(d+f,0):R(f,d);b>>0;if(!m(a))throw TypeError(); -for(var d=[],g=0;g>>0;if(!m(a))throw TypeError();for(var c=1>>0;if(!m(a))throw TypeError();for(var c=1>>0;if(!m(a))throw TypeError();for(var d=0;d>>0;if(0===f)return-1;var c=0,d;0=f)return-1;for(c=0<=c?c:M(f-u(c),0);c>>0,c=Array(f),d=0;d>>0;if(0===f)return-1;var c=f;1>>0;if(!m(a))throw TypeError();var d=[];d.length=c;for(var g=0;g>>0;if(!m(a))throw TypeError();if(0===f&&1===arguments.length)throw TypeError();var c=0,d;for(d=2<=arguments.length?arguments[1]:b._getter(c++);c>>0;if(!m(a))throw TypeError(); -if(0===f&&1===arguments.length)throw TypeError();var f=f-1,c;for(c=2<=arguments.length?arguments[1]:b._getter(f--);0<=f;)c=a.call(s,c,b._getter(f),f,b),f--;return c}});Object.defineProperty(n.prototype,"reverse",{value:function(){if(this===s||null===this)throw TypeError();for(var a=Object(this),b=a.length>>>0,f=V(b/2),c=0,b=b-1;carguments.length)throw SyntaxError("Not enough arguments"); -var f,c,d,g,l,n;if("object"===typeof arguments[0]&&arguments[0].constructor===this.constructor){f=arguments[0];c=arguments[1]>>>0;if(c+f.length>this.length)throw RangeError("Offset plus length of array is out of range");n=this.byteOffset+c*this.BYTES_PER_ELEMENT;c=f.length*this.BYTES_PER_ELEMENT;if(f.buffer===this.buffer){d=[];g=0;for(l=f.byteOffset;g>>0;c=arguments[1]>>>0;if(c+d>this.length)throw RangeError("Offset plus length of array is out of range");for(g=0;g>>0,d=a>>0,d=0>d?M(c+d,0):R(d,c),g=b===s?c:b>>0,c=0>g?M(c+g,0):R(g,c), -g=new f.constructor(c-d),l=0;d>>0;if(!m(a))throw TypeError();for(var d=0;d>>0,c=Array(f),d=0;d>=0;b>>=0;1>arguments.length&&(a=0);2>arguments.length&&(b=this.length);0>a&&(a=this.length+a);0>b&&(b=this.length+b);var f=this.length;a=0>a?0:a>f?f:a;f=this.length;f=(0>b?0:b>f?f:b)-a;0>f&&(f=0);return new this.constructor(this.buffer,this.byteOffset+a*this.BYTES_PER_ELEMENT,f)}});var E=r(1,z,h),S=r(1,w,v),I=r(1,y,v),O=r(2,H,f),aa=r(2,x,J),ha=r(4,A,k),da=r(4,p,B), -U=r(4,G,F),$=r(8,N,Q);a.Int8Array=b.Int8Array=a.Int8Array||E;a.Uint8Array=b.Uint8Array=a.Uint8Array||S;a.Uint8ClampedArray=b.Uint8ClampedArray=a.Uint8ClampedArray||I;a.Int16Array=b.Int16Array=a.Int16Array||O;a.Uint16Array=b.Uint16Array=a.Uint16Array||aa;a.Int32Array=b.Int32Array=a.Int32Array||ha;a.Uint32Array=b.Uint32Array=a.Uint32Array||da;a.Float32Array=b.Float32Array=a.Float32Array||U;a.Float64Array=b.Float64Array=a.Float64Array||$})();(function(){function b(a,f){return m(a.get)?a.get(f):a[f]} -function f(a,b,c){if(!(a instanceof ArrayBuffer||"ArrayBuffer"===d(a)))throw TypeError();b>>>=0;if(b>a.byteLength)throw RangeError("byteOffset out of range");c=c===s?a.byteLength-b:c>>>0;if(b+c>a.byteLength)throw RangeError("byteOffset and length reference an area beyond the end of the buffer");Object.defineProperty(this,"buffer",{value:a});Object.defineProperty(this,"byteLength",{value:c});Object.defineProperty(this,"byteOffset",{value:b})}function c(f){return function(c,d){c>>>=0;if(c+f.BYTES_PER_ELEMENT> -this.byteLength)throw RangeError("Array index out of range");c+=this.byteOffset;for(var g=new a.Uint8Array(this.buffer,c,f.BYTES_PER_ELEMENT),n=[],e=0;e>>=0;if(c+f.BYTES_PER_ELEMENT>this.byteLength)throw RangeError("Array index out of range");d=new f([d]);d=new a.Uint8Array(d.buffer);var n=[],e;for(e=0;e must be a string");d.assert(!b||Array.isArray(b),"Session.join: must be an array []");d.assert(!c|| -"string"===typeof c,"Session.join: must be a string");if(this.isOpen)throw"session already open";this._goodbye_sent=!1;this._realm=a;var f={};f.roles=WAMP_FEATURES;b&&(f.authmethods=b);c&&(f.authid=c);this._send_wamp([1,a,f])};w.prototype.leave=function(a,b){d.assert(!a||"string"===typeof a,"Session.leave: must be a string");d.assert(!b||"string"===typeof b,"Session.leave: must be a string");if(!this.isOpen)throw"session not open";a||(a="wamp.close.normal");var c={};b&& -(c.message=b);this._send_wamp([6,c,a]);this._goodbye_sent=!0};w.prototype.call=function(b,c,g,f){d.assert("string"===typeof b,"Session.call: must be a string");d.assert(!c||Array.isArray(c),"Session.call: must be an array []");d.assert(!g||g instanceof Object,"Session.call: must be an object {}");d.assert(!f||f instanceof Object,"Session.call: must be an object {}");if(!this.isOpen)throw"session not open";var l=a(),n=this._defer();this._call_reqs[l]=[n,f];b=[48, -l,f||{},this.resolve(b)];c&&(b.push(c),g&&b.push(g));this._send_wamp(b);return n.promise.then?n.promise:n};w.prototype.publish=function(b,c,g,f){d.assert("string"===typeof b,"Session.publish: must be a string");d.assert(!c||Array.isArray(c),"Session.publish: must be an array []");d.assert(!g||g instanceof Object,"Session.publish: must be an object {}");d.assert(!f||f instanceof Object,"Session.publish: must be an object {}");if(!this.isOpen)throw"session not open"; -var l=f&&f.acknowledge,n=null,e=a();l&&(n=this._defer(),this._publish_reqs[e]=[n,f]);b=[16,e,f||{},this.resolve(b)];c&&(b.push(c),g&&b.push(g));this._send_wamp(b);if(n)return n.promise.then?n.promise:n};w.prototype.subscribe=function(b,c,g){d.assert("string"===typeof b,"Session.subscribe: must be a string");d.assert("function"===typeof c,"Session.subscribe: must be a function");d.assert(!g||g instanceof Object,"Session.subscribe: must be an object {}");if(!this.isOpen)throw"session not open"; -var f=a(),l=this._defer();this._subscribe_reqs[f]=[l,b,c,g];c=[32,f];g?c.push(g):c.push({});c.push(this.resolve(b));this._send_wamp(c);return l.promise.then?l.promise:l};w.prototype.register=function(b,c,g){d.assert("string"===typeof b,"Session.register: must be a string");d.assert("function"===typeof c,"Session.register: must be a function");d.assert(!g||g instanceof Object,"Session.register: must be an object {}");if(!this.isOpen)throw"session not open";var f=a(), -l=this._defer();this._register_reqs[f]=[l,b,c,g];c=[64,f];g?c.push(g):c.push({});c.push(this.resolve(b));this._send_wamp(c);return l.promise.then?l.promise:l};w.prototype.unsubscribe=function(b){d.assert(b instanceof l,"Session.unsubscribe: must be an instance of class autobahn.Subscription");if(!this.isOpen)throw"session not open";if(!(b.active&&b.id in this._subscriptions))throw"subscription not active";var c=this._subscriptions[b.id],g=c.indexOf(b);if(-1===g)throw"subscription not active"; -c.splice(g,1);b.active=!1;g=this._defer();c.length?g.resolve(!1):(c=a(),this._unsubscribe_reqs[c]=[g,b.id],this._send_wamp([34,c,b.id]));return g.promise.then?g.promise:g};w.prototype.unregister=function(b){d.assert(b instanceof z,"Session.unregister: must be an instance of class autobahn.Registration");if(!this.isOpen)throw"session not open";if(!(b.active&&b.id in this._registrations))throw"registration not active";var c=a(),g=this._defer();this._unregister_reqs[c]=[g,b];this._send_wamp([66, -c,b.id]);return g.promise.then?g.promise:g};w.prototype.prefix=function(a,b){d.assert("string"===typeof a,"Session.prefix: must be a string");d.assert(!b||"string"===typeof b,"Session.prefix: must be a string or falsy");b?this._prefixes[a]=b:a in this._prefixes&&delete this._prefixes[a]};w.prototype.resolve=function(a){d.assert("string"===typeof a,"Session.resolve: must be a string");var b=a.indexOf(":");if(0<=b){var c=a.substring(0,b);if(c in this._prefixes)return this._prefixes[c]+ -"."+a.substring(b+1);throw"cannot resolve CURIE prefix '"+c+"'";}return a};k.Session=w;k.Invocation=m;k.Event=q;k.Result=g;k.Error=n;k.Subscription=l;k.Registration=z;k.Publication=P}).call(this,"undefined"!==typeof self?self:"undefined"!==typeof window?window:{})},{"./log.js":7,"./util.js":19,when:77,"when/function":54}],17:[function(h,p,k){function b(b){a.assert(void 0!==b.url,"options.url missing");a.assert("string"===typeof b.url,"options.url must be a string");this._options=b}var a=h("../util.js"), -c=h("../log.js");h("when");b.prototype.type="longpoll";b.prototype.create=function(){var b=this;c.debug("longpoll.Factory.create");var d={protocol:void 0,send:void 0,close:void 0,onmessage:function(){},onopen:function(){},onclose:function(){},info:{type:"longpoll",url:null,protocol:"wamp.2.json"},_run:function(){var m=null,q=!1,g=b._options.request_timeout||2E3;a.http_post(b._options.url+"/open",JSON.stringify({protocols:["wamp.2.json"]}),g).then(function(n){function l(){c.debug("longpoll.Transport: polling for message ..."); -a.http_post(z+"/receive",null,g).then(function(a){a&&(a=JSON.parse(a),c.debug("longpoll.Transport: message received",a),d.onmessage(a));q||l()},function(a){c.debug("longpoll.Transport: could not receive message",a.code,a.text);q=!0;d.onclose({code:1001,reason:"transport receive failure (HTTP/POST status "+a.code+" - '"+a.text+"')",wasClean:!1})})}m=JSON.parse(n);var z=b._options.url+"/"+m.transport;d.info.url=z;c.debug("longpoll.Transport: open",m);d.close=function(b,l){if(q)throw"transport is already closing"; -q=!0;a.http_post(z+"/close",null,g).then(function(){c.debug("longpoll.Transport: transport closed");d.onclose({code:1E3,reason:"transport closed",wasClean:!0})},function(a){c.debug("longpoll.Transport: could not close transport",a.code,a.text)})};d.send=function(b){if(q)throw"transport is closing or closed already";c.debug("longpoll.Transport: sending message ...",b);b=JSON.stringify(b);a.http_post(z+"/send",b,g).then(function(){c.debug("longpoll.Transport: message sent")},function(a){c.debug("longpoll.Transport: could not send message", -a.code,a.text);q=!0;d.onclose({code:1001,reason:"transport send failure (HTTP/POST status "+a.code+" - '"+a.text+"')",wasClean:!1})})};l();d.onopen()},function(a){c.debug("longpoll.Transport: could not open transport",a.code,a.text);q=!0;d.onclose({code:1001,reason:"transport open failure (HTTP/POST status "+a.code+" - '"+a.text+"')",wasClean:!1})})}};d._run();return d};k.Factory=b},{"../log.js":7,"../util.js":19,when:77}],18:[function(h,p,k){(function(b){function a(a){c.assert(void 0!==a.url,"options.url missing"); -c.assert("string"===typeof a.url,"options.url must be a string");a.protocols?c.assert(Array.isArray(a.protocols),"options.protocols must be an array"):a.protocols=["wamp.2.json"];this._options=a}var c=h("../util.js"),e=h("../log.js");a.prototype.type="websocket";a.prototype.create=function(){var a=this,c={protocol:void 0,send:void 0,close:void 0,onmessage:function(){},onopen:function(){},onclose:function(){},info:{type:"websocket",url:null,protocol:"wamp.2.json"}};"window"in b?function(){var b;if("WebSocket"in -window)b=a._options.protocols?new window.WebSocket(a._options.url,a._options.protocols):new window.WebSocket(a._options.url);else if("MozWebSocket"in window)b=a._options.protocols?new window.MozWebSocket(a._options.url,a._options.protocols):new window.MozWebSocket(a._options.url);else throw"browser does not support WebSocket";b.onmessage=function(a){e.debug("WebSocket transport receive",a.data);a=JSON.parse(a.data);c.onmessage(a)};b.onopen=function(){c.info.url=a._options.url;c.onopen()};b.onclose= -function(a){c.onclose({code:a.code,reason:a.message,wasClean:a.wasClean})};c.send=function(a){a=JSON.stringify(a);e.debug("WebSocket transport send",a);b.send(a)};c.close=function(a,c){b.close(a,c)}}():function(){var b=h("ws"),g,n;a._options.protocols?(n=a._options.protocols,Array.isArray(n)&&(n=n.join(",")),g=new b(a._options.url,{protocol:n})):g=new b(a._options.url);c.send=function(a){a=JSON.stringify(a);g.send(a,{binary:!1})};c.close=function(a,b){g.close()};g.on("open",function(){c.onopen()}); -g.on("message",function(a,b){if(!b.binary){var d=JSON.parse(a);c.onmessage(d)}});g.on("close",function(a,b){c.onclose({code:a,reason:b,wasClean:1E3===a})});g.on("error",function(a){c.onclose({code:1006,reason:"",wasClean:!1})})}();return c};k.Factory=a}).call(this,"undefined"!==typeof self?self:"undefined"!==typeof window?window:{})},{"../log.js":7,"../util.js":19,ws:78}],19:[function(h,p,k){(function(b){var a=h("./log.js"),c=h("when"),e=function(a,c){if(!a){if(e.useDebugger||"AUTOBAHN_DEBUG"in b&& -AUTOBAHN_DEBUG)debugger;throw Error(c||"Assertion failed!");}};k.rand_normal=function(a,b){var c,g;do c=2*Math.random()-1,g=2*Math.random()-1,g=c*c+g*g;while(1<=g||0==g);g=Math.sqrt(-2*Math.log(g)/g);return(a||0)+c*g*(b||1)};k.assert=e;k.http_post=function(b,e,q){a.debug("new http_post request",b,e,q);var g=c.defer(),n=new XMLHttpRequest;n.onreadystatechange=function(){if(4===n.readyState){var a=1223===n.status?204:n.status;200===a&&g.resolve(n.responseText);if(204===a)g.resolve();else{var b=null; -try{b=n.statusText}catch(c){}g.reject({code:a,text:b})}}};n.open("POST",b,!0);n.setRequestHeader("Content-type","application/json; charset=utf-8");0b;b++)a[b]=128>b?b<<1:b<<1^283;for(var c=0,v=0,b=0;256>b;b++){var k=v^v<<1^v<<2^v<<3^v<<4,k=k>>>8^k&255^99;e[c]=k;d[k]=c;var A=a[c],p=a[A],C=a[p],B=257*a[k]^16843008*k;m[c]=B<<24|B>>>8;q[c]=B<<16|B>>>16;g[c]=B<<8|B>>>24;n[c]=B;B=16843009*C^65537*p^257*A^16843008*c;l[k]=B<<24|B>>>8;z[k]=B<<16|B>>>16;h[k]=B<< -8|B>>>24;w[k]=B;c?(c=A^a[a[a[C^A]]],v^=a[a[v]]):c=v=1}})();var v=[0,1,2,4,8,16,32,64,128,27,54],c=c.AES=a.extend({_doReset:function(){for(var a=this._key,b=a.words,c=a.sigBytes/4,a=4*((this._nRounds=c+6)+1),d=this._keySchedule=[],g=0;g>>24]<<24|e[n>>>16&255]<<16|e[n>>>8&255]<<8|e[n&255]):(n=n<<8|n>>>24,n=e[n>>>24]<<24|e[n>>>16&255]<<16|e[n>>>8&255]<<8|e[n&255],n^=v[g/c|0]<<24);d[g]=d[g-c]^n}b=this._invKeySchedule=[];for(c=0;cc||4>=g?n:l[e[n>>>24]]^z[e[n>>>16&255]]^h[e[n>>>8&255]]^w[e[n&255]]},encryptBlock:function(a,b){this._doCryptBlock(a,b,this._keySchedule,m,q,g,n,e)},decryptBlock:function(a,b){var c=a[b+1];a[b+1]=a[b+3];a[b+3]=c;this._doCryptBlock(a,b,this._invKeySchedule,l,z,h,w,d);c=a[b+1];a[b+1]=a[b+3];a[b+3]=c},_doCryptBlock:function(a,b,c,d,g,l,n,e){for(var m=this._nRounds,z=a[b]^c[0],q=a[b+1]^c[1],v=a[b+2]^c[2],h=a[b+3]^c[3],w=4,P=1;P>>24]^g[q>>>16&255]^l[v>>>8& -255]^n[h&255]^c[w++],p=d[q>>>24]^g[v>>>16&255]^l[h>>>8&255]^n[z&255]^c[w++],r=d[v>>>24]^g[h>>>16&255]^l[z>>>8&255]^n[q&255]^c[w++],h=d[h>>>24]^g[z>>>16&255]^l[q>>>8&255]^n[v&255]^c[w++],z=k,q=p,v=r;k=(e[z>>>24]<<24|e[q>>>16&255]<<16|e[v>>>8&255]<<8|e[h&255])^c[w++];p=(e[q>>>24]<<24|e[v>>>16&255]<<16|e[h>>>8&255]<<8|e[z&255])^c[w++];r=(e[v>>>24]<<24|e[h>>>16&255]<<16|e[z>>>8&255]<<8|e[q&255])^c[w++];h=(e[h>>>24]<<24|e[z>>>16&255]<<16|e[q>>>8&255]<<8|e[v&255])^c[w++];a[b]=k;a[b+1]=p;a[b+2]=r;a[b+3]= -h},keySize:8});b.AES=a._createHelper(c)})();return b.AES})},{"./cipher-core":21,"./core":22,"./enc-base64":23,"./evpkdf":25,"./md5":30}],21:[function(h,p,k){(function(b,a){"object"===typeof k?p.exports=k=a(h("./core")):a(b.CryptoJS)})(this,function(b){b.lib.Cipher||function(a){var c=b.lib,e=c.Base,d=c.WordArray,m=c.BufferedBlockAlgorithm,q=b.enc.Base64,g=b.algo.EvpKDF,n=c.Cipher=m.extend({cfg:e.extend(),createEncryptor:function(a,b){return this.create(this._ENC_XFORM_MODE,a,b)},createDecryptor:function(a, -b){return this.create(this._DEC_XFORM_MODE,a,b)},init:function(a,b,c){this.cfg=this.cfg.extend(c);this._xformMode=a;this._key=b;this.reset()},reset:function(){m.reset.call(this);this._doReset()},process:function(a){this._append(a);return this._process()},finalize:function(a){a&&this._append(a);return this._doFinalize()},keySize:4,ivSize:4,_ENC_XFORM_MODE:1,_DEC_XFORM_MODE:2,_createHelper:function(){return function(a){return{encrypt:function(b,c,d){return("string"==typeof c?y:v).encrypt(a,b,c,d)}, -decrypt:function(b,c,d){return("string"==typeof c?y:v).decrypt(a,b,c,d)}}}}()});c.StreamCipher=n.extend({_doFinalize:function(){return this._process(!0)},blockSize:1});var l=b.mode={},z=c.BlockCipherMode=e.extend({createEncryptor:function(a,b){return this.Encryptor.create(a,b)},createDecryptor:function(a,b){return this.Decryptor.create(a,b)},init:function(a,b){this._cipher=a;this._iv=b}}),l=l.CBC=function(){function b(c,d,f){var g=this._iv;g?this._iv=a:g=this._prevBlock;for(var l=0;l>>2]&255}};c.BlockCipher=n.extend({cfg:n.cfg.extend({mode:l,padding:h}),reset:function(){n.reset.call(this);var a=this.cfg,b=a.iv,a=a.mode;if(this._xformMode==this._ENC_XFORM_MODE)var c=a.createEncryptor;else c=a.createDecryptor,this._minBufferSize=1;this._mode=c.call(a,this,b&&b.words)},_doProcessBlock:function(a,b){this._mode.processBlock(a,b)},_doFinalize:function(){var a=this.cfg.padding;if(this._xformMode==this._ENC_XFORM_MODE){a.pad(this._data,this.blockSize);var b=this._process(!0)}else b= -this._process(!0),a.unpad(b);return b},blockSize:4});var w=c.CipherParams=e.extend({init:function(a){this.mixIn(a)},toString:function(a){return(a||this.formatter).stringify(this)}}),l=(b.format={}).OpenSSL={stringify:function(a){var b=a.ciphertext;a=a.salt;return(a?d.create([1398893684,1701076831]).concat(a).concat(b):b).toString(q)},parse:function(a){a=q.parse(a);var b=a.words;if(1398893684==b[0]&&1701076831==b[1]){var c=d.create(b.slice(2,4));b.splice(0,4);a.sigBytes-=16}return w.create({ciphertext:a, -salt:c})}},v=c.SerializableCipher=e.extend({cfg:e.extend({format:l}),encrypt:function(a,b,c,d){d=this.cfg.extend(d);var g=a.createEncryptor(c,d);b=g.finalize(b);g=g.cfg;return w.create({ciphertext:b,key:c,iv:g.iv,algorithm:a,mode:g.mode,padding:g.padding,blockSize:a.blockSize,formatter:d.format})},decrypt:function(a,b,c,d){d=this.cfg.extend(d);b=this._parse(b,d.format);return a.createDecryptor(c,d).finalize(b.ciphertext)},_parse:function(a,b){return"string"==typeof a?b.parse(a,this):a}}),e=(b.kdf= -{}).OpenSSL={execute:function(a,b,c,l){l||(l=d.random(8));a=g.create({keySize:b+c}).compute(a,l);c=d.create(a.words.slice(b),4*c);a.sigBytes=4*b;return w.create({key:a,iv:c,salt:l})}},y=c.PasswordBasedCipher=v.extend({cfg:v.cfg.extend({kdf:e}),encrypt:function(a,b,c,d){d=this.cfg.extend(d);c=d.kdf.execute(c,a.keySize,a.ivSize);d.iv=c.iv;a=v.encrypt.call(this,a,b,c.key,d);a.mixIn(c);return a},decrypt:function(a,b,c,d){d=this.cfg.extend(d);b=this._parse(b,d.format);c=d.kdf.execute(c,a.keySize,a.ivSize, -b.salt);d.iv=c.iv;return v.decrypt.call(this,a,b,c.key,d)}})}()})},{"./core":22}],22:[function(h,p,k){(function(b,a){"object"===typeof k?p.exports=k=a():b.CryptoJS=a()})(this,function(){var b=b||function(a,b){var e={},d=e.lib={},m=d.Base=function(){function a(){}return{extend:function(b){a.prototype=this;var c=new a;b&&c.mixIn(b);c.hasOwnProperty("init")||(c.init=function(){c.$super.init.apply(this,arguments)});c.init.prototype=c;c.$super=this;return c},create:function(){var a=this.extend();a.init.apply(a, -arguments);return a},init:function(){},mixIn:function(a){for(var b in a)a.hasOwnProperty(b)&&(this[b]=a[b]);a.hasOwnProperty("toString")&&(this.toString=a.toString)},clone:function(){return this.init.prototype.extend(this)}}}(),q=d.WordArray=m.extend({init:function(a,d){a=this.words=a||[];this.sigBytes=d!=b?d:4*a.length},toString:function(a){return(a||n).stringify(this)},concat:function(a){var b=this.words,c=a.words,d=this.sigBytes;a=a.sigBytes;this.clamp();if(d%4)for(var g=0;g>>2]|= -(c[g>>>2]>>>24-g%4*8&255)<<24-(d+g)%4*8;else if(65535>>2]=c[g>>>2];else b.push.apply(b,c);this.sigBytes+=a;return this},clamp:function(){var b=this.words,c=this.sigBytes;b[c>>>2]&=4294967295<<32-c%4*8;b.length=a.ceil(c/4)},clone:function(){var a=m.clone.call(this);a.words=this.words.slice(0);return a},random:function(b){for(var c=[],d=0;d>>2]>>>24-d%4*8&255;c.push((g>>>4).toString(16));c.push((g&15).toString(16))}return c.join("")},parse:function(a){for(var b=a.length,c=[],d=0;d>>3]|=parseInt(a.substr(d,2),16)<<24-d%8*4;return new q.init(c,b/2)}},l=g.Latin1={stringify:function(a){var b=a.words;a=a.sigBytes;for(var c=[],d=0;d>>2]>>>24-d%4*8&255));return c.join("")},parse:function(a){for(var b=a.length,c=[],d=0;d>>2]|=(a.charCodeAt(d)&255)<< -24-d%4*8;return new q.init(c,b)}},z=g.Utf8={stringify:function(a){try{return decodeURIComponent(escape(l.stringify(a)))}catch(b){throw Error("Malformed UTF-8 data");}},parse:function(a){return l.parse(unescape(encodeURIComponent(a)))}},h=d.BufferedBlockAlgorithm=m.extend({reset:function(){this._data=new q.init;this._nDataBytes=0},_append:function(a){"string"==typeof a&&(a=z.parse(a));this._data.concat(a);this._nDataBytes+=a.sigBytes},_process:function(b){var c=this._data,d=c.words,g=c.sigBytes,l= -this.blockSize,n=g/(4*l),n=b?a.ceil(n):a.max((n|0)-this._minBufferSize,0);b=n*l;g=a.min(4*b,g);if(b){for(var e=0;e>>2]>>>24-q%4*8&255)<<16|(b[q+1>>>2]>>>24-(q+1)%4*8&255)<<8|b[q+2>>>2]>>>24-(q+2)%4*8&255,n=0;4>n&&q+0.75*n>>6*(3-n)&63));if(b=m.charAt(64))for(;a.length%4;)a.push(b);return a.join("")},parse:function(b){var e=b.length,d=this._map,m=d.charAt(64);m&&(m=b.indexOf(m),-1!=m&&(e=m));for(var m=[],q=0,g=0;g>>6-g%4*2;m[q>>>2]|=(n| -l)<<24-q%4*8;q++}return a.create(m,q)},_map:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="}})();return b.enc.Base64})},{"./core":22}],24:[function(h,p,k){(function(b,a){"object"===typeof k?p.exports=k=a(h("./core")):a(b.CryptoJS)})(this,function(b){(function(){function a(a){return a<<8&4278255360|a>>>8&16711935}var c=b.lib.WordArray,e=b.enc;e.Utf16=e.Utf16BE={stringify:function(a){var b=a.words;a=a.sigBytes;for(var c=[],g=0;g>>2]>>>16-g% -4*8&65535));return c.join("")},parse:function(a){for(var b=a.length,e=[],g=0;g>>1]|=a.charCodeAt(g)<<16-g%2*16;return c.create(e,2*b)}};e.Utf16LE={stringify:function(b){var c=b.words;b=b.sigBytes;for(var e=[],g=0;g>>2]>>>16-g%4*8&65535);e.push(String.fromCharCode(n))}return e.join("")},parse:function(b){for(var e=b.length,q=[],g=0;g>>1]|=a(b.charCodeAt(g)<<16-g%2*16);return c.create(q,2*e)}}})();return b.enc.Utf16})},{"./core":22}],25:[function(h,p,k){(function(b, -a,c){"object"===typeof k?p.exports=k=a(h("./core"),h("./sha1"),h("./hmac")):a(b.CryptoJS)})(this,function(b){(function(){var a=b.lib,c=a.Base,e=a.WordArray,a=b.algo,d=a.EvpKDF=c.extend({cfg:c.extend({keySize:4,hasher:a.MD5,iterations:1}),init:function(a){this.cfg=this.cfg.extend(a)},compute:function(a,b){for(var c=this.cfg,d=c.hasher.create(),l=e.create(),z=l.words,h=c.keySize,c=c.iterations;z.lengthm&&(e=b.finalize(e));e.clamp();for(var q=this._oKey=e.clone(),g=this._iKey=e.clone(),n=q.words,l=g.words,z=0;z>>2]|=a[q]<<24-q%4*8;c.call(this,m,b)}else c.apply(this,arguments)}).prototype=a}})();return b.lib.WordArray})},{"./core":22}],30:[function(h,p,k){(function(b,a){"object"===typeof k?p.exports=k=a(h("./core")):a(b.CryptoJS)})(this,function(b){(function(a){function c(a,b,c,d,g,l,f){a=a+(b&c|~b&d)+g+f;return(a<>>32-l)+b}function e(a,b,c,d,g,l,f){a=a+(b&d|c&~d)+g+f;return(a<>>32-l)+b}function d(a,b,c,d,g,l,f){a=a+(b^c^d)+g+f;return(a<>>32-l)+b}function m(a,b,c,d,g,l,f){a= -a+(c^(b|~d))+g+f;return(a<>>32-l)+b}var q=b.lib,g=q.WordArray,n=q.Hasher,q=b.algo,l=[];(function(){for(var b=0;64>b;b++)l[b]=4294967296*a.abs(a.sin(b+1))|0})();q=q.MD5=n.extend({_doReset:function(){this._hash=new g.init([1732584193,4023233417,2562383102,271733878])},_doProcessBlock:function(a,b){for(var g=0;16>g;g++){var n=b+g,q=a[n];a[n]=(q<<8|q>>>24)&16711935|(q<<24|q>>>8)&4278255360}var g=this._hash.words,n=a[b+0],q=a[b+1],h=a[b+2],f=a[b+3],k=a[b+4],p=a[b+5],A=a[b+6],L=a[b+7],C=a[b+8],B=a[b+ -9],E=a[b+10],I=a[b+11],Q=a[b+12],N=a[b+13],F=a[b+14],G=a[b+15],s=g[0],t=g[1],r=g[2],u=g[3],s=c(s,t,r,u,n,7,l[0]),u=c(u,s,t,r,q,12,l[1]),r=c(r,u,s,t,h,17,l[2]),t=c(t,r,u,s,f,22,l[3]),s=c(s,t,r,u,k,7,l[4]),u=c(u,s,t,r,p,12,l[5]),r=c(r,u,s,t,A,17,l[6]),t=c(t,r,u,s,L,22,l[7]),s=c(s,t,r,u,C,7,l[8]),u=c(u,s,t,r,B,12,l[9]),r=c(r,u,s,t,E,17,l[10]),t=c(t,r,u,s,I,22,l[11]),s=c(s,t,r,u,Q,7,l[12]),u=c(u,s,t,r,N,12,l[13]),r=c(r,u,s,t,F,17,l[14]),t=c(t,r,u,s,G,22,l[15]),s=e(s,t,r,u,q,5,l[16]),u=e(u,s,t,r,A,9,l[17]), -r=e(r,u,s,t,I,14,l[18]),t=e(t,r,u,s,n,20,l[19]),s=e(s,t,r,u,p,5,l[20]),u=e(u,s,t,r,E,9,l[21]),r=e(r,u,s,t,G,14,l[22]),t=e(t,r,u,s,k,20,l[23]),s=e(s,t,r,u,B,5,l[24]),u=e(u,s,t,r,F,9,l[25]),r=e(r,u,s,t,f,14,l[26]),t=e(t,r,u,s,C,20,l[27]),s=e(s,t,r,u,N,5,l[28]),u=e(u,s,t,r,h,9,l[29]),r=e(r,u,s,t,L,14,l[30]),t=e(t,r,u,s,Q,20,l[31]),s=d(s,t,r,u,p,4,l[32]),u=d(u,s,t,r,C,11,l[33]),r=d(r,u,s,t,I,16,l[34]),t=d(t,r,u,s,F,23,l[35]),s=d(s,t,r,u,q,4,l[36]),u=d(u,s,t,r,k,11,l[37]),r=d(r,u,s,t,L,16,l[38]),t=d(t, -r,u,s,E,23,l[39]),s=d(s,t,r,u,N,4,l[40]),u=d(u,s,t,r,n,11,l[41]),r=d(r,u,s,t,f,16,l[42]),t=d(t,r,u,s,A,23,l[43]),s=d(s,t,r,u,B,4,l[44]),u=d(u,s,t,r,Q,11,l[45]),r=d(r,u,s,t,G,16,l[46]),t=d(t,r,u,s,h,23,l[47]),s=m(s,t,r,u,n,6,l[48]),u=m(u,s,t,r,L,10,l[49]),r=m(r,u,s,t,F,15,l[50]),t=m(t,r,u,s,p,21,l[51]),s=m(s,t,r,u,Q,6,l[52]),u=m(u,s,t,r,f,10,l[53]),r=m(r,u,s,t,E,15,l[54]),t=m(t,r,u,s,q,21,l[55]),s=m(s,t,r,u,C,6,l[56]),u=m(u,s,t,r,G,10,l[57]),r=m(r,u,s,t,A,15,l[58]),t=m(t,r,u,s,N,21,l[59]),s=m(s,t, -r,u,k,6,l[60]),u=m(u,s,t,r,I,10,l[61]),r=m(r,u,s,t,h,15,l[62]),t=m(t,r,u,s,B,21,l[63]);g[0]=g[0]+s|0;g[1]=g[1]+t|0;g[2]=g[2]+r|0;g[3]=g[3]+u|0},_doFinalize:function(){var b=this._data,c=b.words,d=8*this._nDataBytes,g=8*b.sigBytes;c[g>>>5]|=128<<24-g%32;var l=a.floor(d/4294967296);c[(g+64>>>9<<4)+15]=(l<<8|l>>>24)&16711935|(l<<24|l>>>8)&4278255360;c[(g+64>>>9<<4)+14]=(d<<8|d>>>24)&16711935|(d<<24|d>>>8)&4278255360;b.sigBytes=4*(c.length+1);this._process();b=this._hash;c=b.words;for(d=0;4>d;d++)g=c[d], -c[d]=(g<<8|g>>>24)&16711935|(g<<24|g>>>8)&4278255360;return b},clone:function(){var a=n.clone.call(this);a._hash=this._hash.clone();return a}});b.MD5=n._createHelper(q);b.HmacMD5=n._createHmacHelper(q)})(Math);return b.MD5})},{"./core":22}],31:[function(h,p,k){(function(b,a,c){"object"===typeof k?p.exports=k=a(h("./core"),h("./cipher-core")):a(b.CryptoJS)})(this,function(b){b.mode.CFB=function(){function a(a,b,c,q){var g=this._iv;g?(g=g.slice(0),this._iv=void 0):g=this._prevBlock;q.encryptBlock(g, -0);for(q=0;q>24&255)){var b=a>>16&255,c=a>>8&255,g=a&255;255===b?(b=0,255===c?(c=0,255===g?g=0:++g):++c):++b;a=0+(b<<16)+(c<<8);a+=g}else a+=16777216;return a}var c=b.lib.BlockCipherMode.extend(),e=c.Encryptor=c.extend({processBlock:function(b,c){var e=this._cipher,g=e.blockSize,n=this._iv,l=this._counter;n&&(l=this._counter=n.slice(0),this._iv=void 0);n=l;0===(n[0]=a(n[0]))&&(n[1]=a(n[1]));l=l.slice(0);e.encryptBlock(l,0); -for(e=0;e>>2]|=d<<24-e%4*8;a.sigBytes+=d},unpad:function(a){a.sigBytes-=a.words[a.sigBytes-1>>>2]&255}};return b.pad.Ansix923})},{"./cipher-core":21,"./core":22}],37:[function(h,p,k){(function(b,a,c){"object"===typeof k?p.exports=k=a(h("./core"),h("./cipher-core")): -a(b.CryptoJS)})(this,function(b){b.pad.Iso10126={pad:function(a,c){var e=4*c,e=e-a.sigBytes%e;a.concat(b.lib.WordArray.random(e-1)).concat(b.lib.WordArray.create([e<<24],1))},unpad:function(a){a.sigBytes-=a.words[a.sigBytes-1>>>2]&255}};return b.pad.Iso10126})},{"./cipher-core":21,"./core":22}],38:[function(h,p,k){(function(b,a,c){"object"===typeof k?p.exports=k=a(h("./core"),h("./cipher-core")):a(b.CryptoJS)})(this,function(b){b.pad.Iso97971={pad:function(a,c){a.concat(b.lib.WordArray.create([2147483648], -1));b.pad.ZeroPadding.pad(a,c)},unpad:function(a){b.pad.ZeroPadding.unpad(a);a.sigBytes--}};return b.pad.Iso97971})},{"./cipher-core":21,"./core":22}],39:[function(h,p,k){(function(b,a,c){"object"===typeof k?p.exports=k=a(h("./core"),h("./cipher-core")):a(b.CryptoJS)})(this,function(b){b.pad.NoPadding={pad:function(){},unpad:function(){}};return b.pad.NoPadding})},{"./cipher-core":21,"./core":22}],40:[function(h,p,k){(function(b,a,c){"object"===typeof k?p.exports=k=a(h("./core"),h("./cipher-core")): -a(b.CryptoJS)})(this,function(b){b.pad.ZeroPadding={pad:function(a,b){var e=4*b;a.clamp();a.sigBytes+=e-(a.sigBytes%e||e)},unpad:function(a){for(var b=a.words,e=a.sigBytes-1;!(b[e>>>2]>>>24-e%4*8&255);)e--;a.sigBytes=e+1}};return b.pad.ZeroPadding})},{"./cipher-core":21,"./core":22}],41:[function(h,p,k){(function(b,a,c){"object"===typeof k?p.exports=k=a(h("./core"),h("./sha1"),h("./hmac")):a(b.CryptoJS)})(this,function(b){(function(){var a=b.lib,c=a.Base,e=a.WordArray,a=b.algo,d=a.HMAC,m=a.PBKDF2= -c.extend({cfg:c.extend({keySize:4,hasher:a.SHA1,iterations:1}),init:function(a){this.cfg=this.cfg.extend(a)},compute:function(a,b){for(var c=this.cfg,l=d.create(c.hasher,a),m=e.create(),h=e.create([1]),w=m.words,k=h.words,y=c.keySize,c=c.iterations;w.lengthc;c++)d[c]=b[c];b[0]=b[0]+1295307597+this._b|0;b[1]=b[1]+3545052371+(b[0]>>>0>>0?1:0)|0;b[2]=b[2]+886263092+(b[1]>>>0>>0?1:0)|0;b[3]=b[3]+1295307597+(b[2]>>>0>>0?1:0)|0;b[4]=b[4]+ -3545052371+(b[3]>>>0>>0?1:0)|0;b[5]=b[5]+886263092+(b[4]>>>0>>0?1:0)|0;b[6]=b[6]+1295307597+(b[5]>>>0>>0?1:0)|0;b[7]=b[7]+3545052371+(b[6]>>>0>>0?1:0)|0;this._b=b[7]>>>0>>0?1:0;for(c=0;8>c;c++){var e=a[c]+b[c],h=e&65535,q=e>>>16;m[c]=((h*h>>>17)+h*q>>>15)+q*q^((e&4294901760)*e|0)+((e&65535)*e|0)}a[0]=m[0]+(m[7]<<16|m[7]>>>16)+(m[6]<<16|m[6]>>>16)|0;a[1]=m[1]+(m[0]<<8|m[0]>>>24)+m[7]|0;a[2]=m[2]+(m[1]<<16|m[1]>>>16)+(m[0]<<16|m[0]>>>16)|0;a[3]=m[3]+(m[2]<<8|m[2]>>>24)+ -m[1]|0;a[4]=m[4]+(m[3]<<16|m[3]>>>16)+(m[2]<<16|m[2]>>>16)|0;a[5]=m[5]+(m[4]<<8|m[4]>>>24)+m[3]|0;a[6]=m[6]+(m[5]<<16|m[5]>>>16)+(m[4]<<16|m[4]>>>16)|0;a[7]=m[7]+(m[6]<<8|m[6]>>>24)+m[5]|0}var c=b.lib.StreamCipher,e=[],d=[],m=[],h=b.algo.RabbitLegacy=c.extend({_doReset:function(){for(var b=this._key.words,c=this.cfg.iv,d=this._X=[b[0],b[3]<<16|b[2]>>>16,b[1],b[0]<<16|b[3]>>>16,b[2],b[1]<<16|b[0]>>>16,b[3],b[2]<<16|b[1]>>>16],b=this._C=[b[2]<<16|b[2]>>>16,b[0]&4294901760|b[1]&65535,b[3]<<16|b[3]>>> -16,b[1]&4294901760|b[2]&65535,b[0]<<16|b[0]>>>16,b[2]&4294901760|b[3]&65535,b[1]<<16|b[1]>>>16,b[3]&4294901760|b[0]&65535],e=this._b=0;4>e;e++)a.call(this);for(e=0;8>e;e++)b[e]^=d[e+4&7];if(c){var d=c.words,c=d[0],d=d[1],c=(c<<8|c>>>24)&16711935|(c<<24|c>>>8)&4278255360,d=(d<<8|d>>>24)&16711935|(d<<24|d>>>8)&4278255360,e=c>>>16|d&4294901760,m=d<<16|c&65535;b[0]^=c;b[1]^=e;b[2]^=d;b[3]^=m;b[4]^=c;b[5]^=e;b[6]^=d;b[7]^=m;for(e=0;4>e;e++)a.call(this)}},_doProcessBlock:function(b,c){var d=this._X;a.call(this); -e[0]=d[0]^d[5]>>>16^d[3]<<16;e[1]=d[2]^d[7]>>>16^d[5]<<16;e[2]=d[4]^d[1]>>>16^d[7]<<16;e[3]=d[6]^d[3]>>>16^d[1]<<16;for(d=0;4>d;d++)e[d]=(e[d]<<8|e[d]>>>24)&16711935|(e[d]<<24|e[d]>>>8)&4278255360,b[c+d]^=e[d]},blockSize:4,ivSize:2});b.RabbitLegacy=c._createHelper(h)})();return b.RabbitLegacy})},{"./cipher-core":21,"./core":22,"./enc-base64":23,"./evpkdf":25,"./md5":30}],43:[function(h,p,k){(function(b,a,c){"object"===typeof k?p.exports=k=a(h("./core"),h("./enc-base64"),h("./md5"),h("./evpkdf"),h("./cipher-core")): -a(b.CryptoJS)})(this,function(b){(function(){function a(){for(var a=this._X,b=this._C,c=0;8>c;c++)d[c]=b[c];b[0]=b[0]+1295307597+this._b|0;b[1]=b[1]+3545052371+(b[0]>>>0>>0?1:0)|0;b[2]=b[2]+886263092+(b[1]>>>0>>0?1:0)|0;b[3]=b[3]+1295307597+(b[2]>>>0>>0?1:0)|0;b[4]=b[4]+3545052371+(b[3]>>>0>>0?1:0)|0;b[5]=b[5]+886263092+(b[4]>>>0>>0?1:0)|0;b[6]=b[6]+1295307597+(b[5]>>>0>>0?1:0)|0;b[7]=b[7]+3545052371+(b[6]>>>0>>0?1:0)|0;this._b=b[7]>>>0>>0?1:0;for(c= -0;8>c;c++){var e=a[c]+b[c],h=e&65535,q=e>>>16;m[c]=((h*h>>>17)+h*q>>>15)+q*q^((e&4294901760)*e|0)+((e&65535)*e|0)}a[0]=m[0]+(m[7]<<16|m[7]>>>16)+(m[6]<<16|m[6]>>>16)|0;a[1]=m[1]+(m[0]<<8|m[0]>>>24)+m[7]|0;a[2]=m[2]+(m[1]<<16|m[1]>>>16)+(m[0]<<16|m[0]>>>16)|0;a[3]=m[3]+(m[2]<<8|m[2]>>>24)+m[1]|0;a[4]=m[4]+(m[3]<<16|m[3]>>>16)+(m[2]<<16|m[2]>>>16)|0;a[5]=m[5]+(m[4]<<8|m[4]>>>24)+m[3]|0;a[6]=m[6]+(m[5]<<16|m[5]>>>16)+(m[4]<<16|m[4]>>>16)|0;a[7]=m[7]+(m[6]<<8|m[6]>>>24)+m[5]|0}var c=b.lib.StreamCipher, -e=[],d=[],m=[],h=b.algo.Rabbit=c.extend({_doReset:function(){for(var b=this._key.words,c=this.cfg.iv,d=0;4>d;d++)b[d]=(b[d]<<8|b[d]>>>24)&16711935|(b[d]<<24|b[d]>>>8)&4278255360;for(var e=this._X=[b[0],b[3]<<16|b[2]>>>16,b[1],b[0]<<16|b[3]>>>16,b[2],b[1]<<16|b[0]>>>16,b[3],b[2]<<16|b[1]>>>16],b=this._C=[b[2]<<16|b[2]>>>16,b[0]&4294901760|b[1]&65535,b[3]<<16|b[3]>>>16,b[1]&4294901760|b[2]&65535,b[0]<<16|b[0]>>>16,b[2]&4294901760|b[3]&65535,b[1]<<16|b[1]>>>16,b[3]&4294901760|b[0]&65535],d=this._b=0;4> -d;d++)a.call(this);for(d=0;8>d;d++)b[d]^=e[d+4&7];if(c){var d=c.words,c=d[0],d=d[1],c=(c<<8|c>>>24)&16711935|(c<<24|c>>>8)&4278255360,d=(d<<8|d>>>24)&16711935|(d<<24|d>>>8)&4278255360,e=c>>>16|d&4294901760,m=d<<16|c&65535;b[0]^=c;b[1]^=e;b[2]^=d;b[3]^=m;b[4]^=c;b[5]^=e;b[6]^=d;b[7]^=m;for(d=0;4>d;d++)a.call(this)}},_doProcessBlock:function(b,c){var d=this._X;a.call(this);e[0]=d[0]^d[5]>>>16^d[3]<<16;e[1]=d[2]^d[7]>>>16^d[5]<<16;e[2]=d[4]^d[1]>>>16^d[7]<<16;e[3]=d[6]^d[3]>>>16^d[1]<<16;for(d=0;4>d;d++)e[d]= -(e[d]<<8|e[d]>>>24)&16711935|(e[d]<<24|e[d]>>>8)&4278255360,b[c+d]^=e[d]},blockSize:4,ivSize:2});b.Rabbit=c._createHelper(h)})();return b.Rabbit})},{"./cipher-core":21,"./core":22,"./enc-base64":23,"./evpkdf":25,"./md5":30}],44:[function(h,p,k){(function(b,a,c){"object"===typeof k?p.exports=k=a(h("./core"),h("./enc-base64"),h("./md5"),h("./evpkdf"),h("./cipher-core")):a(b.CryptoJS)})(this,function(b){(function(){function a(){for(var a=this._S,b=this._i,c=this._j,d=0,e=0;4>e;e++){var b=(b+1)%256,c= -(c+a[b])%256,h=a[b];a[b]=a[c];a[c]=h;d|=a[(a[b]+a[c])%256]<<24-8*e}this._i=b;this._j=c;return d}var c=b.lib.StreamCipher,e=b.algo,d=e.RC4=c.extend({_doReset:function(){for(var a=this._key,b=a.words,a=a.sigBytes,c=this._S=[],d=0;256>d;d++)c[d]=d;for(var e=d=0;256>d;d++){var h=d%a,e=(e+c[d]+(b[h>>>2]>>>24-h%4*8&255))%256,h=c[d];c[d]=c[e];c[e]=h}this._i=this._j=0},_doProcessBlock:function(b,c){b[c]^=a.call(this)},keySize:8,ivSize:0});b.RC4=c._createHelper(d);e=e.RC4Drop=d.extend({cfg:d.cfg.extend({drop:192}), -_doReset:function(){d._doReset.call(this);for(var b=this.cfg.drop;0>>32-b}a=b.lib;var e=a.WordArray,d=a.Hasher;a=b.algo;var h=e.create([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,7,4,13,1,10,6,15,3,12, -0,9,5,2,14,11,8,3,10,14,4,9,15,8,1,2,7,0,6,13,11,5,12,1,9,11,10,0,8,12,4,13,3,7,15,14,5,6,2,4,0,5,9,7,12,2,10,14,1,3,8,11,6,15,13]),q=e.create([5,14,7,0,9,2,11,4,13,6,15,8,1,10,3,12,6,11,3,7,0,13,5,10,14,15,8,12,4,9,1,2,15,5,1,3,7,14,6,9,11,8,12,2,10,0,4,13,8,6,4,1,3,11,15,0,5,12,2,13,9,7,10,14,12,15,10,4,1,5,8,7,6,2,13,14,0,3,9,11]),g=e.create([11,14,15,12,5,8,7,9,11,13,14,15,6,7,9,8,7,6,8,13,11,9,7,15,7,12,15,9,11,7,13,12,11,13,6,7,14,9,13,15,14,8,13,6,5,12,7,5,11,12,14,15,14,15,9,8,9,14,5,6,8, -6,5,12,9,15,5,11,6,8,13,12,5,12,13,14,11,8,5,6]),n=e.create([8,9,9,11,13,15,15,5,7,7,8,11,14,14,12,6,9,13,15,7,12,8,9,11,7,7,12,7,6,15,13,11,9,7,15,11,8,6,6,14,12,13,5,14,13,13,7,5,15,5,8,11,14,14,6,14,6,9,12,9,12,5,15,8,8,5,12,9,12,5,14,6,8,13,6,5,15,13,11,11]),l=e.create([0,1518500249,1859775393,2400959708,2840853838]),z=e.create([1352829926,1548603684,1836072691,2053994217,0]);a=a.RIPEMD160=d.extend({_doReset:function(){this._hash=e.create([1732584193,4023233417,2562383102,271733878,3285377520])}, -_doProcessBlock:function(a,b){for(var d=0;16>d;d++){var e=b+d,k=a[e];a[e]=(k<<8|k>>>24)&16711935|(k<<24|k>>>8)&4278255360}var e=this._hash.words,k=l.words,f=z.words,p=h.words,J=q.words,A=g.words,L=n.words,C,B,E,I,Q,N,F,G,s,t;N=C=e[0];F=B=e[1];G=E=e[2];s=I=e[3];t=Q=e[4];for(var r,d=0;80>d;d+=1)r=C+a[b+p[d]]|0,r=16>d?r+((B^E^I)+k[0]):32>d?r+((B&E|~B&I)+k[1]):48>d?r+(((B|~E)^I)+k[2]):64>d?r+((B&I|E&~I)+k[3]):r+((B^(E|~I))+k[4]),r|=0,r=c(r,A[d]),r=r+Q|0,C=Q,Q=I,I=c(E,10),E=B,B=r,r=N+a[b+J[d]]|0,r=16> -d?r+((F^(G|~s))+f[0]):32>d?r+((F&s|G&~s)+f[1]):48>d?r+(((F|~G)^s)+f[2]):64>d?r+((F&G|~F&s)+f[3]):r+((F^G^s)+f[4]),r|=0,r=c(r,L[d]),r=r+t|0,N=t,t=s,s=c(G,10),G=F,F=r;r=e[1]+E+s|0;e[1]=e[2]+I+t|0;e[2]=e[3]+Q+N|0;e[3]=e[4]+C+F|0;e[4]=e[0]+B+G|0;e[0]=r},_doFinalize:function(){var a=this._data,b=a.words,c=8*this._nDataBytes,d=8*a.sigBytes;b[d>>>5]|=128<<24-d%32;b[(d+64>>>9<<4)+14]=(c<<8|c>>>24)&16711935|(c<<24|c>>>8)&4278255360;a.sigBytes=4*(b.length+1);this._process();a=this._hash;b=a.words;for(c=0;5> -c;c++)d=b[c],b[c]=(d<<8|d>>>24)&16711935|(d<<24|d>>>8)&4278255360;return a},clone:function(){var a=d.clone.call(this);a._hash=this._hash.clone();return a}});b.RIPEMD160=d._createHelper(a);b.HmacRIPEMD160=d._createHmacHelper(a)})(Math);return b.RIPEMD160})},{"./core":22}],46:[function(h,p,k){(function(b,a){"object"===typeof k?p.exports=k=a(h("./core")):a(b.CryptoJS)})(this,function(b){(function(){var a=b.lib,c=a.WordArray,e=a.Hasher,d=[],a=b.algo.SHA1=e.extend({_doReset:function(){this._hash=new c.init([1732584193, -4023233417,2562383102,271733878,3285377520])},_doProcessBlock:function(a,b){for(var c=this._hash.words,e=c[0],l=c[1],h=c[2],k=c[3],w=c[4],p=0;80>p;p++){if(16>p)d[p]=a[b+p]|0;else{var y=d[p-3]^d[p-8]^d[p-14]^d[p-16];d[p]=y<<1|y>>>31}y=(e<<5|e>>>27)+w+d[p];y=20>p?y+((l&h|~l&k)+1518500249):40>p?y+((l^h^k)+1859775393):60>p?y+((l&h|l&k|h&k)-1894007588):y+((l^h^k)-899497514);w=k;k=h;h=l<<30|l>>>2;l=e;e=y}c[0]=c[0]+e|0;c[1]=c[1]+l|0;c[2]=c[2]+h|0;c[3]=c[3]+k|0;c[4]=c[4]+w|0},_doFinalize:function(){var a= -this._data,b=a.words,c=8*this._nDataBytes,d=8*a.sigBytes;b[d>>>5]|=128<<24-d%32;b[(d+64>>>9<<4)+14]=Math.floor(c/4294967296);b[(d+64>>>9<<4)+15]=c;a.sigBytes=4*b.length;this._process();return this._hash},clone:function(){var a=e.clone.call(this);a._hash=this._hash.clone();return a}});b.SHA1=e._createHelper(a);b.HmacSHA1=e._createHmacHelper(a)})();return b.SHA1})},{"./core":22}],47:[function(h,p,k){(function(b,a,c){"object"===typeof k?p.exports=k=a(h("./core"),h("./sha256")):a(b.CryptoJS)})(this,function(b){(function(){var a= -b.lib.WordArray,c=b.algo,e=c.SHA256,c=c.SHA224=e.extend({_doReset:function(){this._hash=new a.init([3238371032,914150663,812702999,4144912697,4290775857,1750603025,1694076839,3204075428])},_doFinalize:function(){var a=e._doFinalize.call(this);a.sigBytes-=4;return a}});b.SHA224=e._createHelper(c);b.HmacSHA224=e._createHmacHelper(c)})();return b.SHA224})},{"./core":22,"./sha256":48}],48:[function(h,p,k){(function(b,a){"object"===typeof k?p.exports=k=a(h("./core")):a(b.CryptoJS)})(this,function(b){(function(a){var c= -b.lib,e=c.WordArray,d=c.Hasher,c=b.algo,h=[],q=[];(function(){function b(c){for(var d=a.sqrt(c),g=2;g<=d;g++)if(!(c%g))return!1;return!0}function c(a){return 4294967296*(a-(a|0))|0}for(var d=2,g=0;64>g;)b(d)&&(8>g&&(h[g]=c(a.pow(d,0.5))),q[g]=c(a.pow(d,1/3)),g++),d++})();var g=[],c=c.SHA256=d.extend({_doReset:function(){this._hash=new e.init(h.slice(0))},_doProcessBlock:function(a,b){for(var c=this._hash.words,d=c[0],e=c[1],h=c[2],m=c[3],k=c[4],f=c[5],p=c[6],J=c[7],A=0;64>A;A++){if(16>A)g[A]=a[b+ -A]|0;else{var L=g[A-15],C=g[A-2];g[A]=((L<<25|L>>>7)^(L<<14|L>>>18)^L>>>3)+g[A-7]+((C<<15|C>>>17)^(C<<13|C>>>19)^C>>>10)+g[A-16]}L=J+((k<<26|k>>>6)^(k<<21|k>>>11)^(k<<7|k>>>25))+(k&f^~k&p)+q[A]+g[A];C=((d<<30|d>>>2)^(d<<19|d>>>13)^(d<<10|d>>>22))+(d&e^d&h^e&h);J=p;p=f;f=k;k=m+L|0;m=h;h=e;e=d;d=L+C|0}c[0]=c[0]+d|0;c[1]=c[1]+e|0;c[2]=c[2]+h|0;c[3]=c[3]+m|0;c[4]=c[4]+k|0;c[5]=c[5]+f|0;c[6]=c[6]+p|0;c[7]=c[7]+J|0},_doFinalize:function(){var b=this._data,c=b.words,d=8*this._nDataBytes,g=8*b.sigBytes;c[g>>> -5]|=128<<24-g%32;c[(g+64>>>9<<4)+14]=a.floor(d/4294967296);c[(g+64>>>9<<4)+15]=d;b.sigBytes=4*c.length;this._process();return this._hash},clone:function(){var a=d.clone.call(this);a._hash=this._hash.clone();return a}});b.SHA256=d._createHelper(c);b.HmacSHA256=d._createHmacHelper(c)})(Math);return b.SHA256})},{"./core":22}],49:[function(h,p,k){(function(b,a,c){"object"===typeof k?p.exports=k=a(h("./core"),h("./x64-core")):a(b.CryptoJS)})(this,function(b){(function(a){var c=b.lib,e=c.WordArray,d=c.Hasher, -h=b.x64.Word,c=b.algo,q=[],g=[],n=[];(function(){for(var a=1,b=0,c=0;24>c;c++){q[a+5*b]=(c+1)*(c+2)/2%64;var d=(2*a+3*b)%5,a=b%5,b=d}for(a=0;5>a;a++)for(b=0;5>b;b++)g[a+5*b]=b+(2*a+3*b)%5*5;a=1;for(b=0;24>b;b++){for(var e=d=c=0;7>e;e++){if(a&1){var l=(1<l?d^=1<a;a++)l[a]=h.create()})();c=c.SHA3=d.extend({cfg:d.cfg.extend({outputLength:512}),_doReset:function(){for(var a=this._state=[],b=0;25>b;b++)a[b]= -new h.init;this.blockSize=(1600-2*this.cfg.outputLength)/32},_doProcessBlock:function(a,b){for(var c=this._state,d=this.blockSize/2,e=0;e>>24)&16711935|(h<<24|h>>>8)&4278255360,f=(f<<8|f>>>24)&16711935|(f<<24|f>>>8)&4278255360,m=c[e];m.high^=f;m.low^=h}for(d=0;24>d;d++){for(e=0;5>e;e++){for(var k=h=0,p=0;5>p;p++)m=c[e+5*p],h^=m.high,k^=m.low;m=l[e];m.high=h;m.low=k}for(e=0;5>e;e++)for(m=l[(e+4)%5],h=l[(e+1)%5],f=h.high,p=h.low,h=m.high^(f<<1|p>>>31),k= -m.low^(p<<1|f>>>31),p=0;5>p;p++)m=c[e+5*p],m.high^=h,m.low^=k;for(f=1;25>f;f++)m=c[f],e=m.high,m=m.low,p=q[f],32>p?(h=e<>>32-p,k=m<>>32-p):(h=m<>>64-p,k=e<>>64-p),m=l[g[f]],m.high=h,m.low=k;m=l[0];e=c[0];m.high=e.high;m.low=e.low;for(e=0;5>e;e++)for(p=0;5>p;p++)f=e+5*p,m=c[f],h=l[f],f=l[(e+1)%5+5*p],k=l[(e+2)%5+5*p],m.high=h.high^~f.high&k.high,m.low=h.low^~f.low&k.low;m=c[0];e=n[d];m.high^=e.high;m.low^=e.low}},_doFinalize:function(){var b=this._data,c=b.words,d=8*b.sigBytes, -g=32*this.blockSize;c[d>>>5]|=1<<24-d%32;c[(a.ceil((d+1)/g)*g>>>5)-1]|=128;b.sigBytes=4*c.length;this._process();for(var b=this._state,c=this.cfg.outputLength/8,d=c/8,g=[],l=0;l>>24)&16711935|(f<<24|f>>>8)&4278255360,h=(h<<8|h>>>24)&16711935|(h<<24|h>>>8)&4278255360;g.push(h);g.push(f)}return new e.init(g,c)},clone:function(){for(var a=d.clone.call(this),b=a._state=this._state.slice(0),c=0;25>c;c++)b[c]=b[c].clone();return a}});b.SHA3=d._createHelper(c); -b.HmacSHA3=d._createHmacHelper(c)})(Math);return b.SHA3})},{"./core":22,"./x64-core":53}],50:[function(h,p,k){(function(b,a,c){"object"===typeof k?p.exports=k=a(h("./core"),h("./x64-core"),h("./sha512")):a(b.CryptoJS)})(this,function(b){(function(){var a=b.x64,c=a.Word,e=a.WordArray,a=b.algo,d=a.SHA512,a=a.SHA384=d.extend({_doReset:function(){this._hash=new e.init([new c.init(3418070365,3238371032),new c.init(1654270250,914150663),new c.init(2438529370,812702999),new c.init(355462360,4144912697), -new c.init(1731405415,4290775857),new c.init(2394180231,1750603025),new c.init(3675008525,1694076839),new c.init(1203062813,3204075428)])},_doFinalize:function(){var a=d._doFinalize.call(this);a.sigBytes-=16;return a}});b.SHA384=d._createHelper(a);b.HmacSHA384=d._createHmacHelper(a)})();return b.SHA384})},{"./core":22,"./sha512":51,"./x64-core":53}],51:[function(h,p,k){(function(b,a,c){"object"===typeof k?p.exports=k=a(h("./core"),h("./x64-core")):a(b.CryptoJS)})(this,function(b){(function(){function a(){return d.create.apply(d, -arguments)}var c=b.lib.Hasher,e=b.x64,d=e.Word,h=e.WordArray,e=b.algo,q=[a(1116352408,3609767458),a(1899447441,602891725),a(3049323471,3964484399),a(3921009573,2173295548),a(961987163,4081628472),a(1508970993,3053834265),a(2453635748,2937671579),a(2870763221,3664609560),a(3624381080,2734883394),a(310598401,1164996542),a(607225278,1323610764),a(1426881987,3590304994),a(1925078388,4068182383),a(2162078206,991336113),a(2614888103,633803317),a(3248222580,3479774868),a(3835390401,2666613458),a(4022224774, -944711139),a(264347078,2341262773),a(604807628,2007800933),a(770255983,1495990901),a(1249150122,1856431235),a(1555081692,3175218132),a(1996064986,2198950837),a(2554220882,3999719339),a(2821834349,766784016),a(2952996808,2566594879),a(3210313671,3203337956),a(3336571891,1034457026),a(3584528711,2466948901),a(113926993,3758326383),a(338241895,168717936),a(666307205,1188179964),a(773529912,1546045734),a(1294757372,1522805485),a(1396182291,2643833823),a(1695183700,2343527390),a(1986661051,1014477480), -a(2177026350,1206759142),a(2456956037,344077627),a(2730485921,1290863460),a(2820302411,3158454273),a(3259730800,3505952657),a(3345764771,106217008),a(3516065817,3606008344),a(3600352804,1432725776),a(4094571909,1467031594),a(275423344,851169720),a(430227734,3100823752),a(506948616,1363258195),a(659060556,3750685593),a(883997877,3785050280),a(958139571,3318307427),a(1322822218,3812723403),a(1537002063,2003034995),a(1747873779,3602036899),a(1955562222,1575990012),a(2024104815,1125592928),a(2227730452, -2716904306),a(2361852424,442776044),a(2428436474,593698344),a(2756734187,3733110249),a(3204031479,2999351573),a(3329325298,3815920427),a(3391569614,3928383900),a(3515267271,566280711),a(3940187606,3454069534),a(4118630271,4000239992),a(116418474,1914138554),a(174292421,2731055270),a(289380356,3203993006),a(460393269,320620315),a(685471733,587496836),a(852142971,1086792851),a(1017036298,365543100),a(1126000580,2618297676),a(1288033470,3409855158),a(1501505948,4234509866),a(1607167915,987167468),a(1816402316, -1246189591)],g=[];(function(){for(var b=0;80>b;b++)g[b]=a()})();e=e.SHA512=c.extend({_doReset:function(){this._hash=new h.init([new d.init(1779033703,4089235720),new d.init(3144134277,2227873595),new d.init(1013904242,4271175723),new d.init(2773480762,1595750129),new d.init(1359893119,2917565137),new d.init(2600822924,725511199),new d.init(528734635,4215389547),new d.init(1541459225,327033209)])},_doProcessBlock:function(a,b){for(var c=this._hash.words,d=c[0],e=c[1],h=c[2],m=c[3],k=c[4],f=c[5],p= -c[6],c=c[7],J=d.high,A=d.low,L=e.high,C=e.low,B=h.high,E=h.low,I=m.high,Q=m.low,N=k.high,F=k.low,G=f.high,s=f.low,t=p.high,r=p.low,u=c.high,V=c.low,S=J,M=A,R=L,O=C,aa=B,ea=E,ma=I,fa=Q,X=N,T=F,ja=G,ia=s,ka=t,ga=r,ha=u,da=V,U=0;80>U;U++){var $=g[U];if(16>U)var W=$.high=a[b+2*U]|0,D=$.low=a[b+2*U+1]|0;else{var W=g[U-15],D=W.high,Y=W.low,W=(D>>>1|Y<<31)^(D>>>8|Y<<24)^D>>>7,Y=(Y>>>1|D<<31)^(Y>>>8|D<<24)^(Y>>>7|D<<25),ca=g[U-2],D=ca.high,K=ca.low,ca=(D>>>19|K<<13)^(D<<3|K>>>29)^D>>>6,K=(K>>>19|D<<13)^(K<< -3|D>>>29)^(K>>>6|D<<26),D=g[U-7],na=D.high,ba=g[U-16],Z=ba.high,ba=ba.low,D=Y+D.low,W=W+na+(D>>>0>>0?1:0),D=D+K,W=W+ca+(D>>>0>>0?1:0),D=D+ba,W=W+Z+(D>>>0>>0?1:0);$.high=W;$.low=D}var na=X&ja^~X&ka,ba=T&ia^~T&ga,$=S&R^S&aa^R&aa,pa=M&O^M&ea^O&ea,Y=(S>>>28|M<<4)^(S<<30|M>>>2)^(S<<25|M>>>7),ca=(M>>>28|S<<4)^(M<<30|S>>>2)^(M<<25|S>>>7),K=q[U],qa=K.high,oa=K.low,K=da+((T>>>14|X<<18)^(T>>>18|X<<14)^(T<<23|X>>>9)),Z=ha+((X>>>14|T<<18)^(X>>>18|T<<14)^(X<<23|T>>>9))+(K>>>0>>0?1:0),K=K+ba,Z=Z+ -na+(K>>>0>>0?1:0),K=K+oa,Z=Z+qa+(K>>>0>>0?1:0),K=K+D,Z=Z+W+(K>>>0>>0?1:0),D=ca+pa,$=Y+$+(D>>>0>>0?1:0),ha=ka,da=ga,ka=ja,ga=ia,ja=X,ia=T,T=fa+K|0,X=ma+Z+(T>>>0>>0?1:0)|0,ma=aa,fa=ea,aa=R,ea=O,R=S,O=M,M=K+D|0,S=Z+$+(M>>>0>>0?1:0)|0}A=d.low=A+M;d.high=J+S+(A>>>0>>0?1:0);C=e.low=C+O;e.high=L+R+(C>>>0>>0?1:0);E=h.low=E+ea;h.high=B+aa+(E>>>0>>0?1:0);Q=m.low=Q+fa;m.high=I+ma+(Q>>>0>>0?1:0);F=k.low=F+T;k.high=N+X+(F>>>0>>0?1:0);s=f.low=s+ia;f.high=G+ja+(s>>>0>> -0?1:0);r=p.low=r+ga;p.high=t+ka+(r>>>0>>0?1:0);V=c.low=V+da;c.high=u+ha+(V>>>0>>0?1:0)},_doFinalize:function(){var a=this._data,b=a.words,c=8*this._nDataBytes,d=8*a.sigBytes;b[d>>>5]|=128<<24-d%32;b[(d+128>>>10<<5)+30]=Math.floor(c/4294967296);b[(d+128>>>10<<5)+31]=c;a.sigBytes=4*b.length;this._process();return this._hash.toX32()},clone:function(){var a=c.clone.call(this);a._hash=this._hash.clone();return a},blockSize:32});b.SHA512=c._createHelper(e);b.HmacSHA512=c._createHmacHelper(e)})(); -return b.SHA512})},{"./core":22,"./x64-core":53}],52:[function(h,p,k){(function(b,a,c){"object"===typeof k?p.exports=k=a(h("./core"),h("./enc-base64"),h("./md5"),h("./evpkdf"),h("./cipher-core")):a(b.CryptoJS)})(this,function(b){(function(){function a(a,b){var c=(this._lBlock>>>a^this._rBlock)&b;this._rBlock^=c;this._lBlock^=c<>>a^this._lBlock)&b;this._lBlock^=c;this._rBlock^=c<c;c++){var d=k[c]-1;b[c]=a[d>>>5]>>> -31-d%32&1}a=this._subKeys=[];for(d=0;16>d;d++){for(var f=a[d]=[],e=n[d],c=0;24>c;c++)f[c/6|0]|=b[(g[c]-1+e)%28]<<31-c%6,f[4+(c/6|0)]|=b[28+(g[c+24]-1+e)%28]<<31-c%6;f[0]=f[0]<<1|f[0]>>>31;for(c=1;7>c;c++)f[c]>>>=4*(c-1)+3;f[7]=f[7]<<5|f[7]>>>27}b=this._invSubKeys=[];for(c=0;16>c;c++)b[c]=a[15-c]},encryptBlock:function(a,b){this._doCryptBlock(a,b,this._subKeys)},decryptBlock:function(a,b){this._doCryptBlock(a,b,this._invSubKeys)},_doCryptBlock:function(b,d,e){this._lBlock=b[d];this._rBlock=b[d+1]; -a.call(this,4,252645135);a.call(this,16,65535);c.call(this,2,858993459);c.call(this,8,16711935);a.call(this,1,1431655765);for(var g=0;16>g;g++){for(var f=e[g],h=this._lBlock,m=this._rBlock,n=0,k=0;8>k;k++)n|=l[k][((m^f[k])&p[k])>>>0];this._lBlock=m;this._rBlock=h^n}e=this._lBlock;this._lBlock=this._rBlock;this._rBlock=e;a.call(this,1,1431655765);c.call(this,8,16711935);c.call(this,2,858993459);a.call(this,16,65535);a.call(this,4,252645135);b[d]=this._lBlock;b[d+1]=this._rBlock},keySize:2,ivSize:2, -blockSize:2});b.DES=e._createHelper(P);h=h.TripleDES=e.extend({_doReset:function(){var a=this._key.words;this._des1=P.createEncryptor(d.create(a.slice(0,2)));this._des2=P.createEncryptor(d.create(a.slice(2,4)));this._des3=P.createEncryptor(d.create(a.slice(4,6)))},encryptBlock:function(a,b){this._des1.encryptBlock(a,b);this._des2.decryptBlock(a,b);this._des3.encryptBlock(a,b)},decryptBlock:function(a,b){this._des3.decryptBlock(a,b);this._des2.encryptBlock(a,b);this._des1.decryptBlock(a,b)},keySize:6, -ivSize:2,blockSize:2});b.TripleDES=e._createHelper(h)})();return b.TripleDES})},{"./cipher-core":21,"./core":22,"./enc-base64":23,"./evpkdf":25,"./md5":30}],53:[function(h,p,k){(function(b,a){"object"===typeof k?p.exports=k=a(h("./core")):a(b.CryptoJS)})(this,function(b){(function(a){var c=b.lib,e=c.Base,d=c.WordArray,c=b.x64={};c.Word=e.extend({init:function(a,b){this.high=a;this.low=b}});c.WordArray=e.extend({init:function(b,c){b=this.words=b||[];this.sigBytes=c!=a?c:8*b.length},toX32:function(){for(var a= -this.words,b=a.length,c=[],e=0;e>>0,h=Array(f),l,m,p;for(l=0;larguments.length?e:3= 2.8.0",ws:">= 0.4.31","crypto-js":">= 3.1.2-2"},devDependencies:{browserify:">= 3.28.1",nodeunit:">= 0.8.6"},repository:{type:"git",url:"git://github.com/tavendo/AutobahnJS.git"},keywords:["WAMP","WebSocket","RPC","PubSub"],author:"Tavendo GmbH",license:"MIT"}},{}]},{},[4])(4)}); diff --git a/examples/twisted/wamp/authentication/otp/index.html b/examples/twisted/wamp/authentication/otp/index.html deleted file mode 100644 index 7878dcb1..00000000 --- a/examples/twisted/wamp/authentication/otp/index.html +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - -

WAMP One-Time-Password Authentication

-

Open JavaScript console to watch output.

- - - - - - diff --git a/examples/twisted/wamp/authentication/otp/server.py b/examples/twisted/wamp/authentication/otp/server.py deleted file mode 100644 index dd40ecfa..00000000 --- a/examples/twisted/wamp/authentication/otp/server.py +++ /dev/null @@ -1,233 +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 json -import datetime - -from twisted.python import log -from twisted.internet import defer - - -from autobahn import util -from autobahn.wamp import types -from autobahn.wamp import auth -from autobahn.twisted.wamp import ApplicationSession, RouterSession -from autobahn.twisted.websocket import WampWebSocketServerProtocol, WampWebSocketServerFactory - - -class UserDb: - - """ - A fake user database. - """ - - def __init__(self): - self._secrets = {} - - def add(self, authid, authrole, secret): - self._secrets[authid] = (secret, authrole) - - def get(self, authid): - # we return a deferred to simulate an asynchronous lookup - return defer.succeed(self._secrets.get(authid, (None, None))) - - -class PendingAuth: - - """ - User for tracking pending authentications. - """ - - def __init__(self, secret, authid, authrole, authmethod, authprovider): - self.secret = secret - self.authid = authid - self.authrole = authrole - self.authmethod = authmethod - self.authprovider = authprovider - - -class MyRouterSession(RouterSession): - - """ - Our custom router session that authenticates via WAMP-CRA. - """ - - @defer.inlineCallbacks - def onHello(self, realm, details): - """ - Callback fired when client wants to attach session. - """ - print("onHello: {} {}".format(realm, details)) - - try: - - self._pending_auth = None - - if details.authmethods: - for authmethod in details.authmethods: - if authmethod == u"totp": - - # lookup user in user DB - secret, authrole = yield self.factory.userdb.get(details.authid) - - # if user found .. - if secret: - - # setup pending auth - self._pending_auth = PendingAuth(secret, details.authid, authrole, authmethod, "userdb") - - defer.returnValue(types.Challenge('totp')) - - # deny client - defer.returnValue(types.Deny()) - except Exception as e: - print e - - def onAuthenticate(self, signature, extra): - """ - Callback fired when a client responds to an authentication challenge. - """ - print("onAuthenticate: {} {}".format(signature, extra)) - - # if there is a pending auth, and the signature provided by client matches .. - if self._pending_auth: - if signature == auth.compute_totp(self._pending_auth.secret) or \ - signature == auth.compute_totp(self._pending_auth.secret, 1) or \ - signature == auth.compute_totp(self._pending_auth.secret, -1): - # accept the client - return types.Accept(authid=self._pending_auth.authid, - authrole=self._pending_auth.authrole, - authmethod=self._pending_auth.authmethod, - authprovider=self._pending_auth.authprovider) - - # deny client - return types.Deny() - - -class TimeService(ApplicationSession): - - """ - A simple time service application component. - """ - - def onJoin(self, details): - print("session attached") - - def utcnow(): - now = datetime.datetime.utcnow() - return now.strftime("%Y-%m-%dT%H:%M:%SZ") - - self.register(utcnow, 'com.timeservice.now') - - -if __name__ == '__main__': - - import sys - import argparse - - from twisted.python import log - from twisted.internet.endpoints import serverFromString - - # 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-WebSocket server with this application component, e.g. 'timeservice.TimeServiceBackend', or None.") - - parser.add_argument("--websocket", type=str, default="tcp:8080", - help='WebSocket server Twisted endpoint descriptor, e.g. "tcp:9000" or "unix:/tmp/mywebsocket".') - - parser.add_argument("--wsurl", type=str, default="ws://localhost:8080", - help='WebSocket URL (must suit the endpoint), e.g. "ws://localhost:9000".') - - args = parser.parse_args() - - log.startLogging(sys.stdout) - - # we use an Autobahn utility to install the "best" available Twisted reactor - ## - from autobahn.twisted.choosereactor import install_reactor - reactor = install_reactor() - if args.debug: - print("Running on reactor {}".format(reactor)) - - # create a WAMP router factory - ## - from autobahn.twisted.wamp import RouterFactory - router_factory = RouterFactory() - - # create a user DB - ## - userdb = UserDb() - userdb.add(authid="peter", authrole="user", secret="MFRGGZDFMZTWQ2LK") - - # create a WAMP router session factory - ## - from autobahn.twisted.wamp import RouterSessionFactory - session_factory = RouterSessionFactory(router_factory) - session_factory.session = MyRouterSession - session_factory.userdb = userdb - - # start an embedded application component .. - ## - component_config = types.ComponentConfig(realm="realm1") - component_session = TimeService(component_config) - session_factory.add(component_session) - - # create a WAMP-over-WebSocket transport server factory - ## - from autobahn.twisted.websocket import WampWebSocketServerFactory - transport_factory = WampWebSocketServerFactory(session_factory, args.wsurl, debug=False, debug_wamp=args.debug) - transport_factory.setProtocolOptions(failByDrop=False) - - from twisted.web.server import Site - from twisted.web.static import File - from autobahn.twisted.resource import WebSocketResource - - # we serve static files under "/" .. - root = File(".") - - # .. and our WebSocket server under "/ws" - resource = WebSocketResource(transport_factory) - root.putChild("ws", resource) - - # run both under one Twisted Web Site - site = Site(root) - site.noisy = False - site.log = lambda _: None - - # start the WebSocket server from an endpoint - ## - server = serverFromString(reactor, args.websocket) - server.listen(site) - - # now enter the Twisted reactor loop - ## - reactor.run() diff --git a/examples/twisted/wamp/authentication/persona/Makefile b/examples/twisted/wamp/authentication/persona/Makefile deleted file mode 100644 index 84a692af..00000000 --- a/examples/twisted/wamp/authentication/persona/Makefile +++ /dev/null @@ -1,5 +0,0 @@ -server: - PYTHONPATH="../../../../../autobahn" python server.py - -client: - PYTHONPATH="../../../../../autobahn" python client.py diff --git a/examples/twisted/wamp/authentication/persona/README.md b/examples/twisted/wamp/authentication/persona/README.md deleted file mode 100644 index 93895af0..00000000 --- a/examples/twisted/wamp/authentication/persona/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# Authenticating WAMP with Mozilla Persona - -Run this demo by starting the server - - python server.py -d - -and opening `http://127.0.0.1:8080` in your browser. Open the JavaScript console and login via Mozilla Persona. - - -> Note: To change the server URL, you also need to change the "Mozilla Persona Domain" used in the code. \ No newline at end of file diff --git a/examples/twisted/wamp/authentication/persona/index.html b/examples/twisted/wamp/authentication/persona/index.html deleted file mode 100644 index 22663b4d..00000000 --- a/examples/twisted/wamp/authentication/persona/index.html +++ /dev/null @@ -1,85 +0,0 @@ - - - - - - -

Authenticated WAMP with Mozilla Persona

-

Open JavaScript console to watch output.

- -

Login

-
Not logged in.
- - - -

RPC Result

-
No result yet.
- - - - - - - diff --git a/examples/twisted/wamp/authentication/persona/server.py b/examples/twisted/wamp/authentication/persona/server.py deleted file mode 100644 index 7df29e7c..00000000 --- a/examples/twisted/wamp/authentication/persona/server.py +++ /dev/null @@ -1,302 +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 -import json -import urllib -import Cookie - -from autobahn.twisted.wamp import ApplicationSession - -from twisted.python import log -from twisted.internet.defer import Deferred - -from autobahn.twisted.websocket import WampWebSocketServerProtocol, WampWebSocketServerFactory -from autobahn.twisted.wamp import RouterSession -from autobahn.wamp import types - -from autobahn.util import newid, utcnow -from autobahn.websocket import http - - -class TimeService(ApplicationSession): - - """ - A simple time service application component. - """ - - def onJoin(self, details): - print("session attached") - - def utcnow(): - now = datetime.datetime.utcnow() - return now.strftime("%Y-%m-%dT%H:%M:%SZ") - - self.register(utcnow, 'com.timeservice.now') - - -class ServerProtocol(WampWebSocketServerProtocol): - - """ - A WAMP-WebSocket transport that supports Cookie based authentication. - """ - - # authid -> cookie -> set(connection) - - def onConnect(self, request): - protocol, headers = WampWebSocketServerProtocol.onConnect(self, request) - - # our cookie tracking ID - self._cbtid = None - - # see if there already is a cookie set .. - if 'cookie' in request.headers: - try: - cookie = Cookie.SimpleCookie() - cookie.load(str(request.headers['cookie'])) - except Cookie.CookieError: - pass - else: - if 'cbtid' in cookie: - cbtid = cookie['cbtid'].value - if cbtid in self.factory._cookies: - self._cbtid = cbtid - log.msg("Cookie already set: %s" % self._cbtid) - - # if no cookie is set, create a new one .. - if self._cbtid is None: - - self._cbtid = newid() - maxAge = 86400 - - cbtData = {'created': utcnow(), - 'authenticated': None, - 'maxAge': maxAge, - 'connections': set()} - - self.factory._cookies[self._cbtid] = cbtData - - # do NOT add the "secure" cookie attribute! "secure" refers to the - # scheme of the Web page that triggered the WS, not WS itself!! - ## - headers['Set-Cookie'] = 'cbtid=%s;max-age=%d' % (self._cbtid, maxAge) - log.msg("Setting new cookie: %s" % self._cbtid) - - # add this WebSocket connection to the set of connections - # associated with the same cookie - self.factory._cookies[self._cbtid]['connections'].add(self) - - self._authenticated = self.factory._cookies[self._cbtid]['authenticated'] - - # accept the WebSocket connection, speaking subprotocol `protocol` - # and setting HTTP headers `headers` - return (protocol, headers) - - -class MyRouterSession(RouterSession): - - def onOpen(self, transport): - """ - Callback fired when transport (WebSocket connection) was established. - """ - RouterSession.onOpen(self, transport) - print("MyRouterSession.onOpen: transport authenticated = {}".format(self._transport._authenticated)) - - def onHello(self, realm, details): - """ - Callback fired when client wants to attach session. - """ - print("MyRouterSession.onHello: {} {}".format(realm, details)) - - for authmethod in details.authmethods: - if authmethod == u"cookie" and self._transport._authenticated is not None: - # already authenticated via Cookie on transport - return types.Accept(authid=self._transport._authenticated, authrole="user", authmethod="cookie") - elif authmethod == u"mozilla-persona": - # not yet authenticated: send challenge - return types.Challenge("mozilla-persona") - - return types.Deny() - - def onLeave(self, details): - """ - Callback fired when a client session leaves. - """ - if details.reason == "wamp.close.logout": - # if asked to logout, set cookie to "not authenticated" .. - cookie = self._transport.factory._cookies[self._transport._cbtid] - cookie['authenticated'] = None - - # .. and kill all currently connected clients (for the cookie) - for proto in cookie['connections']: - proto.sendClose() - - def onAuthenticate(self, signature, extra): - """ - Callback fired when a client responds to an authentication challenge. - """ - print("onAuthenticate: {} {}".format(signature, extra)) - - dres = Deferred() - - # The client did it's Mozilla Persona authentication thing - # and now wants to verify the authentication and login. - assertion = signature - audience = 'http://127.0.0.1:8080/' - - # To verify the authentication, we need to send a HTTP/POST - # to Mozilla Persona. When successful, Persona will send us - # back something like: - - # { - # "audience": "http://192.168.1.130:8080/", - # "expires": 1393681951257, - # "issuer": "gmail.login.persona.org", - # "email": "tobias.oberstein@gmail.com", - # "status": "okay" - # } - - headers = {'Content-Type': 'application/x-www-form-urlencoded'} - body = urllib.urlencode({'audience': audience, 'assertion': assertion}) - - from twisted.web.client import getPage - d = getPage(url="https://verifier.login.persona.org/verify", - method='POST', - postdata=body, - headers=headers) - - log.msg("Authentication request sent.") - - def done(res): - res = json.loads(res) - try: - if res['status'] == 'okay': - # Mozilla Persona successfully authenticated the user - - # remember the user's email address. this marks the cookie as - # authenticated - self._transport.factory._cookies[self._transport._cbtid]['authenticated'] = res['email'] - - log.msg("Authenticated user {}".format(res['email'])) - dres.callback(types.Accept(authid=res['email'], authrole="user", authmethod="mozilla-persona")) - else: - log.msg("Authentication failed!") - dres.callback(types.Deny()) - except Exception as e: - print "ERRR", e - - def error(err): - log.msg("Authentication request failed: {}".format(err.value)) - dres.callback(types.Deny()) - - d.addCallbacks(done, error) - - return dres - - -if __name__ == '__main__': - - import sys - import argparse - - from twisted.python import log - from twisted.internet.endpoints import serverFromString - - # 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-WebSocket server with this application component, e.g. 'timeservice.TimeServiceBackend', or None.") - - parser.add_argument("--websocket", type=str, default="tcp:8080", - help='WebSocket server Twisted endpoint descriptor, e.g. "tcp:9000" or "unix:/tmp/mywebsocket".') - - parser.add_argument("--wsurl", type=str, default="ws://localhost:8080", - help='WebSocket URL (must suit the endpoint), e.g. "ws://localhost:9000".') - - args = parser.parse_args() - - log.startLogging(sys.stdout) - - # we use an Autobahn utility to install the "best" available Twisted reactor - ## - from autobahn.twisted.choosereactor import install_reactor - reactor = install_reactor() - if args.debug: - print("Running on reactor {}".format(reactor)) - - # create a WAMP router factory - ## - from autobahn.twisted.wamp import RouterFactory - router_factory = RouterFactory() - - # create a WAMP router session factory - ## - from autobahn.twisted.wamp import RouterSessionFactory - session_factory = RouterSessionFactory(router_factory) - session_factory.session = MyRouterSession - - # start an embedded application component .. - ## - component_config = types.ComponentConfig(realm="realm1") - component_session = TimeService(component_config) - session_factory.add(component_session) - - # create a WAMP-over-WebSocket transport server factory - ## - from autobahn.twisted.websocket import WampWebSocketServerFactory - transport_factory = WampWebSocketServerFactory(session_factory, args.wsurl, debug=False, debug_wamp=args.debug) - transport_factory.protocol = ServerProtocol - transport_factory._cookies = {} - - transport_factory.setProtocolOptions(failByDrop=False) - - from twisted.web.server import Site - from twisted.web.static import File - from autobahn.twisted.resource import WebSocketResource - - # we serve static files under "/" .. - root = File(".") - - # .. and our WebSocket server under "/ws" - resource = WebSocketResource(transport_factory) - root.putChild("ws", resource) - - # run both under one Twisted Web Site - site = Site(root) - - # start the WebSocket server from an endpoint - ## - server = serverFromString(reactor, args.websocket) - server.listen(site) - - # now enter the Twisted reactor loop - ## - reactor.run() diff --git a/examples/twisted/wamp/authentication/ticket/Makefile b/examples/twisted/wamp/authentication/ticket/Makefile deleted file mode 100644 index 84a692af..00000000 --- a/examples/twisted/wamp/authentication/ticket/Makefile +++ /dev/null @@ -1,5 +0,0 @@ -server: - PYTHONPATH="../../../../../autobahn" python server.py - -client: - PYTHONPATH="../../../../../autobahn" python client.py diff --git a/examples/twisted/wamp/authentication/ticket/README.md b/examples/twisted/wamp/authentication/ticket/README.md deleted file mode 100644 index 4e06ab99..00000000 --- a/examples/twisted/wamp/authentication/ticket/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# WAMP Ticket-based Authentication - -Run this demo by starting the server - - python server.py - -and opening `http://127.0.0.1:8080` in your browser. Open the JavaScript console to watch output. diff --git a/examples/twisted/wamp/authentication/ticket/autobahn.min.js b/examples/twisted/wamp/authentication/ticket/autobahn.min.js deleted file mode 100644 index 7500d3c2..00000000 --- a/examples/twisted/wamp/authentication/ticket/autobahn.min.js +++ /dev/null @@ -1,271 +0,0 @@ -/* - - Counter block mode compatible with Dr Brian Gladman fileenc.c - derived from CryptoJS.mode.CTR - Jan Hruby jhruby.web@gmail.com - - (c) 2012 by C?dric Mesnil. All rights reserved. - - Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - - - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - MIT License (c) copyright 2013-2014 original author or authors MIT License (c) copyright 2010-2014 original author or authors MIT License (c) copyright 2010-2014 original author or authors MIT License (c) copyright 2010-2014 original author or authors MIT License (c) copyright 2010-2014 original author or authors MIT License (c) copyright 2010-2014 original author or authors MIT License (c) copyright 2010-2014 original author or authors MIT License (c) copyright 2010-2014 original author or authors MIT License (c) copyright 2010-2014 original author or authors MIT License (c) copyright 2010-2014 original author or authors MIT License (c) copyright 2010-2014 original author or authors MIT License (c) copyright 2010-2014 original author or authors MIT License (c) copyright 2010-2014 original author or authors MIT License (c) copyright 2010-2014 original author or authors MIT License (c) copyright 2010-2014 original author or authors MIT License (c) copyright 2010-2014 original author or authors MIT License (c) copyright 2010-2014 original author or authors MIT License (c) copyright 2010-2014 original author or authors MIT License (c) copyright 2010-2014 original author or authors MIT License (c) copyright 2010-2014 original author or authors MIT License (c) copyright 2010-2014 original author or authors MIT License (c) copyright 2010-2014 original author or authors MIT License (c) copyright 2010-2014 original author or authors MIT License (c) copyright 2010-2014 original author or authors */ -!function(la){if("object"==typeof exports)module.exports=la();else if("function"==typeof define&&define.amd)define(la);else{var h;"undefined"!=typeof window?h=window:"undefined"!=typeof global?h=global:"undefined"!=typeof self&&(h=self);h.autobahn=la()}}(function(){return function h(p,k,b){function a(d,e){if(!k[d]){if(!p[d]){var q="function"==typeof require&&require;if(!e&&q)return q(d,!0);if(c)return c(d,!0);throw Error("Cannot find module '"+d+"'");}q=k[d]={exports:{}};p[d][0].call(q.exports,function(c){var n= -p[d][1][c];return a(n?n:c)},q,q.exports,h,p,k,b)}return k[d].exports}for(var c="function"==typeof require&&require,e=0;ethis._max_retry_delay&&(this._retry_delay= -this._max_retry_delay);this._retry_count+=1;var a;a=this._retry&&this._retry_count<=this._max_retries?{count:this._retry_count,delay:this._retry_delay,will_retry:!0}:{count:null,delay:null,will_retry:!1};this._retry_delay_growth&&(this._retry_delay*=this._retry_delay_growth);return a};q.prototype.open=function(){function a(){b._transport=b._create_transport();if(b._transport)b._session=new c.Session(b._transport,b._defer,b._options.onchallenge),b._session_close_reason=null,b._session_close_message= -null,b._transport.onopen=function(){b._autoreconnect_reset();b._connect_successes+=1;b._session.join(b._options.realm,b._options.authmethods,b._options.authid)},b._session.onjoin=function(a){if(b.onopen)try{b.onopen(b._session,a)}catch(c){d.debug("Exception raised from app code while firing Connection.onopen()",c)}},b._session.onleave=function(a,c){b._session_close_reason=a;b._session_close_message=c.message||"";b._retry=!1;b._transport.close(1E3)},b._transport.onclose=function(c){b._autoreconnect_reset_timer(); -var e=b._transport=null;0===b._connect_successes?(e="unreachable",b._retry_if_unreachable||(b._retry=!1)):e=c.wasClean?"closed":"lost";c=b._autoreconnect_advance();if(b.onclose){var m={reason:b._session_close_reason,message:b._session_close_message,retry_delay:c.delay,retry_count:c.count,will_retry:c.will_retry};try{var q=b.onclose(e,m)}catch(v){d.debug("Exception raised from app code while firing Connection.onclose()",v)}}b._session&&(b._session._id=null,b._session=null,b._session_close_reason=null, -b._session_close_message=null);b._retry&&!q&&(c.will_retry?(b._is_retrying=!0,d.debug("retrying in "+c.delay+" s"),b._retry_timer=setTimeout(a,1E3*c.delay)):d.debug("giving up trying to reconnect"))};else if(b._retry=!1,b.onclose)b.onclose("unsupported",{reason:null,message:null,retry_delay:null,retry_count:null,will_retry:!1})}var b=this;if(b._transport)throw"connection already open (or opening)";b._autoreconnect_reset();b._retry=!0;a()};q.prototype.close=function(a,c){if(!this._transport&&!this._is_retrying)throw"connection already closed"; -this._retry=!1;this._session&&this._session.isOpen?this._session.leave(a,c):this._transport&&this._transport.close(1E3)};Object.defineProperty(q.prototype,"defer",{get:function(){return this._defer}});Object.defineProperty(q.prototype,"session",{get:function(){return this._session}});Object.defineProperty(q.prototype,"isOpen",{get:function(){return this._session&&this._session.isOpen?!0:!1}});Object.defineProperty(q.prototype,"isConnected",{get:function(){return this._transport?!0:!1}});Object.defineProperty(q.prototype, -"transport",{get:function(){return this._transport?this._transport:{info:{type:"none",url:null,protocol:null}}}});Object.defineProperty(q.prototype,"isRetrying",{get:function(){return this._is_retrying}});k.Connection=q}).call(this,"undefined"!==typeof self?self:"undefined"!==typeof window?window:{})},{"./autobahn.js":4,"./log.js":7,"./session.js":16,"./util.js":19,when:77}],7:[function(h,p,k){(function(b){var a=function(){};"AUTOBAHN_DEBUG"in b&&AUTOBAHN_DEBUG&&"console"in b&&(a=function(){console.log.apply(console, -arguments)});k.debug=a}).call(this,"undefined"!==typeof self?self:"undefined"!==typeof window?window:{})},{}],8:[function(h,p,k){h("./polyfill/object");h("./polyfill/array");h("./polyfill/string");h("./polyfill/function");h("./polyfill/console");h("./polyfill/typedarray");h("./polyfill/json")},{"./polyfill/array":9,"./polyfill/console":10,"./polyfill/function":11,"./polyfill/json":12,"./polyfill/object":13,"./polyfill/string":14,"./polyfill/typedarray":15}],9:[function(h,p,k){"function"!==typeof Array.prototype.reduce&& -(Array.prototype.reduce=function(b){var a,c,e,d;if(null===this||"undefined"===typeof this)throw new TypeError("Array.prototype.reduce called on null or undefined");if("function"!==typeof b)throw new TypeError(b+" is not a function");c=Object(this);a=c.length>>>0;d=0;if(2<=arguments.length)e=arguments[1];else{for(;d=a)throw new TypeError("Reduce of empty array with no initial value");e=c[d++]}for(;da&&(a+=this.length);0>a&&(a=0);for(var c=this.length;aa&&(a+=this.length);a>this.length-1&&(a=this.length-1);for(a++;0>>0)-1,e;if(2<=arguments.length)e=arguments[1];else{for(;0<= -c&&!c in a;)c--;if(0>c)throw new TypeError("Reduce of empty array with no initial value");e=a[c--]}for(;0<=c;c--)c in a&&(e=b(e,a[c],c,a));return e})},{}],10:[function(h,p,k){(function(b){(function(a){a||(a=window.console={log:function(a,b,d,m,q){},info:function(a,b,d,m,q){},warn:function(a,b,d,m,q){},error:function(a,b,d,m,q){},assert:function(a,b){}});"object"===typeof a.log&&(a.log=Function.prototype.call.bind(a.log,a),a.info=Function.prototype.call.bind(a.info,a),a.warn=Function.prototype.call.bind(a.warn, -a),a.error=Function.prototype.call.bind(a.error,a),a.debug=Function.prototype.call.bind(a.info,a));"group"in a||(a.group=function(b){a.info("\n--- "+b+" ---\n")});"groupEnd"in a||(a.groupEnd=function(){a.log("\n")});"assert"in a||(a.assert=function(a,b){if(!a)try{throw Error("assertion failed: "+b);}catch(d){setTimeout(function(){throw d;},0)}});"time"in a||function(){var b={};a.time=function(a){b[a]=(new Date).getTime()};a.timeEnd=function(e){var d=(new Date).getTime();a.info(e+": "+(e in b?d-b[e]: -0)+"ms")}}()})(b.console)}).call(this,"undefined"!==typeof self?self:"undefined"!==typeof window?window:{})},{}],11:[function(h,p,k){Function.prototype.bind||(Function.prototype.bind=function(b){var a=this,c=Array.prototype.slice.call(arguments,1);return function(){return a.apply(b,Array.prototype.concat.apply(c,arguments))}})},{}],12:[function(h,p,k){"object"!==typeof JSON&&(JSON={});(function(){function b(a){return 10>a?"0"+a:a}function a(a){d.lastIndex=0;return d.test(a)?'"'+a.replace(d,function(a){var b= -g[a];return"string"===typeof b?b:"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+a+'"'}function c(b,g){var d,e,v,y,H=m,f,x=g[b];x&&"object"===typeof x&&"function"===typeof x.toJSON&&(x=x.toJSON(b));"function"===typeof n&&(x=n.call(g,b,x));switch(typeof x){case "string":return a(x);case "number":return isFinite(x)?String(x):"null";case "boolean":case "null":return String(x);case "object":if(!x)return"null";m+=q;f=[];if("[object Array]"===Object.prototype.toString.apply(x)){y=x.length; -for(d=0;dt)throw RangeError("Array too large for polyfill"); -var f;for(f=0;f>f}function l(a,b){var f=32-b;return a<>>f}function z(a){return[a&255]}function h(a){return n(a[0],8)}function w(a){return[a&255]}function v(a){return l(a[0],8)}function y(a){a=aa(Number(a));return[0>a?0:255>8&255,a&255]}function f(a){return n(a[0]<<8|a[1],16)}function x(a){return[a>>8&255,a&255]}function J(a){return l(a[0]<<8|a[1],16)}function A(a){return[a>>24&255,a>>16&255,a>>8&255, -a&255]}function k(a){return n(a[0]<<24|a[1]<<16|a[2]<<8|a[3],32)}function p(a){return[a>>24&255,a>>16&255,a>>8&255,a&255]}function B(a){return l(a[0]<<24|a[1]<<16|a[2]<<8|a[3],32)}function E(a,b,f){function c(a){var b=V(a);a-=b;return 0.5>a?b:0.5a?1:0):0===a?(e=l=0,d=-Infinity===1/a?1:0):(d=0>a,a=u(a),a>=O(2,1-g)?(l=R(V(S(a)/r),1023),e=c(a/O(2,l)*O(2,f)),2<=e/O(2,f)&&(l+=1,e=1), -l>g?(l=(1<>=1;c.reverse();d=c.join("");a=(1<c?-0:0}function Q(a){return I(a,11,52)}function N(a){return E(a,11,52)}function F(a){return I(a,8,23)}function G(a){return E(a,8,23)}var s=void 0,t=1E5,r=Math.LN2,u=Math.abs,V=Math.floor,S=Math.log,M=Math.max,R=Math.min,O=Math.pow,aa=Math.round;(function(){var a=Object.defineProperty,b;try{b=Object.defineProperty({},"x",{})}catch(f){b=!1}a&&b||(Object.defineProperty=function(b,f,c){if(a)try{return a(b, -f,c)}catch(g){}if(b!==Object(b))throw TypeError("Object.defineProperty called on non-object");Object.prototype.__defineGetter__&&"get"in c&&Object.prototype.__defineGetter__.call(b,f,c.get);Object.prototype.__defineSetter__&&"set"in c&&Object.prototype.__defineSetter__.call(b,f,c.set);"value"in c&&(b[f]=c.value);return b})})();(function(){function l(a){a>>=0;if(0>a)throw RangeError("ArrayBuffer size is not a small enough positive integer.");Object.defineProperty(this,"byteLength",{value:a});Object.defineProperty(this, -"_bytes",{value:Array(a)});for(var b=0;b>=0;if(0>a)throw RangeError("length is not a small enough positive integer.");Object.defineProperty(this,"length",{value:a});Object.defineProperty(this,"byteLength",{value:a*this.BYTES_PER_ELEMENT});Object.defineProperty(this,"buffer",{value:new l(this.byteLength)});Object.defineProperty(this,"byteOffset",{value:0})}.apply(this,arguments);if(1<=arguments.length&& -"object"===e(arguments[0])&&arguments[0]instanceof n)return function(a){if(this.constructor!==a.constructor)throw TypeError();var b=a.length*this.BYTES_PER_ELEMENT;Object.defineProperty(this,"buffer",{value:new l(b)});Object.defineProperty(this,"byteLength",{value:b});Object.defineProperty(this,"byteOffset",{value:0});Object.defineProperty(this,"length",{value:a.length});for(b=0;b>>=0;if(b>a.byteLength)throw RangeError("byteOffset out of range");if(b%this.BYTES_PER_ELEMENT)throw RangeError("buffer length minus the byteOffset is not a multiple of the element size.");if(f===s){var c=a.byteLength-b;if(c%this.BYTES_PER_ELEMENT)throw RangeError("length of buffer minus byteOffset not a multiple of the element size");f=c/this.BYTES_PER_ELEMENT}else f>>>=0,c=f*this.BYTES_PER_ELEMENT;if(b+c>a.byteLength)throw RangeError("byteOffset and length reference an area beyond the end of the buffer"); -Object.defineProperty(this,"buffer",{value:a});Object.defineProperty(this,"byteLength",{value:c});Object.defineProperty(this,"byteOffset",{value:b});Object.defineProperty(this,"length",{value:f})}.apply(this,arguments);throw TypeError();}function r(a,b,f){var c=function(){Object.defineProperty(this,"constructor",{value:c});n.apply(this,arguments);g(this)};"__proto__"in c?c.__proto__=n:(c.from=n.from,c.of=n.of);c.BYTES_PER_ELEMENT=a;var d=function(){};d.prototype=t;c.prototype=new d;Object.defineProperty(c.prototype, -"BYTES_PER_ELEMENT",{value:a});Object.defineProperty(c.prototype,"_pack",{value:b});Object.defineProperty(c.prototype,"_unpack",{value:f});return c}a.ArrayBuffer=a.ArrayBuffer||l;Object.defineProperty(n,"from",{value:function(a){return new this(a)}});Object.defineProperty(n,"of",{value:function(){return new this(arguments)}});var t={};n.prototype=t;Object.defineProperty(n.prototype,"_getter",{value:function(a){if(1>arguments.length)throw SyntaxError("Not enough arguments");a>>>=0;if(a>=this.length)return s; -var b=[],f,c;f=0;for(c=this.byteOffset+a*this.BYTES_PER_ELEMENT;farguments.length)throw SyntaxError("Not enough arguments");a>>>=0;if(!(a>=this.length)){var f=this._pack(b),c,d;c=0;for(d=this.byteOffset+a*this.BYTES_PER_ELEMENT;c>>0,d=M(d,0);a>>=0;a=0>a?M(d+a,0):R(a,d);b>>=0;b=0>b?M(d+b,0):R(b,d);f=f===s?d:f>>0;f=0>f?M(d+f,0):R(f,d);d=R(f-b,d-a);from>>0;if(!m(a))throw TypeError();for(var d=0;d>>0,d=M(d,0);b>>=0;b=0>b?M(d+b,0):R(b,d);f=f===s?d:f>>0;for(d=0>f?M(d+f,0):R(f,d);b>>0;if(!m(a))throw TypeError(); -for(var d=[],g=0;g>>0;if(!m(a))throw TypeError();for(var c=1>>0;if(!m(a))throw TypeError();for(var c=1>>0;if(!m(a))throw TypeError();for(var d=0;d>>0;if(0===f)return-1;var c=0,d;0=f)return-1;for(c=0<=c?c:M(f-u(c),0);c>>0,c=Array(f),d=0;d>>0;if(0===f)return-1;var c=f;1>>0;if(!m(a))throw TypeError();var d=[];d.length=c;for(var g=0;g>>0;if(!m(a))throw TypeError();if(0===f&&1===arguments.length)throw TypeError();var c=0,d;for(d=2<=arguments.length?arguments[1]:b._getter(c++);c>>0;if(!m(a))throw TypeError(); -if(0===f&&1===arguments.length)throw TypeError();var f=f-1,c;for(c=2<=arguments.length?arguments[1]:b._getter(f--);0<=f;)c=a.call(s,c,b._getter(f),f,b),f--;return c}});Object.defineProperty(n.prototype,"reverse",{value:function(){if(this===s||null===this)throw TypeError();for(var a=Object(this),b=a.length>>>0,f=V(b/2),c=0,b=b-1;carguments.length)throw SyntaxError("Not enough arguments"); -var f,c,d,g,l,n;if("object"===typeof arguments[0]&&arguments[0].constructor===this.constructor){f=arguments[0];c=arguments[1]>>>0;if(c+f.length>this.length)throw RangeError("Offset plus length of array is out of range");n=this.byteOffset+c*this.BYTES_PER_ELEMENT;c=f.length*this.BYTES_PER_ELEMENT;if(f.buffer===this.buffer){d=[];g=0;for(l=f.byteOffset;g>>0;c=arguments[1]>>>0;if(c+d>this.length)throw RangeError("Offset plus length of array is out of range");for(g=0;g>>0,d=a>>0,d=0>d?M(c+d,0):R(d,c),g=b===s?c:b>>0,c=0>g?M(c+g,0):R(g,c), -g=new f.constructor(c-d),l=0;d>>0;if(!m(a))throw TypeError();for(var d=0;d>>0,c=Array(f),d=0;d>=0;b>>=0;1>arguments.length&&(a=0);2>arguments.length&&(b=this.length);0>a&&(a=this.length+a);0>b&&(b=this.length+b);var f=this.length;a=0>a?0:a>f?f:a;f=this.length;f=(0>b?0:b>f?f:b)-a;0>f&&(f=0);return new this.constructor(this.buffer,this.byteOffset+a*this.BYTES_PER_ELEMENT,f)}});var E=r(1,z,h),S=r(1,w,v),I=r(1,y,v),O=r(2,H,f),aa=r(2,x,J),ha=r(4,A,k),da=r(4,p,B), -U=r(4,G,F),$=r(8,N,Q);a.Int8Array=b.Int8Array=a.Int8Array||E;a.Uint8Array=b.Uint8Array=a.Uint8Array||S;a.Uint8ClampedArray=b.Uint8ClampedArray=a.Uint8ClampedArray||I;a.Int16Array=b.Int16Array=a.Int16Array||O;a.Uint16Array=b.Uint16Array=a.Uint16Array||aa;a.Int32Array=b.Int32Array=a.Int32Array||ha;a.Uint32Array=b.Uint32Array=a.Uint32Array||da;a.Float32Array=b.Float32Array=a.Float32Array||U;a.Float64Array=b.Float64Array=a.Float64Array||$})();(function(){function b(a,f){return m(a.get)?a.get(f):a[f]} -function f(a,b,c){if(!(a instanceof ArrayBuffer||"ArrayBuffer"===d(a)))throw TypeError();b>>>=0;if(b>a.byteLength)throw RangeError("byteOffset out of range");c=c===s?a.byteLength-b:c>>>0;if(b+c>a.byteLength)throw RangeError("byteOffset and length reference an area beyond the end of the buffer");Object.defineProperty(this,"buffer",{value:a});Object.defineProperty(this,"byteLength",{value:c});Object.defineProperty(this,"byteOffset",{value:b})}function c(f){return function(c,d){c>>>=0;if(c+f.BYTES_PER_ELEMENT> -this.byteLength)throw RangeError("Array index out of range");c+=this.byteOffset;for(var g=new a.Uint8Array(this.buffer,c,f.BYTES_PER_ELEMENT),n=[],e=0;e>>=0;if(c+f.BYTES_PER_ELEMENT>this.byteLength)throw RangeError("Array index out of range");d=new f([d]);d=new a.Uint8Array(d.buffer);var n=[],e;for(e=0;e must be a string");d.assert(!b||Array.isArray(b),"Session.join: must be an array []");d.assert(!c|| -"string"===typeof c,"Session.join: must be a string");if(this.isOpen)throw"session already open";this._goodbye_sent=!1;this._realm=a;var f={};f.roles=WAMP_FEATURES;b&&(f.authmethods=b);c&&(f.authid=c);this._send_wamp([1,a,f])};w.prototype.leave=function(a,b){d.assert(!a||"string"===typeof a,"Session.leave: must be a string");d.assert(!b||"string"===typeof b,"Session.leave: must be a string");if(!this.isOpen)throw"session not open";a||(a="wamp.close.normal");var c={};b&& -(c.message=b);this._send_wamp([6,c,a]);this._goodbye_sent=!0};w.prototype.call=function(b,c,g,f){d.assert("string"===typeof b,"Session.call: must be a string");d.assert(!c||Array.isArray(c),"Session.call: must be an array []");d.assert(!g||g instanceof Object,"Session.call: must be an object {}");d.assert(!f||f instanceof Object,"Session.call: must be an object {}");if(!this.isOpen)throw"session not open";var l=a(),n=this._defer();this._call_reqs[l]=[n,f];b=[48, -l,f||{},this.resolve(b)];c&&(b.push(c),g&&b.push(g));this._send_wamp(b);return n.promise.then?n.promise:n};w.prototype.publish=function(b,c,g,f){d.assert("string"===typeof b,"Session.publish: must be a string");d.assert(!c||Array.isArray(c),"Session.publish: must be an array []");d.assert(!g||g instanceof Object,"Session.publish: must be an object {}");d.assert(!f||f instanceof Object,"Session.publish: must be an object {}");if(!this.isOpen)throw"session not open"; -var l=f&&f.acknowledge,n=null,e=a();l&&(n=this._defer(),this._publish_reqs[e]=[n,f]);b=[16,e,f||{},this.resolve(b)];c&&(b.push(c),g&&b.push(g));this._send_wamp(b);if(n)return n.promise.then?n.promise:n};w.prototype.subscribe=function(b,c,g){d.assert("string"===typeof b,"Session.subscribe: must be a string");d.assert("function"===typeof c,"Session.subscribe: must be a function");d.assert(!g||g instanceof Object,"Session.subscribe: must be an object {}");if(!this.isOpen)throw"session not open"; -var f=a(),l=this._defer();this._subscribe_reqs[f]=[l,b,c,g];c=[32,f];g?c.push(g):c.push({});c.push(this.resolve(b));this._send_wamp(c);return l.promise.then?l.promise:l};w.prototype.register=function(b,c,g){d.assert("string"===typeof b,"Session.register: must be a string");d.assert("function"===typeof c,"Session.register: must be a function");d.assert(!g||g instanceof Object,"Session.register: must be an object {}");if(!this.isOpen)throw"session not open";var f=a(), -l=this._defer();this._register_reqs[f]=[l,b,c,g];c=[64,f];g?c.push(g):c.push({});c.push(this.resolve(b));this._send_wamp(c);return l.promise.then?l.promise:l};w.prototype.unsubscribe=function(b){d.assert(b instanceof l,"Session.unsubscribe: must be an instance of class autobahn.Subscription");if(!this.isOpen)throw"session not open";if(!(b.active&&b.id in this._subscriptions))throw"subscription not active";var c=this._subscriptions[b.id],g=c.indexOf(b);if(-1===g)throw"subscription not active"; -c.splice(g,1);b.active=!1;g=this._defer();c.length?g.resolve(!1):(c=a(),this._unsubscribe_reqs[c]=[g,b.id],this._send_wamp([34,c,b.id]));return g.promise.then?g.promise:g};w.prototype.unregister=function(b){d.assert(b instanceof z,"Session.unregister: must be an instance of class autobahn.Registration");if(!this.isOpen)throw"session not open";if(!(b.active&&b.id in this._registrations))throw"registration not active";var c=a(),g=this._defer();this._unregister_reqs[c]=[g,b];this._send_wamp([66, -c,b.id]);return g.promise.then?g.promise:g};w.prototype.prefix=function(a,b){d.assert("string"===typeof a,"Session.prefix: must be a string");d.assert(!b||"string"===typeof b,"Session.prefix: must be a string or falsy");b?this._prefixes[a]=b:a in this._prefixes&&delete this._prefixes[a]};w.prototype.resolve=function(a){d.assert("string"===typeof a,"Session.resolve: must be a string");var b=a.indexOf(":");if(0<=b){var c=a.substring(0,b);if(c in this._prefixes)return this._prefixes[c]+ -"."+a.substring(b+1);throw"cannot resolve CURIE prefix '"+c+"'";}return a};k.Session=w;k.Invocation=m;k.Event=q;k.Result=g;k.Error=n;k.Subscription=l;k.Registration=z;k.Publication=P}).call(this,"undefined"!==typeof self?self:"undefined"!==typeof window?window:{})},{"./log.js":7,"./util.js":19,when:77,"when/function":54}],17:[function(h,p,k){function b(b){a.assert(void 0!==b.url,"options.url missing");a.assert("string"===typeof b.url,"options.url must be a string");this._options=b}var a=h("../util.js"), -c=h("../log.js");h("when");b.prototype.type="longpoll";b.prototype.create=function(){var b=this;c.debug("longpoll.Factory.create");var d={protocol:void 0,send:void 0,close:void 0,onmessage:function(){},onopen:function(){},onclose:function(){},info:{type:"longpoll",url:null,protocol:"wamp.2.json"},_run:function(){var m=null,q=!1,g=b._options.request_timeout||2E3;a.http_post(b._options.url+"/open",JSON.stringify({protocols:["wamp.2.json"]}),g).then(function(n){function l(){c.debug("longpoll.Transport: polling for message ..."); -a.http_post(z+"/receive",null,g).then(function(a){a&&(a=JSON.parse(a),c.debug("longpoll.Transport: message received",a),d.onmessage(a));q||l()},function(a){c.debug("longpoll.Transport: could not receive message",a.code,a.text);q=!0;d.onclose({code:1001,reason:"transport receive failure (HTTP/POST status "+a.code+" - '"+a.text+"')",wasClean:!1})})}m=JSON.parse(n);var z=b._options.url+"/"+m.transport;d.info.url=z;c.debug("longpoll.Transport: open",m);d.close=function(b,l){if(q)throw"transport is already closing"; -q=!0;a.http_post(z+"/close",null,g).then(function(){c.debug("longpoll.Transport: transport closed");d.onclose({code:1E3,reason:"transport closed",wasClean:!0})},function(a){c.debug("longpoll.Transport: could not close transport",a.code,a.text)})};d.send=function(b){if(q)throw"transport is closing or closed already";c.debug("longpoll.Transport: sending message ...",b);b=JSON.stringify(b);a.http_post(z+"/send",b,g).then(function(){c.debug("longpoll.Transport: message sent")},function(a){c.debug("longpoll.Transport: could not send message", -a.code,a.text);q=!0;d.onclose({code:1001,reason:"transport send failure (HTTP/POST status "+a.code+" - '"+a.text+"')",wasClean:!1})})};l();d.onopen()},function(a){c.debug("longpoll.Transport: could not open transport",a.code,a.text);q=!0;d.onclose({code:1001,reason:"transport open failure (HTTP/POST status "+a.code+" - '"+a.text+"')",wasClean:!1})})}};d._run();return d};k.Factory=b},{"../log.js":7,"../util.js":19,when:77}],18:[function(h,p,k){(function(b){function a(a){c.assert(void 0!==a.url,"options.url missing"); -c.assert("string"===typeof a.url,"options.url must be a string");a.protocols?c.assert(Array.isArray(a.protocols),"options.protocols must be an array"):a.protocols=["wamp.2.json"];this._options=a}var c=h("../util.js"),e=h("../log.js");a.prototype.type="websocket";a.prototype.create=function(){var a=this,c={protocol:void 0,send:void 0,close:void 0,onmessage:function(){},onopen:function(){},onclose:function(){},info:{type:"websocket",url:null,protocol:"wamp.2.json"}};"window"in b?function(){var b;if("WebSocket"in -window)b=a._options.protocols?new window.WebSocket(a._options.url,a._options.protocols):new window.WebSocket(a._options.url);else if("MozWebSocket"in window)b=a._options.protocols?new window.MozWebSocket(a._options.url,a._options.protocols):new window.MozWebSocket(a._options.url);else throw"browser does not support WebSocket";b.onmessage=function(a){e.debug("WebSocket transport receive",a.data);a=JSON.parse(a.data);c.onmessage(a)};b.onopen=function(){c.info.url=a._options.url;c.onopen()};b.onclose= -function(a){c.onclose({code:a.code,reason:a.message,wasClean:a.wasClean})};c.send=function(a){a=JSON.stringify(a);e.debug("WebSocket transport send",a);b.send(a)};c.close=function(a,c){b.close(a,c)}}():function(){var b=h("ws"),g,n;a._options.protocols?(n=a._options.protocols,Array.isArray(n)&&(n=n.join(",")),g=new b(a._options.url,{protocol:n})):g=new b(a._options.url);c.send=function(a){a=JSON.stringify(a);g.send(a,{binary:!1})};c.close=function(a,b){g.close()};g.on("open",function(){c.onopen()}); -g.on("message",function(a,b){if(!b.binary){var d=JSON.parse(a);c.onmessage(d)}});g.on("close",function(a,b){c.onclose({code:a,reason:b,wasClean:1E3===a})});g.on("error",function(a){c.onclose({code:1006,reason:"",wasClean:!1})})}();return c};k.Factory=a}).call(this,"undefined"!==typeof self?self:"undefined"!==typeof window?window:{})},{"../log.js":7,"../util.js":19,ws:78}],19:[function(h,p,k){(function(b){var a=h("./log.js"),c=h("when"),e=function(a,c){if(!a){if(e.useDebugger||"AUTOBAHN_DEBUG"in b&& -AUTOBAHN_DEBUG)debugger;throw Error(c||"Assertion failed!");}};k.rand_normal=function(a,b){var c,g;do c=2*Math.random()-1,g=2*Math.random()-1,g=c*c+g*g;while(1<=g||0==g);g=Math.sqrt(-2*Math.log(g)/g);return(a||0)+c*g*(b||1)};k.assert=e;k.http_post=function(b,e,q){a.debug("new http_post request",b,e,q);var g=c.defer(),n=new XMLHttpRequest;n.onreadystatechange=function(){if(4===n.readyState){var a=1223===n.status?204:n.status;200===a&&g.resolve(n.responseText);if(204===a)g.resolve();else{var b=null; -try{b=n.statusText}catch(c){}g.reject({code:a,text:b})}}};n.open("POST",b,!0);n.setRequestHeader("Content-type","application/json; charset=utf-8");0b;b++)a[b]=128>b?b<<1:b<<1^283;for(var c=0,v=0,b=0;256>b;b++){var k=v^v<<1^v<<2^v<<3^v<<4,k=k>>>8^k&255^99;e[c]=k;d[k]=c;var A=a[c],p=a[A],C=a[p],B=257*a[k]^16843008*k;m[c]=B<<24|B>>>8;q[c]=B<<16|B>>>16;g[c]=B<<8|B>>>24;n[c]=B;B=16843009*C^65537*p^257*A^16843008*c;l[k]=B<<24|B>>>8;z[k]=B<<16|B>>>16;h[k]=B<< -8|B>>>24;w[k]=B;c?(c=A^a[a[a[C^A]]],v^=a[a[v]]):c=v=1}})();var v=[0,1,2,4,8,16,32,64,128,27,54],c=c.AES=a.extend({_doReset:function(){for(var a=this._key,b=a.words,c=a.sigBytes/4,a=4*((this._nRounds=c+6)+1),d=this._keySchedule=[],g=0;g>>24]<<24|e[n>>>16&255]<<16|e[n>>>8&255]<<8|e[n&255]):(n=n<<8|n>>>24,n=e[n>>>24]<<24|e[n>>>16&255]<<16|e[n>>>8&255]<<8|e[n&255],n^=v[g/c|0]<<24);d[g]=d[g-c]^n}b=this._invKeySchedule=[];for(c=0;cc||4>=g?n:l[e[n>>>24]]^z[e[n>>>16&255]]^h[e[n>>>8&255]]^w[e[n&255]]},encryptBlock:function(a,b){this._doCryptBlock(a,b,this._keySchedule,m,q,g,n,e)},decryptBlock:function(a,b){var c=a[b+1];a[b+1]=a[b+3];a[b+3]=c;this._doCryptBlock(a,b,this._invKeySchedule,l,z,h,w,d);c=a[b+1];a[b+1]=a[b+3];a[b+3]=c},_doCryptBlock:function(a,b,c,d,g,l,n,e){for(var m=this._nRounds,z=a[b]^c[0],q=a[b+1]^c[1],v=a[b+2]^c[2],h=a[b+3]^c[3],w=4,P=1;P>>24]^g[q>>>16&255]^l[v>>>8& -255]^n[h&255]^c[w++],p=d[q>>>24]^g[v>>>16&255]^l[h>>>8&255]^n[z&255]^c[w++],r=d[v>>>24]^g[h>>>16&255]^l[z>>>8&255]^n[q&255]^c[w++],h=d[h>>>24]^g[z>>>16&255]^l[q>>>8&255]^n[v&255]^c[w++],z=k,q=p,v=r;k=(e[z>>>24]<<24|e[q>>>16&255]<<16|e[v>>>8&255]<<8|e[h&255])^c[w++];p=(e[q>>>24]<<24|e[v>>>16&255]<<16|e[h>>>8&255]<<8|e[z&255])^c[w++];r=(e[v>>>24]<<24|e[h>>>16&255]<<16|e[z>>>8&255]<<8|e[q&255])^c[w++];h=(e[h>>>24]<<24|e[z>>>16&255]<<16|e[q>>>8&255]<<8|e[v&255])^c[w++];a[b]=k;a[b+1]=p;a[b+2]=r;a[b+3]= -h},keySize:8});b.AES=a._createHelper(c)})();return b.AES})},{"./cipher-core":21,"./core":22,"./enc-base64":23,"./evpkdf":25,"./md5":30}],21:[function(h,p,k){(function(b,a){"object"===typeof k?p.exports=k=a(h("./core")):a(b.CryptoJS)})(this,function(b){b.lib.Cipher||function(a){var c=b.lib,e=c.Base,d=c.WordArray,m=c.BufferedBlockAlgorithm,q=b.enc.Base64,g=b.algo.EvpKDF,n=c.Cipher=m.extend({cfg:e.extend(),createEncryptor:function(a,b){return this.create(this._ENC_XFORM_MODE,a,b)},createDecryptor:function(a, -b){return this.create(this._DEC_XFORM_MODE,a,b)},init:function(a,b,c){this.cfg=this.cfg.extend(c);this._xformMode=a;this._key=b;this.reset()},reset:function(){m.reset.call(this);this._doReset()},process:function(a){this._append(a);return this._process()},finalize:function(a){a&&this._append(a);return this._doFinalize()},keySize:4,ivSize:4,_ENC_XFORM_MODE:1,_DEC_XFORM_MODE:2,_createHelper:function(){return function(a){return{encrypt:function(b,c,d){return("string"==typeof c?y:v).encrypt(a,b,c,d)}, -decrypt:function(b,c,d){return("string"==typeof c?y:v).decrypt(a,b,c,d)}}}}()});c.StreamCipher=n.extend({_doFinalize:function(){return this._process(!0)},blockSize:1});var l=b.mode={},z=c.BlockCipherMode=e.extend({createEncryptor:function(a,b){return this.Encryptor.create(a,b)},createDecryptor:function(a,b){return this.Decryptor.create(a,b)},init:function(a,b){this._cipher=a;this._iv=b}}),l=l.CBC=function(){function b(c,d,f){var g=this._iv;g?this._iv=a:g=this._prevBlock;for(var l=0;l>>2]&255}};c.BlockCipher=n.extend({cfg:n.cfg.extend({mode:l,padding:h}),reset:function(){n.reset.call(this);var a=this.cfg,b=a.iv,a=a.mode;if(this._xformMode==this._ENC_XFORM_MODE)var c=a.createEncryptor;else c=a.createDecryptor,this._minBufferSize=1;this._mode=c.call(a,this,b&&b.words)},_doProcessBlock:function(a,b){this._mode.processBlock(a,b)},_doFinalize:function(){var a=this.cfg.padding;if(this._xformMode==this._ENC_XFORM_MODE){a.pad(this._data,this.blockSize);var b=this._process(!0)}else b= -this._process(!0),a.unpad(b);return b},blockSize:4});var w=c.CipherParams=e.extend({init:function(a){this.mixIn(a)},toString:function(a){return(a||this.formatter).stringify(this)}}),l=(b.format={}).OpenSSL={stringify:function(a){var b=a.ciphertext;a=a.salt;return(a?d.create([1398893684,1701076831]).concat(a).concat(b):b).toString(q)},parse:function(a){a=q.parse(a);var b=a.words;if(1398893684==b[0]&&1701076831==b[1]){var c=d.create(b.slice(2,4));b.splice(0,4);a.sigBytes-=16}return w.create({ciphertext:a, -salt:c})}},v=c.SerializableCipher=e.extend({cfg:e.extend({format:l}),encrypt:function(a,b,c,d){d=this.cfg.extend(d);var g=a.createEncryptor(c,d);b=g.finalize(b);g=g.cfg;return w.create({ciphertext:b,key:c,iv:g.iv,algorithm:a,mode:g.mode,padding:g.padding,blockSize:a.blockSize,formatter:d.format})},decrypt:function(a,b,c,d){d=this.cfg.extend(d);b=this._parse(b,d.format);return a.createDecryptor(c,d).finalize(b.ciphertext)},_parse:function(a,b){return"string"==typeof a?b.parse(a,this):a}}),e=(b.kdf= -{}).OpenSSL={execute:function(a,b,c,l){l||(l=d.random(8));a=g.create({keySize:b+c}).compute(a,l);c=d.create(a.words.slice(b),4*c);a.sigBytes=4*b;return w.create({key:a,iv:c,salt:l})}},y=c.PasswordBasedCipher=v.extend({cfg:v.cfg.extend({kdf:e}),encrypt:function(a,b,c,d){d=this.cfg.extend(d);c=d.kdf.execute(c,a.keySize,a.ivSize);d.iv=c.iv;a=v.encrypt.call(this,a,b,c.key,d);a.mixIn(c);return a},decrypt:function(a,b,c,d){d=this.cfg.extend(d);b=this._parse(b,d.format);c=d.kdf.execute(c,a.keySize,a.ivSize, -b.salt);d.iv=c.iv;return v.decrypt.call(this,a,b,c.key,d)}})}()})},{"./core":22}],22:[function(h,p,k){(function(b,a){"object"===typeof k?p.exports=k=a():b.CryptoJS=a()})(this,function(){var b=b||function(a,b){var e={},d=e.lib={},m=d.Base=function(){function a(){}return{extend:function(b){a.prototype=this;var c=new a;b&&c.mixIn(b);c.hasOwnProperty("init")||(c.init=function(){c.$super.init.apply(this,arguments)});c.init.prototype=c;c.$super=this;return c},create:function(){var a=this.extend();a.init.apply(a, -arguments);return a},init:function(){},mixIn:function(a){for(var b in a)a.hasOwnProperty(b)&&(this[b]=a[b]);a.hasOwnProperty("toString")&&(this.toString=a.toString)},clone:function(){return this.init.prototype.extend(this)}}}(),q=d.WordArray=m.extend({init:function(a,d){a=this.words=a||[];this.sigBytes=d!=b?d:4*a.length},toString:function(a){return(a||n).stringify(this)},concat:function(a){var b=this.words,c=a.words,d=this.sigBytes;a=a.sigBytes;this.clamp();if(d%4)for(var g=0;g>>2]|= -(c[g>>>2]>>>24-g%4*8&255)<<24-(d+g)%4*8;else if(65535>>2]=c[g>>>2];else b.push.apply(b,c);this.sigBytes+=a;return this},clamp:function(){var b=this.words,c=this.sigBytes;b[c>>>2]&=4294967295<<32-c%4*8;b.length=a.ceil(c/4)},clone:function(){var a=m.clone.call(this);a.words=this.words.slice(0);return a},random:function(b){for(var c=[],d=0;d>>2]>>>24-d%4*8&255;c.push((g>>>4).toString(16));c.push((g&15).toString(16))}return c.join("")},parse:function(a){for(var b=a.length,c=[],d=0;d>>3]|=parseInt(a.substr(d,2),16)<<24-d%8*4;return new q.init(c,b/2)}},l=g.Latin1={stringify:function(a){var b=a.words;a=a.sigBytes;for(var c=[],d=0;d>>2]>>>24-d%4*8&255));return c.join("")},parse:function(a){for(var b=a.length,c=[],d=0;d>>2]|=(a.charCodeAt(d)&255)<< -24-d%4*8;return new q.init(c,b)}},z=g.Utf8={stringify:function(a){try{return decodeURIComponent(escape(l.stringify(a)))}catch(b){throw Error("Malformed UTF-8 data");}},parse:function(a){return l.parse(unescape(encodeURIComponent(a)))}},h=d.BufferedBlockAlgorithm=m.extend({reset:function(){this._data=new q.init;this._nDataBytes=0},_append:function(a){"string"==typeof a&&(a=z.parse(a));this._data.concat(a);this._nDataBytes+=a.sigBytes},_process:function(b){var c=this._data,d=c.words,g=c.sigBytes,l= -this.blockSize,n=g/(4*l),n=b?a.ceil(n):a.max((n|0)-this._minBufferSize,0);b=n*l;g=a.min(4*b,g);if(b){for(var e=0;e>>2]>>>24-q%4*8&255)<<16|(b[q+1>>>2]>>>24-(q+1)%4*8&255)<<8|b[q+2>>>2]>>>24-(q+2)%4*8&255,n=0;4>n&&q+0.75*n>>6*(3-n)&63));if(b=m.charAt(64))for(;a.length%4;)a.push(b);return a.join("")},parse:function(b){var e=b.length,d=this._map,m=d.charAt(64);m&&(m=b.indexOf(m),-1!=m&&(e=m));for(var m=[],q=0,g=0;g>>6-g%4*2;m[q>>>2]|=(n| -l)<<24-q%4*8;q++}return a.create(m,q)},_map:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="}})();return b.enc.Base64})},{"./core":22}],24:[function(h,p,k){(function(b,a){"object"===typeof k?p.exports=k=a(h("./core")):a(b.CryptoJS)})(this,function(b){(function(){function a(a){return a<<8&4278255360|a>>>8&16711935}var c=b.lib.WordArray,e=b.enc;e.Utf16=e.Utf16BE={stringify:function(a){var b=a.words;a=a.sigBytes;for(var c=[],g=0;g>>2]>>>16-g% -4*8&65535));return c.join("")},parse:function(a){for(var b=a.length,e=[],g=0;g>>1]|=a.charCodeAt(g)<<16-g%2*16;return c.create(e,2*b)}};e.Utf16LE={stringify:function(b){var c=b.words;b=b.sigBytes;for(var e=[],g=0;g>>2]>>>16-g%4*8&65535);e.push(String.fromCharCode(n))}return e.join("")},parse:function(b){for(var e=b.length,q=[],g=0;g>>1]|=a(b.charCodeAt(g)<<16-g%2*16);return c.create(q,2*e)}}})();return b.enc.Utf16})},{"./core":22}],25:[function(h,p,k){(function(b, -a,c){"object"===typeof k?p.exports=k=a(h("./core"),h("./sha1"),h("./hmac")):a(b.CryptoJS)})(this,function(b){(function(){var a=b.lib,c=a.Base,e=a.WordArray,a=b.algo,d=a.EvpKDF=c.extend({cfg:c.extend({keySize:4,hasher:a.MD5,iterations:1}),init:function(a){this.cfg=this.cfg.extend(a)},compute:function(a,b){for(var c=this.cfg,d=c.hasher.create(),l=e.create(),z=l.words,h=c.keySize,c=c.iterations;z.lengthm&&(e=b.finalize(e));e.clamp();for(var q=this._oKey=e.clone(),g=this._iKey=e.clone(),n=q.words,l=g.words,z=0;z>>2]|=a[q]<<24-q%4*8;c.call(this,m,b)}else c.apply(this,arguments)}).prototype=a}})();return b.lib.WordArray})},{"./core":22}],30:[function(h,p,k){(function(b,a){"object"===typeof k?p.exports=k=a(h("./core")):a(b.CryptoJS)})(this,function(b){(function(a){function c(a,b,c,d,g,l,f){a=a+(b&c|~b&d)+g+f;return(a<>>32-l)+b}function e(a,b,c,d,g,l,f){a=a+(b&d|c&~d)+g+f;return(a<>>32-l)+b}function d(a,b,c,d,g,l,f){a=a+(b^c^d)+g+f;return(a<>>32-l)+b}function m(a,b,c,d,g,l,f){a= -a+(c^(b|~d))+g+f;return(a<>>32-l)+b}var q=b.lib,g=q.WordArray,n=q.Hasher,q=b.algo,l=[];(function(){for(var b=0;64>b;b++)l[b]=4294967296*a.abs(a.sin(b+1))|0})();q=q.MD5=n.extend({_doReset:function(){this._hash=new g.init([1732584193,4023233417,2562383102,271733878])},_doProcessBlock:function(a,b){for(var g=0;16>g;g++){var n=b+g,q=a[n];a[n]=(q<<8|q>>>24)&16711935|(q<<24|q>>>8)&4278255360}var g=this._hash.words,n=a[b+0],q=a[b+1],h=a[b+2],f=a[b+3],k=a[b+4],p=a[b+5],A=a[b+6],L=a[b+7],C=a[b+8],B=a[b+ -9],E=a[b+10],I=a[b+11],Q=a[b+12],N=a[b+13],F=a[b+14],G=a[b+15],s=g[0],t=g[1],r=g[2],u=g[3],s=c(s,t,r,u,n,7,l[0]),u=c(u,s,t,r,q,12,l[1]),r=c(r,u,s,t,h,17,l[2]),t=c(t,r,u,s,f,22,l[3]),s=c(s,t,r,u,k,7,l[4]),u=c(u,s,t,r,p,12,l[5]),r=c(r,u,s,t,A,17,l[6]),t=c(t,r,u,s,L,22,l[7]),s=c(s,t,r,u,C,7,l[8]),u=c(u,s,t,r,B,12,l[9]),r=c(r,u,s,t,E,17,l[10]),t=c(t,r,u,s,I,22,l[11]),s=c(s,t,r,u,Q,7,l[12]),u=c(u,s,t,r,N,12,l[13]),r=c(r,u,s,t,F,17,l[14]),t=c(t,r,u,s,G,22,l[15]),s=e(s,t,r,u,q,5,l[16]),u=e(u,s,t,r,A,9,l[17]), -r=e(r,u,s,t,I,14,l[18]),t=e(t,r,u,s,n,20,l[19]),s=e(s,t,r,u,p,5,l[20]),u=e(u,s,t,r,E,9,l[21]),r=e(r,u,s,t,G,14,l[22]),t=e(t,r,u,s,k,20,l[23]),s=e(s,t,r,u,B,5,l[24]),u=e(u,s,t,r,F,9,l[25]),r=e(r,u,s,t,f,14,l[26]),t=e(t,r,u,s,C,20,l[27]),s=e(s,t,r,u,N,5,l[28]),u=e(u,s,t,r,h,9,l[29]),r=e(r,u,s,t,L,14,l[30]),t=e(t,r,u,s,Q,20,l[31]),s=d(s,t,r,u,p,4,l[32]),u=d(u,s,t,r,C,11,l[33]),r=d(r,u,s,t,I,16,l[34]),t=d(t,r,u,s,F,23,l[35]),s=d(s,t,r,u,q,4,l[36]),u=d(u,s,t,r,k,11,l[37]),r=d(r,u,s,t,L,16,l[38]),t=d(t, -r,u,s,E,23,l[39]),s=d(s,t,r,u,N,4,l[40]),u=d(u,s,t,r,n,11,l[41]),r=d(r,u,s,t,f,16,l[42]),t=d(t,r,u,s,A,23,l[43]),s=d(s,t,r,u,B,4,l[44]),u=d(u,s,t,r,Q,11,l[45]),r=d(r,u,s,t,G,16,l[46]),t=d(t,r,u,s,h,23,l[47]),s=m(s,t,r,u,n,6,l[48]),u=m(u,s,t,r,L,10,l[49]),r=m(r,u,s,t,F,15,l[50]),t=m(t,r,u,s,p,21,l[51]),s=m(s,t,r,u,Q,6,l[52]),u=m(u,s,t,r,f,10,l[53]),r=m(r,u,s,t,E,15,l[54]),t=m(t,r,u,s,q,21,l[55]),s=m(s,t,r,u,C,6,l[56]),u=m(u,s,t,r,G,10,l[57]),r=m(r,u,s,t,A,15,l[58]),t=m(t,r,u,s,N,21,l[59]),s=m(s,t, -r,u,k,6,l[60]),u=m(u,s,t,r,I,10,l[61]),r=m(r,u,s,t,h,15,l[62]),t=m(t,r,u,s,B,21,l[63]);g[0]=g[0]+s|0;g[1]=g[1]+t|0;g[2]=g[2]+r|0;g[3]=g[3]+u|0},_doFinalize:function(){var b=this._data,c=b.words,d=8*this._nDataBytes,g=8*b.sigBytes;c[g>>>5]|=128<<24-g%32;var l=a.floor(d/4294967296);c[(g+64>>>9<<4)+15]=(l<<8|l>>>24)&16711935|(l<<24|l>>>8)&4278255360;c[(g+64>>>9<<4)+14]=(d<<8|d>>>24)&16711935|(d<<24|d>>>8)&4278255360;b.sigBytes=4*(c.length+1);this._process();b=this._hash;c=b.words;for(d=0;4>d;d++)g=c[d], -c[d]=(g<<8|g>>>24)&16711935|(g<<24|g>>>8)&4278255360;return b},clone:function(){var a=n.clone.call(this);a._hash=this._hash.clone();return a}});b.MD5=n._createHelper(q);b.HmacMD5=n._createHmacHelper(q)})(Math);return b.MD5})},{"./core":22}],31:[function(h,p,k){(function(b,a,c){"object"===typeof k?p.exports=k=a(h("./core"),h("./cipher-core")):a(b.CryptoJS)})(this,function(b){b.mode.CFB=function(){function a(a,b,c,q){var g=this._iv;g?(g=g.slice(0),this._iv=void 0):g=this._prevBlock;q.encryptBlock(g, -0);for(q=0;q>24&255)){var b=a>>16&255,c=a>>8&255,g=a&255;255===b?(b=0,255===c?(c=0,255===g?g=0:++g):++c):++b;a=0+(b<<16)+(c<<8);a+=g}else a+=16777216;return a}var c=b.lib.BlockCipherMode.extend(),e=c.Encryptor=c.extend({processBlock:function(b,c){var e=this._cipher,g=e.blockSize,n=this._iv,l=this._counter;n&&(l=this._counter=n.slice(0),this._iv=void 0);n=l;0===(n[0]=a(n[0]))&&(n[1]=a(n[1]));l=l.slice(0);e.encryptBlock(l,0); -for(e=0;e>>2]|=d<<24-e%4*8;a.sigBytes+=d},unpad:function(a){a.sigBytes-=a.words[a.sigBytes-1>>>2]&255}};return b.pad.Ansix923})},{"./cipher-core":21,"./core":22}],37:[function(h,p,k){(function(b,a,c){"object"===typeof k?p.exports=k=a(h("./core"),h("./cipher-core")): -a(b.CryptoJS)})(this,function(b){b.pad.Iso10126={pad:function(a,c){var e=4*c,e=e-a.sigBytes%e;a.concat(b.lib.WordArray.random(e-1)).concat(b.lib.WordArray.create([e<<24],1))},unpad:function(a){a.sigBytes-=a.words[a.sigBytes-1>>>2]&255}};return b.pad.Iso10126})},{"./cipher-core":21,"./core":22}],38:[function(h,p,k){(function(b,a,c){"object"===typeof k?p.exports=k=a(h("./core"),h("./cipher-core")):a(b.CryptoJS)})(this,function(b){b.pad.Iso97971={pad:function(a,c){a.concat(b.lib.WordArray.create([2147483648], -1));b.pad.ZeroPadding.pad(a,c)},unpad:function(a){b.pad.ZeroPadding.unpad(a);a.sigBytes--}};return b.pad.Iso97971})},{"./cipher-core":21,"./core":22}],39:[function(h,p,k){(function(b,a,c){"object"===typeof k?p.exports=k=a(h("./core"),h("./cipher-core")):a(b.CryptoJS)})(this,function(b){b.pad.NoPadding={pad:function(){},unpad:function(){}};return b.pad.NoPadding})},{"./cipher-core":21,"./core":22}],40:[function(h,p,k){(function(b,a,c){"object"===typeof k?p.exports=k=a(h("./core"),h("./cipher-core")): -a(b.CryptoJS)})(this,function(b){b.pad.ZeroPadding={pad:function(a,b){var e=4*b;a.clamp();a.sigBytes+=e-(a.sigBytes%e||e)},unpad:function(a){for(var b=a.words,e=a.sigBytes-1;!(b[e>>>2]>>>24-e%4*8&255);)e--;a.sigBytes=e+1}};return b.pad.ZeroPadding})},{"./cipher-core":21,"./core":22}],41:[function(h,p,k){(function(b,a,c){"object"===typeof k?p.exports=k=a(h("./core"),h("./sha1"),h("./hmac")):a(b.CryptoJS)})(this,function(b){(function(){var a=b.lib,c=a.Base,e=a.WordArray,a=b.algo,d=a.HMAC,m=a.PBKDF2= -c.extend({cfg:c.extend({keySize:4,hasher:a.SHA1,iterations:1}),init:function(a){this.cfg=this.cfg.extend(a)},compute:function(a,b){for(var c=this.cfg,l=d.create(c.hasher,a),m=e.create(),h=e.create([1]),w=m.words,k=h.words,y=c.keySize,c=c.iterations;w.lengthc;c++)d[c]=b[c];b[0]=b[0]+1295307597+this._b|0;b[1]=b[1]+3545052371+(b[0]>>>0>>0?1:0)|0;b[2]=b[2]+886263092+(b[1]>>>0>>0?1:0)|0;b[3]=b[3]+1295307597+(b[2]>>>0>>0?1:0)|0;b[4]=b[4]+ -3545052371+(b[3]>>>0>>0?1:0)|0;b[5]=b[5]+886263092+(b[4]>>>0>>0?1:0)|0;b[6]=b[6]+1295307597+(b[5]>>>0>>0?1:0)|0;b[7]=b[7]+3545052371+(b[6]>>>0>>0?1:0)|0;this._b=b[7]>>>0>>0?1:0;for(c=0;8>c;c++){var e=a[c]+b[c],h=e&65535,q=e>>>16;m[c]=((h*h>>>17)+h*q>>>15)+q*q^((e&4294901760)*e|0)+((e&65535)*e|0)}a[0]=m[0]+(m[7]<<16|m[7]>>>16)+(m[6]<<16|m[6]>>>16)|0;a[1]=m[1]+(m[0]<<8|m[0]>>>24)+m[7]|0;a[2]=m[2]+(m[1]<<16|m[1]>>>16)+(m[0]<<16|m[0]>>>16)|0;a[3]=m[3]+(m[2]<<8|m[2]>>>24)+ -m[1]|0;a[4]=m[4]+(m[3]<<16|m[3]>>>16)+(m[2]<<16|m[2]>>>16)|0;a[5]=m[5]+(m[4]<<8|m[4]>>>24)+m[3]|0;a[6]=m[6]+(m[5]<<16|m[5]>>>16)+(m[4]<<16|m[4]>>>16)|0;a[7]=m[7]+(m[6]<<8|m[6]>>>24)+m[5]|0}var c=b.lib.StreamCipher,e=[],d=[],m=[],h=b.algo.RabbitLegacy=c.extend({_doReset:function(){for(var b=this._key.words,c=this.cfg.iv,d=this._X=[b[0],b[3]<<16|b[2]>>>16,b[1],b[0]<<16|b[3]>>>16,b[2],b[1]<<16|b[0]>>>16,b[3],b[2]<<16|b[1]>>>16],b=this._C=[b[2]<<16|b[2]>>>16,b[0]&4294901760|b[1]&65535,b[3]<<16|b[3]>>> -16,b[1]&4294901760|b[2]&65535,b[0]<<16|b[0]>>>16,b[2]&4294901760|b[3]&65535,b[1]<<16|b[1]>>>16,b[3]&4294901760|b[0]&65535],e=this._b=0;4>e;e++)a.call(this);for(e=0;8>e;e++)b[e]^=d[e+4&7];if(c){var d=c.words,c=d[0],d=d[1],c=(c<<8|c>>>24)&16711935|(c<<24|c>>>8)&4278255360,d=(d<<8|d>>>24)&16711935|(d<<24|d>>>8)&4278255360,e=c>>>16|d&4294901760,m=d<<16|c&65535;b[0]^=c;b[1]^=e;b[2]^=d;b[3]^=m;b[4]^=c;b[5]^=e;b[6]^=d;b[7]^=m;for(e=0;4>e;e++)a.call(this)}},_doProcessBlock:function(b,c){var d=this._X;a.call(this); -e[0]=d[0]^d[5]>>>16^d[3]<<16;e[1]=d[2]^d[7]>>>16^d[5]<<16;e[2]=d[4]^d[1]>>>16^d[7]<<16;e[3]=d[6]^d[3]>>>16^d[1]<<16;for(d=0;4>d;d++)e[d]=(e[d]<<8|e[d]>>>24)&16711935|(e[d]<<24|e[d]>>>8)&4278255360,b[c+d]^=e[d]},blockSize:4,ivSize:2});b.RabbitLegacy=c._createHelper(h)})();return b.RabbitLegacy})},{"./cipher-core":21,"./core":22,"./enc-base64":23,"./evpkdf":25,"./md5":30}],43:[function(h,p,k){(function(b,a,c){"object"===typeof k?p.exports=k=a(h("./core"),h("./enc-base64"),h("./md5"),h("./evpkdf"),h("./cipher-core")): -a(b.CryptoJS)})(this,function(b){(function(){function a(){for(var a=this._X,b=this._C,c=0;8>c;c++)d[c]=b[c];b[0]=b[0]+1295307597+this._b|0;b[1]=b[1]+3545052371+(b[0]>>>0>>0?1:0)|0;b[2]=b[2]+886263092+(b[1]>>>0>>0?1:0)|0;b[3]=b[3]+1295307597+(b[2]>>>0>>0?1:0)|0;b[4]=b[4]+3545052371+(b[3]>>>0>>0?1:0)|0;b[5]=b[5]+886263092+(b[4]>>>0>>0?1:0)|0;b[6]=b[6]+1295307597+(b[5]>>>0>>0?1:0)|0;b[7]=b[7]+3545052371+(b[6]>>>0>>0?1:0)|0;this._b=b[7]>>>0>>0?1:0;for(c= -0;8>c;c++){var e=a[c]+b[c],h=e&65535,q=e>>>16;m[c]=((h*h>>>17)+h*q>>>15)+q*q^((e&4294901760)*e|0)+((e&65535)*e|0)}a[0]=m[0]+(m[7]<<16|m[7]>>>16)+(m[6]<<16|m[6]>>>16)|0;a[1]=m[1]+(m[0]<<8|m[0]>>>24)+m[7]|0;a[2]=m[2]+(m[1]<<16|m[1]>>>16)+(m[0]<<16|m[0]>>>16)|0;a[3]=m[3]+(m[2]<<8|m[2]>>>24)+m[1]|0;a[4]=m[4]+(m[3]<<16|m[3]>>>16)+(m[2]<<16|m[2]>>>16)|0;a[5]=m[5]+(m[4]<<8|m[4]>>>24)+m[3]|0;a[6]=m[6]+(m[5]<<16|m[5]>>>16)+(m[4]<<16|m[4]>>>16)|0;a[7]=m[7]+(m[6]<<8|m[6]>>>24)+m[5]|0}var c=b.lib.StreamCipher, -e=[],d=[],m=[],h=b.algo.Rabbit=c.extend({_doReset:function(){for(var b=this._key.words,c=this.cfg.iv,d=0;4>d;d++)b[d]=(b[d]<<8|b[d]>>>24)&16711935|(b[d]<<24|b[d]>>>8)&4278255360;for(var e=this._X=[b[0],b[3]<<16|b[2]>>>16,b[1],b[0]<<16|b[3]>>>16,b[2],b[1]<<16|b[0]>>>16,b[3],b[2]<<16|b[1]>>>16],b=this._C=[b[2]<<16|b[2]>>>16,b[0]&4294901760|b[1]&65535,b[3]<<16|b[3]>>>16,b[1]&4294901760|b[2]&65535,b[0]<<16|b[0]>>>16,b[2]&4294901760|b[3]&65535,b[1]<<16|b[1]>>>16,b[3]&4294901760|b[0]&65535],d=this._b=0;4> -d;d++)a.call(this);for(d=0;8>d;d++)b[d]^=e[d+4&7];if(c){var d=c.words,c=d[0],d=d[1],c=(c<<8|c>>>24)&16711935|(c<<24|c>>>8)&4278255360,d=(d<<8|d>>>24)&16711935|(d<<24|d>>>8)&4278255360,e=c>>>16|d&4294901760,m=d<<16|c&65535;b[0]^=c;b[1]^=e;b[2]^=d;b[3]^=m;b[4]^=c;b[5]^=e;b[6]^=d;b[7]^=m;for(d=0;4>d;d++)a.call(this)}},_doProcessBlock:function(b,c){var d=this._X;a.call(this);e[0]=d[0]^d[5]>>>16^d[3]<<16;e[1]=d[2]^d[7]>>>16^d[5]<<16;e[2]=d[4]^d[1]>>>16^d[7]<<16;e[3]=d[6]^d[3]>>>16^d[1]<<16;for(d=0;4>d;d++)e[d]= -(e[d]<<8|e[d]>>>24)&16711935|(e[d]<<24|e[d]>>>8)&4278255360,b[c+d]^=e[d]},blockSize:4,ivSize:2});b.Rabbit=c._createHelper(h)})();return b.Rabbit})},{"./cipher-core":21,"./core":22,"./enc-base64":23,"./evpkdf":25,"./md5":30}],44:[function(h,p,k){(function(b,a,c){"object"===typeof k?p.exports=k=a(h("./core"),h("./enc-base64"),h("./md5"),h("./evpkdf"),h("./cipher-core")):a(b.CryptoJS)})(this,function(b){(function(){function a(){for(var a=this._S,b=this._i,c=this._j,d=0,e=0;4>e;e++){var b=(b+1)%256,c= -(c+a[b])%256,h=a[b];a[b]=a[c];a[c]=h;d|=a[(a[b]+a[c])%256]<<24-8*e}this._i=b;this._j=c;return d}var c=b.lib.StreamCipher,e=b.algo,d=e.RC4=c.extend({_doReset:function(){for(var a=this._key,b=a.words,a=a.sigBytes,c=this._S=[],d=0;256>d;d++)c[d]=d;for(var e=d=0;256>d;d++){var h=d%a,e=(e+c[d]+(b[h>>>2]>>>24-h%4*8&255))%256,h=c[d];c[d]=c[e];c[e]=h}this._i=this._j=0},_doProcessBlock:function(b,c){b[c]^=a.call(this)},keySize:8,ivSize:0});b.RC4=c._createHelper(d);e=e.RC4Drop=d.extend({cfg:d.cfg.extend({drop:192}), -_doReset:function(){d._doReset.call(this);for(var b=this.cfg.drop;0>>32-b}a=b.lib;var e=a.WordArray,d=a.Hasher;a=b.algo;var h=e.create([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,7,4,13,1,10,6,15,3,12, -0,9,5,2,14,11,8,3,10,14,4,9,15,8,1,2,7,0,6,13,11,5,12,1,9,11,10,0,8,12,4,13,3,7,15,14,5,6,2,4,0,5,9,7,12,2,10,14,1,3,8,11,6,15,13]),q=e.create([5,14,7,0,9,2,11,4,13,6,15,8,1,10,3,12,6,11,3,7,0,13,5,10,14,15,8,12,4,9,1,2,15,5,1,3,7,14,6,9,11,8,12,2,10,0,4,13,8,6,4,1,3,11,15,0,5,12,2,13,9,7,10,14,12,15,10,4,1,5,8,7,6,2,13,14,0,3,9,11]),g=e.create([11,14,15,12,5,8,7,9,11,13,14,15,6,7,9,8,7,6,8,13,11,9,7,15,7,12,15,9,11,7,13,12,11,13,6,7,14,9,13,15,14,8,13,6,5,12,7,5,11,12,14,15,14,15,9,8,9,14,5,6,8, -6,5,12,9,15,5,11,6,8,13,12,5,12,13,14,11,8,5,6]),n=e.create([8,9,9,11,13,15,15,5,7,7,8,11,14,14,12,6,9,13,15,7,12,8,9,11,7,7,12,7,6,15,13,11,9,7,15,11,8,6,6,14,12,13,5,14,13,13,7,5,15,5,8,11,14,14,6,14,6,9,12,9,12,5,15,8,8,5,12,9,12,5,14,6,8,13,6,5,15,13,11,11]),l=e.create([0,1518500249,1859775393,2400959708,2840853838]),z=e.create([1352829926,1548603684,1836072691,2053994217,0]);a=a.RIPEMD160=d.extend({_doReset:function(){this._hash=e.create([1732584193,4023233417,2562383102,271733878,3285377520])}, -_doProcessBlock:function(a,b){for(var d=0;16>d;d++){var e=b+d,k=a[e];a[e]=(k<<8|k>>>24)&16711935|(k<<24|k>>>8)&4278255360}var e=this._hash.words,k=l.words,f=z.words,p=h.words,J=q.words,A=g.words,L=n.words,C,B,E,I,Q,N,F,G,s,t;N=C=e[0];F=B=e[1];G=E=e[2];s=I=e[3];t=Q=e[4];for(var r,d=0;80>d;d+=1)r=C+a[b+p[d]]|0,r=16>d?r+((B^E^I)+k[0]):32>d?r+((B&E|~B&I)+k[1]):48>d?r+(((B|~E)^I)+k[2]):64>d?r+((B&I|E&~I)+k[3]):r+((B^(E|~I))+k[4]),r|=0,r=c(r,A[d]),r=r+Q|0,C=Q,Q=I,I=c(E,10),E=B,B=r,r=N+a[b+J[d]]|0,r=16> -d?r+((F^(G|~s))+f[0]):32>d?r+((F&s|G&~s)+f[1]):48>d?r+(((F|~G)^s)+f[2]):64>d?r+((F&G|~F&s)+f[3]):r+((F^G^s)+f[4]),r|=0,r=c(r,L[d]),r=r+t|0,N=t,t=s,s=c(G,10),G=F,F=r;r=e[1]+E+s|0;e[1]=e[2]+I+t|0;e[2]=e[3]+Q+N|0;e[3]=e[4]+C+F|0;e[4]=e[0]+B+G|0;e[0]=r},_doFinalize:function(){var a=this._data,b=a.words,c=8*this._nDataBytes,d=8*a.sigBytes;b[d>>>5]|=128<<24-d%32;b[(d+64>>>9<<4)+14]=(c<<8|c>>>24)&16711935|(c<<24|c>>>8)&4278255360;a.sigBytes=4*(b.length+1);this._process();a=this._hash;b=a.words;for(c=0;5> -c;c++)d=b[c],b[c]=(d<<8|d>>>24)&16711935|(d<<24|d>>>8)&4278255360;return a},clone:function(){var a=d.clone.call(this);a._hash=this._hash.clone();return a}});b.RIPEMD160=d._createHelper(a);b.HmacRIPEMD160=d._createHmacHelper(a)})(Math);return b.RIPEMD160})},{"./core":22}],46:[function(h,p,k){(function(b,a){"object"===typeof k?p.exports=k=a(h("./core")):a(b.CryptoJS)})(this,function(b){(function(){var a=b.lib,c=a.WordArray,e=a.Hasher,d=[],a=b.algo.SHA1=e.extend({_doReset:function(){this._hash=new c.init([1732584193, -4023233417,2562383102,271733878,3285377520])},_doProcessBlock:function(a,b){for(var c=this._hash.words,e=c[0],l=c[1],h=c[2],k=c[3],w=c[4],p=0;80>p;p++){if(16>p)d[p]=a[b+p]|0;else{var y=d[p-3]^d[p-8]^d[p-14]^d[p-16];d[p]=y<<1|y>>>31}y=(e<<5|e>>>27)+w+d[p];y=20>p?y+((l&h|~l&k)+1518500249):40>p?y+((l^h^k)+1859775393):60>p?y+((l&h|l&k|h&k)-1894007588):y+((l^h^k)-899497514);w=k;k=h;h=l<<30|l>>>2;l=e;e=y}c[0]=c[0]+e|0;c[1]=c[1]+l|0;c[2]=c[2]+h|0;c[3]=c[3]+k|0;c[4]=c[4]+w|0},_doFinalize:function(){var a= -this._data,b=a.words,c=8*this._nDataBytes,d=8*a.sigBytes;b[d>>>5]|=128<<24-d%32;b[(d+64>>>9<<4)+14]=Math.floor(c/4294967296);b[(d+64>>>9<<4)+15]=c;a.sigBytes=4*b.length;this._process();return this._hash},clone:function(){var a=e.clone.call(this);a._hash=this._hash.clone();return a}});b.SHA1=e._createHelper(a);b.HmacSHA1=e._createHmacHelper(a)})();return b.SHA1})},{"./core":22}],47:[function(h,p,k){(function(b,a,c){"object"===typeof k?p.exports=k=a(h("./core"),h("./sha256")):a(b.CryptoJS)})(this,function(b){(function(){var a= -b.lib.WordArray,c=b.algo,e=c.SHA256,c=c.SHA224=e.extend({_doReset:function(){this._hash=new a.init([3238371032,914150663,812702999,4144912697,4290775857,1750603025,1694076839,3204075428])},_doFinalize:function(){var a=e._doFinalize.call(this);a.sigBytes-=4;return a}});b.SHA224=e._createHelper(c);b.HmacSHA224=e._createHmacHelper(c)})();return b.SHA224})},{"./core":22,"./sha256":48}],48:[function(h,p,k){(function(b,a){"object"===typeof k?p.exports=k=a(h("./core")):a(b.CryptoJS)})(this,function(b){(function(a){var c= -b.lib,e=c.WordArray,d=c.Hasher,c=b.algo,h=[],q=[];(function(){function b(c){for(var d=a.sqrt(c),g=2;g<=d;g++)if(!(c%g))return!1;return!0}function c(a){return 4294967296*(a-(a|0))|0}for(var d=2,g=0;64>g;)b(d)&&(8>g&&(h[g]=c(a.pow(d,0.5))),q[g]=c(a.pow(d,1/3)),g++),d++})();var g=[],c=c.SHA256=d.extend({_doReset:function(){this._hash=new e.init(h.slice(0))},_doProcessBlock:function(a,b){for(var c=this._hash.words,d=c[0],e=c[1],h=c[2],m=c[3],k=c[4],f=c[5],p=c[6],J=c[7],A=0;64>A;A++){if(16>A)g[A]=a[b+ -A]|0;else{var L=g[A-15],C=g[A-2];g[A]=((L<<25|L>>>7)^(L<<14|L>>>18)^L>>>3)+g[A-7]+((C<<15|C>>>17)^(C<<13|C>>>19)^C>>>10)+g[A-16]}L=J+((k<<26|k>>>6)^(k<<21|k>>>11)^(k<<7|k>>>25))+(k&f^~k&p)+q[A]+g[A];C=((d<<30|d>>>2)^(d<<19|d>>>13)^(d<<10|d>>>22))+(d&e^d&h^e&h);J=p;p=f;f=k;k=m+L|0;m=h;h=e;e=d;d=L+C|0}c[0]=c[0]+d|0;c[1]=c[1]+e|0;c[2]=c[2]+h|0;c[3]=c[3]+m|0;c[4]=c[4]+k|0;c[5]=c[5]+f|0;c[6]=c[6]+p|0;c[7]=c[7]+J|0},_doFinalize:function(){var b=this._data,c=b.words,d=8*this._nDataBytes,g=8*b.sigBytes;c[g>>> -5]|=128<<24-g%32;c[(g+64>>>9<<4)+14]=a.floor(d/4294967296);c[(g+64>>>9<<4)+15]=d;b.sigBytes=4*c.length;this._process();return this._hash},clone:function(){var a=d.clone.call(this);a._hash=this._hash.clone();return a}});b.SHA256=d._createHelper(c);b.HmacSHA256=d._createHmacHelper(c)})(Math);return b.SHA256})},{"./core":22}],49:[function(h,p,k){(function(b,a,c){"object"===typeof k?p.exports=k=a(h("./core"),h("./x64-core")):a(b.CryptoJS)})(this,function(b){(function(a){var c=b.lib,e=c.WordArray,d=c.Hasher, -h=b.x64.Word,c=b.algo,q=[],g=[],n=[];(function(){for(var a=1,b=0,c=0;24>c;c++){q[a+5*b]=(c+1)*(c+2)/2%64;var d=(2*a+3*b)%5,a=b%5,b=d}for(a=0;5>a;a++)for(b=0;5>b;b++)g[a+5*b]=b+(2*a+3*b)%5*5;a=1;for(b=0;24>b;b++){for(var e=d=c=0;7>e;e++){if(a&1){var l=(1<l?d^=1<a;a++)l[a]=h.create()})();c=c.SHA3=d.extend({cfg:d.cfg.extend({outputLength:512}),_doReset:function(){for(var a=this._state=[],b=0;25>b;b++)a[b]= -new h.init;this.blockSize=(1600-2*this.cfg.outputLength)/32},_doProcessBlock:function(a,b){for(var c=this._state,d=this.blockSize/2,e=0;e>>24)&16711935|(h<<24|h>>>8)&4278255360,f=(f<<8|f>>>24)&16711935|(f<<24|f>>>8)&4278255360,m=c[e];m.high^=f;m.low^=h}for(d=0;24>d;d++){for(e=0;5>e;e++){for(var k=h=0,p=0;5>p;p++)m=c[e+5*p],h^=m.high,k^=m.low;m=l[e];m.high=h;m.low=k}for(e=0;5>e;e++)for(m=l[(e+4)%5],h=l[(e+1)%5],f=h.high,p=h.low,h=m.high^(f<<1|p>>>31),k= -m.low^(p<<1|f>>>31),p=0;5>p;p++)m=c[e+5*p],m.high^=h,m.low^=k;for(f=1;25>f;f++)m=c[f],e=m.high,m=m.low,p=q[f],32>p?(h=e<>>32-p,k=m<>>32-p):(h=m<>>64-p,k=e<>>64-p),m=l[g[f]],m.high=h,m.low=k;m=l[0];e=c[0];m.high=e.high;m.low=e.low;for(e=0;5>e;e++)for(p=0;5>p;p++)f=e+5*p,m=c[f],h=l[f],f=l[(e+1)%5+5*p],k=l[(e+2)%5+5*p],m.high=h.high^~f.high&k.high,m.low=h.low^~f.low&k.low;m=c[0];e=n[d];m.high^=e.high;m.low^=e.low}},_doFinalize:function(){var b=this._data,c=b.words,d=8*b.sigBytes, -g=32*this.blockSize;c[d>>>5]|=1<<24-d%32;c[(a.ceil((d+1)/g)*g>>>5)-1]|=128;b.sigBytes=4*c.length;this._process();for(var b=this._state,c=this.cfg.outputLength/8,d=c/8,g=[],l=0;l>>24)&16711935|(f<<24|f>>>8)&4278255360,h=(h<<8|h>>>24)&16711935|(h<<24|h>>>8)&4278255360;g.push(h);g.push(f)}return new e.init(g,c)},clone:function(){for(var a=d.clone.call(this),b=a._state=this._state.slice(0),c=0;25>c;c++)b[c]=b[c].clone();return a}});b.SHA3=d._createHelper(c); -b.HmacSHA3=d._createHmacHelper(c)})(Math);return b.SHA3})},{"./core":22,"./x64-core":53}],50:[function(h,p,k){(function(b,a,c){"object"===typeof k?p.exports=k=a(h("./core"),h("./x64-core"),h("./sha512")):a(b.CryptoJS)})(this,function(b){(function(){var a=b.x64,c=a.Word,e=a.WordArray,a=b.algo,d=a.SHA512,a=a.SHA384=d.extend({_doReset:function(){this._hash=new e.init([new c.init(3418070365,3238371032),new c.init(1654270250,914150663),new c.init(2438529370,812702999),new c.init(355462360,4144912697), -new c.init(1731405415,4290775857),new c.init(2394180231,1750603025),new c.init(3675008525,1694076839),new c.init(1203062813,3204075428)])},_doFinalize:function(){var a=d._doFinalize.call(this);a.sigBytes-=16;return a}});b.SHA384=d._createHelper(a);b.HmacSHA384=d._createHmacHelper(a)})();return b.SHA384})},{"./core":22,"./sha512":51,"./x64-core":53}],51:[function(h,p,k){(function(b,a,c){"object"===typeof k?p.exports=k=a(h("./core"),h("./x64-core")):a(b.CryptoJS)})(this,function(b){(function(){function a(){return d.create.apply(d, -arguments)}var c=b.lib.Hasher,e=b.x64,d=e.Word,h=e.WordArray,e=b.algo,q=[a(1116352408,3609767458),a(1899447441,602891725),a(3049323471,3964484399),a(3921009573,2173295548),a(961987163,4081628472),a(1508970993,3053834265),a(2453635748,2937671579),a(2870763221,3664609560),a(3624381080,2734883394),a(310598401,1164996542),a(607225278,1323610764),a(1426881987,3590304994),a(1925078388,4068182383),a(2162078206,991336113),a(2614888103,633803317),a(3248222580,3479774868),a(3835390401,2666613458),a(4022224774, -944711139),a(264347078,2341262773),a(604807628,2007800933),a(770255983,1495990901),a(1249150122,1856431235),a(1555081692,3175218132),a(1996064986,2198950837),a(2554220882,3999719339),a(2821834349,766784016),a(2952996808,2566594879),a(3210313671,3203337956),a(3336571891,1034457026),a(3584528711,2466948901),a(113926993,3758326383),a(338241895,168717936),a(666307205,1188179964),a(773529912,1546045734),a(1294757372,1522805485),a(1396182291,2643833823),a(1695183700,2343527390),a(1986661051,1014477480), -a(2177026350,1206759142),a(2456956037,344077627),a(2730485921,1290863460),a(2820302411,3158454273),a(3259730800,3505952657),a(3345764771,106217008),a(3516065817,3606008344),a(3600352804,1432725776),a(4094571909,1467031594),a(275423344,851169720),a(430227734,3100823752),a(506948616,1363258195),a(659060556,3750685593),a(883997877,3785050280),a(958139571,3318307427),a(1322822218,3812723403),a(1537002063,2003034995),a(1747873779,3602036899),a(1955562222,1575990012),a(2024104815,1125592928),a(2227730452, -2716904306),a(2361852424,442776044),a(2428436474,593698344),a(2756734187,3733110249),a(3204031479,2999351573),a(3329325298,3815920427),a(3391569614,3928383900),a(3515267271,566280711),a(3940187606,3454069534),a(4118630271,4000239992),a(116418474,1914138554),a(174292421,2731055270),a(289380356,3203993006),a(460393269,320620315),a(685471733,587496836),a(852142971,1086792851),a(1017036298,365543100),a(1126000580,2618297676),a(1288033470,3409855158),a(1501505948,4234509866),a(1607167915,987167468),a(1816402316, -1246189591)],g=[];(function(){for(var b=0;80>b;b++)g[b]=a()})();e=e.SHA512=c.extend({_doReset:function(){this._hash=new h.init([new d.init(1779033703,4089235720),new d.init(3144134277,2227873595),new d.init(1013904242,4271175723),new d.init(2773480762,1595750129),new d.init(1359893119,2917565137),new d.init(2600822924,725511199),new d.init(528734635,4215389547),new d.init(1541459225,327033209)])},_doProcessBlock:function(a,b){for(var c=this._hash.words,d=c[0],e=c[1],h=c[2],m=c[3],k=c[4],f=c[5],p= -c[6],c=c[7],J=d.high,A=d.low,L=e.high,C=e.low,B=h.high,E=h.low,I=m.high,Q=m.low,N=k.high,F=k.low,G=f.high,s=f.low,t=p.high,r=p.low,u=c.high,V=c.low,S=J,M=A,R=L,O=C,aa=B,ea=E,ma=I,fa=Q,X=N,T=F,ja=G,ia=s,ka=t,ga=r,ha=u,da=V,U=0;80>U;U++){var $=g[U];if(16>U)var W=$.high=a[b+2*U]|0,D=$.low=a[b+2*U+1]|0;else{var W=g[U-15],D=W.high,Y=W.low,W=(D>>>1|Y<<31)^(D>>>8|Y<<24)^D>>>7,Y=(Y>>>1|D<<31)^(Y>>>8|D<<24)^(Y>>>7|D<<25),ca=g[U-2],D=ca.high,K=ca.low,ca=(D>>>19|K<<13)^(D<<3|K>>>29)^D>>>6,K=(K>>>19|D<<13)^(K<< -3|D>>>29)^(K>>>6|D<<26),D=g[U-7],na=D.high,ba=g[U-16],Z=ba.high,ba=ba.low,D=Y+D.low,W=W+na+(D>>>0>>0?1:0),D=D+K,W=W+ca+(D>>>0>>0?1:0),D=D+ba,W=W+Z+(D>>>0>>0?1:0);$.high=W;$.low=D}var na=X&ja^~X&ka,ba=T&ia^~T&ga,$=S&R^S&aa^R&aa,pa=M&O^M&ea^O&ea,Y=(S>>>28|M<<4)^(S<<30|M>>>2)^(S<<25|M>>>7),ca=(M>>>28|S<<4)^(M<<30|S>>>2)^(M<<25|S>>>7),K=q[U],qa=K.high,oa=K.low,K=da+((T>>>14|X<<18)^(T>>>18|X<<14)^(T<<23|X>>>9)),Z=ha+((X>>>14|T<<18)^(X>>>18|T<<14)^(X<<23|T>>>9))+(K>>>0>>0?1:0),K=K+ba,Z=Z+ -na+(K>>>0>>0?1:0),K=K+oa,Z=Z+qa+(K>>>0>>0?1:0),K=K+D,Z=Z+W+(K>>>0>>0?1:0),D=ca+pa,$=Y+$+(D>>>0>>0?1:0),ha=ka,da=ga,ka=ja,ga=ia,ja=X,ia=T,T=fa+K|0,X=ma+Z+(T>>>0>>0?1:0)|0,ma=aa,fa=ea,aa=R,ea=O,R=S,O=M,M=K+D|0,S=Z+$+(M>>>0>>0?1:0)|0}A=d.low=A+M;d.high=J+S+(A>>>0>>0?1:0);C=e.low=C+O;e.high=L+R+(C>>>0>>0?1:0);E=h.low=E+ea;h.high=B+aa+(E>>>0>>0?1:0);Q=m.low=Q+fa;m.high=I+ma+(Q>>>0>>0?1:0);F=k.low=F+T;k.high=N+X+(F>>>0>>0?1:0);s=f.low=s+ia;f.high=G+ja+(s>>>0>> -0?1:0);r=p.low=r+ga;p.high=t+ka+(r>>>0>>0?1:0);V=c.low=V+da;c.high=u+ha+(V>>>0>>0?1:0)},_doFinalize:function(){var a=this._data,b=a.words,c=8*this._nDataBytes,d=8*a.sigBytes;b[d>>>5]|=128<<24-d%32;b[(d+128>>>10<<5)+30]=Math.floor(c/4294967296);b[(d+128>>>10<<5)+31]=c;a.sigBytes=4*b.length;this._process();return this._hash.toX32()},clone:function(){var a=c.clone.call(this);a._hash=this._hash.clone();return a},blockSize:32});b.SHA512=c._createHelper(e);b.HmacSHA512=c._createHmacHelper(e)})(); -return b.SHA512})},{"./core":22,"./x64-core":53}],52:[function(h,p,k){(function(b,a,c){"object"===typeof k?p.exports=k=a(h("./core"),h("./enc-base64"),h("./md5"),h("./evpkdf"),h("./cipher-core")):a(b.CryptoJS)})(this,function(b){(function(){function a(a,b){var c=(this._lBlock>>>a^this._rBlock)&b;this._rBlock^=c;this._lBlock^=c<>>a^this._lBlock)&b;this._lBlock^=c;this._rBlock^=c<c;c++){var d=k[c]-1;b[c]=a[d>>>5]>>> -31-d%32&1}a=this._subKeys=[];for(d=0;16>d;d++){for(var f=a[d]=[],e=n[d],c=0;24>c;c++)f[c/6|0]|=b[(g[c]-1+e)%28]<<31-c%6,f[4+(c/6|0)]|=b[28+(g[c+24]-1+e)%28]<<31-c%6;f[0]=f[0]<<1|f[0]>>>31;for(c=1;7>c;c++)f[c]>>>=4*(c-1)+3;f[7]=f[7]<<5|f[7]>>>27}b=this._invSubKeys=[];for(c=0;16>c;c++)b[c]=a[15-c]},encryptBlock:function(a,b){this._doCryptBlock(a,b,this._subKeys)},decryptBlock:function(a,b){this._doCryptBlock(a,b,this._invSubKeys)},_doCryptBlock:function(b,d,e){this._lBlock=b[d];this._rBlock=b[d+1]; -a.call(this,4,252645135);a.call(this,16,65535);c.call(this,2,858993459);c.call(this,8,16711935);a.call(this,1,1431655765);for(var g=0;16>g;g++){for(var f=e[g],h=this._lBlock,m=this._rBlock,n=0,k=0;8>k;k++)n|=l[k][((m^f[k])&p[k])>>>0];this._lBlock=m;this._rBlock=h^n}e=this._lBlock;this._lBlock=this._rBlock;this._rBlock=e;a.call(this,1,1431655765);c.call(this,8,16711935);c.call(this,2,858993459);a.call(this,16,65535);a.call(this,4,252645135);b[d]=this._lBlock;b[d+1]=this._rBlock},keySize:2,ivSize:2, -blockSize:2});b.DES=e._createHelper(P);h=h.TripleDES=e.extend({_doReset:function(){var a=this._key.words;this._des1=P.createEncryptor(d.create(a.slice(0,2)));this._des2=P.createEncryptor(d.create(a.slice(2,4)));this._des3=P.createEncryptor(d.create(a.slice(4,6)))},encryptBlock:function(a,b){this._des1.encryptBlock(a,b);this._des2.decryptBlock(a,b);this._des3.encryptBlock(a,b)},decryptBlock:function(a,b){this._des3.decryptBlock(a,b);this._des2.encryptBlock(a,b);this._des1.decryptBlock(a,b)},keySize:6, -ivSize:2,blockSize:2});b.TripleDES=e._createHelper(h)})();return b.TripleDES})},{"./cipher-core":21,"./core":22,"./enc-base64":23,"./evpkdf":25,"./md5":30}],53:[function(h,p,k){(function(b,a){"object"===typeof k?p.exports=k=a(h("./core")):a(b.CryptoJS)})(this,function(b){(function(a){var c=b.lib,e=c.Base,d=c.WordArray,c=b.x64={};c.Word=e.extend({init:function(a,b){this.high=a;this.low=b}});c.WordArray=e.extend({init:function(b,c){b=this.words=b||[];this.sigBytes=c!=a?c:8*b.length},toX32:function(){for(var a= -this.words,b=a.length,c=[],e=0;e>>0,h=Array(f),l,m,p;for(l=0;larguments.length?e:3= 2.8.0",ws:">= 0.4.31","crypto-js":">= 3.1.2-2"},devDependencies:{browserify:">= 3.28.1",nodeunit:">= 0.8.6"},repository:{type:"git",url:"git://github.com/tavendo/AutobahnJS.git"},keywords:["WAMP","WebSocket","RPC","PubSub"],author:"Tavendo GmbH",license:"MIT"}},{}]},{},[4])(4)}); diff --git a/examples/twisted/wamp/authentication/ticket/index.html b/examples/twisted/wamp/authentication/ticket/index.html deleted file mode 100644 index 3482f4f8..00000000 --- a/examples/twisted/wamp/authentication/ticket/index.html +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - -

WAMP Ticket-based Authentication

-

Open JavaScript console to watch output.

- - - - - - diff --git a/examples/twisted/wamp/authentication/ticket/server.py b/examples/twisted/wamp/authentication/ticket/server.py deleted file mode 100644 index 5092cc9d..00000000 --- a/examples/twisted/wamp/authentication/ticket/server.py +++ /dev/null @@ -1,229 +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 json -import datetime - -from twisted.python import log -from twisted.internet import defer - - -from autobahn import util -from autobahn.wamp import types -from autobahn.twisted.wamp import ApplicationSession, RouterSession -from autobahn.twisted.websocket import WampWebSocketServerProtocol, WampWebSocketServerFactory - - -class UserDb: - - """ - A fake user database. - """ - - def __init__(self): - self._tickets = {} - - def add(self, authid, authrole, ticket): - self._tickets[authid] = (ticket, authrole) - return self._tickets[authid] - - def get(self, authid): - # we return a deferred to simulate an asynchronous lookup - return defer.succeed(self._tickets.get(authid, (None, None))) - - -class PendingAuth: - - """ - User for tracking pending authentications. - """ - - def __init__(self, ticket, authid, authrole, authmethod, authprovider): - self.signature = ticket - self.authid = authid - self.authrole = authrole - self.authmethod = authmethod - self.authprovider = authprovider - - -class MyRouterSession(RouterSession): - - """ - Our custom router session that authenticates via WAMP-CRA. - """ - - @defer.inlineCallbacks - def onHello(self, realm, details): - """ - Callback fired when client wants to attach session. - """ - print("onHello: {} {}".format(realm, details)) - - self._pending_auth = None - - if details.authmethods: - for authmethod in details.authmethods: - if authmethod == u"ticket": - - # lookup user in user DB - ticket, authrole = yield self.factory.userdb.get(details.authid) - - # if user found .. - if ticket: - - # setup pending auth - self._pending_auth = PendingAuth(ticket, - details.authid, authrole, authmethod, "userdb") - - defer.returnValue(types.Challenge('ticket')) - - # deny client - defer.returnValue(types.Deny()) - - def onAuthenticate(self, signature, extra): - """ - Callback fired when a client responds to an authentication challenge. - """ - print("onAuthenticate: {} {}".format(signature, extra)) - - # if there is a pending auth, and the signature provided by client matches .. - if self._pending_auth and signature == self._pending_auth.signature: - - # accept the client - return types.Accept(authid=self._pending_auth.authid, - authrole=self._pending_auth.authrole, - authmethod=self._pending_auth.authmethod, - authprovider=self._pending_auth.authprovider) - - # deny client - return types.Deny() - - -class TimeService(ApplicationSession): - - """ - A simple time service application component. - """ - - def onJoin(self, details): - print("session attached") - - def utcnow(): - now = datetime.datetime.utcnow() - return now.strftime("%Y-%m-%dT%H:%M:%SZ") - - self.register(utcnow, 'com.timeservice.now') - - -if __name__ == '__main__': - - import sys - import argparse - - from twisted.python import log - from twisted.internet.endpoints import serverFromString - - # 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-WebSocket server with this application component, e.g. 'timeservice.TimeServiceBackend', or None.") - - parser.add_argument("--websocket", type=str, default="tcp:8080", - help='WebSocket server Twisted endpoint descriptor, e.g. "tcp:9000" or "unix:/tmp/mywebsocket".') - - parser.add_argument("--wsurl", type=str, default="ws://localhost:8080", - help='WebSocket URL (must suit the endpoint), e.g. "ws://localhost:9000".') - - args = parser.parse_args() - - log.startLogging(sys.stdout) - - # we use an Autobahn utility to install the "best" available Twisted reactor - ## - from autobahn.twisted.choosereactor import install_reactor - reactor = install_reactor() - if args.debug: - print("Running on reactor {}".format(reactor)) - - # create a WAMP router factory - ## - from autobahn.twisted.wamp import RouterFactory - router_factory = RouterFactory() - - # create a user DB - ## - userdb = UserDb() - userdb.add(authid="peter", authrole="user", ticket="magic_secret_1") - userdb.add(authid="joe", authrole="user", ticket="magic_secret_2") - - # create a WAMP router session factory - ## - from autobahn.twisted.wamp import RouterSessionFactory - session_factory = RouterSessionFactory(router_factory) - session_factory.session = MyRouterSession - session_factory.userdb = userdb - - # start an embedded application component .. - ## - component_config = types.ComponentConfig(realm="realm1") - component_session = TimeService(component_config) - session_factory.add(component_session) - - # create a WAMP-over-WebSocket transport server factory - ## - from autobahn.twisted.websocket import WampWebSocketServerFactory - transport_factory = WampWebSocketServerFactory(session_factory, args.wsurl, debug=False, debug_wamp=args.debug) - transport_factory.setProtocolOptions(failByDrop=False) - - from twisted.web.server import Site - from twisted.web.static import File - from autobahn.twisted.resource import WebSocketResource - - # we serve static files under "/" .. - root = File(".") - - # .. and our WebSocket server under "/ws" - resource = WebSocketResource(transport_factory) - root.putChild("ws", resource) - - # run both under one Twisted Web Site - site = Site(root) - site.noisy = False - site.log = lambda _: None - - # start the WebSocket server from an endpoint - ## - server = serverFromString(reactor, args.websocket) - server.listen(site) - - # now enter the Twisted reactor loop - ## - reactor.run() diff --git a/examples/twisted/wamp/authentication/wampcra/Makefile b/examples/twisted/wamp/authentication/wampcra/Makefile deleted file mode 100644 index f3ebba0d..00000000 --- a/examples/twisted/wamp/authentication/wampcra/Makefile +++ /dev/null @@ -1,5 +0,0 @@ -server: - PYTHONPATH="../../../../../autobahn" python server.py -d - -client: - PYTHONPATH="../../../../../autobahn" python client.py diff --git a/examples/twisted/wamp/authentication/wampcra/README.md b/examples/twisted/wamp/authentication/wampcra/README.md deleted file mode 100644 index bf40e3c6..00000000 --- a/examples/twisted/wamp/authentication/wampcra/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# WAMP Challenge-Response Authentication - -Run this demo by starting the server - - python server.py - -and opening `http://127.0.0.1:8080` in your browser. Open the JavaScript console to watch output. diff --git a/examples/twisted/wamp/authentication/wampcra/client.py b/examples/twisted/wamp/authentication/wampcra/client.py deleted file mode 100644 index 5b2beaf7..00000000 --- a/examples/twisted/wamp/authentication/wampcra/client.py +++ /dev/null @@ -1,109 +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 - -from twisted.python import log -from twisted.internet import reactor -from twisted.internet.defer import inlineCallbacks -from twisted.internet.endpoints import clientFromString - -from autobahn.twisted import wamp, websocket -from autobahn.wamp import types -from autobahn.wamp import auth - - -PASSWORDS = { - u'peter': u'secret1', - u'joe': u'secret2' -} - -USER = u'peter' -# USER = u'joe' - - -class MyFrontendComponent(wamp.ApplicationSession): - - def onConnect(self): - self.join(self.config.realm, [u"wampcra"], USER) - - def onChallenge(self, challenge): - print challenge - if challenge.method == u"wampcra": - if u'salt' in challenge.extra: - key = auth.derive_key(PASSWORDS[USER].encode('utf8'), - challenge.extra['salt'].encode('utf8'), - challenge.extra.get('iterations', None), - challenge.extra.get('keylen', None)) - else: - key = PASSWORDS[USER].encode('utf8') - signature = auth.compute_wcs(key, challenge.extra['challenge'].encode('utf8')) - return signature.decode('ascii') - else: - raise Exception("don't know how to compute challenge for authmethod {}".format(challenge.method)) - - @inlineCallbacks - def onJoin(self, details): - - # call a remote procedure - ## - try: - now = yield self.call(u'com.timeservice.now') - except Exception as e: - print("Error: {}".format(e)) - else: - print("Current time from time service: {}".format(now)) - - self.leave() - - def onLeave(self, details): - print("onLeave: {}".format(details)) - self.disconnect() - - def onDisconnect(self): - reactor.stop() - - -if __name__ == '__main__': - - # 0) start logging to console - log.startLogging(sys.stdout) - - # 1) create a WAMP application session factory - component_config = types.ComponentConfig(realm="realm1") - session_factory = wamp.ApplicationSessionFactory(config=component_config) - session_factory.session = MyFrontendComponent - - # 2) create a WAMP-over-WebSocket transport client factory - transport_factory = websocket.WampWebSocketClientFactory(session_factory, - url="ws://127.0.0.1:8080/ws", debug=False, debug_wamp=False) - - # 3) start the client from a Twisted endpoint - client = clientFromString(reactor, "tcp:127.0.0.1:8080") - client.connect(transport_factory) - - # 4) now enter the Twisted reactor loop - reactor.run() diff --git a/examples/twisted/wamp/authentication/wampcra/index.html b/examples/twisted/wamp/authentication/wampcra/index.html deleted file mode 100644 index 41cabfef..00000000 --- a/examples/twisted/wamp/authentication/wampcra/index.html +++ /dev/null @@ -1,70 +0,0 @@ - - - - - - -

WAMP Challenge-Response Authentication

-

Open JavaScript console to watch output.

- - - - - - diff --git a/examples/twisted/wamp/authentication/wampcra/server.py b/examples/twisted/wamp/authentication/wampcra/server.py deleted file mode 100644 index d6f68efe..00000000 --- a/examples/twisted/wamp/authentication/wampcra/server.py +++ /dev/null @@ -1,268 +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 json -import datetime - -from twisted.python import log -from twisted.internet import defer - - -from autobahn import util -from autobahn.wamp import auth -from autobahn.wamp import types -from autobahn.twisted.wamp import ApplicationSession, RouterSession -from autobahn.twisted.websocket import WampWebSocketServerProtocol, WampWebSocketServerFactory - - -class UserDb: - - """ - A fake user database. - """ - - def __init__(self): - self._creds = {} - - def add(self, authid, authrole, secret, salt=None): - if salt: - key = auth.derive_key(secret.encode('utf8'), salt.encode('utf8')).decode('ascii') - else: - key = secret - self._creds[authid] = (salt, key, authrole) - return self._creds[authid] - - def get(self, authid): - # we return a deferred to simulate an asynchronous lookup - return defer.succeed(self._creds.get(authid, (None, None, None))) - - -class PendingAuth: - - """ - User for tracking pending authentications. - """ - - def __init__(self, key, session, authid, authrole, authmethod, authprovider): - self.authid = authid - self.authrole = authrole - self.authmethod = authmethod - self.authprovider = authprovider - - self.session = session - self.timestamp = util.utcnow() - self.nonce = util.newid() - - challenge_obj = { - 'authid': self.authid, - 'authrole': self.authrole, - 'authmethod': self.authmethod, - 'authprovider': self.authprovider, - 'session': self.session, - 'nonce': self.nonce, - 'timestamp': self.timestamp - } - self.challenge = json.dumps(challenge_obj, ensure_ascii=False) - self.signature = auth.compute_wcs(key.encode('utf8'), self.challenge.encode('utf8')).decode('ascii') - - -class MyRouterSession(RouterSession): - - """ - Our custom router session that authenticates via WAMP-CRA. - """ - - @defer.inlineCallbacks - def onHello(self, realm, details): - """ - Callback fired when client wants to attach session. - """ - print("onHello: {} {}".format(realm, details)) - - self._pending_auth = None - - if details.authmethods: - for authmethod in details.authmethods: - if authmethod == u"wampcra": - - # lookup user in user DB - salt, key, role = yield self.factory.userdb.get(details.authid) - - # if user found .. - if key: - - # setup pending auth - self._pending_auth = PendingAuth(key, details.pending_session, - details.authid, role, authmethod, u"userdb") - - # send challenge to client - extra = { - u'challenge': self._pending_auth.challenge - } - - # when using salted passwords, provide the client with - # the salt and then PBKDF2 parameters used - if salt: - extra[u'salt'] = salt - extra[u'iterations'] = 1000 - extra[u'keylen'] = 32 - - defer.returnValue(types.Challenge(u'wampcra', extra)) - - # deny client - defer.returnValue(types.Deny()) - - def onAuthenticate(self, signature, extra): - """ - Callback fired when a client responds to an authentication challenge. - """ - print("onAuthenticate: {} {}".format(signature, extra)) - - # if there is a pending auth, and the signature provided by client matches .. - if self._pending_auth: - - if signature == self._pending_auth.signature: - - # accept the client - return types.Accept(authid=self._pending_auth.authid, - authrole=self._pending_auth.authrole, - authmethod=self._pending_auth.authmethod, - authprovider=self._pending_auth.authprovider) - else: - - # deny client - return types.Deny(message=u"signature is invalid") - else: - - # deny client - return types.Deny(message=u"no pending authentication") - - -class TimeService(ApplicationSession): - - """ - A simple time service application component. - """ - - def onJoin(self, details): - print("session attached") - - def utcnow(): - now = datetime.datetime.utcnow() - return now.strftime(u"%Y-%m-%dT%H:%M:%SZ") - - self.register(utcnow, u'com.timeservice.now') - - -if __name__ == '__main__': - - import sys - import argparse - - from twisted.python import log - from twisted.internet.endpoints import serverFromString - - # 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-WebSocket server with this application component, e.g. 'timeservice.TimeServiceBackend', or None.") - - parser.add_argument("--websocket", type=str, default="tcp:8080", - help='WebSocket server Twisted endpoint descriptor, e.g. "tcp:9000" or "unix:/tmp/mywebsocket".') - - parser.add_argument("--wsurl", type=str, default="ws://localhost:8080", - help='WebSocket URL (must suit the endpoint), e.g. "ws://localhost:9000".') - - args = parser.parse_args() - - log.startLogging(sys.stdout) - - # we use an Autobahn utility to install the "best" available Twisted reactor - ## - from autobahn.twisted.choosereactor import install_reactor - reactor = install_reactor() - if args.debug: - print("Running on reactor {}".format(reactor)) - - # create a WAMP router factory - ## - from autobahn.twisted.wamp import RouterFactory - router_factory = RouterFactory() - - # create a user DB - ## - userdb = UserDb() - userdb.add(authid=u"peter", authrole=u"user", secret=u"secret1", salt=u"salt123") - userdb.add(authid=u"joe", authrole=u"user", secret=u"secret2") - - # create a WAMP router session factory - ## - from autobahn.twisted.wamp import RouterSessionFactory - session_factory = RouterSessionFactory(router_factory) - session_factory.session = MyRouterSession - session_factory.userdb = userdb - - # start an embedded application component .. - ## - component_config = types.ComponentConfig(realm=u"realm1") - component_session = TimeService(component_config) - session_factory.add(component_session) - - # create a WAMP-over-WebSocket transport server factory - ## - from autobahn.twisted.websocket import WampWebSocketServerFactory - transport_factory = WampWebSocketServerFactory(session_factory, args.wsurl, debug=False, debug_wamp=args.debug) - transport_factory.setProtocolOptions(failByDrop=False) - - from twisted.web.server import Site - from twisted.web.static import File - from autobahn.twisted.resource import WebSocketResource - - # we serve static files under "/" .. - root = File(".") - - # .. and our WebSocket server under "/ws" - resource = WebSocketResource(transport_factory) - root.putChild("ws", resource) - - # run both under one Twisted Web Site - site = Site(root) - site.noisy = False - site.log = lambda _: None - - # start the WebSocket server from an endpoint - ## - server = serverFromString(reactor, args.websocket) - server.listen(site) - - # now enter the Twisted reactor loop - ## - reactor.run() diff --git a/examples/twisted/wamp/authorization/README.md b/examples/twisted/wamp/authorization/README.md deleted file mode 100644 index 031aeb65..00000000 --- a/examples/twisted/wamp/authorization/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# Custom Authorization - -This example demonstrates how to authorize WAMP clients at a router to perform actions, like "is this client allow to publish to topic X?". - -## Running the Example - -To run this example, start the router - -```sh -python router.py -``` - -and then start the client with an application component - -```sh -python client.py -``` diff --git a/examples/twisted/wamp/authorization/client.py b/examples/twisted/wamp/authorization/client.py deleted file mode 100644 index c4b8c7ab..00000000 --- a/examples/twisted/wamp/authorization/client.py +++ /dev/null @@ -1,57 +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. -# -############################################################################### - -from twisted.internet.defer import inlineCallbacks - -from autobahn.wamp.types import PublishOptions -from autobahn.twisted.util import sleep -from autobahn.twisted.wamp import ApplicationSession - - -class Component(ApplicationSession): - - @inlineCallbacks - def onJoin(self, details): - print("session attached") - - counter = 0 - topics = ['com.myapp.topic1', 'com.foobar.topic2'] - while True: - for topic in topics: - try: - yield self.publish(topic, counter, options=PublishOptions(acknowledge=True)) - print("Event published to {}".format(topic)) - except Exception as e: - print("Publication to {} failed: {}".format(topic, e)) - - counter += 1 - yield sleep(1) - - -if __name__ == '__main__': - from autobahn.twisted.wamp import ApplicationRunner - runner = ApplicationRunner("ws://127.0.0.1:8080/ws", "realm1") - runner.run(Component) diff --git a/examples/twisted/wamp/authorization/router.py b/examples/twisted/wamp/authorization/router.py deleted file mode 100644 index de8e9eac..00000000 --- a/examples/twisted/wamp/authorization/router.py +++ /dev/null @@ -1,96 +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. -# -############################################################################### - -from autobahn.wamp.exception import ApplicationError -from autobahn.wamp.interfaces import IRouter -from autobahn.twisted.wamp import Router - - -class MyRouter(Router): - - def authorize(self, session, uri, action): - print("MyRouter.authorize: {} {} {}".format(session, uri, action)) - - # we only allow publish to topics 'com.myapp.*' - ## - if uri.startswith('com.myapp.') and action == IRouter.ACTION_PUBLISH: - return True - else: - raise ApplicationError(ApplicationError.NOT_AUTHORIZED, "only publish to com.myapp.* allowed") - - -if __name__ == '__main__': - - import sys - import argparse - - from twisted.python import log - from twisted.internet.endpoints import serverFromString - - # parse command line arguments - ## - parser = argparse.ArgumentParser() - - parser.add_argument("-d", "--debug", action="store_true", - help="Enable debug output.") - - parser.add_argument("--endpoint", type=str, default="tcp:8080", - help='Twisted server endpoint descriptor, e.g. "tcp:8080" or "unix:/tmp/mywebsocket".') - - args = parser.parse_args() - log.startLogging(sys.stdout) - - # we use an Autobahn utility to install the "best" available Twisted reactor - ## - from autobahn.twisted.choosereactor import install_reactor - reactor = install_reactor() - print("Running on reactor {}".format(reactor)) - - # create a WAMP router factory - ## - from autobahn.twisted.wamp import RouterFactory - router_factory = RouterFactory() - router_factory.router = MyRouter - - # create a WAMP router session factory - ## - from autobahn.twisted.wamp import RouterSessionFactory - session_factory = RouterSessionFactory(router_factory) - - # create a WAMP-over-WebSocket transport server factory - ## - from autobahn.twisted.websocket import WampWebSocketServerFactory - transport_factory = WampWebSocketServerFactory(session_factory, debug=args.debug) - transport_factory.setProtocolOptions(failByDrop=False) - - # start the server from an endpoint - ## - server = serverFromString(reactor, args.endpoint) - server.listen(transport_factory) - - # now enter the Twisted reactor loop - ## - reactor.run() diff --git a/examples/twisted/wamp/basic/.gitignore b/examples/twisted/wamp/basic/.gitignore deleted file mode 100644 index eb03e3e1..00000000 --- a/examples/twisted/wamp/basic/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -node_modules -*.log diff --git a/examples/twisted/wamp/basic/README.md b/examples/twisted/wamp/basic/README.md deleted file mode 100644 index ef4bd7e3..00000000 --- a/examples/twisted/wamp/basic/README.md +++ /dev/null @@ -1,129 +0,0 @@ -# WAMP Programming Examples - -## Examples - -1. RPC - * [Arguments](rpc/arguments) - * [Complex](rpc/complex) - * [Decorators](rpc/decorators) - * [Errors](rpc/errors) - * [Options](rpc/options) - * [Progress](rpc/progress) - * [Slow Square](rpc/slowsquare) - * [Time Service](rpc/timeservice) -2. PubSub - * [Basic](pubsub/basic) - * [Complex](pubsub/complex) - * [Decorators](pubsub/decorators) - * [Options](pubsub/options) - * [Unsubscribe](pubsub/unsubscribe) - - -## How to run - -To run the following examples, you need a WAMP router. - -For example, you can use the included basic WAMP router by doing - -```shell -python basicrouter.py -``` - -Or you can use [Crossbar.io](http://crossbar.io): - -```shell -mkdir mynode -cd mynode -crossbar init -crossbar start -``` - -The examples usually contain two components: - - * frontend - * backend - -Each component is provided in two languages: - - * Python - * JavaScript - -The JavaScript version can run on the browser or in NodeJS. - -To run an example, you can have three terminal sessions open with: - - 1. router - 2. frontend - 3. backend - -E.g. the Python examples can be run - -```shell -cd pubsub/basic -python backend.py -``` - - -## Hosting - -Crossbar.io is a WAMP router that can also act as a host for WAMP application components. E.g. to let Crossbar.io host a backend application component, you can use a node configuration like this: - -```javascript - -{ - "controller": { - }, - "workers": [ - { - "type": "router", - "options": { - "pythonpath": ["f:\\scm\\tavendo\\autobahn\\AutobahnPython\\examples\\twisted\\wamp\\basic"] - }, - "realms": [ - { - "name": "realm1", - "roles": [ - { - "name": "anonymous", - "permissions": [ - { - "uri": "*", - "publish": true, - "subscribe": true, - "call": true, - "register": true - } - ] - } - ] - } - ], - "components": [ - { - "type": "class", - "classname": "pubsub.complex.backend.Component", - "realm": "realm1" - } - ], - "transports": [ - { - "type": "web", - "endpoint": { - "type": "tcp", - "port": 8080 - }, - "paths": { - "/": { - "type": "static", - "directory": ".." - }, - "ws": { - "type": "websocket" - } - } - } - ] - } - ] -} -``` \ No newline at end of file diff --git a/examples/twisted/wamp/basic/basicrouter.py b/examples/twisted/wamp/basic/basicrouter.py deleted file mode 100644 index 20682544..00000000 --- a/examples/twisted/wamp/basic/basicrouter.py +++ /dev/null @@ -1,77 +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 - - from twisted.python import log - from twisted.internet.endpoints import serverFromString - - # parse command line arguments - ## - parser = argparse.ArgumentParser() - - parser.add_argument("-d", "--debug", action="store_true", - help="Enable debug output.") - - parser.add_argument("--endpoint", type=str, default="tcp:8080", - help='Twisted server endpoint descriptor, e.g. "tcp:8080" or "unix:/tmp/mywebsocket".') - - args = parser.parse_args() - log.startLogging(sys.stdout) - - # we use an Autobahn utility to install the "best" available Twisted reactor - ## - from autobahn.twisted.choosereactor import install_reactor - reactor = install_reactor() - print("Running on reactor {}".format(reactor)) - - # create a WAMP router factory - ## - from autobahn.twisted.wamp import RouterFactory - router_factory = RouterFactory() - - # create a WAMP router session factory - ## - from autobahn.twisted.wamp import RouterSessionFactory - session_factory = RouterSessionFactory(router_factory) - - # create a WAMP-over-WebSocket transport server factory - ## - from autobahn.twisted.websocket import WampWebSocketServerFactory - transport_factory = WampWebSocketServerFactory(session_factory, debug=args.debug) - transport_factory.setProtocolOptions(failByDrop=False) - - # start the server from an endpoint - ## - server = serverFromString(reactor, args.endpoint) - server.listen(transport_factory) - - # now enter the Twisted reactor loop - ## - reactor.run() diff --git a/examples/twisted/wamp/basic/pubsub/__init__.py b/examples/twisted/wamp/basic/pubsub/__init__.py deleted file mode 100644 index 8a4c67bf..00000000 --- a/examples/twisted/wamp/basic/pubsub/__init__.py +++ /dev/null @@ -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. -# -############################################################################### diff --git a/examples/twisted/wamp/basic/pubsub/basic/__init__.py b/examples/twisted/wamp/basic/pubsub/basic/__init__.py deleted file mode 100644 index 8a4c67bf..00000000 --- a/examples/twisted/wamp/basic/pubsub/basic/__init__.py +++ /dev/null @@ -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. -# -############################################################################### diff --git a/examples/twisted/wamp/basic/pubsub/complex/README.md b/examples/twisted/wamp/basic/pubsub/complex/README.md deleted file mode 100644 index 175216b0..00000000 --- a/examples/twisted/wamp/basic/pubsub/complex/README.md +++ /dev/null @@ -1,8 +0,0 @@ -This demo shows complex arguments sent as a PubSub payload event. - -Learn more: - -* [Autobahn|Python documentation](http://autobahn.ws/python) -* [Autobahn|JS documentation](http://autobahn.ws/js) -* Publishing in the [WAMP spec](https://github.com/tavendo/WAMP/blob/master/spec/basic.md#publishing-and-events) -* [How to run this demo](https://github.com/tavendo/AutobahnPython/tree/master/examples/twisted/wamp/basic) \ No newline at end of file diff --git a/examples/twisted/wamp/basic/pubsub/complex/__init__.py b/examples/twisted/wamp/basic/pubsub/complex/__init__.py deleted file mode 100644 index 8a4c67bf..00000000 --- a/examples/twisted/wamp/basic/pubsub/complex/__init__.py +++ /dev/null @@ -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. -# -############################################################################### diff --git a/examples/twisted/wamp/basic/pubsub/decorators/__init__.py b/examples/twisted/wamp/basic/pubsub/decorators/__init__.py deleted file mode 100644 index 8a4c67bf..00000000 --- a/examples/twisted/wamp/basic/pubsub/decorators/__init__.py +++ /dev/null @@ -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. -# -############################################################################### diff --git a/examples/twisted/wamp/basic/pubsub/options/__init__.py b/examples/twisted/wamp/basic/pubsub/options/__init__.py deleted file mode 100644 index 8a4c67bf..00000000 --- a/examples/twisted/wamp/basic/pubsub/options/__init__.py +++ /dev/null @@ -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. -# -############################################################################### diff --git a/examples/twisted/wamp/basic/pubsub/unsubscribe/__init__.py b/examples/twisted/wamp/basic/pubsub/unsubscribe/__init__.py deleted file mode 100644 index 8a4c67bf..00000000 --- a/examples/twisted/wamp/basic/pubsub/unsubscribe/__init__.py +++ /dev/null @@ -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. -# -############################################################################### diff --git a/examples/twisted/wamp/basic/rpc/__init__.py b/examples/twisted/wamp/basic/rpc/__init__.py deleted file mode 100644 index 8a4c67bf..00000000 --- a/examples/twisted/wamp/basic/rpc/__init__.py +++ /dev/null @@ -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. -# -############################################################################### diff --git a/examples/twisted/wamp/basic/rpc/arguments/__init__.py b/examples/twisted/wamp/basic/rpc/arguments/__init__.py deleted file mode 100644 index 8a4c67bf..00000000 --- a/examples/twisted/wamp/basic/rpc/arguments/__init__.py +++ /dev/null @@ -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. -# -############################################################################### diff --git a/examples/twisted/wamp/basic/rpc/complex/__init__.py b/examples/twisted/wamp/basic/rpc/complex/__init__.py deleted file mode 100644 index 8a4c67bf..00000000 --- a/examples/twisted/wamp/basic/rpc/complex/__init__.py +++ /dev/null @@ -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. -# -############################################################################### diff --git a/examples/twisted/wamp/basic/rpc/decorators/__init__.py b/examples/twisted/wamp/basic/rpc/decorators/__init__.py deleted file mode 100644 index 8a4c67bf..00000000 --- a/examples/twisted/wamp/basic/rpc/decorators/__init__.py +++ /dev/null @@ -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. -# -############################################################################### diff --git a/examples/twisted/wamp/basic/rpc/errors/__init__.py b/examples/twisted/wamp/basic/rpc/errors/__init__.py deleted file mode 100644 index 8a4c67bf..00000000 --- a/examples/twisted/wamp/basic/rpc/errors/__init__.py +++ /dev/null @@ -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. -# -############################################################################### diff --git a/examples/twisted/wamp/basic/rpc/options/__init__.py b/examples/twisted/wamp/basic/rpc/options/__init__.py deleted file mode 100644 index 8a4c67bf..00000000 --- a/examples/twisted/wamp/basic/rpc/options/__init__.py +++ /dev/null @@ -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. -# -############################################################################### diff --git a/examples/twisted/wamp/basic/rpc/progress/__init__.py b/examples/twisted/wamp/basic/rpc/progress/__init__.py deleted file mode 100644 index 8a4c67bf..00000000 --- a/examples/twisted/wamp/basic/rpc/progress/__init__.py +++ /dev/null @@ -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. -# -############################################################################### diff --git a/examples/twisted/wamp/basic/rpc/slowsquare/__init__.py b/examples/twisted/wamp/basic/rpc/slowsquare/__init__.py deleted file mode 100644 index 8a4c67bf..00000000 --- a/examples/twisted/wamp/basic/rpc/slowsquare/__init__.py +++ /dev/null @@ -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. -# -############################################################################### diff --git a/examples/twisted/wamp/basic/rpc/timeservice/__init__.py b/examples/twisted/wamp/basic/rpc/timeservice/__init__.py deleted file mode 100644 index 8a4c67bf..00000000 --- a/examples/twisted/wamp/basic/rpc/timeservice/__init__.py +++ /dev/null @@ -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. -# -############################################################################### diff --git a/examples/twisted/wamp/beginner/Makefile b/examples/twisted/wamp/beginner/Makefile deleted file mode 100644 index 7957aef9..00000000 --- a/examples/twisted/wamp/beginner/Makefile +++ /dev/null @@ -1,5 +0,0 @@ -test_server: - PYTHONPATH="../../../../autobahn" python server.py - -test_client: - PYTHONPATH="../../../../autobahn" python client.py diff --git a/examples/twisted/wamp/beginner/client.py b/examples/twisted/wamp/beginner/client.py deleted file mode 100644 index b3ccfcb9..00000000 --- a/examples/twisted/wamp/beginner/client.py +++ /dev/null @@ -1,105 +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 - -from twisted.python import log -from twisted.internet import reactor -from twisted.internet.defer import inlineCallbacks -from twisted.internet.endpoints import clientFromString - -from autobahn.twisted import wamp, websocket -from autobahn.wamp import types - - -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. - """ - - @inlineCallbacks - def onJoin(self, details): - - # call a remote procedure - # - try: - now = yield 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 self.subscribe(on_event, u'com.myapp.topic1') - print("Subscribed with subscription ID {}".format(sub.id)) - - def onDisconnect(self): - reactor.stop() - - -if __name__ == '__main__': - - # 0) start logging to console - log.startLogging(sys.stdout) - - # 1) create a WAMP application session factory - component_config = types.ComponentConfig(realm="realm1") - session_factory = wamp.ApplicationSessionFactory(config=component_config) - session_factory.session = MyFrontendComponent - - # optional: use specific set of serializers - if False: - serializers = None - else: - from autobahn.wamp.serializer import * - serializers = [] - # serializers.append(JsonSerializer(batched = True)) - # serializers.append(MsgPackSerializer(batched = True)) - serializers.append(JsonSerializer()) - # serializers.append(MsgPackSerializer()) - - # 2) create a WAMP-over-WebSocket transport client factory - transport_factory = websocket.WampWebSocketClientFactory(session_factory, - serializers=serializers, debug=False, debug_wamp=False) - - # 3) start the client from a Twisted endpoint - client = clientFromString(reactor, "tcp:127.0.0.1:8080") - client.connect(transport_factory) - - # 4) now enter the Twisted reactor loop - reactor.run() diff --git a/examples/twisted/wamp/beginner/server.py b/examples/twisted/wamp/beginner/server.py deleted file mode 100644 index 559e265f..00000000 --- a/examples/twisted/wamp/beginner/server.py +++ /dev/null @@ -1,98 +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 - -from twisted.python import log -from twisted.internet import reactor -from twisted.internet.defer import inlineCallbacks -from twisted.internet.endpoints import serverFromString - -from autobahn.wamp import types -from autobahn.twisted.util import sleep -from autobahn.twisted 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. - """ - - @inlineCallbacks - 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 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 sleep(1) - - -if __name__ == '__main__': - - # 0) start logging to console - log.startLogging(sys.stdout) - - # 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 - component_config = types.ComponentConfig(realm="realm1") - component_session = MyBackendComponent(component_config) - session_factory.add(component_session) - - # 4) create a WAMP-over-WebSocket transport server factory - transport_factory = websocket.WampWebSocketServerFactory(session_factory, - debug=False, - debug_wamp=False) - - # 5) start the server from a Twisted endpoint - server = serverFromString(reactor, "tcp:8080") - server.listen(transport_factory) - - # 6) now enter the Twisted reactor loop - reactor.run() diff --git a/examples/twisted/wamp/longpoll/Makefile b/examples/twisted/wamp/longpoll/Makefile deleted file mode 100644 index 6e12cfe0..00000000 --- a/examples/twisted/wamp/longpoll/Makefile +++ /dev/null @@ -1,28 +0,0 @@ -test: - PYTHONPATH=../../../../autobahn python server.py - -open: - curl -H "Content-Type: application/json" \ - -d '{"protocols": ["wamp.2.json"]}' \ - http://127.0.0.1:8080/lp/open - -receive: - curl -H "Content-Type: application/json" -d "" \ - http://127.0.0.1:8080/lp/kjmd3sBLOUnb3Fyr/receive - -hello: - curl -H "Content-Type: application/json" \ - -d '[1, "realm1", {"roles": {"subscriber": {}, "publisher": {}}}]' \ - http://127.0.0.1:8080/lp/kjmd3sBLOUnb3Fyr/send - -subscribe: - curl -H "Content-Type: application/json" \ - -d '[32, 1, {}, "com.myapp.topic1"]' \ - http://127.0.0.1:8080/lp/kjmd3sBLOUnb3Fyr/send - -close: - curl -H "Content-Type: application/json" -d '' \ - http://127.0.0.1:8080/lp/kjmd3sBLOUnb3Fyr/close - -test_ser: - PYTHONPATH=../../../../autobahn python test.py diff --git a/examples/twisted/wamp/longpoll/README.md b/examples/twisted/wamp/longpoll/README.md deleted file mode 100644 index ec36e4b8..00000000 --- a/examples/twisted/wamp/longpoll/README.md +++ /dev/null @@ -1,35 +0,0 @@ -# WAMP-over-Longpoll - -WAMP can run over Long-poll, a HTTP-bassed fallback for browsers lacking native WebSocket support. - -The implementation in AutobahnPython follows the specification of the transport in the [WAMP Advanced Profile](https://github.com/tavendo/WAMP/blob/master/spec/advanced.md#long-poll-transport). - -The example here includes a WAMP router running two services on a Web transport: - - * **/ws**: WAMP-over-WebSocket transport - * **/lp**: WAMP-over-Long-poll transport - -The Long-poll transport can be tested with **curl**. - -Start the router in a first terminal: - -```shell -make test -``` - -Run the following in a second terminal: - -```shell -make open -make receive -``` - -Run the following in a thrid terminal, restarting the `make receive` command in the second terminal: - -```shell -make hello -make subscribe -``` - -As you continue to restart `make receive`, WAMP events should be received. - diff --git a/examples/twisted/wamp/longpoll/server.py b/examples/twisted/wamp/longpoll/server.py deleted file mode 100644 index 8588a8e8..00000000 --- a/examples/twisted/wamp/longpoll/server.py +++ /dev/null @@ -1,88 +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 - -from twisted.python import log -from twisted.internet import reactor -from twisted.internet.defer import inlineCallbacks -from twisted.internet.endpoints import serverFromString -from twisted.web.server import Site -from twisted.web.static import File - -from autobahn.wamp import types -from autobahn.twisted.util import sleep - -from autobahn.twisted import wamp, websocket -from autobahn.twisted.resource import WebSocketResource -from autobahn.twisted.longpoll import WampLongPollResource - - -class MyBackendComponent(wamp.ApplicationSession): - - @inlineCallbacks - def onJoin(self, details): - - counter = 0 - while True: - self.publish(u'com.myapp.topic1', counter) - print("Published event.") - counter += 1 - yield sleep(2) - - -if __name__ == '__main__': - - log.startLogging(sys.stdout) - - router_factory = wamp.RouterFactory() - session_factory = wamp.RouterSessionFactory(router_factory) - - component_config = types.ComponentConfig(realm="realm1") - component_session = MyBackendComponent(component_config) - session_factory.add(component_session) - - ws_factory = websocket.WampWebSocketServerFactory(session_factory, - debug=False, - debug_wamp=False) - ws_factory.startFactory() - - ws_resource = WebSocketResource(ws_factory) - lp_resource = WampLongPollResource(session_factory, debug=True, debug_transport_id="kjmd3sBLOUnb3Fyr") - - root = File(".") - root.putChild("ws", ws_resource) - root.putChild("lp", lp_resource) - - web_factory = Site(root) - web_factory.noisy = False - - server = serverFromString(reactor, "tcp:8080") - server.listen(web_factory) - - reactor.run() diff --git a/examples/twisted/wamp/overview/backend.py b/examples/twisted/wamp/overview/backend.py new file mode 100644 index 00000000..e8dda49c --- /dev/null +++ b/examples/twisted/wamp/overview/backend.py @@ -0,0 +1,31 @@ +from os import environ +from twisted.internet.defer import inlineCallbacks +from twisted.internet.task import LoopingCall +from autobahn.twisted.wamp import ApplicationSession, ApplicationRunner +# or: from autobahn.asyncio.wamp import ApplicationSession + +class MyComponent(ApplicationSession): + @inlineCallbacks + def onJoin(self, details): + # publish an event every second. The event payloads can be + # anything JSON- and msgpack- serializable + def publish(): + return self.publish('com.myapp.hello', 'Hello, world!') + LoopingCall(publish).start(1) + + # 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 self.register(add2, 'com.myapp.add2'); + + +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) diff --git a/examples/twisted/wamp/overview/frontend.py b/examples/twisted/wamp/overview/frontend.py new file mode 100644 index 00000000..28ff6e22 --- /dev/null +++ b/examples/twisted/wamp/overview/frontend.py @@ -0,0 +1,27 @@ +from os import environ +from twisted.internet.defer import inlineCallbacks +from autobahn.twisted.wamp import ApplicationSession, ApplicationRunner +# or: from autobahn.asyncio.wamp import ApplicationSession + +class MyComponent(ApplicationSession): + @inlineCallbacks + 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 self.subscribe(onevent, 'com.myapp.hello') + + # call a remote procedure. + res = yield 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) diff --git a/examples/twisted/wamp/pubsub/basic/.crossbar/config.json b/examples/twisted/wamp/pubsub/basic/.crossbar/config.json new file mode 100644 index 00000000..1f7d73c2 --- /dev/null +++ b/examples/twisted/wamp/pubsub/basic/.crossbar/config.json @@ -0,0 +1,47 @@ + +{ + "controller": { + }, + "workers": [ + { + "type": "router", + "realms": [ + { + "name": "realm1", + "roles": [ + { + "name": "anonymous", + "permissions": [ + { + "uri": "*", + "publish": true, + "subscribe": true, + "call": true, + "register": true + } + ] + } + ] + } + ], + "transports": [ + { + "type": "web", + "endpoint": { + "type": "tcp", + "port": 8080 + }, + "paths": { + "/": { + "type": "static", + "directory": ".." + }, + "ws": { + "type": "websocket" + } + } + } + ] + } + ] +} diff --git a/examples/twisted/wamp/basic/pubsub/basic/backend.html b/examples/twisted/wamp/pubsub/basic/backend.html similarity index 100% rename from examples/twisted/wamp/basic/pubsub/basic/backend.html rename to examples/twisted/wamp/pubsub/basic/backend.html diff --git a/examples/twisted/wamp/basic/pubsub/basic/backend.js b/examples/twisted/wamp/pubsub/basic/backend.js similarity index 95% rename from examples/twisted/wamp/basic/pubsub/basic/backend.js rename to examples/twisted/wamp/pubsub/basic/backend.js index 53b82843..efadd33e 100644 --- a/examples/twisted/wamp/basic/pubsub/basic/backend.js +++ b/examples/twisted/wamp/pubsub/basic/backend.js @@ -7,7 +7,7 @@ try { var connection = new autobahn.Connection({ url: 'ws://127.0.0.1:8080/ws', - realm: 'realm1'} + realm: 'crossbardemo'} ); connection.onopen = function (session) { diff --git a/examples/twisted/wamp/basic/pubsub/basic/backend.py b/examples/twisted/wamp/pubsub/basic/backend.py similarity index 79% rename from examples/twisted/wamp/basic/pubsub/basic/backend.py rename to examples/twisted/wamp/pubsub/basic/backend.py index 0794ef8f..875ce4e4 100644 --- a/examples/twisted/wamp/basic/pubsub/basic/backend.py +++ b/examples/twisted/wamp/pubsub/basic/backend.py @@ -24,14 +24,16 @@ # ############################################################################### +from __future__ import print_function +from os import environ + from twisted.internet.defer import inlineCallbacks from autobahn.twisted.util import sleep -from autobahn.twisted.wamp import ApplicationSession +from autobahn.twisted.wamp import ApplicationSession, ApplicationRunner class Component(ApplicationSession): - """ An application component that publishes an event every second. """ @@ -41,13 +43,17 @@ class Component(ApplicationSession): print("session attached") counter = 0 while True: - print(".") + print('backend publishing "com.myapp.topic1"', counter) self.publish('com.myapp.topic1', counter) counter += 1 yield sleep(1) 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) diff --git a/examples/twisted/wamp/basic/pubsub/basic/frontend.html b/examples/twisted/wamp/pubsub/basic/frontend.html similarity index 100% rename from examples/twisted/wamp/basic/pubsub/basic/frontend.html rename to examples/twisted/wamp/pubsub/basic/frontend.html diff --git a/examples/twisted/wamp/basic/pubsub/basic/frontend.js b/examples/twisted/wamp/pubsub/basic/frontend.js similarity index 95% rename from examples/twisted/wamp/basic/pubsub/basic/frontend.js rename to examples/twisted/wamp/pubsub/basic/frontend.js index ef5b8ea4..07cf0435 100644 --- a/examples/twisted/wamp/basic/pubsub/basic/frontend.js +++ b/examples/twisted/wamp/pubsub/basic/frontend.js @@ -7,7 +7,7 @@ try { var connection = new autobahn.Connection({ url: 'ws://127.0.0.1:8080/ws', - realm: 'realm1'} + realm: 'crossbardemo'} ); connection.onopen = function (session) { diff --git a/examples/twisted/wamp/basic/pubsub/basic/frontend.py b/examples/twisted/wamp/pubsub/basic/frontend.py similarity index 62% rename from examples/twisted/wamp/basic/pubsub/basic/frontend.py rename to examples/twisted/wamp/pubsub/basic/frontend.py index fccbf551..3eca849e 100644 --- a/examples/twisted/wamp/basic/pubsub/basic/frontend.py +++ b/examples/twisted/wamp/pubsub/basic/frontend.py @@ -24,39 +24,50 @@ # ############################################################################### +from __future__ import print_function +from os import environ + from twisted.internet import reactor from twisted.internet.defer import inlineCallbacks -from autobahn.twisted.wamp import ApplicationSession +from autobahn.twisted.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. """ @inlineCallbacks def onJoin(self, details): print("session attached") - self.received = 0 + sub = yield self.subscribe(self.on_event, 'com.myapp.topic1') + print("Subscribed to 'com.myapp.topic1' with {}".format(sub.id)) - def on_event(i): - print("Got event: {}".format(i)) - self.received += 1 - if self.received > 5: - self.leave() - - yield self.subscribe(on_event, 'com.myapp.topic1') + def on_event(self, i): + print("Got event: {}".format(i)) + self.received += 1 + # self.config.extra for configuration, etc. (see [A]) + if self.received > self.config.extra['max_events']: + print("Received enough events; disconnecting.") + self.leave() def onDisconnect(self): print("disconnected") - reactor.stop() + if reactor.running: + reactor.stop() 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", + extra=dict( + max_events=5, # [A] pass in additional configuration + ), + debug_wamp=False, # optional; log many WAMP details + debug=False, # optional; log even more details + ) runner.run(Component) diff --git a/examples/twisted/wamp/basic/pubsub/complex/backend.html b/examples/twisted/wamp/pubsub/complex/backend.html similarity index 100% rename from examples/twisted/wamp/basic/pubsub/complex/backend.html rename to examples/twisted/wamp/pubsub/complex/backend.html diff --git a/examples/twisted/wamp/basic/pubsub/complex/backend.js b/examples/twisted/wamp/pubsub/complex/backend.js similarity index 96% rename from examples/twisted/wamp/basic/pubsub/complex/backend.js rename to examples/twisted/wamp/pubsub/complex/backend.js index 90a40e84..57b7e19b 100644 --- a/examples/twisted/wamp/basic/pubsub/complex/backend.js +++ b/examples/twisted/wamp/pubsub/complex/backend.js @@ -7,7 +7,7 @@ try { var connection = new autobahn.Connection({ url: 'ws://127.0.0.1:8080/ws', - realm: 'realm1'} + realm: 'crossbardemo'} ); function randint(min, max) { diff --git a/examples/twisted/wamp/basic/pubsub/complex/backend.py b/examples/twisted/wamp/pubsub/complex/backend.py similarity index 78% rename from examples/twisted/wamp/basic/pubsub/complex/backend.py rename to examples/twisted/wamp/pubsub/complex/backend.py index 0a2a6a57..b75c62fa 100644 --- a/examples/twisted/wamp/basic/pubsub/complex/backend.py +++ b/examples/twisted/wamp/pubsub/complex/backend.py @@ -24,16 +24,18 @@ # ############################################################################### +from __future__ import print_function + import random +from os import environ from twisted.internet.defer import inlineCallbacks from autobahn.twisted.util import sleep -from autobahn.twisted.wamp import ApplicationSession +from autobahn.twisted.wamp import ApplicationSession, ApplicationRunner class Component(ApplicationSession): - """ An application component that publishes events with no payload and with complex payload every second. @@ -45,17 +47,23 @@ class Component(ApplicationSession): counter = 0 while True: - print(".") + print("publish: com.myapp.heartbeat") self.publish('com.myapp.heartbeat') obj = {'counter': counter, 'foo': [1, 2, 3]} - self.publish('com.myapp.topic2', random.randint(0, 100), 23, c="Hello", d=obj) + print("publish: com.myapp.topic2", obj) + self.publish('com.myapp.topic2', random.randint(0, 100), 23, + c="Hello", d=obj) counter += 1 yield sleep(1) 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) diff --git a/examples/twisted/wamp/basic/pubsub/complex/frontend.html b/examples/twisted/wamp/pubsub/complex/frontend.html similarity index 100% rename from examples/twisted/wamp/basic/pubsub/complex/frontend.html rename to examples/twisted/wamp/pubsub/complex/frontend.html diff --git a/examples/twisted/wamp/basic/pubsub/complex/frontend.js b/examples/twisted/wamp/pubsub/complex/frontend.js similarity index 96% rename from examples/twisted/wamp/basic/pubsub/complex/frontend.js rename to examples/twisted/wamp/pubsub/complex/frontend.js index 047a48ea..d51732dc 100644 --- a/examples/twisted/wamp/basic/pubsub/complex/frontend.js +++ b/examples/twisted/wamp/pubsub/complex/frontend.js @@ -7,7 +7,7 @@ try { var connection = new autobahn.Connection({ url: 'ws://127.0.0.1:8080/ws', - realm: 'realm1'} + realm: 'crossbardemo'} ); connection.onopen = function (session) { diff --git a/examples/twisted/wamp/basic/pubsub/complex/frontend.py b/examples/twisted/wamp/pubsub/complex/frontend.py similarity index 78% rename from examples/twisted/wamp/basic/pubsub/complex/frontend.py rename to examples/twisted/wamp/pubsub/complex/frontend.py index cce068da..d78b58b5 100644 --- a/examples/twisted/wamp/basic/pubsub/complex/frontend.py +++ b/examples/twisted/wamp/pubsub/complex/frontend.py @@ -24,12 +24,13 @@ # ############################################################################### +from os import environ + from twisted.internet import reactor from twisted.internet.defer import inlineCallbacks from autobahn.wamp.types import SubscribeOptions -from autobahn.twisted.util import sleep -from autobahn.twisted.wamp import ApplicationSession +from autobahn.twisted.wamp import ApplicationSession, ApplicationRunner class Component(ApplicationSession): @@ -46,9 +47,12 @@ class Component(ApplicationSession): self.received = 0 def on_heartbeat(details=None): - print("Got heartbeat (publication ID {})".format(details.publication)) + print("heartbeat (publication ID {})".format(details.publication)) - yield self.subscribe(on_heartbeat, 'com.myapp.heartbeat', options=SubscribeOptions(details_arg='details')) + yield self.subscribe( + on_heartbeat, 'com.myapp.heartbeat', + options=SubscribeOptions(details_arg='details') + ) def on_topic2(a, b, c=None, d=None): print("Got event: {} {} {} {}".format(a, b, c, d)) @@ -63,6 +67,10 @@ class Component(ApplicationSession): 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) diff --git a/examples/twisted/wamp/basic/pubsub/decorators/backend.html b/examples/twisted/wamp/pubsub/decorators/backend.html similarity index 100% rename from examples/twisted/wamp/basic/pubsub/decorators/backend.html rename to examples/twisted/wamp/pubsub/decorators/backend.html diff --git a/examples/twisted/wamp/basic/pubsub/decorators/backend.js b/examples/twisted/wamp/pubsub/decorators/backend.js similarity index 95% rename from examples/twisted/wamp/basic/pubsub/decorators/backend.js rename to examples/twisted/wamp/pubsub/decorators/backend.js index 013a343a..dbd66879 100644 --- a/examples/twisted/wamp/basic/pubsub/decorators/backend.js +++ b/examples/twisted/wamp/pubsub/decorators/backend.js @@ -7,7 +7,7 @@ try { var connection = new autobahn.Connection({ url: 'ws://127.0.0.1:8080/ws', - realm: 'realm1'} + realm: 'crossbardemo'} ); connection.onopen = function (session) { diff --git a/examples/twisted/wamp/basic/pubsub/decorators/backend.py b/examples/twisted/wamp/pubsub/decorators/backend.py similarity index 78% rename from examples/twisted/wamp/basic/pubsub/decorators/backend.py rename to examples/twisted/wamp/pubsub/decorators/backend.py index 662d7790..48598e1a 100644 --- a/examples/twisted/wamp/basic/pubsub/decorators/backend.py +++ b/examples/twisted/wamp/pubsub/decorators/backend.py @@ -24,10 +24,13 @@ # ############################################################################### +from __future__ import print_function + +from os import environ from twisted.internet.defer import inlineCallbacks from autobahn.twisted.util import sleep -from autobahn.twisted.wamp import ApplicationSession +from autobahn.twisted.wamp import ApplicationSession, ApplicationRunner class Component(ApplicationSession): @@ -41,14 +44,21 @@ class Component(ApplicationSession): print("session attached") counter = 0 while True: - print(".") + print("publish: com.myapp.topic1", counter) self.publish('com.myapp.topic1', counter) + + print("publish: com.myapp.topic2") self.publish('com.myapp.topic2', "Hello world.") + counter += 1 yield sleep(1) 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) diff --git a/examples/twisted/wamp/basic/pubsub/decorators/frontend.html b/examples/twisted/wamp/pubsub/decorators/frontend.html similarity index 100% rename from examples/twisted/wamp/basic/pubsub/decorators/frontend.html rename to examples/twisted/wamp/pubsub/decorators/frontend.html diff --git a/examples/twisted/wamp/basic/pubsub/decorators/frontend.js b/examples/twisted/wamp/pubsub/decorators/frontend.js similarity index 96% rename from examples/twisted/wamp/basic/pubsub/decorators/frontend.js rename to examples/twisted/wamp/pubsub/decorators/frontend.js index 15c4db83..be90be5a 100644 --- a/examples/twisted/wamp/basic/pubsub/decorators/frontend.js +++ b/examples/twisted/wamp/pubsub/decorators/frontend.js @@ -7,7 +7,7 @@ try { var connection = new autobahn.Connection({ url: 'ws://127.0.0.1:8080/ws', - realm: 'realm1'} + realm: 'crossbardemo'} ); connection.onopen = function (session) { diff --git a/examples/twisted/wamp/basic/pubsub/decorators/frontend.py b/examples/twisted/wamp/pubsub/decorators/frontend.py similarity index 75% rename from examples/twisted/wamp/basic/pubsub/decorators/frontend.py rename to examples/twisted/wamp/pubsub/decorators/frontend.py index 6e267443..f3778de1 100644 --- a/examples/twisted/wamp/basic/pubsub/decorators/frontend.py +++ b/examples/twisted/wamp/pubsub/decorators/frontend.py @@ -24,11 +24,15 @@ # ############################################################################### +from __future__ import print_function + +from os import environ from twisted.internet import reactor from twisted.internet.defer import inlineCallbacks +from twisted.python.failure import Failure from autobahn import wamp -from autobahn.twisted.wamp import ApplicationSession +from autobahn.twisted.wamp import ApplicationSession, ApplicationRunner class Component(ApplicationSession): @@ -44,24 +48,18 @@ class Component(ApplicationSession): # subscribe all methods on this object decorated with "@wamp.subscribe" # as PubSub event handlers - ## results = yield self.subscribe(self) - for success, res in results: - if success: - # res is an Subscription instance - print("Ok, subscribed handler with subscription ID {}".format(res.id)) - else: - # res is an Failure instance - print("Failed to subscribe handler: {}".format(res.value)) - @wamp.subscribe('com.myapp.topic1') + # check we didn't have any errors + for sub in results: + if isinstance(sub, Failure): + print("subscribe failed:", sub.getErrorMessage()) + + @wamp.subscribe(u'com.myapp.topic1') def onEvent1(self, i): print("Got event on topic1: {}".format(i)) - self.received += 1 - if self.received > 5: - self.leave() - @wamp.subscribe('com.myapp.topic2') + @wamp.subscribe(u'com.myapp.topic2') def onEvent2(self, msg): print("Got event on topic2: {}".format(msg)) @@ -71,6 +69,10 @@ class Component(ApplicationSession): 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) diff --git a/examples/twisted/wamp/basic/pubsub/options/backend.html b/examples/twisted/wamp/pubsub/options/backend.html similarity index 100% rename from examples/twisted/wamp/basic/pubsub/options/backend.html rename to examples/twisted/wamp/pubsub/options/backend.html diff --git a/examples/twisted/wamp/basic/pubsub/options/backend.js b/examples/twisted/wamp/pubsub/options/backend.js similarity index 96% rename from examples/twisted/wamp/basic/pubsub/options/backend.js rename to examples/twisted/wamp/pubsub/options/backend.js index 2826e091..a60cbaee 100644 --- a/examples/twisted/wamp/basic/pubsub/options/backend.js +++ b/examples/twisted/wamp/pubsub/options/backend.js @@ -7,7 +7,7 @@ try { var connection = new autobahn.Connection({ url: 'ws://127.0.0.1:8080/ws', - realm: 'realm1'} + realm: 'crossbardemo'} ); connection.onopen = function (session) { diff --git a/examples/twisted/wamp/basic/pubsub/options/backend.py b/examples/twisted/wamp/pubsub/options/backend.py similarity index 70% rename from examples/twisted/wamp/basic/pubsub/options/backend.py rename to examples/twisted/wamp/pubsub/options/backend.py index 440ac0ed..0e9103f5 100644 --- a/examples/twisted/wamp/basic/pubsub/options/backend.py +++ b/examples/twisted/wamp/pubsub/options/backend.py @@ -24,15 +24,17 @@ # ############################################################################### +from __future__ import print_function + +from os import environ from twisted.internet.defer import inlineCallbacks from autobahn.wamp.types import PublishOptions from autobahn.twisted.util import sleep -from autobahn.twisted.wamp import ApplicationSession +from autobahn.twisted.wamp import ApplicationSession, ApplicationRunner class Component(ApplicationSession): - """ An application component that publishes an event every second. """ @@ -48,15 +50,26 @@ class Component(ApplicationSession): counter = 0 while True: - print(".") - publication = yield self.publish('com.myapp.topic1', counter, - options=PublishOptions(acknowledge=True, discloseMe=True, excludeMe=False)) - print("Event published with publication ID {}".format(publication.id)) + print("publish: com.myapp.topic1", counter) + pub_options = PublishOptions( + acknowledge=True, + disclose_me=True, + exclude_me=False + ) + publication = yield self.publish( + 'com.myapp.topic1', counter, + options=pub_options, + ) + print("Published with publication ID {}".format(publication.id)) counter += 1 yield sleep(1) 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) diff --git a/examples/twisted/wamp/basic/pubsub/options/frontend.html b/examples/twisted/wamp/pubsub/options/frontend.html similarity index 100% rename from examples/twisted/wamp/basic/pubsub/options/frontend.html rename to examples/twisted/wamp/pubsub/options/frontend.html diff --git a/examples/twisted/wamp/basic/pubsub/options/frontend.js b/examples/twisted/wamp/pubsub/options/frontend.js similarity index 96% rename from examples/twisted/wamp/basic/pubsub/options/frontend.js rename to examples/twisted/wamp/pubsub/options/frontend.js index 9a48e2e0..3276bf4a 100644 --- a/examples/twisted/wamp/basic/pubsub/options/frontend.js +++ b/examples/twisted/wamp/pubsub/options/frontend.js @@ -7,7 +7,7 @@ try { var connection = new autobahn.Connection({ url: 'ws://127.0.0.1:8080/ws', - realm: 'realm1'} + realm: 'crossbardemo'} ); connection.onopen = function (session) { diff --git a/examples/twisted/wamp/basic/pubsub/options/frontend.py b/examples/twisted/wamp/pubsub/options/frontend.py similarity index 80% rename from examples/twisted/wamp/basic/pubsub/options/frontend.py rename to examples/twisted/wamp/pubsub/options/frontend.py index 71a34212..31773dc7 100644 --- a/examples/twisted/wamp/basic/pubsub/options/frontend.py +++ b/examples/twisted/wamp/pubsub/options/frontend.py @@ -24,12 +24,14 @@ # ############################################################################### +from __future__ import print_function + +from os import environ from twisted.internet import reactor from twisted.internet.defer import inlineCallbacks from autobahn.wamp.types import SubscribeOptions -from autobahn.twisted.util import sleep -from autobahn.twisted.wamp import ApplicationSession +from autobahn.twisted.wamp import ApplicationSession, ApplicationRunner class Component(ApplicationSession): @@ -46,7 +48,8 @@ class Component(ApplicationSession): self.received = 0 def on_event(i, details=None): - print("Got event, publication ID {}, publisher {}: {}".format(details.publication, details.publisher, i)) + msg = "Got event, publication ID {}, publisher {}: {}" + print(msg.format(details.publication, details.publisher, i)) self.received += 1 if self.received > 5: self.leave() @@ -60,6 +63,10 @@ class Component(ApplicationSession): 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) diff --git a/examples/twisted/wamp/pubsub/tls/README.md b/examples/twisted/wamp/pubsub/tls/README.md new file mode 100644 index 00000000..d7e0d354 --- /dev/null +++ b/examples/twisted/wamp/pubsub/tls/README.md @@ -0,0 +1,30 @@ +# TLS + +This demonstrates how to use a custom `sslContextFactory` for +SSL4ClientEndpoints to control how TLS verification is +done. Specifically, we connect via wss:// to a TLS-enabled backend +with a self-signed certificate. + +Use the script "create-self-signed-cert.sh" to create a new +certificate in `server.crt` (with corresponding private key +`server.key`). You can teach crossbar about your certificate by adding +a "transport" configuration like the following (this can be dropped +straight into examples/router/.crossbar/config.json):: + + { + "type": "websocket", + "id": "tls_test0", + "endpoint": { + "type": "tcp", + "port": 8083, + "tls": { + "key": "../../twisted/wamp/pubsub/tls/server.key", + "certificate": "../../twisted/wamp/pubsub/tls/server.crt" + } + } + } + +`backend_selfsigned.py` is designed to connect to a transport +configured as above, and also needs access to the `server.crt` +file. So you can simply run `create-self-signed-cert.sh` here and the +above should read the same files directly. diff --git a/examples/twisted/wamp/pubsub/tls/backend_selfsigned.py b/examples/twisted/wamp/pubsub/tls/backend_selfsigned.py new file mode 100644 index 00000000..a277f477 --- /dev/null +++ b/examples/twisted/wamp/pubsub/tls/backend_selfsigned.py @@ -0,0 +1,72 @@ +############################################################################### +# +# 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 + +from os import environ +from autobahn.twisted.util import sleep +from autobahn.twisted.wamp import ApplicationSession, ApplicationRunner + +from twisted.internet.defer import inlineCallbacks +from twisted.internet._sslverify import OpenSSLCertificateAuthorities +from twisted.internet.ssl import CertificateOptions +from OpenSSL import crypto + + +class Component(ApplicationSession): + """ + An application component that publishes an event every second. + """ + + @inlineCallbacks + def onJoin(self, details): + counter = 0 + while True: + print("publish: com.myapp.topic1", counter) + yield self.publish('com.myapp.topic1', counter) + counter += 1 + yield sleep(1) + + +if __name__ == '__main__': + # load the self-signed cert the server is using + cert = crypto.load_certificate( + crypto.FILETYPE_PEM, + unicode(open('./server.crt', 'r').read()) + ) + # tell Twisted to use just the one certificate we loaded to verify connections + options = CertificateOptions( + trustRoot=OpenSSLCertificateAuthorities([cert]), + ) + # ...which we pass as "ssl=" to ApplicationRunner (passed to SSL4ClientEndpoint) + 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) diff --git a/examples/twisted/wamp/pubsub/tls/create-self-signed-cert.sh b/examples/twisted/wamp/pubsub/tls/create-self-signed-cert.sh new file mode 100755 index 00000000..b30d8f28 --- /dev/null +++ b/examples/twisted/wamp/pubsub/tls/create-self-signed-cert.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +openssl req -nodes -new -x509 -keyout server.key \ + -subj '/C=DE/ST=Bavaria/L=Erlangen/O=Tavendo/CN=localhost/' \ + -out server.crt diff --git a/examples/twisted/wamp/basic/pubsub/unsubscribe/backend.html b/examples/twisted/wamp/pubsub/unsubscribe/backend.html similarity index 100% rename from examples/twisted/wamp/basic/pubsub/unsubscribe/backend.html rename to examples/twisted/wamp/pubsub/unsubscribe/backend.html diff --git a/examples/twisted/wamp/basic/pubsub/unsubscribe/backend.js b/examples/twisted/wamp/pubsub/unsubscribe/backend.js similarity index 94% rename from examples/twisted/wamp/basic/pubsub/unsubscribe/backend.js rename to examples/twisted/wamp/pubsub/unsubscribe/backend.js index fe1a2194..7c528830 100644 --- a/examples/twisted/wamp/basic/pubsub/unsubscribe/backend.js +++ b/examples/twisted/wamp/pubsub/unsubscribe/backend.js @@ -7,7 +7,7 @@ try { var connection = new autobahn.Connection({ url: 'ws://127.0.0.1:8080/ws', - realm: 'realm1'} + realm: 'crossbardemo'} ); connection.onopen = function (session) { diff --git a/examples/twisted/wamp/basic/pubsub/unsubscribe/backend.py b/examples/twisted/wamp/pubsub/unsubscribe/backend.py similarity index 80% rename from examples/twisted/wamp/basic/pubsub/unsubscribe/backend.py rename to examples/twisted/wamp/pubsub/unsubscribe/backend.py index 436228db..5f1d0240 100644 --- a/examples/twisted/wamp/basic/pubsub/unsubscribe/backend.py +++ b/examples/twisted/wamp/pubsub/unsubscribe/backend.py @@ -24,14 +24,16 @@ # ############################################################################### +from __future__ import print_function + +from os import environ from twisted.internet.defer import inlineCallbacks from autobahn.twisted.util import sleep -from autobahn.twisted.wamp import ApplicationSession +from autobahn.twisted.wamp import ApplicationSession, ApplicationRunner class Component(ApplicationSession): - """ An application component that publishes an event every second. """ @@ -42,13 +44,17 @@ class Component(ApplicationSession): counter = 0 while True: - print(".") + print("publish: com.myapp.topic1", counter) self.publish('com.myapp.topic1', counter) counter += 1 yield sleep(1) 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) diff --git a/examples/twisted/wamp/basic/pubsub/unsubscribe/frontend.html b/examples/twisted/wamp/pubsub/unsubscribe/frontend.html similarity index 100% rename from examples/twisted/wamp/basic/pubsub/unsubscribe/frontend.html rename to examples/twisted/wamp/pubsub/unsubscribe/frontend.html diff --git a/examples/twisted/wamp/basic/pubsub/unsubscribe/frontend.js b/examples/twisted/wamp/pubsub/unsubscribe/frontend.js similarity index 97% rename from examples/twisted/wamp/basic/pubsub/unsubscribe/frontend.js rename to examples/twisted/wamp/pubsub/unsubscribe/frontend.js index 35756351..36534d67 100644 --- a/examples/twisted/wamp/basic/pubsub/unsubscribe/frontend.js +++ b/examples/twisted/wamp/pubsub/unsubscribe/frontend.js @@ -7,7 +7,7 @@ try { var connection = new autobahn.Connection({ url: 'ws://127.0.0.1:8080/ws', - realm: 'realm1'} + realm: 'crossbardemo'} ); connection.onopen = function (session) { diff --git a/examples/twisted/wamp/basic/pubsub/unsubscribe/frontend.py b/examples/twisted/wamp/pubsub/unsubscribe/frontend.py similarity index 69% rename from examples/twisted/wamp/basic/pubsub/unsubscribe/frontend.py rename to examples/twisted/wamp/pubsub/unsubscribe/frontend.py index 69b77208..ae37308d 100644 --- a/examples/twisted/wamp/basic/pubsub/unsubscribe/frontend.py +++ b/examples/twisted/wamp/pubsub/unsubscribe/frontend.py @@ -24,15 +24,16 @@ # ############################################################################### +from __future__ import print_function + +from os import environ from twisted.internet import reactor from twisted.internet.defer import inlineCallbacks -from autobahn.twisted.util import sleep -from autobahn.twisted.wamp import ApplicationSession +from autobahn.twisted.wamp import ApplicationSession, ApplicationRunner class Component(ApplicationSession): - """ An application component that subscribes and receives events. After receiving 5 events, it unsubscribes, sleeps and then @@ -41,29 +42,26 @@ class Component(ApplicationSession): @inlineCallbacks def test(self): - self.received = 0 + self.sub = yield self.subscribe(self.on_event, 'com.myapp.topic1') + print("Subscribed with subscription ID {}".format(self.sub.id)) - @inlineCallbacks - def on_event(i): - print("Got event: {}".format(i)) - self.received += 1 - if self.received > 5: - self.runs += 1 - if self.runs > 1: - self.leave() - else: - yield self.subscription.unsubscribe() - print("Unsubscribed .. continue in 2s ..") - reactor.callLater(2, self.test) - - self.subscription = yield self.subscribe(on_event, 'com.myapp.topic1') - print("Subscribed with subscription ID {}".format(self.subscription.id)) + @inlineCallbacks + def on_event(self, i): + print("Got event: {}".format(i)) + self.received += 1 + if self.received > 5: + self.runs += 1 + if self.runs > 1: + self.leave() + else: + yield self.sub.unsubscribe() + print("Unsubscribed .. continue in 5s ..") + reactor.callLater(5, self.test) @inlineCallbacks def onJoin(self, details): print("session attached") - self.runs = 0 yield self.test() @@ -73,6 +71,10 @@ class Component(ApplicationSession): 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) diff --git a/examples/twisted/wamp/basic/rpc/arguments/backend.html b/examples/twisted/wamp/rpc/arguments/backend.html similarity index 100% rename from examples/twisted/wamp/basic/rpc/arguments/backend.html rename to examples/twisted/wamp/rpc/arguments/backend.html diff --git a/examples/twisted/wamp/basic/rpc/arguments/backend.js b/examples/twisted/wamp/rpc/arguments/backend.js similarity index 96% rename from examples/twisted/wamp/basic/rpc/arguments/backend.js rename to examples/twisted/wamp/rpc/arguments/backend.js index b883089a..7721d469 100644 --- a/examples/twisted/wamp/basic/rpc/arguments/backend.js +++ b/examples/twisted/wamp/rpc/arguments/backend.js @@ -9,12 +9,12 @@ try { var connection = new autobahn.Connection({ url: 'ws://127.0.0.1:8080/ws', - realm: 'realm1'} + realm: 'crossbardemo'} ); connection.onopen = function (session) { - function ping() { + function ping() { } function add2(args) { diff --git a/examples/twisted/wamp/basic/rpc/arguments/backend.py b/examples/twisted/wamp/rpc/arguments/backend.py similarity index 84% rename from examples/twisted/wamp/basic/rpc/arguments/backend.py rename to examples/twisted/wamp/rpc/arguments/backend.py index bb4c2b2e..6a144bf6 100644 --- a/examples/twisted/wamp/basic/rpc/arguments/backend.py +++ b/examples/twisted/wamp/rpc/arguments/backend.py @@ -24,15 +24,16 @@ # ############################################################################### +from os import environ from twisted.internet.defer import inlineCallbacks -from autobahn.twisted.wamp import ApplicationSession +from autobahn.twisted.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. """ @inlineCallbacks @@ -59,10 +60,14 @@ class Component(ApplicationSession): yield self.register(stars, u'com.arguments.stars') yield self.register(orders, u'com.arguments.orders') yield self.register(arglen, u'com.arguments.arglen') - print("procedures registered") + print("Procedures registered; ready for frontend.") 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) diff --git a/examples/twisted/wamp/basic/rpc/arguments/frontend.html b/examples/twisted/wamp/rpc/arguments/frontend.html similarity index 100% rename from examples/twisted/wamp/basic/rpc/arguments/frontend.html rename to examples/twisted/wamp/rpc/arguments/frontend.html diff --git a/examples/twisted/wamp/basic/rpc/arguments/frontend.js b/examples/twisted/wamp/rpc/arguments/frontend.js similarity index 98% rename from examples/twisted/wamp/basic/rpc/arguments/frontend.js rename to examples/twisted/wamp/rpc/arguments/frontend.js index 86ce30f5..ed8e0862 100644 --- a/examples/twisted/wamp/basic/rpc/arguments/frontend.js +++ b/examples/twisted/wamp/rpc/arguments/frontend.js @@ -9,7 +9,7 @@ try { var connection = new autobahn.Connection({ url: 'ws://127.0.0.1:8080/ws', - realm: 'realm1'} + realm: 'crossbardemo'} ); connection.onopen = function (session) { diff --git a/examples/twisted/wamp/basic/rpc/arguments/frontend.py b/examples/twisted/wamp/rpc/arguments/frontend.py similarity index 90% rename from examples/twisted/wamp/basic/rpc/arguments/frontend.py rename to examples/twisted/wamp/rpc/arguments/frontend.py index cb38c6d2..1529d111 100644 --- a/examples/twisted/wamp/basic/rpc/arguments/frontend.py +++ b/examples/twisted/wamp/rpc/arguments/frontend.py @@ -24,14 +24,14 @@ # ############################################################################### +from os import environ from twisted.internet import reactor from twisted.internet.defer import inlineCallbacks -from autobahn.twisted.wamp import ApplicationSession +from autobahn.twisted.wamp import ApplicationSession, ApplicationRunner class Component(ApplicationSession): - """ An application component calling the different backend procedures. """ @@ -84,6 +84,10 @@ class Component(ApplicationSession): 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) diff --git a/examples/twisted/wamp/basic/rpc/complex/backend.html b/examples/twisted/wamp/rpc/complex/backend.html similarity index 100% rename from examples/twisted/wamp/basic/rpc/complex/backend.html rename to examples/twisted/wamp/rpc/complex/backend.html diff --git a/examples/twisted/wamp/basic/rpc/complex/backend.js b/examples/twisted/wamp/rpc/complex/backend.js similarity index 97% rename from examples/twisted/wamp/basic/rpc/complex/backend.js rename to examples/twisted/wamp/rpc/complex/backend.js index 709d8419..20734f44 100644 --- a/examples/twisted/wamp/basic/rpc/complex/backend.js +++ b/examples/twisted/wamp/rpc/complex/backend.js @@ -7,7 +7,7 @@ try { var connection = new autobahn.Connection({ url: 'ws://127.0.0.1:8080/ws', - realm: 'realm1'} + realm: 'crossbardemo'} ); connection.onopen = function (session) { diff --git a/examples/twisted/wamp/basic/rpc/complex/backend.py b/examples/twisted/wamp/rpc/complex/backend.py similarity index 85% rename from examples/twisted/wamp/basic/rpc/complex/backend.py rename to examples/twisted/wamp/rpc/complex/backend.py index 0dda5a64..51765aa9 100644 --- a/examples/twisted/wamp/basic/rpc/complex/backend.py +++ b/examples/twisted/wamp/rpc/complex/backend.py @@ -24,10 +24,11 @@ # ############################################################################### +from os import environ from twisted.internet.defer import inlineCallbacks from autobahn.wamp.types import CallResult -from autobahn.twisted.wamp import ApplicationSession +from autobahn.twisted.wamp import ApplicationSession, ApplicationRunner class Component(ApplicationSession): @@ -56,6 +57,10 @@ class Component(ApplicationSession): 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) diff --git a/examples/twisted/wamp/basic/rpc/complex/frontend.html b/examples/twisted/wamp/rpc/complex/frontend.html similarity index 100% rename from examples/twisted/wamp/basic/rpc/complex/frontend.html rename to examples/twisted/wamp/rpc/complex/frontend.html diff --git a/examples/twisted/wamp/basic/rpc/complex/frontend.js b/examples/twisted/wamp/rpc/complex/frontend.js similarity index 97% rename from examples/twisted/wamp/basic/rpc/complex/frontend.js rename to examples/twisted/wamp/rpc/complex/frontend.js index 9c2394c0..94b0fada 100644 --- a/examples/twisted/wamp/basic/rpc/complex/frontend.js +++ b/examples/twisted/wamp/rpc/complex/frontend.js @@ -9,7 +9,7 @@ try { var connection = new autobahn.Connection({ url: 'ws://127.0.0.1:8080/ws', - realm: 'realm1'} + realm: 'crossbardemo'} ); connection.onopen = function (session) { diff --git a/examples/twisted/wamp/basic/rpc/complex/frontend.py b/examples/twisted/wamp/rpc/complex/frontend.py similarity index 85% rename from examples/twisted/wamp/basic/rpc/complex/frontend.py rename to examples/twisted/wamp/rpc/complex/frontend.py index 20d76f33..3f513895 100644 --- a/examples/twisted/wamp/basic/rpc/complex/frontend.py +++ b/examples/twisted/wamp/rpc/complex/frontend.py @@ -24,14 +24,14 @@ # ############################################################################### +from os import environ from twisted.internet import reactor from twisted.internet.defer import inlineCallbacks -from autobahn.twisted.wamp import ApplicationSession +from autobahn.twisted.wamp import ApplicationSession, ApplicationRunner class Component(ApplicationSession): - """ Application component that calls procedures which produce complex results and showing how to access those. @@ -55,6 +55,10 @@ class Component(ApplicationSession): 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) diff --git a/examples/twisted/wamp/basic/rpc/decorators/backend.py b/examples/twisted/wamp/rpc/decorators/backend.py similarity index 58% rename from examples/twisted/wamp/basic/rpc/decorators/backend.py rename to examples/twisted/wamp/rpc/decorators/backend.py index 25e684d1..f571e864 100644 --- a/examples/twisted/wamp/basic/rpc/decorators/backend.py +++ b/examples/twisted/wamp/rpc/decorators/backend.py @@ -24,19 +24,20 @@ # ############################################################################### +from os import environ from twisted.internet.defer import inlineCallbacks +from twisted.python.failure import Failure from autobahn import wamp -from autobahn.twisted.wamp import ApplicationSession +from autobahn.twisted.wamp import ApplicationSession, ApplicationRunner -class MyService1: - - @wamp.register('com.mathservice.add2') +class MyService1(object): + @wamp.register(u'com.mathservice.add2') def add2(self, x, y): return x + y - @wamp.register('com.mathservice.mul2') + @wamp.register(u'com.mathservice.mul2') def mul2(self, x, y): return x * y @@ -51,26 +52,33 @@ class Component(ApplicationSession): def onJoin(self, details): print("session attached") - # register all methods on this object decorated with "@wamp.register" - # as a RPC endpoint - ## + # to use this session to register all the @register decorated + # methods, we call register with the object; so here we create + # a MyService1 instance and register all the methods on it and + # on ourselves + + results = [] svc1 = MyService1() - for obj in [self, svc1]: - results = yield self.register(obj) - for success, res in results: - if success: - # res is an Registration instance - print("Ok, registered procedure on {} with registration ID {}".format(obj, res.id)) - else: - # res is an Failure instance - print("Failed to register procedure: {}".format(res.value)) + # register all @register-decorated methods from "svc1": + res = yield self.register(svc1) + results.extend(res) - @wamp.register('com.mathservice.square2') + # register all our own @register-decorated methods: + res = yield self.register(self) + results.extend(res) + + for res in results: + if isinstance(res, Failure): + print("Failed to register procedure: {}".format(res.value)) + else: + print("registration ID {}: {}".format(res.id, res.procedure)) + + @wamp.register(u'com.mathservice.square2') def square2(self, x, y): return x * x + y * y - @wamp.register('com.mathservice.div2') + @wamp.register(u'com.mathservice.div2') def div2(self, x, y): if y: return float(x) / float(y) @@ -79,6 +87,10 @@ class Component(ApplicationSession): 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) diff --git a/examples/twisted/wamp/basic/rpc/decorators/frontend.html b/examples/twisted/wamp/rpc/decorators/frontend.html similarity index 100% rename from examples/twisted/wamp/basic/rpc/decorators/frontend.html rename to examples/twisted/wamp/rpc/decorators/frontend.html diff --git a/examples/twisted/wamp/basic/rpc/decorators/frontend.js b/examples/twisted/wamp/rpc/decorators/frontend.js similarity index 97% rename from examples/twisted/wamp/basic/rpc/decorators/frontend.js rename to examples/twisted/wamp/rpc/decorators/frontend.js index f8225f4c..c6419d48 100644 --- a/examples/twisted/wamp/basic/rpc/decorators/frontend.js +++ b/examples/twisted/wamp/rpc/decorators/frontend.js @@ -9,7 +9,7 @@ try { var connection = new autobahn.Connection({ url: 'ws://127.0.0.1:8080/ws', - realm: 'realm1'} + realm: 'crossbardemo'} ); connection.onopen = function (session) { diff --git a/examples/twisted/wamp/basic/rpc/decorators/frontend.py b/examples/twisted/wamp/rpc/decorators/frontend.py similarity index 86% rename from examples/twisted/wamp/basic/rpc/decorators/frontend.py rename to examples/twisted/wamp/rpc/decorators/frontend.py index ad450a22..ac41d9e2 100644 --- a/examples/twisted/wamp/basic/rpc/decorators/frontend.py +++ b/examples/twisted/wamp/rpc/decorators/frontend.py @@ -24,14 +24,14 @@ # ############################################################################### +from os import environ from twisted.internet import reactor from twisted.internet.defer import inlineCallbacks -from autobahn.twisted.wamp import ApplicationSession +from autobahn.twisted.wamp import ApplicationSession, ApplicationRunner class Component(ApplicationSession): - """ An application component calling the different backend procedures. """ @@ -60,6 +60,10 @@ class Component(ApplicationSession): 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) diff --git a/examples/twisted/wamp/basic/rpc/errors/backend.html b/examples/twisted/wamp/rpc/errors/backend.html similarity index 100% rename from examples/twisted/wamp/basic/rpc/errors/backend.html rename to examples/twisted/wamp/rpc/errors/backend.html diff --git a/examples/twisted/wamp/basic/rpc/errors/backend.js b/examples/twisted/wamp/rpc/errors/backend.js similarity index 97% rename from examples/twisted/wamp/basic/rpc/errors/backend.js rename to examples/twisted/wamp/rpc/errors/backend.js index 446d95d3..ce074d90 100644 --- a/examples/twisted/wamp/basic/rpc/errors/backend.js +++ b/examples/twisted/wamp/rpc/errors/backend.js @@ -7,7 +7,7 @@ try { var connection = new autobahn.Connection({ url: 'ws://127.0.0.1:8080/ws', - realm: 'realm1'} + realm: 'crossbardemo'} ); connection.onopen = function (session) { diff --git a/examples/twisted/wamp/basic/rpc/errors/backend.py b/examples/twisted/wamp/rpc/errors/backend.py similarity index 87% rename from examples/twisted/wamp/basic/rpc/errors/backend.py rename to examples/twisted/wamp/rpc/errors/backend.py index 3777f87b..3a5b228f 100644 --- a/examples/twisted/wamp/basic/rpc/errors/backend.py +++ b/examples/twisted/wamp/rpc/errors/backend.py @@ -25,17 +25,17 @@ ############################################################################### import math +from os import environ from twisted.internet.defer import inlineCallbacks from autobahn import wamp from autobahn.wamp.exception import ApplicationError -from autobahn.twisted.wamp import ApplicationSession +from autobahn.twisted.wamp import ApplicationSession, ApplicationRunner -@wamp.error("com.myapp.error1") +@wamp.error(u"com.myapp.error1") class AppError1(Exception): - """ An application specific exception that is decorated with a WAMP URI, and hence can be automapped by Autobahn. @@ -43,7 +43,6 @@ class AppError1(Exception): class Component(ApplicationSession): - """ Example WAMP application backend that raised exceptions. """ @@ -61,7 +60,7 @@ class Component(ApplicationSession): # this also will raise, if x < 0 return math.sqrt(x) - yield self.register(sqrt, 'com.myapp.sqrt') + yield self.register(sqrt, u'com.myapp.sqrt') # raising WAMP application exceptions ## @@ -93,6 +92,10 @@ class Component(ApplicationSession): 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) diff --git a/examples/twisted/wamp/basic/rpc/errors/frontend.html b/examples/twisted/wamp/rpc/errors/frontend.html similarity index 100% rename from examples/twisted/wamp/basic/rpc/errors/frontend.html rename to examples/twisted/wamp/rpc/errors/frontend.html diff --git a/examples/twisted/wamp/basic/rpc/errors/frontend.js b/examples/twisted/wamp/rpc/errors/frontend.js similarity index 96% rename from examples/twisted/wamp/basic/rpc/errors/frontend.js rename to examples/twisted/wamp/rpc/errors/frontend.js index 396e2068..6bff47a2 100644 --- a/examples/twisted/wamp/basic/rpc/errors/frontend.js +++ b/examples/twisted/wamp/rpc/errors/frontend.js @@ -9,7 +9,7 @@ try { var connection = new autobahn.Connection({ url: 'ws://127.0.0.1:8080/ws', - realm: 'realm1'} + realm: 'crossbardemo'} ); connection.onopen = function (session) { diff --git a/examples/twisted/wamp/basic/rpc/errors/frontend.py b/examples/twisted/wamp/rpc/errors/frontend.py similarity index 86% rename from examples/twisted/wamp/basic/rpc/errors/frontend.py rename to examples/twisted/wamp/rpc/errors/frontend.py index 9d695728..61dd92e7 100644 --- a/examples/twisted/wamp/basic/rpc/errors/frontend.py +++ b/examples/twisted/wamp/rpc/errors/frontend.py @@ -25,18 +25,18 @@ ############################################################################### import math +from os import environ from twisted.internet import reactor from twisted.internet.defer import inlineCallbacks from autobahn import wamp from autobahn.wamp.exception import ApplicationError -from autobahn.twisted.wamp import ApplicationSession +from autobahn.twisted.wamp import ApplicationSession, ApplicationRunner -@wamp.error("com.myapp.error1") +@wamp.error(u"com.myapp.error1") class AppError1(Exception): - """ An application specific exception that is decorated with a WAMP URI, and hence can be automapped by Autobahn. @@ -44,7 +44,6 @@ class AppError1(Exception): class Component(ApplicationSession): - """ Example WAMP application frontend that catches exceptions. """ @@ -82,6 +81,7 @@ class Component(ApplicationSession): except AppError1 as e: print("Compare Error: {}".format(e)) + print("Exiting; we received only errors we expected.") self.leave() def onDisconnect(self): @@ -90,6 +90,10 @@ class Component(ApplicationSession): 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) diff --git a/examples/twisted/wamp/basic/rpc/options/backend.html b/examples/twisted/wamp/rpc/options/backend.html similarity index 100% rename from examples/twisted/wamp/basic/rpc/options/backend.html rename to examples/twisted/wamp/rpc/options/backend.html diff --git a/examples/twisted/wamp/basic/rpc/options/backend.js b/examples/twisted/wamp/rpc/options/backend.js similarity index 97% rename from examples/twisted/wamp/basic/rpc/options/backend.js rename to examples/twisted/wamp/rpc/options/backend.js index 43087fe6..64d96870 100644 --- a/examples/twisted/wamp/basic/rpc/options/backend.js +++ b/examples/twisted/wamp/rpc/options/backend.js @@ -7,7 +7,7 @@ try { var connection = new autobahn.Connection({ url: 'ws://127.0.0.1:8080/ws', - realm: 'realm1'} + realm: 'crossbardemo'} ); connection.onopen = function (session) { diff --git a/examples/twisted/wamp/basic/rpc/options/backend.py b/examples/twisted/wamp/rpc/options/backend.py similarity index 87% rename from examples/twisted/wamp/basic/rpc/options/backend.py rename to examples/twisted/wamp/rpc/options/backend.py index d93a9d74..20c82c33 100644 --- a/examples/twisted/wamp/basic/rpc/options/backend.py +++ b/examples/twisted/wamp/rpc/options/backend.py @@ -24,10 +24,11 @@ # ############################################################################### +from os import environ from twisted.internet.defer import inlineCallbacks from autobahn.wamp.types import RegisterOptions, PublishOptions -from autobahn.twisted.wamp import ApplicationSession +from autobahn.twisted.wamp import ApplicationSession, ApplicationRunner class Component(ApplicationSession): @@ -60,6 +61,10 @@ class Component(ApplicationSession): 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) diff --git a/examples/twisted/wamp/basic/rpc/options/frontend.html b/examples/twisted/wamp/rpc/options/frontend.html similarity index 100% rename from examples/twisted/wamp/basic/rpc/options/frontend.html rename to examples/twisted/wamp/rpc/options/frontend.html diff --git a/examples/twisted/wamp/basic/rpc/options/frontend.js b/examples/twisted/wamp/rpc/options/frontend.js similarity index 97% rename from examples/twisted/wamp/basic/rpc/options/frontend.js rename to examples/twisted/wamp/rpc/options/frontend.js index 6df48bf3..6e18b18a 100644 --- a/examples/twisted/wamp/basic/rpc/options/frontend.js +++ b/examples/twisted/wamp/rpc/options/frontend.js @@ -9,7 +9,7 @@ try { var connection = new autobahn.Connection({ url: 'ws://127.0.0.1:8080/ws', - realm: 'realm1'} + realm: 'crossbardemo'} ); connection.onopen = function (session) { diff --git a/examples/twisted/wamp/basic/rpc/options/frontend.py b/examples/twisted/wamp/rpc/options/frontend.py similarity index 84% rename from examples/twisted/wamp/basic/rpc/options/frontend.py rename to examples/twisted/wamp/rpc/options/frontend.py index a8972fbb..946df70a 100644 --- a/examples/twisted/wamp/basic/rpc/options/frontend.py +++ b/examples/twisted/wamp/rpc/options/frontend.py @@ -24,15 +24,15 @@ # ############################################################################### +from os import environ from twisted.internet import reactor from twisted.internet.defer import inlineCallbacks from autobahn.wamp.types import CallOptions -from autobahn.twisted.wamp import ApplicationSession +from autobahn.twisted.wamp import ApplicationSession, ApplicationRunner class Component(ApplicationSession): - """ An application component calling the different backend procedures. """ @@ -47,7 +47,7 @@ class Component(ApplicationSession): yield self.subscribe(on_event, 'com.myapp.square_on_nonpositive') for val in [2, 0, -2]: - res = yield self.call('com.myapp.square', val, options=CallOptions(discloseMe=True)) + res = yield self.call('com.myapp.square', val, options=CallOptions(disclose_me=True)) print("Squared {} = {}".format(val, res)) self.leave() @@ -58,6 +58,10 @@ class Component(ApplicationSession): 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) diff --git a/examples/twisted/wamp/basic/rpc/progress/backend.html b/examples/twisted/wamp/rpc/progress/backend.html similarity index 100% rename from examples/twisted/wamp/basic/rpc/progress/backend.html rename to examples/twisted/wamp/rpc/progress/backend.html diff --git a/examples/twisted/wamp/basic/rpc/progress/backend.js b/examples/twisted/wamp/rpc/progress/backend.js similarity index 97% rename from examples/twisted/wamp/basic/rpc/progress/backend.js rename to examples/twisted/wamp/rpc/progress/backend.js index d8906e42..5498e6bf 100644 --- a/examples/twisted/wamp/basic/rpc/progress/backend.js +++ b/examples/twisted/wamp/rpc/progress/backend.js @@ -9,7 +9,7 @@ try { var connection = new autobahn.Connection({ url: 'ws://127.0.0.1:8080/ws', - realm: 'realm1'} + realm: 'crossbardemo'} ); connection.onopen = function (session) { diff --git a/examples/twisted/wamp/basic/rpc/progress/backend.py b/examples/twisted/wamp/rpc/progress/backend.py similarity index 86% rename from examples/twisted/wamp/basic/rpc/progress/backend.py rename to examples/twisted/wamp/rpc/progress/backend.py index 593d91e7..4b71814f 100644 --- a/examples/twisted/wamp/basic/rpc/progress/backend.py +++ b/examples/twisted/wamp/rpc/progress/backend.py @@ -24,15 +24,15 @@ # ############################################################################### +from os import environ from twisted.internet.defer import inlineCallbacks, returnValue from autobahn.wamp.types import RegisterOptions from autobahn.twisted.util import sleep -from autobahn.twisted.wamp import ApplicationSession +from autobahn.twisted.wamp import ApplicationSession, ApplicationRunner class Component(ApplicationSession): - """ Application component that produces progressive results. """ @@ -59,6 +59,10 @@ class Component(ApplicationSession): 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) diff --git a/examples/twisted/wamp/basic/rpc/progress/frontend.html b/examples/twisted/wamp/rpc/progress/frontend.html similarity index 100% rename from examples/twisted/wamp/basic/rpc/progress/frontend.html rename to examples/twisted/wamp/rpc/progress/frontend.html diff --git a/examples/twisted/wamp/basic/rpc/progress/frontend.js b/examples/twisted/wamp/rpc/progress/frontend.js similarity index 95% rename from examples/twisted/wamp/basic/rpc/progress/frontend.js rename to examples/twisted/wamp/rpc/progress/frontend.js index d23583e3..118fe2da 100644 --- a/examples/twisted/wamp/basic/rpc/progress/frontend.js +++ b/examples/twisted/wamp/rpc/progress/frontend.js @@ -7,7 +7,7 @@ try { var connection = new autobahn.Connection({ url: 'ws://127.0.0.1:8080/ws', - realm: 'realm1'} + realm: 'crossbardemo'} ); connection.onopen = function (session) { diff --git a/examples/twisted/wamp/basic/rpc/progress/frontend.py b/examples/twisted/wamp/rpc/progress/frontend.py similarity index 85% rename from examples/twisted/wamp/basic/rpc/progress/frontend.py rename to examples/twisted/wamp/rpc/progress/frontend.py index 57118c88..00c6b3d5 100644 --- a/examples/twisted/wamp/basic/rpc/progress/frontend.py +++ b/examples/twisted/wamp/rpc/progress/frontend.py @@ -24,15 +24,15 @@ # ############################################################################### +from os import environ from twisted.internet import reactor from twisted.internet.defer import inlineCallbacks from autobahn.wamp.types import CallOptions -from autobahn.twisted.wamp import ApplicationSession +from autobahn.twisted.wamp import ApplicationSession, ApplicationRunner class Component(ApplicationSession): - """ Application component that consumes progressive results. """ @@ -56,6 +56,10 @@ class Component(ApplicationSession): 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) diff --git a/examples/twisted/wamp/basic/rpc/slowsquare/backend.html b/examples/twisted/wamp/rpc/slowsquare/backend.html similarity index 100% rename from examples/twisted/wamp/basic/rpc/slowsquare/backend.html rename to examples/twisted/wamp/rpc/slowsquare/backend.html diff --git a/examples/twisted/wamp/basic/rpc/slowsquare/backend.js b/examples/twisted/wamp/rpc/slowsquare/backend.js similarity index 97% rename from examples/twisted/wamp/basic/rpc/slowsquare/backend.js rename to examples/twisted/wamp/rpc/slowsquare/backend.js index 2bf88333..fca216b3 100644 --- a/examples/twisted/wamp/basic/rpc/slowsquare/backend.js +++ b/examples/twisted/wamp/rpc/slowsquare/backend.js @@ -9,7 +9,7 @@ try { var connection = new autobahn.Connection({ url: 'ws://127.0.0.1:8080/ws', - realm: 'realm1'} + realm: 'crossbardemo'} ); connection.onopen = function (session) { diff --git a/examples/twisted/wamp/basic/rpc/slowsquare/backend.py b/examples/twisted/wamp/rpc/slowsquare/backend.py similarity index 84% rename from examples/twisted/wamp/basic/rpc/slowsquare/backend.py rename to examples/twisted/wamp/rpc/slowsquare/backend.py index 3164140f..beb1a319 100644 --- a/examples/twisted/wamp/basic/rpc/slowsquare/backend.py +++ b/examples/twisted/wamp/rpc/slowsquare/backend.py @@ -24,15 +24,15 @@ # ############################################################################### +from os import environ from twisted.internet.defer import inlineCallbacks, \ returnValue -from autobahn.twisted.wamp import ApplicationSession +from autobahn.twisted.wamp import ApplicationSession, ApplicationRunner from autobahn.twisted.util import sleep class Component(ApplicationSession): - """ A math service application component. """ @@ -57,6 +57,10 @@ class Component(ApplicationSession): 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) diff --git a/examples/twisted/wamp/basic/rpc/slowsquare/frontend.html b/examples/twisted/wamp/rpc/slowsquare/frontend.html similarity index 100% rename from examples/twisted/wamp/basic/rpc/slowsquare/frontend.html rename to examples/twisted/wamp/rpc/slowsquare/frontend.html diff --git a/examples/twisted/wamp/basic/rpc/slowsquare/frontend.js b/examples/twisted/wamp/rpc/slowsquare/frontend.js similarity index 97% rename from examples/twisted/wamp/basic/rpc/slowsquare/frontend.js rename to examples/twisted/wamp/rpc/slowsquare/frontend.js index fa0bf405..58d4d84a 100644 --- a/examples/twisted/wamp/basic/rpc/slowsquare/frontend.js +++ b/examples/twisted/wamp/rpc/slowsquare/frontend.js @@ -11,7 +11,7 @@ try { var connection = new autobahn.Connection({ url: 'ws://127.0.0.1:8080/ws', - realm: 'realm1'} + realm: 'crossbardemo'} ); connection.onopen = function (session) { diff --git a/examples/twisted/wamp/basic/rpc/slowsquare/frontend.py b/examples/twisted/wamp/rpc/slowsquare/frontend.py similarity index 86% rename from examples/twisted/wamp/basic/rpc/slowsquare/frontend.py rename to examples/twisted/wamp/rpc/slowsquare/frontend.py index a46a4e36..3e2b6904 100644 --- a/examples/twisted/wamp/basic/rpc/slowsquare/frontend.py +++ b/examples/twisted/wamp/rpc/slowsquare/frontend.py @@ -25,15 +25,15 @@ ############################################################################### import time +from os import environ from twisted.internet import reactor from twisted.internet.defer import DeferredList -from autobahn.twisted.wamp import ApplicationSession +from autobahn.twisted.wamp import ApplicationSession, ApplicationRunner class Component(ApplicationSession): - """ An application component using the time service. """ @@ -65,6 +65,10 @@ class Component(ApplicationSession): 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) diff --git a/examples/twisted/wamp/basic/rpc/timeservice/backend.html b/examples/twisted/wamp/rpc/timeservice/backend.html similarity index 100% rename from examples/twisted/wamp/basic/rpc/timeservice/backend.html rename to examples/twisted/wamp/rpc/timeservice/backend.html diff --git a/examples/twisted/wamp/basic/rpc/timeservice/backend.js b/examples/twisted/wamp/rpc/timeservice/backend.js similarity index 96% rename from examples/twisted/wamp/basic/rpc/timeservice/backend.js rename to examples/twisted/wamp/rpc/timeservice/backend.js index 754eb103..89826887 100644 --- a/examples/twisted/wamp/basic/rpc/timeservice/backend.js +++ b/examples/twisted/wamp/rpc/timeservice/backend.js @@ -7,7 +7,7 @@ try { var connection = new autobahn.Connection({ url: 'ws://127.0.0.1:8080/ws', - realm: 'realm1'} + realm: 'crossbardemo'} ); connection.onopen = function (session) { diff --git a/examples/twisted/wamp/basic/rpc/timeservice/backend.py b/examples/twisted/wamp/rpc/timeservice/backend.py similarity index 84% rename from examples/twisted/wamp/basic/rpc/timeservice/backend.py rename to examples/twisted/wamp/rpc/timeservice/backend.py index bf448031..a61eb854 100644 --- a/examples/twisted/wamp/basic/rpc/timeservice/backend.py +++ b/examples/twisted/wamp/rpc/timeservice/backend.py @@ -24,15 +24,15 @@ # ############################################################################### +from os import environ import datetime from twisted.internet.defer import inlineCallbacks -from autobahn.twisted.wamp import ApplicationSession +from autobahn.twisted.wamp import ApplicationSession, ApplicationRunner class Component(ApplicationSession): - """ A simple time service application component. """ @@ -54,6 +54,10 @@ class Component(ApplicationSession): if __name__ == '__main__': - from autobahn.twisted.wamp import ApplicationRunner - runner = ApplicationRunner("ws://127.0.0.1:8080/ws", "realm1", standalone=True, debug=False) + 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) diff --git a/examples/twisted/wamp/basic/rpc/timeservice/frontend.html b/examples/twisted/wamp/rpc/timeservice/frontend.html similarity index 100% rename from examples/twisted/wamp/basic/rpc/timeservice/frontend.html rename to examples/twisted/wamp/rpc/timeservice/frontend.html diff --git a/examples/twisted/wamp/basic/rpc/timeservice/frontend.js b/examples/twisted/wamp/rpc/timeservice/frontend.js similarity index 95% rename from examples/twisted/wamp/basic/rpc/timeservice/frontend.js rename to examples/twisted/wamp/rpc/timeservice/frontend.js index a230df95..01b883e2 100644 --- a/examples/twisted/wamp/basic/rpc/timeservice/frontend.js +++ b/examples/twisted/wamp/rpc/timeservice/frontend.js @@ -7,7 +7,7 @@ try { var connection = new autobahn.Connection({ url: 'ws://127.0.0.1:8080/ws', - realm: 'realm1'} + realm: 'crossbardemo'} ); connection.onopen = function (session) { diff --git a/examples/twisted/wamp/basic/rpc/timeservice/frontend.py b/examples/twisted/wamp/rpc/timeservice/frontend.py similarity index 84% rename from examples/twisted/wamp/basic/rpc/timeservice/frontend.py rename to examples/twisted/wamp/rpc/timeservice/frontend.py index 53000059..8f8f07d5 100644 --- a/examples/twisted/wamp/basic/rpc/timeservice/frontend.py +++ b/examples/twisted/wamp/rpc/timeservice/frontend.py @@ -24,14 +24,14 @@ # ############################################################################### +from os import environ from twisted.internet import reactor from twisted.internet.defer import inlineCallbacks -from autobahn.twisted.wamp import ApplicationSession +from autobahn.twisted.wamp import ApplicationSession, ApplicationRunner class Component(ApplicationSession): - """ An application component using the time service. """ @@ -54,6 +54,10 @@ class Component(ApplicationSession): 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) diff --git a/examples/twisted/wamp/session/__init__.py b/examples/twisted/wamp/session/__init__.py deleted file mode 100644 index 8a4c67bf..00000000 --- a/examples/twisted/wamp/session/__init__.py +++ /dev/null @@ -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. -# -############################################################################### diff --git a/examples/twisted/wamp/session/fromoutside/README.md b/examples/twisted/wamp/session/fromoutside/README.md deleted file mode 100644 index 6480d528..00000000 --- a/examples/twisted/wamp/session/fromoutside/README.md +++ /dev/null @@ -1,13 +0,0 @@ -This example demonstrates how to access an app session instance from outside - via the session factory. - -It runs an application component as a client connected to a WAMP router. - -Start a WAMP router: - - python ../../server.py - -Start the backend component (which will run inside a client connecting to the router): - - python client.py - -Open `frontend.html` in your browser. \ No newline at end of file diff --git a/examples/twisted/wamp/session/fromoutside/client.py b/examples/twisted/wamp/session/fromoutside/client.py deleted file mode 100644 index 4649224e..00000000 --- a/examples/twisted/wamp/session/fromoutside/client.py +++ /dev/null @@ -1,105 +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. -# -############################################################################### - -from twisted.internet.defer import inlineCallbacks - -from autobahn.twisted.util import sleep -from autobahn.twisted.wamp import ApplicationSession - - -class MyAppComponent(ApplicationSession): - - def onJoin(self, details): - if not self.factory._myAppSession: - self.factory._myAppSession = self - - def onLeave(self, details): - if self.factory._myAppSession == self: - self.factory._myAppSession = None - - -if __name__ == '__main__': - - import sys - - from twisted.python import log - from twisted.internet.endpoints import clientFromString - log.startLogging(sys.stdout) - - # we use an Autobahn utility to import the "best" available Twisted reactor - ## - from autobahn.twisted.choosereactor import install_reactor - reactor = install_reactor() - print("Running on reactor {}".format(reactor)) - - # create a WAMP application session factory - ## - from autobahn.twisted.wamp import ApplicationSessionFactory - session_factory = ApplicationSessionFactory() - - # .. and set the session class on the factory - ## - session_factory.session = MyAppComponent - - # since we are running this component as a client, there - # will be only 1 app session instance anyway. We'll store a - # reference on the session factory, so we can access it - # from "outside" the session instance later (see below) - ## - session_factory._myAppSession = None - - # create a WAMP-over-WebSocket transport client factory - ## - from autobahn.twisted.websocket import WampWebSocketClientFactory - transport_factory = WampWebSocketClientFactory(session_factory, "ws://127.0.0.1:8080/ws") - - # start a WebSocket client from an endpoint - ## - client = clientFromString(reactor, "tcp:127.0.0.1:8080") - client.connect(transport_factory) - - # publish an event every second from the (single) application session - # that get created by the session factory - ## - @inlineCallbacks - def pub(): - counter = 0 - while True: - # here we can access the app session that was created .. - ## - if session_factory._myAppSession: - session_factory._myAppSession.publish('com.myapp.topic123', counter) - print("published event", counter) - else: - print("no session") - counter += 1 - yield sleep(1) - - pub() - - # now enter the Twisted reactor loop - ## - reactor.run() diff --git a/examples/twisted/wamp/session/fromoutside/frontend.html b/examples/twisted/wamp/session/fromoutside/frontend.html deleted file mode 100644 index f1d81dc4..00000000 --- a/examples/twisted/wamp/session/fromoutside/frontend.html +++ /dev/null @@ -1,32 +0,0 @@ - - - -

PubSub Basic Frontend

-

Open JavaScript console to watch output.

- - - - diff --git a/examples/twisted/wamp/session/series/__init__.py b/examples/twisted/wamp/session/series/__init__.py deleted file mode 100644 index 8a4c67bf..00000000 --- a/examples/twisted/wamp/session/series/__init__.py +++ /dev/null @@ -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. -# -############################################################################### diff --git a/examples/twisted/wamp/session/series/frontend.py b/examples/twisted/wamp/session/series/frontend.py deleted file mode 100644 index a4462637..00000000 --- a/examples/twisted/wamp/session/series/frontend.py +++ /dev/null @@ -1,76 +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 twisted.internet import reactor -from twisted.internet.defer import inlineCallbacks - -from autobahn.twisted.wamp import ApplicationSession - - -class Component(ApplicationSession): - - """ - An application component using the time service - during 3 subsequent WAMP sessions, while the - underlying transport continues to exist. - """ - - def __init__(self, config): - ApplicationSession.__init__(self, config) - self.count = 0 - - @inlineCallbacks - def onJoin(self, details): - print("Realm joined (WAMP session started).") - - try: - now = yield self.call('com.timeservice.now') - except Exception as e: - print("Error: {}".format(e)) - else: - print("Current time from time service: {}".format(now)) - - self.leave() - - def onLeave(self, details): - print("Realm left (WAMP session ended).") - self.count += 1 - if self.count < 3: - self.join("realm1") - else: - self.disconnect() - - def onDisconnect(self): - print("Transport disconnected.") - reactor.stop() - - -if __name__ == '__main__': - from autobahn.twisted.wamp import ApplicationRunner - runner = ApplicationRunner("ws://127.0.0.1:8080/ws", "realm1") - runner.run(Component) diff --git a/examples/twisted/wamp/validation/README.md b/examples/twisted/wamp/validation/README.md deleted file mode 100644 index 4246960c..00000000 --- a/examples/twisted/wamp/validation/README.md +++ /dev/null @@ -1,45 +0,0 @@ -# Custom Validation - -WAMP is dynamically typed, which means, the application level payload of calls and events can be arbitrary, and the application components must be able to handle this. - -Also, a WAMP router won't care at all about the application payload transported, other than converting between different serialization formats. - -On the other hand, there are situations where we might want to impose some form of typing and validation to the application payloads. - -The basic router `autobahn.wamp.router.Router` includes a `validate` hook to validate the application payload of calls, call results, call errors and published events. - -To create a router that implements some form of payload validation, override the hook and throw exceptions whenever the payload does not conform. - - -## Example - -Here is a custom router that will validate event publication to topic `com.myapp.topic1`. The payload of events published to that topic MUST BE a single positional integer, and that integer MUST BE even. - -```python -from autobahn.wamp.exception import ApplicationError -from autobahn.wamp.router import Router - -class MyRouter(Router): - - def validate(self, payload_type, uri, args, kwargs): - if payload_type == 'event' and uri == 'com.myapp.topic1': - if len(args) == 1 and type(args[0]) == int and args[0] % 2 == 0 and kwargs is None: - return # ok, valid - else: - raise ApplicationError(ApplicationError.INVALID_ARGUMENT, "invalid event payload for topic {} - must be a single, even integer".format(uri)) -``` - -> Note that payloads of different type (other than `event`) or any other URI are not validated here, but simply accepted. - -To run this example, start the router - -```sh -python router.py -``` - -and then start the client with an application component - -```sh -python client.py -``` - diff --git a/examples/twisted/wamp/validation/client.py b/examples/twisted/wamp/validation/client.py deleted file mode 100644 index c79293b4..00000000 --- a/examples/twisted/wamp/validation/client.py +++ /dev/null @@ -1,78 +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. -# -############################################################################### - -from twisted.internet.defer import inlineCallbacks - -from autobahn.wamp.types import PublishOptions -from autobahn.twisted.util import sleep -from autobahn.twisted.wamp import ApplicationSession - - -class Component(ApplicationSession): - - """ - An application component that publishes an event every second. - """ - - @inlineCallbacks - def onJoin(self, details): - print("session attached") - - def add2(a, b): - return a + b - - yield self.register(add2, 'com.myapp.add2') - - def on_event(i): - print("Got event: {}".format(i)) - - yield self.subscribe(on_event, 'com.myapp.topic1') - - counter = 0 - while True: - print(".") - - try: - res = yield self.call('com.myapp.add2', counter, 7) - print("Got call result: {}".format(res)) - except Exception as e: - print("Call failed: {}".format(e)) - - try: - publication = yield self.publish('com.myapp.topic1', counter, - options=PublishOptions(acknowledge=True, discloseMe=True, excludeMe=False)) - print("Event published with publication ID {}".format(publication.id)) - except Exception as e: - print("Publication failed: {}".format(e)) - - counter += 1 - yield sleep(1) - - -if __name__ == '__main__': - from autobahn.twisted.wamp import ApplicationRunner - runner = ApplicationRunner("ws://127.0.0.1:8080/ws", "realm1") - runner.run(Component) diff --git a/examples/twisted/wamp/validation/router.py b/examples/twisted/wamp/validation/router.py deleted file mode 100644 index 7109ebfb..00000000 --- a/examples/twisted/wamp/validation/router.py +++ /dev/null @@ -1,94 +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. -# -############################################################################### - -from autobahn.wamp.exception import ApplicationError -from autobahn.twisted.wamp import Router - - -class MyRouter(Router): - - def validate(self, payload_type, uri, args, kwargs): - print("MyRouter.validate: {} {} {} {}".format(payload_type, uri, args, kwargs)) - - if payload_type == 'event' and uri == 'com.myapp.topic1': - if len(args) == 1 and isinstance(args[0], int) and args[0] % 2 == 0 and kwargs is None: - print("event payload validated for {}".format(uri)) - else: - raise ApplicationError(ApplicationError.INVALID_ARGUMENT, "invalid event payload for topic {} - must be a single integer".format(uri)) - - -if __name__ == '__main__': - - import sys - import argparse - - from twisted.python import log - from twisted.internet.endpoints import serverFromString - - # parse command line arguments - ## - parser = argparse.ArgumentParser() - - parser.add_argument("-d", "--debug", action="store_true", - help="Enable debug output.") - - parser.add_argument("--endpoint", type=str, default="tcp:8080", - help='Twisted server endpoint descriptor, e.g. "tcp:8080" or "unix:/tmp/mywebsocket".') - - args = parser.parse_args() - log.startLogging(sys.stdout) - - # we use an Autobahn utility to install the "best" available Twisted reactor - ## - from autobahn.twisted.choosereactor import install_reactor - reactor = install_reactor() - print("Running on reactor {}".format(reactor)) - - # create a WAMP router factory - ## - from autobahn.twisted.wamp import RouterFactory - router_factory = RouterFactory() - router_factory.router = MyRouter - - # create a WAMP router session factory - ## - from autobahn.twisted.wamp import RouterSessionFactory - session_factory = RouterSessionFactory(router_factory) - - # create a WAMP-over-WebSocket transport server factory - ## - from autobahn.twisted.websocket import WampWebSocketServerFactory - transport_factory = WampWebSocketServerFactory(session_factory, debug=args.debug) - transport_factory.setProtocolOptions(failByDrop=False) - - # start the server from an endpoint - ## - server = serverFromString(reactor, args.endpoint) - server.listen(transport_factory) - - # now enter the Twisted reactor loop - ## - reactor.run() diff --git a/examples/twisted/wamp/wamplet/votegame/votegame/__init__.py b/examples/twisted/wamp/wamplet/votegame/votegame/__init__.py deleted file mode 100644 index 8a4c67bf..00000000 --- a/examples/twisted/wamp/wamplet/votegame/votegame/__init__.py +++ /dev/null @@ -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. -# -############################################################################### diff --git a/examples/twisted/wamp/wamplet/wampirc/wampirc/__init__.py b/examples/twisted/wamp/wamplet/wampirc/wampirc/__init__.py deleted file mode 100644 index 8a4c67bf..00000000 --- a/examples/twisted/wamp/wamplet/wampirc/wampirc/__init__.py +++ /dev/null @@ -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. -# -############################################################################### diff --git a/examples/twisted/wamp/wamplet/wampirc/wampirc/service.py b/examples/twisted/wamp/wamplet/wampirc/wampirc/service.py index a78c8364..c56c8e14 100644 --- a/examples/twisted/wamp/wamplet/wampirc/wampirc/service.py +++ b/examples/twisted/wamp/wamplet/wampirc/wampirc/service.py @@ -121,7 +121,7 @@ if __name__ == '__main__': # test drive the component during development .. runner = ApplicationRunner( - url="ws://localhost:8080/ws", + url="ws://127.0.0.1:8080/ws", realm="realm1", extra=extra, debug=False, # low-level WebSocket debugging diff --git a/examples/twisted/wamp/wamplet/wamplet1/wamplet1/__init__.py b/examples/twisted/wamp/wamplet/wamplet1/wamplet1/__init__.py deleted file mode 100644 index 8a4c67bf..00000000 --- a/examples/twisted/wamp/wamplet/wamplet1/wamplet1/__init__.py +++ /dev/null @@ -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. -# -############################################################################### diff --git a/examples/twisted/websocket/auth_persona/server.py b/examples/twisted/websocket/auth_persona/server.py index 3a49bac1..e5070cd1 100644 --- a/examples/twisted/websocket/auth_persona/server.py +++ b/examples/twisted/websocket/auth_persona/server.py @@ -230,7 +230,7 @@ if __name__ == '__main__': print("Running Autobahn|Python {}".format(autobahn.version)) # our WebSocket server factory - factory = PersonaServerFactory("ws://localhost:8080") + factory = PersonaServerFactory("ws://127.0.0.1:8080") # we serve static files under "/" .. root = File(".") diff --git a/examples/twisted/websocket/broadcast/client.py b/examples/twisted/websocket/broadcast/client.py index 59c1e08d..337e0720 100644 --- a/examples/twisted/websocket/broadcast/client.py +++ b/examples/twisted/websocket/broadcast/client.py @@ -53,7 +53,7 @@ class BroadcastClientProtocol(WebSocketClientProtocol): if __name__ == '__main__': if len(sys.argv) < 2: - print("Need the WebSocket server address, i.e. ws://localhost:9000") + print("Need the WebSocket server address, i.e. ws://127.0.0.1:9000") sys.exit(1) factory = WebSocketClientFactory(sys.argv[1]) diff --git a/examples/twisted/websocket/broadcast/server.py b/examples/twisted/websocket/broadcast/server.py index 906f2210..6d15ad86 100644 --- a/examples/twisted/websocket/broadcast/server.py +++ b/examples/twisted/websocket/broadcast/server.py @@ -112,7 +112,7 @@ if __name__ == '__main__': ServerFactory = BroadcastServerFactory # ServerFactory = BroadcastPreparedServerFactory - factory = ServerFactory("ws://localhost:9000", + factory = ServerFactory("ws://127.0.0.1:9000", debug=debug, debugCodePaths=debug) diff --git a/examples/twisted/websocket/echo/client.py b/examples/twisted/websocket/echo/client.py index 19cb14ec..5f453964 100644 --- a/examples/twisted/websocket/echo/client.py +++ b/examples/twisted/websocket/echo/client.py @@ -63,7 +63,7 @@ if __name__ == '__main__': log.startLogging(sys.stdout) - factory = WebSocketClientFactory("ws://localhost:9000", debug=False) + factory = WebSocketClientFactory("ws://127.0.0.1:9000", debug=False) factory.protocol = MyClientProtocol reactor.connectTCP("127.0.0.1", 9000, factory) diff --git a/examples/twisted/websocket/echo/client_coroutines.py b/examples/twisted/websocket/echo/client_coroutines.py index d5899f55..79911937 100644 --- a/examples/twisted/websocket/echo/client_coroutines.py +++ b/examples/twisted/websocket/echo/client_coroutines.py @@ -70,7 +70,7 @@ if __name__ == '__main__': log.startLogging(sys.stdout) - factory = WebSocketClientFactory("ws://localhost:9000", debug=False) + factory = WebSocketClientFactory("ws://127.0.0.1:9000", debug=False) factory.protocol = MyClientProtocol reactor.connectTCP("127.0.0.1", 9000, factory) diff --git a/examples/twisted/websocket/echo/server.py b/examples/twisted/websocket/echo/server.py index 066cea76..a859dd1b 100644 --- a/examples/twisted/websocket/echo/server.py +++ b/examples/twisted/websocket/echo/server.py @@ -58,7 +58,7 @@ if __name__ == '__main__': log.startLogging(sys.stdout) - factory = WebSocketServerFactory("ws://localhost:9000", debug=False) + factory = WebSocketServerFactory("ws://127.0.0.1:9000", debug=False) factory.protocol = MyServerProtocol # factory.setProtocolOptions(maxConnections=2) diff --git a/examples/twisted/websocket/echo_compressed/client.py b/examples/twisted/websocket/echo_compressed/client.py index 219d9c3c..44dc1151 100644 --- a/examples/twisted/websocket/echo_compressed/client.py +++ b/examples/twisted/websocket/echo_compressed/client.py @@ -59,7 +59,7 @@ class EchoClientProtocol(WebSocketClientProtocol): if __name__ == '__main__': if len(sys.argv) < 2: - print("Need the WebSocket server address, i.e. ws://localhost:9000") + print("Need the WebSocket server address, i.e. ws://127.0.0.1:9000") sys.exit(1) if len(sys.argv) > 2 and sys.argv[2] == 'debug': diff --git a/examples/twisted/websocket/echo_compressed/client_advanced.py b/examples/twisted/websocket/echo_compressed/client_advanced.py index 3e7a5b97..68fd3cc2 100644 --- a/examples/twisted/websocket/echo_compressed/client_advanced.py +++ b/examples/twisted/websocket/echo_compressed/client_advanced.py @@ -56,7 +56,7 @@ class EchoClientProtocol(WebSocketClientProtocol): if __name__ == '__main__': if len(sys.argv) < 2: - print "Need the WebSocket server address, i.e. ws://localhost:9000" + print "Need the WebSocket server address, i.e. ws://127.0.0.1:9000" sys.exit(1) if len(sys.argv) > 2 and sys.argv[2] == 'debug': diff --git a/examples/twisted/websocket/echo_compressed/server.py b/examples/twisted/websocket/echo_compressed/server.py index 9f1a2f7b..a947cd41 100644 --- a/examples/twisted/websocket/echo_compressed/server.py +++ b/examples/twisted/websocket/echo_compressed/server.py @@ -59,7 +59,7 @@ if __name__ == '__main__': else: debug = False - factory = WebSocketServerFactory("ws://localhost:9000", + factory = WebSocketServerFactory("ws://127.0.0.1:9000", debug=debug, debugCodePaths=debug) diff --git a/examples/twisted/websocket/echo_compressed/server_advanced.py b/examples/twisted/websocket/echo_compressed/server_advanced.py index 1bc3f5d2..4200dbcb 100644 --- a/examples/twisted/websocket/echo_compressed/server_advanced.py +++ b/examples/twisted/websocket/echo_compressed/server_advanced.py @@ -58,7 +58,7 @@ if __name__ == '__main__': else: debug = False - factory = WebSocketServerFactory("ws://localhost:9000", + factory = WebSocketServerFactory("ws://127.0.0.1:9000", debug=debug, debugCodePaths=debug) diff --git a/examples/twisted/websocket/echo_endpoints/client.py b/examples/twisted/websocket/echo_endpoints/client.py index de04db2f..819699ca 100644 --- a/examples/twisted/websocket/echo_endpoints/client.py +++ b/examples/twisted/websocket/echo_endpoints/client.py @@ -72,11 +72,11 @@ if __name__ == '__main__': parser.add_argument("-d", "--debug", action="store_true", help="Enable debug output.") - parser.add_argument("--websocket", default="tcp:localhost:9000", - help='WebSocket client Twisted endpoint descriptor, e.g. "tcp:localhost:9000" or "unix:/tmp/mywebsocket".') + parser.add_argument("--websocket", default="tcp:127.0.0.1:9000", + help='WebSocket client Twisted endpoint descriptor, e.g. "tcp:127.0.0.1:9000" or "unix:/tmp/mywebsocket".') - parser.add_argument("--wsurl", default="ws://localhost:9000", - help='WebSocket URL (must suit the endpoint), e.g. "ws://localhost:9000".') + parser.add_argument("--wsurl", default="ws://127.0.0.1:9000", + help='WebSocket URL (must suit the endpoint), e.g. "ws://127.0.0.1:9000".') args = parser.parse_args() diff --git a/examples/twisted/websocket/echo_endpoints/server.py b/examples/twisted/websocket/echo_endpoints/server.py index 1b1555cb..af8a4aef 100644 --- a/examples/twisted/websocket/echo_endpoints/server.py +++ b/examples/twisted/websocket/echo_endpoints/server.py @@ -70,8 +70,8 @@ if __name__ == '__main__': parser.add_argument("--websocket", default="tcp:9000", help='WebSocket server Twisted endpoint descriptor, e.g. "tcp:9000" or "unix:/tmp/mywebsocket".') - parser.add_argument("--wsurl", default="ws://localhost:9000", - help='WebSocket URL (must suit the endpoint), e.g. "ws://localhost:9000".') + parser.add_argument("--wsurl", default="ws://127.0.0.1:9000", + help='WebSocket URL (must suit the endpoint), e.g. "ws://127.0.0.1:9000".') parser.add_argument("--web", default="tcp:8080", help='Web server endpoint descriptor, e.g. "tcp:8080".') diff --git a/examples/twisted/websocket/echo_httpheaders/client.py b/examples/twisted/websocket/echo_httpheaders/client.py index 0e5dfede..14725842 100644 --- a/examples/twisted/websocket/echo_httpheaders/client.py +++ b/examples/twisted/websocket/echo_httpheaders/client.py @@ -54,7 +54,7 @@ class EchoClientProtocol(WebSocketClientProtocol): if __name__ == '__main__': if len(sys.argv) < 2: - print("Need the WebSocket server address, i.e. ws://localhost:9000") + print("Need the WebSocket server address, i.e. ws://127.0.0.1:9000") sys.exit(1) if len(sys.argv) > 2 and sys.argv[2] == 'debug': diff --git a/examples/twisted/websocket/echo_httpheaders/server.py b/examples/twisted/websocket/echo_httpheaders/server.py index 7e64d3ad..046ce263 100644 --- a/examples/twisted/websocket/echo_httpheaders/server.py +++ b/examples/twisted/websocket/echo_httpheaders/server.py @@ -65,7 +65,7 @@ if __name__ == '__main__': headers = {'MyCustomServerHeader': 'Foobar'} - factory = WebSocketServerFactory("ws://localhost:9000", + factory = WebSocketServerFactory("ws://127.0.0.1:9000", headers=headers, debug=debug, debugCodePaths=debug) diff --git a/examples/twisted/websocket/echo_multicore/server.py b/examples/twisted/websocket/echo_multicore/server.py index 16cb0af0..88312c12 100644 --- a/examples/twisted/websocket/echo_multicore/server.py +++ b/examples/twisted/websocket/echo_multicore/server.py @@ -355,7 +355,7 @@ if __name__ == '__main__': DEFAULT_WORKERS = psutil.NUM_CPUS parser = argparse.ArgumentParser(description='Autobahn WebSocket Echo Multicore Server') - parser.add_argument('--wsuri', dest='wsuri', type=str, default='ws://localhost:9000', help='The WebSocket URI the server is listening on, e.g. ws://localhost:9000.') + parser.add_argument('--wsuri', dest='wsuri', type=str, default='ws://127.0.0.1:9000', help='The WebSocket URI the server is listening on, e.g. ws://localhost:9000.') parser.add_argument('--port', dest='port', type=int, default=8080, help='Port to listen on for embedded Web server. Set to 0 to disable.') parser.add_argument('--workers', dest='workers', type=int, default=DEFAULT_WORKERS, help='Number of workers to spawn - should fit the number of (physical) CPU cores.') parser.add_argument('--noaffinity', dest='noaffinity', action="store_true", default=False, help='Do not set worker/CPU affinity.') diff --git a/examples/twisted/websocket/echo_service/echows/echoservice.py b/examples/twisted/websocket/echo_service/echows/echoservice.py index 145867d8..528921da 100644 --- a/examples/twisted/websocket/echo_service/echows/echoservice.py +++ b/examples/twisted/websocket/echo_service/echows/echoservice.py @@ -60,7 +60,7 @@ class EchoService(service.Service): def startService(self): - factory = WebSocketServerFactory("ws://localhost:%d" % self.port, debug=self.debug) + factory = WebSocketServerFactory("ws://127.0.0.1:%d" % self.port, debug=self.debug) factory.protocol = EchoServerProtocol factory.setProtocolOptions(allowHixie76=True) # needed if Hixie76 is to be supported diff --git a/examples/twisted/websocket/echo_site/server.py b/examples/twisted/websocket/echo_site/server.py index 7d048070..d055d7f8 100644 --- a/examples/twisted/websocket/echo_site/server.py +++ b/examples/twisted/websocket/echo_site/server.py @@ -55,7 +55,7 @@ if __name__ == '__main__': else: debug = False - factory = WebSocketServerFactory("ws://localhost:8080", + factory = WebSocketServerFactory("ws://127.0.0.1:8080", debug=debug, debugCodePaths=debug) diff --git a/examples/twisted/websocket/echo_site_tls/server.py b/examples/twisted/websocket/echo_site_tls/server.py index a5a43b73..c718b459 100644 --- a/examples/twisted/websocket/echo_site_tls/server.py +++ b/examples/twisted/websocket/echo_site_tls/server.py @@ -55,7 +55,7 @@ if __name__ == '__main__': contextFactory = ssl.DefaultOpenSSLContextFactory('keys/server.key', 'keys/server.crt') - factory = WebSocketServerFactory("wss://localhost:8080", + factory = WebSocketServerFactory("wss://127.0.0.1:8080", debug=debug, debugCodePaths=debug) diff --git a/examples/twisted/websocket/echo_tls/client.py b/examples/twisted/websocket/echo_tls/client.py index 331fdef0..55af03f8 100644 --- a/examples/twisted/websocket/echo_tls/client.py +++ b/examples/twisted/websocket/echo_tls/client.py @@ -54,7 +54,7 @@ if __name__ == '__main__': log.startLogging(sys.stdout) parser = OptionParser() - parser.add_option("-u", "--url", dest="url", help="The WebSocket URL", default="wss://localhost:9000") + parser.add_option("-u", "--url", dest="url", help="The WebSocket URL", default="wss://127.0.0.1:9000") (options, args) = parser.parse_args() # create a WS server factory with our protocol diff --git a/examples/twisted/websocket/echo_tls/server.py b/examples/twisted/websocket/echo_tls/server.py index 231cd1a3..42ce15f1 100644 --- a/examples/twisted/websocket/echo_tls/server.py +++ b/examples/twisted/websocket/echo_tls/server.py @@ -56,7 +56,7 @@ if __name__ == '__main__': contextFactory = ssl.DefaultOpenSSLContextFactory('keys/server.key', 'keys/server.crt') - factory = WebSocketServerFactory("wss://localhost:9000", + factory = WebSocketServerFactory("wss://127.0.0.1:9000", debug=debug, debugCodePaths=debug) diff --git a/examples/twisted/websocket/echo_variants/client.py b/examples/twisted/websocket/echo_variants/client.py index 14900bad..8bb6a212 100644 --- a/examples/twisted/websocket/echo_variants/client.py +++ b/examples/twisted/websocket/echo_variants/client.py @@ -51,7 +51,7 @@ class EchoClientProtocol(WebSocketClientProtocol): if __name__ == '__main__': if len(sys.argv) < 2: - print("Need the WebSocket server address, i.e. ws://localhost:9000") + print("Need the WebSocket server address, i.e. ws://127.0.0.1:9000") sys.exit(1) if len(sys.argv) > 2 and sys.argv[2] == 'debug': diff --git a/examples/twisted/websocket/echo_variants/client_reconnecting.py b/examples/twisted/websocket/echo_variants/client_reconnecting.py index e2a5ddcc..5b6ac12a 100644 --- a/examples/twisted/websocket/echo_variants/client_reconnecting.py +++ b/examples/twisted/websocket/echo_variants/client_reconnecting.py @@ -73,7 +73,7 @@ class EchoClientFactory(ReconnectingClientFactory, WebSocketClientFactory): if __name__ == '__main__': if len(sys.argv) < 2: - print("Need the WebSocket server address, i.e. ws://localhost:9000") + print("Need the WebSocket server address, i.e. ws://127.0.0.1:9000") sys.exit(1) if len(sys.argv) > 2 and sys.argv[2] == 'debug': diff --git a/examples/twisted/websocket/echo_variants/client_with_params.py b/examples/twisted/websocket/echo_variants/client_with_params.py index 394e185e..c2ac5ddb 100644 --- a/examples/twisted/websocket/echo_variants/client_with_params.py +++ b/examples/twisted/websocket/echo_variants/client_with_params.py @@ -59,7 +59,7 @@ class EchoClientFactory(WebSocketClientFactory): if __name__ == '__main__': if len(sys.argv) < 2: - print "Need the WebSocket server address, i.e. ws://localhost:9000" + print "Need the WebSocket server address, i.e. ws://127.0.0.1:9000" sys.exit(1) factory = EchoClientFactory(sys.argv[1]) diff --git a/examples/twisted/websocket/echo_variants/client_with_proxy.py b/examples/twisted/websocket/echo_variants/client_with_proxy.py index aa858223..46e8d247 100644 --- a/examples/twisted/websocket/echo_variants/client_with_proxy.py +++ b/examples/twisted/websocket/echo_variants/client_with_proxy.py @@ -51,7 +51,7 @@ class EchoClientProtocol(WebSocketClientProtocol): if __name__ == '__main__': if len(sys.argv) < 2: - print("Need the WebSocket server address, i.e. ws://localhost:9000") + print("Need the WebSocket server address, i.e. ws://127.0.0.1:9000") sys.exit(1) if len(sys.argv) < 3: diff --git a/examples/twisted/websocket/echo_variants/server.py b/examples/twisted/websocket/echo_variants/server.py index a937efba..8223aab3 100644 --- a/examples/twisted/websocket/echo_variants/server.py +++ b/examples/twisted/websocket/echo_variants/server.py @@ -50,7 +50,7 @@ if __name__ == '__main__': else: debug = False - factory = WebSocketServerFactory("ws://localhost:9000", + factory = WebSocketServerFactory("ws://127.0.0.1:9000", debug=debug, debugCodePaths=debug) diff --git a/examples/twisted/websocket/echo_wsfallbacks/server.py b/examples/twisted/websocket/echo_wsfallbacks/server.py index 53a38248..685a48b4 100644 --- a/examples/twisted/websocket/echo_wsfallbacks/server.py +++ b/examples/twisted/websocket/echo_wsfallbacks/server.py @@ -56,7 +56,7 @@ if __name__ == '__main__': # Our WebSocket server ## - factory = WebSocketServerFactory("ws://localhost:%d" % wsPort, + factory = WebSocketServerFactory("ws://127.0.0.1:%d" % wsPort, debug=debug, debugCodePaths=debug) diff --git a/examples/twisted/websocket/echo_wsfallbacks/web/WebSocketMain.swf b/examples/twisted/websocket/echo_wsfallbacks/web/WebSocketMain.swf deleted file mode 100644 index 05e751bf..00000000 Binary files a/examples/twisted/websocket/echo_wsfallbacks/web/WebSocketMain.swf and /dev/null differ diff --git a/examples/twisted/websocket/echo_wsfallbacks/web/index.html b/examples/twisted/websocket/echo_wsfallbacks/web/index.html index 1c93745d..48bb3946 100644 --- a/examples/twisted/websocket/echo_wsfallbacks/web/index.html +++ b/examples/twisted/websocket/echo_wsfallbacks/web/index.html @@ -14,7 +14,7 @@