def test_explicit_reactor_coroutine(framework): """ If we set an event-loop, Futures + Tasks should use it. """ pytest.importorskip('asyncio') if txaio.using_twisted: pytest.skip() from asyncio import coroutine @coroutine def some_coroutine(): yield 'nothing' with patch.object(txaio.config, 'loop') as fake_loop: txaio.as_future(some_coroutine) if sys.version_info < (3, 4, 2): assert len(fake_loop.method_calls) == 2 c = fake_loop.method_calls[1] assert c[0] == 'call_soon' else: assert len(fake_loop.method_calls) == 1 c = fake_loop.method_calls[0] assert c[0] == 'create_task'
def onClose(self, wasClean): """ Implements :func:`autobahn.wamp.interfaces.ITransportHandler.onClose` """ self._transport = None if self._session_id: # fire callback and close the transport 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") txaio.add_callbacks(d, None, _error) self._session_id = None d = txaio.as_future(self.onDisconnect) def _error(e): return self._swallow_error(e, "While firing onDisconnect") txaio.add_callbacks(d, None, _error)
def onClose(self, wasClean): """ Implements :func:`autobahn.wamp.interfaces.ITransportHandler.onClose` """ self._transport = None if self._session_id: # fire callback and close the transport 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") txaio.add_callbacks(d, None, _error) self._session_id = None d = txaio.as_future(self.onDisconnect, wasClean) def _error(e): return self._swallow_error(e, "While firing onDisconnect") txaio.add_callbacks(d, None, _error)
def test_as_future_immediate_none(framework): ''' Returning None immediately from as_future ''' errors = [] results = [] calls = [] def method(*args, **kw): calls.append((args, kw)) return None f = txaio.as_future(method, 1, 2, 3, key='word') def cb(x): results.append(x) def errback(f): errors.append(f) txaio.add_callbacks(f, cb, errback) run_once() assert len(results) == 1 assert len(errors) == 0 assert results[0] is None assert calls[0] == ((1, 2, 3), dict(key='word'))
def test_as_future_exception(framework): ''' Raises an exception from as_future ''' errors = [] results = [] calls = [] exception = RuntimeError("sadness") def method(*args, **kw): calls.append((args, kw)) raise exception f = txaio.as_future(method, 1, 2, 3, key='word') def cb(x): results.append(x) def errback(f): errors.append(f) txaio.add_callbacks(f, cb, errback) run_once() assert len(results) == 0 assert len(errors) == 1 assert errors[0].value == exception assert calls[0] == ((1, 2, 3), dict(key='word'))
def _authenticator_for_name(config, controller=None): """ :returns: a future which fires with an authenticator function (possibly freshly created) """ create_fqn = config['create'] create_function = _authenticators.get(create_fqn, None) if create_function is None: create_module, create_name = create_fqn.rsplit('.', 1) _mod = importlib.import_module(create_module) try: create_authenticator = getattr(_mod, create_name) except AttributeError: raise RuntimeError( "No function '{}' in module '{}'".format(create_name, create_module) ) create_d = txaio.as_future(create_authenticator, config.get('config', dict()), controller) def got_authenticator(authenticator): _authenticators[create_fqn] = authenticator return authenticator create_d.addCallback(got_authenticator) else: create_d = Deferred() create_d.callback(create_function) return create_d
def _init_function_authenticator(self): self.log.debug('{klass}._init_function_authenticator', klass=self.__class__.__name__) # import the module for the function create_fqn = self._config['create'] if '.' not in create_fqn: return types.Deny( ApplicationError.NO_SUCH_PROCEDURE, "'function' authenticator has no module: '{}'".format(create_fqn) ) if self._config.get('expose_controller', None): from crossbar.worker.controller import WorkerController if not isinstance(self._realm_container, WorkerController): excp = Exception( "Internal Error: Our container '{}' is not a WorkerController".format( self._realm_container, ) ) self.log.failure('{klass} could not expose controller', klass=self.__class__.__name__, failure=excp) raise excp controller = self._realm_container else: controller = None create_d = txaio.as_future(_authenticator_for_name, self._config, controller=controller) def got_authenticator(authenticator): self._authenticator = authenticator create_d.addCallback(got_authenticator) return create_d
def test_as_future_recursive(framework): ''' Returns another Future from as_future ''' errors = [] results = [] calls = [] f1 = txaio.create_future_success(42) def method(*args, **kw): calls.append((args, kw)) return f1 f0 = txaio.as_future(method, 1, 2, 3, key='word') def cb(x): results.append(x) def errback(f): errors.append(f) txaio.add_callbacks(f0, cb, errback) run_once() assert len(results) == 1 assert len(errors) == 0 assert results[0] == 42 assert calls[0] == ((1, 2, 3), dict(key='word'))
def init(error): if error: return error # now call (via direct Python function call) the user provided authenticator (Python function) auth_d = as_future(self._authenticator, realm, details.authid, self._session_details) auth_d.addCallbacks(on_authenticate_ok, on_authenticate_error) return auth_d
def fire(self, event, *args, **kwargs): res = [] if event in self._listeners: for handler in self._listeners[event]: value = txaio.as_future(handler, *args, **kwargs) res.append(value) if self._parent is not None: res.append(self._parent.fire(event, *args, **kwargs)) return txaio.gather(res)
def hello(self, realm, details): # remember the realm the client requested to join (if any) self._realm = realm # remember the authid the client wants to identify as (if any) self._authid = details.authid # use static principal database from configuration if self._config['type'] == 'static': self._authprovider = 'static' if self._authid in self._config.get('principals', {}): principal = self._config['principals'][self._authid] principal['extra'] = details.authextra error = self._assign_principal(principal) if error: return error # now set set signature as expected for WAMP-Ticket self._signature = principal['ticket'] return types.Challenge(self._authmethod) else: return types.Deny( message='no principal with authid "{}" exists'.format( self._authid)) # use configured procedure to dynamically get a ticket for the principal elif self._config['type'] == 'dynamic': self._authprovider = 'dynamic' init_d = as_future(self._init_dynamic_authenticator) def init(result): if result: return result self._session_details[ 'authmethod'] = self._authmethod # from AUTHMETHOD, via base self._session_details['authextra'] = details.authextra return types.Challenge(self._authmethod) init_d.addBoth(init) return init_d else: # should not arrive here, as config errors should be caught earlier return types.Deny( message= 'invalid authentication configuration (authentication type "{}" is unknown)' .format(self._config['type']))
def component_start(comp): # the future from start() errbacks if we fail, or callbacks # when the component is considered "done" (so maybe never) d = txaio.as_future(comp.start, reactor) txaio.add_callbacks( d, partial(component_success, comp), partial(component_failure, comp), ) return d
def test_gather_two(framework): ''' Wait for two Futures. ''' errors = [] results = [] calls = [] def foo(): def codependant(*args, **kw): calls.append((args, kw)) return 42 return txaio.as_future(codependant) def method(*args, **kw): calls.append((args, kw)) return "OHAI" f0 = txaio.as_future(method, 1, 2, 3, key='word') f1 = txaio.as_future(foo) f2 = txaio.gather([f0, f1]) def done(arg): results.append(arg) def error(fail): errors.append(fail) # fail.printTraceback() txaio.add_callbacks(f2, done, error) for f in [f0, f1, f2]: await (f) assert len(results) == 1 assert len(errors) == 0 assert results[0] == ['OHAI', 42] or results[0] == [42, 'OHAI'] assert len(calls) == 2 assert calls[0] == ((1, 2, 3), dict(key='word')) assert calls[1] == (tuple(), dict())
def onOpen(self, transport): """ Implements :func:`autobahn.wamp.interfaces.ITransportHandler.onOpen` """ self._transport = transport d = txaio.as_future(self.onConnect) def _error(e): return self._swallow_error(e, "While firing onConnect") txaio.add_callbacks(d, None, _error)
def on_join(session, details): self.log.debug("session on_join: {details}", details=details) d = txaio.as_future(self._entry, reactor, session) def setup_success(_): self.log.debug("setup_success") def setup_error(err): self.log.debug("setup_error", err) txaio.add_callbacks(d, setup_success, setup_error)
def test_gather_two(): ''' Wait for two Futures. ''' errors = [] results = [] calls = [] def foo(): def codependant(*args, **kw): calls.append((args, kw)) return 42 return txaio.as_future(codependant) def method(*args, **kw): calls.append((args, kw)) return "OHAI" f0 = txaio.as_future(method, 1, 2, 3, key='word') f1 = txaio.as_future(foo) f2 = txaio.gather([f0, f1]) def done(arg): results.append(arg) def error(fail): errors.append(fail) # fail.printTraceback() txaio.add_callbacks(f2, done, error) await(f0) await(f1) await(f2) assert len(results) == 1 assert len(errors) == 0 assert results[0] == ['OHAI', 42] or results[0] == [42, 'OHAI'] assert len(calls) == 2 assert calls[0] == ((1, 2, 3), dict(key='word')) assert calls[1] == (tuple(), dict())
def authorize(self, session, uri, action, options): """ Authorizes a session for an action on an URI. Implements :func:`autobahn.wamp.interfaces.IRouter.authorize` """ assert(type(uri) == str) assert(action in [u'call', u'register', u'publish', u'subscribe']) # the role under which the session that wishes to perform the given action on # the given URI was authenticated under role = session._authrole if role in self._roles: # the authorizer procedure of the role which we will call .. authorize = self._roles[role].authorize d = txaio.as_future(authorize, session, uri, action, options) else: # normally, the role should exist on the router (and hence we should not arrive # here), but the role might have been dynamically removed - and anyway, safety first! d = txaio.create_future_success(False) # XXX would be nicer for dynamic-authorizer authors if we # sanity-checked the return-value ('authorization') here # (i.e. is it a dict? does it have 'allow' in it? does it have # disallowed keys in it?) def got_authorization(authorization): # backward compatibility if isinstance(authorization, bool): authorization = { u'allow': authorization, u'cache': False } if action in [u'call', u'publish']: authorization[u'disclose'] = False auto_disclose_trusted = False if auto_disclose_trusted and role == u'trusted' and action in [u'call', u'publish']: authorization[u'disclose'] = True self.log.debug("Authorized action '{action}' for URI '{uri}' by session {session_id} with authid '{authid}' and authrole '{authrole}' -> authorization: {authorization}", session_id=session._session_id, uri=uri, action=action, authid=session._authid, authrole=session._authrole, authorization=authorization) return authorization d.addCallback(got_authorization) return d
def authorize(self, session, uri, action, options): """ Authorizes a session for an action on an URI. Implements :func:`autobahn.wamp.interfaces.IRouter.authorize` """ assert (type(uri) == str) assert (action in [u'call', u'register', u'publish', u'subscribe']) # the role under which the session that wishes to perform the given action on # the given URI was authenticated under role = session._authrole if role in self._roles: # the authorizer procedure of the role which we will call .. authorize = self._roles[role].authorize d = txaio.as_future(authorize, session, uri, action, options) else: # normally, the role should exist on the router (and hence we should not arrive # here), but the role might have been dynamically removed - and anyway, safety first! d = txaio.create_future_success(False) # XXX would be nicer for dynamic-authorizer authors if we # sanity-checked the return-value ('authorization') here # (i.e. is it a dict? does it have 'allow' in it? does it have # disallowed keys in it?) def got_authorization(authorization): # backward compatibility if isinstance(authorization, bool): authorization = {u'allow': authorization, u'cache': False} if action in [u'call', u'publish']: authorization[u'disclose'] = False auto_disclose_trusted = False if auto_disclose_trusted and role == u'trusted' and action in [ u'call', u'publish' ]: authorization[u'disclose'] = True self.log.debug( "Authorized action '{action}' for URI '{uri}' by session {session_id} with authid '{authid}' and authrole '{authrole}' -> authorization: {authorization}", session_id=session._session_id, uri=uri, action=action, authid=session._authid, authrole=session._authrole, authorization=authorization) return authorization d.addCallback(got_authorization) return d
def error(err): reply = message.Abort(u"wamp.error.cannot_authenticate", u"{0}".format(err.value)) self._transport.send(reply) # fire callback and close the transport details = types.CloseDetails(reply.reason, reply.message) d = txaio.as_future(self.onLeave, details) def _error(e): return self._swallow_error(e, "While firing onLeave") txaio.add_callbacks(d, None, _error) # switching to the callback chain, effectively # cancelling error (which we've now handled) return d
def on_join(session, details): self.log.debug("session on_join: {details}", details=details) d = txaio.as_future(self._entry, reactor, session) def main_success(_): self.log.debug("main_success") txaio.resolve(done, None) def main_error(err): self.log.debug("main_error", err) txaio.reject(done, err) txaio.add_callbacks(d, main_success, main_error)
def init(error): if error: return error self._session_details[ 'authmethod'] = self._authmethod # from AUTHMETHOD, via base self._session_details['authid'] = details.authid self._session_details['authrole'] = details.authrole self._session_details['authextra'] = details.authextra auth_d = txaio.as_future(self._authenticator, realm, details.authid, self._session_details) def on_authenticate_ok(principal): self.log.info( '{klass}.hello(realm="{realm}", details={details}) -> on_authenticate_ok(principal={principal})', klass=self.__class__.__name__, realm=realm, details=details, principal=principal) error = self._assign_principal(principal) if error: return error self._verify_key = VerifyKey( principal['pubkey'], encoder=nacl.encoding.HexEncoder) extra = self._compute_challenge(channel_binding) return types.Challenge(self._authmethod, extra) def on_authenticate_error(err): self.log.info( '{klass}.hello(realm="{realm}", details={details}) -> on_authenticate_error(err={err})', klass=self.__class__.__name__, realm=realm, details=details, err=err) try: return self._marshal_dynamic_authenticator_error(err) except Exception as e: error = ApplicationError.AUTHENTICATION_FAILED message = 'marshalling of function-based authenticator error return failed: {}'.format( e) self.log.warn( '{klass}.hello.on_authenticate_error() - {msg}', msg=message) return types.Deny(error, message) auth_d.addCallbacks(on_authenticate_ok, on_authenticate_error) return auth_d
def stop(self): self._stopping = True if self._session and self._session.is_attached(): return self._session.leave() elif self._delay_f: # This cancel request will actually call the "error" callback of # the _delay_f future. Nothing to worry about. return txaio.as_future(txaio.cancel, self._delay_f) # if (for some reason -- should we log warning here to figure # out if this can evern happen?) we've not fired _done_f, we # do that now (causing our "main" to exit, and thus react() to # quit) if not txaio.is_called(self._done_f): txaio.resolve(self._done_f, None) return txaio.create_future_success(None)
def on_join(session, details): self.log.debug("session on_join: {details}", details=details) self.log.info( 'Successfully connected to transport #{transport_idx}: url={url}', transport_idx=transport.idx, url=transport.config['url'], ) d = txaio.as_future(self._entry, reactor, session) def setup_success(_): self.log.debug("setup_success") def setup_error(err): self.log.debug("setup_error: {err}", err=err) txaio.reject(done, err) txaio.add_callbacks(d, setup_success, setup_error)
def authorize(self, session, uri, action): """ Authorizes a session for an action on an URI. Implements :func:`autobahn.wamp.interfaces.IRouter.authorize` """ assert(type(uri) == six.text_type) assert(action in [u'call', u'register', u'publish', u'subscribe']) # the role under which the session that wishes to perform the given action on # the given URI was authenticated under role = session._authrole if role in self._roles: # the authorizer procedure of the role which we will call .. authorize = self._roles[role].authorize d = txaio.as_future(authorize, session, uri, action) else: # normally, the role should exist on the router (and hence we should not arrive # here), but the role might have been dynamically removed - and anyway, safety first! d = txaio.create_future_success(False) def got_authorization(authorization): # backward compatibility if type(authorization) == bool: authorization = { u'allow': authorization, u'cache': False } if action in [u'call', u'publish']: authorization[u'disclose'] = False self.log.debug("Authorized action '{action}' for URI '{uri}' by session {session_id} with authid '{authid}' and authrole '{authrole}' -> authorization: {authorization}", session_id=session._session_id, uri=uri, action=action, authid=session._authid, authrole=session._authrole, authorization=authorization) return authorization d.addCallback(got_authorization) return d
def authorize(self, session, uri, action): """ Authorizes a session for an action on an URI. Implements :func:`autobahn.wamp.interfaces.IRouter.authorize` """ assert (type(uri) == six.text_type) assert (action in [u'call', u'register', u'publish', u'subscribe']) # the role under which the session that wishes to perform the given action on # the given URI was authenticated under role = session._authrole if role in self._roles: # the authorizer procedure of the role which we will call .. authorize = self._roles[role].authorize d = txaio.as_future(authorize, session, uri, action) else: # normally, the role should exist on the router (and hence we should not arrive # here), but the role might have been dynamically removed - and anyway, safety first! d = txaio.create_future_success(False) def got_authorization(authorization): # backward compatibility if type(authorization) == bool: authorization = {u'allow': authorization, u'cache': False} if action in [u'call', u'publish']: authorization[u'disclose'] = False self.log.info( "Authorized action '{action}' for URI '{uri}' by session {session_id} with authid '{authid}' and authrole '{authrole}' -> authorization: {authorization}", session_id=session._session_id, uri=uri, action=action, authid=session._authid, authrole=session._authrole, authorization=authorization) return authorization d.addCallback(got_authorization) return d
def on_join(session, details): self.log.debug("session on_join: {details}", details=details) transport.connect_sucesses += 1 self.log.info( 'Successfully connected to transport #{transport_idx}: url={url}', transport_idx=transport.idx, url=transport.config['url'], ) d = txaio.as_future(self._entry, reactor, session) def main_success(_): self.log.debug("main_success") session.leave() def main_error(err): self.log.debug("main_error: {err}", err=err) txaio.reject(done, err) # I guess .leave() here too...? txaio.add_callbacks(d, main_success, main_error)
def fire(self, event, *args, **kwargs): """ Fire a particular event. :param event: the event to fire. All other args and kwargs are passed on to the handler(s) for the event. :return: a Deferred/Future gathering all async results from all handlers and/or parent handlers. """ # print("firing '{}' from '{}'".format(event, hash(self))) if self._listeners is None: return txaio.create_future(result=[]) self._check_event(event) res = [] for handler in self._listeners.get(event, set()): future = txaio.as_future(handler, *args, **kwargs) res.append(future) if self._parent is not None: res.append(self._parent.fire(event, *args, **kwargs)) return txaio.gather(res, consume_exceptions=False)
def on_join(session, details): transport.connect_sucesses += 1 self.log.debug("session on_join: {details}", details=details) d = txaio.as_future(self._entry, reactor, session) def main_success(_): self.log.debug("main_success") def leave(): try: session.leave() except SessionNotReady: # someone may have already called # leave() pass txaio.call_later(0, leave) def main_error(err): self.log.debug("main_error: {err}", err=err) txaio.reject(done, err) session.disconnect() txaio.add_callbacks(d, main_success, main_error)
def test_as_future_coroutine(framework): ''' call a coroutine (asyncio) ''' pytest.importorskip('asyncio') # can import asyncio on python3.4, but might still be using # twisted if not txaio.using_asyncio: return errors = [] results = [] calls = [] from asyncio import coroutine @coroutine def method(*args, **kw): calls.append((args, kw)) return 42 f = txaio.as_future(method, 1, 2, 3, key='word') def cb(x): results.append(x) def errback(f): errors.append(f) txaio.add_callbacks(f, cb, errback) run_once() run_once() assert len(results) == 1 assert len(errors) == 0 assert results[0] == 42 assert calls[0] == ((1, 2, 3), dict(key='word'))
def processCall(self, session, call): """ Implements :func:`crossbar.router.interfaces.IDealer.processCall` """ # check procedure URI: for CALL, must be valid URI (either strict or loose), and # all URI components must be non-empty if self._option_uri_strict: uri_is_valid = _URI_PAT_STRICT_NON_EMPTY.match(call.procedure) else: uri_is_valid = _URI_PAT_LOOSE_NON_EMPTY.match(call.procedure) if not uri_is_valid: reply = message.Error(message.Call.MESSAGE_TYPE, call.request, ApplicationError.INVALID_URI, [u"call with invalid procedure URI '{0}' (URI strict checking {1})".format(call.procedure, self._option_uri_strict)]) self._router.send(session, reply) return # get registrations active on the procedure called # registration = self._registration_map.best_matching_observation(call.procedure) if registration: # validate payload (skip in "payload_transparency" mode) # if call.payload is None: try: self._router.validate('call', call.procedure, call.args, call.kwargs) except Exception as e: reply = message.Error(message.Call.MESSAGE_TYPE, call.request, ApplicationError.INVALID_ARGUMENT, [u"call of procedure '{0}' with invalid application payload: {1}".format(call.procedure, e)]) self._router.send(session, reply) return # authorize CALL action # d = txaio.as_future(self._router.authorize, session, call.procedure, RouterAction.ACTION_CALL) def on_authorize_success(authorized): # the call to authorize the action _itself_ succeeded. now go on depending on whether # the action was actually authorized or not .. # if not authorized: reply = message.Error(message.Call.MESSAGE_TYPE, call.request, ApplicationError.NOT_AUTHORIZED, [u"session is not authorized to call procedure '{0}'".format(call.procedure)]) self._router.send(session, reply) else: # determine callee according to invocation policy # if registration.extra.invoke == message.Register.INVOKE_SINGLE: callee = registration.observers[0] elif registration.extra.invoke == message.Register.INVOKE_FIRST: callee = registration.observers[0] elif registration.extra.invoke == message.Register.INVOKE_LAST: callee = registration.observers[len(registration.observers) - 1] elif registration.extra.invoke == message.Register.INVOKE_ROUNDROBIN: callee = registration.observers[registration.extra.roundrobin_current % len(registration.observers)] registration.extra.roundrobin_current += 1 elif registration.extra.invoke == message.Register.INVOKE_RANDOM: callee = registration.observers[random.randint(0, len(registration.observers) - 1)] else: # should not arrive here raise Exception(u"logic error") # new ID for the invocation # invocation_request_id = self._request_id_gen.next() # caller disclosure # if call.disclose_me: caller = session._session_id else: caller = None # for pattern-based registrations, the INVOCATION must contain # the actual procedure being called # if registration.match != message.Register.MATCH_EXACT: procedure = call.procedure else: procedure = None if call.payload: invocation = message.Invocation(invocation_request_id, registration.id, payload=call.payload, timeout=call.timeout, receive_progress=call.receive_progress, caller=caller, procedure=procedure, enc_algo=call.enc_algo, enc_key=call.enc_key, enc_serializer=call.enc_serializer) else: invocation = message.Invocation(invocation_request_id, registration.id, args=call.args, kwargs=call.kwargs, timeout=call.timeout, receive_progress=call.receive_progress, caller=caller, procedure=procedure) self._invocations[invocation_request_id] = InvocationRequest(invocation_request_id, session, call) self._router.send(callee, invocation) def on_authorize_error(err): """ the call to authorize the action _itself_ failed (note this is different from the call to authorize succeed, but the authorization being denied) """ self.log.failure(err) reply = message.Error( message.Call.MESSAGE_TYPE, call.request, ApplicationError.AUTHORIZATION_FAILED, [u"failed to authorize session for calling procedure '{0}': {1}".format(call.procedure, err.value)] ) self._router.send(session, reply) txaio.add_callbacks(d, on_authorize_success, on_authorize_error) else: reply = message.Error(message.Call.MESSAGE_TYPE, call.request, ApplicationError.NO_SUCH_PROCEDURE, [u"no callee registered for procedure <{0}>".format(call.procedure)]) self._router.send(session, reply)
def onMessage(self, msg): """ Implements :func:`autobahn.wamp.interfaces.ITransportHandler.onMessage` """ if self._session_id is None: if not self._pending_session_id: self._pending_session_id = util.id() def welcome(realm, authid=None, authrole=None, authmethod=None, authprovider=None, authextra=None, custom=None): self._realm = realm self._session_id = self._pending_session_id self._pending_session_id = None self._goodbye_sent = False self._router = self._router_factory.get(realm) if not self._router: # should not arrive here raise Exception( "logic error (no realm at a stage were we should have one)" ) self._authid = authid self._authrole = authrole self._authmethod = authmethod self._authprovider = authprovider roles = self._router.attach(self) msg = message.Welcome(self._session_id, roles, realm=realm, authid=authid, authrole=authrole, authmethod=authmethod, authprovider=authprovider, authextra=authextra, custom=custom) self._transport.send(msg) self.onJoin( SessionDetails(self._realm, self._session_id, self._authid, self._authrole, self._authmethod, self._authprovider, self._authextra)) # the first message MUST be HELLO if isinstance(msg, message.Hello): self._session_roles = msg.roles details = types.HelloDetails( realm=msg.realm, authmethods=msg.authmethods, authid=msg.authid, authrole=msg.authrole, authextra=msg.authextra, session_roles=msg.roles, pending_session=self._pending_session_id) d = txaio.as_future(self.onHello, msg.realm, details) def success(res): msg = None if isinstance(res, types.Accept): custom = { # FIXME: # u'x_cb_node_id': self._router_factory._node_id u'x_cb_node_id': None } welcome(res.realm, res.authid, res.authrole, res.authmethod, res.authprovider, res.authextra, custom) elif isinstance(res, types.Challenge): msg = message.Challenge(res.method, res.extra) elif isinstance(res, types.Deny): msg = message.Abort(res.reason, res.message) else: pass if msg: self._transport.send(msg) txaio.add_callbacks(d, success, self._swallow_error_and_abort) elif isinstance(msg, message.Authenticate): d = txaio.as_future(self.onAuthenticate, msg.signature, {}) def success(res): msg = None if isinstance(res, types.Accept): custom = { # FIXME: # u'x_cb_node_id': self._router_factory._node_id u'x_cb_node_id': None } welcome(res.realm, res.authid, res.authrole, res.authmethod, res.authprovider, res.authextra, custom) elif isinstance(res, types.Deny): msg = message.Abort(res.reason, res.message) else: pass if msg: self._transport.send(msg) txaio.add_callbacks(d, success, self._swallow_error_and_abort) elif isinstance(msg, message.Abort): # fire callback and close the transport self.onLeave(types.CloseDetails(msg.reason, msg.message)) self._session_id = None self._pending_session_id = None # self._transport.close() else: # raise ProtocolError(u"PReceived {0} message while session is not joined".format(msg.__class__)) # self.log.warn('Protocol state error - received {message} while session is not joined') # swallow all noise like still getting PUBLISH messages from log event forwarding - maybe FIXME pass else: if isinstance(msg, message.Hello): raise ProtocolError( u"HELLO message received, while session is already established" ) elif isinstance(msg, message.Goodbye): if not self._goodbye_sent: # The peer wants to close: answer with GOODBYE reply. # Note: We MUST NOT send any WAMP message _after_ GOODBYE reply = message.Goodbye() self._transport.send(reply) self._goodbye_sent = True else: # This is the peer's GOODBYE reply to our own earlier GOODBYE pass # We need to first detach the session from the router before # erasing the session ID below .. try: self._router.detach(self) except Exception: self.log.failure("Internal error") # In order to send wamp.session.on_leave properly # (i.e. *with* the proper session_id) we save it previous_session_id = self._session_id # At this point, we've either sent GOODBYE already earlier, # or we have just responded with GOODBYE. In any case, we MUST NOT # send any WAMP message from now on: # clear out session ID, so that anything that might be triggered # in the onLeave below is prohibited from sending WAMP stuff. # E.g. the client might have been subscribed to meta events like # wamp.session.on_leave - and we must not send that client's own # leave to itself! self._session_id = None self._pending_session_id = None # publish event, *after* self._session_id is None so # that we don't publish to ourselves as well (if this # session happens to be subscribed to wamp.session.on_leave) if self._service_session: self._service_session.publish( u'wamp.session.on_leave', previous_session_id, ) # fire callback and close the transport self.onLeave(types.CloseDetails(msg.reason, msg.message)) # don't close the transport, as WAMP allows to reattach a session # to the same or a different realm without closing the transport # self._transport.close() else: self._router.process(self, msg)
def processRegister(self, session, register): """ Implements :func:`crossbar.router.interfaces.IDealer.processRegister` """ # check topic URI: for SUBSCRIBE, must be valid URI (either strict or loose), and all # URI components must be non-empty other than for wildcard subscriptions # if self._option_uri_strict: if register.match == u"wildcard": uri_is_valid = _URI_PAT_STRICT_EMPTY.match(register.procedure) else: uri_is_valid = _URI_PAT_STRICT_NON_EMPTY.match(register.procedure) else: if register.match == u"wildcard": uri_is_valid = _URI_PAT_LOOSE_EMPTY.match(register.procedure) else: uri_is_valid = _URI_PAT_LOOSE_NON_EMPTY.match(register.procedure) if not uri_is_valid: reply = message.Error(message.Register.MESSAGE_TYPE, register.request, ApplicationError.INVALID_URI, [u"register for invalid procedure URI '{0}' (URI strict checking {1})".format(register.procedure, self._option_uri_strict)]) self._router.send(session, reply) return # disallow registration of procedures starting with "wamp." and "crossbar." other than for # trusted sessions (that are sessions built into Crossbar.io) # if session._authrole is not None and session._authrole != u"trusted": is_restricted = register.procedure.startswith(u"wamp.") or register.procedure.startswith(u"crossbar.") if is_restricted: reply = message.Error(message.Register.MESSAGE_TYPE, register.request, ApplicationError.INVALID_URI, [u"register for restricted procedure URI '{0}')".format(register.procedure)]) self._router.send(session, reply) return # get existing registration for procedure / matching strategy - if any # registration = self._registration_map.get_observation(register.procedure, register.match) if registration: # there is an existing registration, and that has an invocation strategy that only allows a single callee # on a the given registration # if registration.extra.invoke == message.Register.INVOKE_SINGLE: reply = message.Error(message.Register.MESSAGE_TYPE, register.request, ApplicationError.PROCEDURE_ALREADY_EXISTS, [u"register for already registered procedure '{0}'".format(register.procedure)]) self._router.send(session, reply) return # there is an existing registration, and that has an invokation strategy different from the one # requested by the new callee # if registration.extra.invoke != register.invoke: reply = message.Error(message.Register.MESSAGE_TYPE, register.request, ApplicationError.PROCEDURE_EXISTS_INVOCATION_POLICY_CONFLICT, [u"register for already registered procedure '{0}' with conflicting invocation policy (has {1} and {2} was requested)".format(register.procedure, registration.extra.invoke, register.invoke)]) self._router.send(session, reply) return # authorize action # d = txaio.as_future(self._router.authorize, session, register.procedure, RouterAction.ACTION_REGISTER) def on_authorize_success(authorized): if not authorized: # error reply since session is not authorized to register # reply = message.Error(message.Register.MESSAGE_TYPE, register.request, ApplicationError.NOT_AUTHORIZED, [u"session is not authorized to register procedure '{0}'".format(register.procedure)]) else: # ok, session authorized to register. now get the registration # registration_extra = RegistrationExtra(register.invoke) registration, was_already_registered, is_first_callee = self._registration_map.add_observer(session, register.procedure, register.match, registration_extra) if not was_already_registered: self._session_to_registrations[session].add(registration) # publish WAMP meta events # if self._router._realm: service_session = self._router._realm.session if service_session and not registration.uri.startswith(u'wamp.'): if is_first_callee: registration_details = { 'id': registration.id, 'created': registration.created, 'uri': registration.uri, 'match': registration.match, 'invoke': registration.extra.invoke, } service_session.publish(u'wamp.registration.on_create', session._session_id, registration_details) if not was_already_registered: service_session.publish(u'wamp.registration.on_register', session._session_id, registration.id) # acknowledge register with registration ID # reply = message.Registered(register.request, registration.id) # send out reply to register requestor # self._router.send(session, reply) def on_authorize_error(err): """ the call to authorize the action _itself_ failed (note this is different from the call to authorize succeed, but the authorization being denied) """ self.log.failure(err) reply = message.Error( message.Register.MESSAGE_TYPE, register.request, ApplicationError.AUTHORIZATION_FAILED, [u"failed to authorize session for registering procedure '{0}': {1}".format(register.procedure, err.value)] ) self._router.send(session, reply) txaio.add_callbacks(d, on_authorize_success, on_authorize_error)
def processSubscribe(self, session, subscribe): """ Implements :func:`crossbar.router.interfaces.IBroker.processSubscribe` """ # check topic URI: for SUBSCRIBE, must be valid URI (either strict or loose), and all # URI components must be non-empty other than for wildcard subscriptions # if self._option_uri_strict: if subscribe.match == u"wildcard": uri_is_valid = _URI_PAT_STRICT_EMPTY.match(subscribe.topic) else: uri_is_valid = _URI_PAT_STRICT_NON_EMPTY.match(subscribe.topic) else: if subscribe.match == u"wildcard": uri_is_valid = _URI_PAT_LOOSE_EMPTY.match(subscribe.topic) else: uri_is_valid = _URI_PAT_LOOSE_NON_EMPTY.match(subscribe.topic) if not uri_is_valid: reply = message.Error(message.Subscribe.MESSAGE_TYPE, subscribe.request, ApplicationError.INVALID_URI, [u"subscribe for invalid topic URI '{0}'".format(subscribe.topic)]) session._transport.send(reply) return # authorize action # d = txaio.as_future(self._router.authorize, session, subscribe.topic, RouterAction.ACTION_SUBSCRIBE) def on_authorize_success(authorized): if not authorized: # error reply since session is not authorized to subscribe # reply = message.Error(message.Subscribe.MESSAGE_TYPE, subscribe.request, ApplicationError.NOT_AUTHORIZED, [u"session is not authorized to subscribe to topic '{0}'".format(subscribe.topic)]) else: # ok, session authorized to subscribe. now get the subscription # subscription, was_already_subscribed, is_first_subscriber = self._subscription_map.add_observer(session, subscribe.topic, subscribe.match) if not was_already_subscribed: self._session_to_subscriptions[session].add(subscription) # publish WAMP meta events # if self._router._realm: service_session = self._router._realm.session if service_session and not subscription.uri.startswith(u'wamp.'): if is_first_subscriber: subscription_details = { 'id': subscription.id, 'created': subscription.created, 'uri': subscription.uri, 'match': subscription.match, } service_session.publish(u'wamp.subscription.on_create', session._session_id, subscription_details) if not was_already_subscribed: service_session.publish(u'wamp.subscription.on_subscribe', session._session_id, subscription.id) # acknowledge subscribe with subscription ID # reply = message.Subscribed(subscribe.request, subscription.id) # send out reply to subscribe requestor # session._transport.send(reply) def on_authorize_error(err): """ the call to authorize the action _itself_ failed (note this is different from the call to authorize succeed, but the authorization being denied) """ # XXX same as another code-block, can we collapse? self.log.failure(err) reply = message.Error( message.Subscribe.MESSAGE_TYPE, subscribe.request, ApplicationError.AUTHORIZATION_FAILED, [u"failed to authorize session for subscribing to topic URI '{0}': {1}".format(subscribe.topic, err.value)] ) session._transport.send(reply) txaio.add_callbacks(d, on_authorize_success, on_authorize_error)
def send(self, msg): """ Implements :func:`autobahn.wamp.interfaces.ITransport.send` """ if isinstance(msg, message.Hello): self._router = self._routerFactory.get(msg.realm) # fake session ID assignment (normally done in WAMP opening handshake) self._session._session_id = util.id() # set fixed/trusted authentication information self._session._authid = self._trusted_authid self._session._authrole = self._trusted_authrole self._session._authmethod = None # FIXME: the following does blow up # self._session._authmethod = u'trusted' self._session._authprovider = None self._session._authextra = None # add app session to router self._router.attach(self._session) # fake app session open details = SessionDetails( self._session._realm, self._session._session_id, self._session._authid, self._session._authrole, self._session._authmethod, self._session._authprovider, self._session._authextra) # have to fire the 'join' notification ourselves, as we're # faking out what the protocol usually does. d = self._session.fire('join', self._session, details) d.addErrback( lambda fail: self._log_error(fail, "While notifying 'join'")) # now fire onJoin (since _log_error returns None, we'll be # back in the callback chain even on errors from 'join' d.addCallback( lambda _: txaio.as_future(self._session.onJoin, details)) d.addErrback( lambda fail: self._swallow_error(fail, "While firing onJoin")) d.addCallback(lambda _: self._session.fire('ready', self._session)) d.addErrback( lambda fail: self._log_error(fail, "While notifying 'ready'")) # app-to-router # elif isinstance(msg, (message.Publish, message.Subscribe, message.Unsubscribe, message.Call, message.Yield, message.Register, message.Unregister, message.Cancel)) or \ (isinstance(msg, message.Error) and msg.request_type == message.Invocation.MESSAGE_TYPE): # deliver message to router # self._router.process(self._session, msg) # router-to-app # elif isinstance(msg, (message.Event, message.Invocation, message.Result, message.Published, message.Subscribed, message.Unsubscribed, message.Registered, message.Unregistered)) or \ (isinstance(msg, message.Error) and (msg.request_type in { message.Call.MESSAGE_TYPE, message.Cancel.MESSAGE_TYPE, message.Register.MESSAGE_TYPE, message.Unregister.MESSAGE_TYPE, message.Publish.MESSAGE_TYPE, message.Subscribe.MESSAGE_TYPE, message.Unsubscribe.MESSAGE_TYPE})): # deliver message to app session # self._session.onMessage(msg) # ignore messages # elif isinstance(msg, message.Goodbye): details = types.CloseDetails(msg.reason, msg.message) session = self._session @inlineCallbacks def do_goodbye(): try: yield session.onLeave(details) except Exception: self._log_error(Failure(), "While firing onLeave") if session._transport: session._transport.close() try: yield session.fire('leave', session, details) except Exception: self._log_error(Failure(), "While notifying 'leave'") try: yield session.fire('disconnect', session) except Exception: self._log_error(Failure(), "While notifying 'disconnect'") if self._router._realm.session: yield self._router._realm.session.publish( u'wamp.session.on_leave', session._session_id, ) d = do_goodbye() d.addErrback(lambda fail: self._log_error(fail, "Internal error")) else: # should not arrive here # raise Exception( "RouterApplicationSession.send: unhandled message {0}".format( msg))
def onMessage(self, msg): """ Implements :func:`autobahn.wamp.interfaces.ITransportHandler.onMessage` """ if self._session_id is None: if not self._pending_session_id: self._pending_session_id = util.id() def welcome(realm, authid=None, authrole=None, authmethod=None, authprovider=None): self._session_id = self._pending_session_id self._pending_session_id = None self._goodbye_sent = False self._router = self._router_factory.get(realm) if not self._router: raise Exception("no such realm") self._authid = authid self._authrole = authrole self._authmethod = authmethod self._authprovider = authprovider roles = self._router.attach(self) msg = message.Welcome(self._session_id, roles, authid=authid, authrole=authrole, authmethod=authmethod, authprovider=authprovider) self._transport.send(msg) self.onJoin(SessionDetails(self._realm, self._session_id, self._authid, self._authrole, self._authmethod, self._authprovider)) # the first message MUST be HELLO if isinstance(msg, message.Hello): self._realm = msg.realm self._session_roles = msg.roles details = types.HelloDetails(msg.roles, msg.authmethods, msg.authid, self._pending_session_id) d = txaio.as_future(self.onHello, self._realm, details) def success(res): msg = None if isinstance(res, types.Accept): welcome(self._realm, res.authid, res.authrole, res.authmethod, res.authprovider) elif isinstance(res, types.Challenge): msg = message.Challenge(res.method, res.extra) elif isinstance(res, types.Deny): msg = message.Abort(res.reason, res.message) else: pass if msg: self._transport.send(msg) txaio.add_callbacks(d, success, self._swallow_error_and_abort) elif isinstance(msg, message.Authenticate): d = txaio.as_future(self.onAuthenticate, msg.signature, {}) def success(res): msg = None if isinstance(res, types.Accept): welcome(self._realm, res.authid, res.authrole, res.authmethod, res.authprovider) elif isinstance(res, types.Deny): msg = message.Abort(res.reason, res.message) else: pass if msg: self._transport.send(msg) txaio.add_callbacks(d, success, self._swallow_error_and_abort) elif isinstance(msg, message.Abort): # fire callback and close the transport self.onLeave(types.CloseDetails(msg.reason, msg.message)) self._session_id = None self._pending_session_id = None # self._transport.close() else: raise ProtocolError("Received {0} message, and session is not yet established".format(msg.__class__)) else: if isinstance(msg, message.Hello): raise ProtocolError(u"HELLO message received, while session is already established") elif isinstance(msg, message.Goodbye): if not self._goodbye_sent: # the peer wants to close: send GOODBYE reply reply = message.Goodbye() self._transport.send(reply) # fire callback and close the transport self.onLeave(types.CloseDetails(msg.reason, msg.message)) self._router.detach(self) self._session_id = None self._pending_session_id = None # self._transport.close() else: self._router.process(self, msg)
def processPublish(self, session, publish): """ Implements :func:`crossbar.router.interfaces.IBroker.processPublish` """ # check topic URI: for PUBLISH, must be valid URI (either strict or loose), and # all URI components must be non-empty if self._option_uri_strict: uri_is_valid = _URI_PAT_STRICT_NON_EMPTY.match(publish.topic) else: uri_is_valid = _URI_PAT_LOOSE_NON_EMPTY.match(publish.topic) if not uri_is_valid: if publish.acknowledge: reply = message.Error(message.Publish.MESSAGE_TYPE, publish.request, ApplicationError.INVALID_URI, [u"publish with invalid topic URI '{0}' (URI strict checking {1})".format(publish.topic, self._option_uri_strict)]) session._transport.send(reply) return # disallow publication to topics starting with "wamp." and # "crossbar." other than for trusted session (that are sessions # built into Crossbar.io) if session._authrole is not None and session._authrole != u"trusted": if publish.topic.startswith(u"wamp.") or publish.topic.startswith(u"crossbar."): if publish.acknowledge: reply = message.Error(message.Publish.MESSAGE_TYPE, publish.request, ApplicationError.INVALID_URI, [u"publish with restricted topic URI '{0}'".format(publish.topic)]) session._transport.send(reply) return # get subscriptions active on the topic published to # subscriptions = self._subscription_map.match_observations(publish.topic) # go on if there are any active subscriptions or the publish is to be acknowledged # otherwise there isn't anything to do anyway. # if subscriptions or publish.acknowledge: # validate payload # try: self._router.validate('event', publish.topic, publish.args, publish.kwargs) except Exception as e: if publish.acknowledge: reply = message.Error(message.Publish.MESSAGE_TYPE, publish.request, ApplicationError.INVALID_ARGUMENT, [u"publish to topic URI '{0}' with invalid application payload: {1}".format(publish.topic, e)]) session._transport.send(reply) return # authorize PUBLISH action # d = txaio.as_future(self._router.authorize, session, publish.topic, RouterAction.ACTION_PUBLISH) def on_authorize_success(authorized): # the call to authorize the action _itself_ succeeded. now go on depending on whether # the action was actually authorized or not .. # if not authorized: if publish.acknowledge: reply = message.Error(message.Publish.MESSAGE_TYPE, publish.request, ApplicationError.NOT_AUTHORIZED, [u"session not authorized to publish to topic '{0}'".format(publish.topic)]) session._transport.send(reply) else: # new ID for the publication # publication = util.id() # send publish acknowledge immediately when requested # if publish.acknowledge: msg = message.Published(publish.request, publication) session._transport.send(msg) # publisher disclosure # if publish.disclose_me: publisher = session._session_id else: publisher = None # skip publisher # if publish.exclude_me is None or publish.exclude_me: me_also = False else: me_also = True # iterate over all subscriptions .. # for subscription in subscriptions: # initial list of receivers are all subscribers on a subscription .. # receivers = subscription.observers # filter by "eligible" receivers # if publish.eligible: # map eligible session IDs to eligible sessions eligible = [] for session_id in publish.eligible: if session_id in self._router._session_id_to_session: eligible.append(self._router._session_id_to_session[session_id]) # filter receivers for eligible sessions receivers = set(eligible) & receivers # remove "excluded" receivers # if publish.exclude: # map excluded session IDs to excluded sessions exclude = [] for s in publish.exclude: if s in self._router._session_id_to_session: exclude.append(self._router._session_id_to_session[s]) # filter receivers for excluded sessions if exclude: receivers = receivers - set(exclude) # if receivers is non-empty, dispatch event .. # if receivers: # for pattern-based subscriptions, the EVENT must contain # the actual topic being published to # if subscription.match != message.Subscribe.MATCH_EXACT: topic = publish.topic else: topic = None msg = message.Event(subscription.id, publication, args=publish.args, kwargs=publish.kwargs, publisher=publisher, topic=topic) for receiver in receivers: if me_also or receiver != session: # the receiving subscriber session might have been lost in the meantime .. if receiver._transport: receiver._transport.send(msg) def on_authorize_error(err): """ the call to authorize the action _itself_ failed (note this is different from the call to authorize succeed, but the authorization being denied) """ self.log.failure(err) if publish.acknowledge: reply = message.Error( message.Publish.MESSAGE_TYPE, publish.request, ApplicationError.AUTHORIZATION_FAILED, [u"failed to authorize session for publishing to topic URI '{0}': {1}".format(publish.topic, err.value)] ) session._transport.send(reply) txaio.add_callbacks(d, on_authorize_success, on_authorize_error)
def send(self, msg): """ Implements :func:`autobahn.wamp.interfaces.ITransport.send` """ if isinstance(msg, message.Hello): self._router = self._routerFactory.get(msg.realm) # fake session ID assignment (normally done in WAMP opening handshake) self._session._session_id = util.id() # set fixed/trusted authentication information self._session._authid = self._trusted_authid self._session._authrole = self._trusted_authrole self._session._authmethod = None # FIXME: the following does blow up # self._session._authmethod = u'trusted' self._session._authprovider = None # add app session to router self._router.attach(self._session) # fake app session open # details = SessionDetails(self._session._realm, self._session._session_id, self._session._authid, self._session._authrole, self._session._authmethod, self._session._authprovider) d = txaio.as_future(self._session.onJoin, details) def _log_error_and_close(fail): self.log.failure("Internal error: {log_failure.value}", failure=fail) self.close() txaio.add_callbacks(d, None, _log_error_and_close) # app-to-router # elif isinstance(msg, message.Publish) or \ isinstance(msg, message.Subscribe) or \ isinstance(msg, message.Unsubscribe) or \ isinstance(msg, message.Call) or \ isinstance(msg, message.Yield) or \ isinstance(msg, message.Register) or \ isinstance(msg, message.Unregister) or \ isinstance(msg, message.Cancel) or \ (isinstance(msg, message.Error) and msg.request_type == message.Invocation.MESSAGE_TYPE): # deliver message to router # self._router.process(self._session, msg) # router-to-app # elif isinstance(msg, message.Event) or \ isinstance(msg, message.Invocation) or \ isinstance(msg, message.Result) or \ isinstance(msg, message.Published) or \ isinstance(msg, message.Subscribed) or \ isinstance(msg, message.Unsubscribed) or \ isinstance(msg, message.Registered) or \ isinstance(msg, message.Unregistered) or \ (isinstance(msg, message.Error) and ( msg.request_type == message.Call.MESSAGE_TYPE or msg.request_type == message.Cancel.MESSAGE_TYPE or msg.request_type == message.Register.MESSAGE_TYPE or msg.request_type == message.Unregister.MESSAGE_TYPE or msg.request_type == message.Publish.MESSAGE_TYPE or msg.request_type == message.Subscribe.MESSAGE_TYPE or msg.request_type == message.Unsubscribe.MESSAGE_TYPE)): # deliver message to app session # self._session.onMessage(msg) else: # should not arrive here # raise Exception("RouterApplicationSession.send: unhandled message {0}".format(msg))
def _connect_once(self, reactor, transport): self.log.info( 'connecting once using transport type "{transport_type}" ' 'over endpoint "{endpoint_desc}"', transport_type=transport.type, endpoint_desc=transport.describe_endpoint(), ) done = txaio.create_future() # factory for ISession objects def create_session(): cfg = ComponentConfig(self._realm, self._extra) try: self._session = session = self.session_factory(cfg) for auth_name, auth_config in self._authentication.items(): if isinstance(auth_config, IAuthenticator): session.add_authenticator(auth_config) else: authenticator = create_authenticator(auth_name, **auth_config) session.add_authenticator(authenticator) except Exception as e: # couldn't instantiate session calls, which is fatal. # let the reconnection logic deal with that f = txaio.create_failure(e) txaio.reject(done, f) raise else: # hook up the listener to the parent so we can bubble # up events happning on the session onto the # connection. This lets you do component.on('join', # cb) which will work just as if you called # session.on('join', cb) for every session created. session._parent = self # listen on leave events; if we get errors # (e.g. no_such_realm), an on_leave can happen without # an on_join before def on_leave(session, details): self.log.info( "session leaving '{details.reason}'", details=details, ) if not txaio.is_called(done): if details.reason in [u"wamp.close.normal"]: txaio.resolve(done, None) else: f = txaio.create_failure( ApplicationError(details.reason) ) txaio.reject(done, f) session.on('leave', on_leave) # if we were given a "main" procedure, we run through # it completely (i.e. until its Deferred fires) and # then disconnect this session def on_join(session, details): transport.connect_sucesses += 1 self.log.debug("session on_join: {details}", details=details) d = txaio.as_future(self._entry, reactor, session) def main_success(_): self.log.debug("main_success") def leave(): try: session.leave() except SessionNotReady: # someone may have already called # leave() pass txaio.call_later(0, leave) def main_error(err): self.log.debug("main_error: {err}", err=err) txaio.reject(done, err) session.disconnect() txaio.add_callbacks(d, main_success, main_error) if self._entry is not None: session.on('join', on_join) # listen on disconnect events. Note that in case we # had a "main" procedure, we could have already # resolve()'d our "done" future def on_disconnect(session, was_clean): self.log.debug( "session on_disconnect: was_clean={was_clean}", was_clean=was_clean, ) if not txaio.is_called(done): if not was_clean: self.log.warn( u"Session disconnected uncleanly" ) else: # eg the session has left the realm, and the transport was properly # shut down. successfully finish the connection txaio.resolve(done, None) session.on('disconnect', on_disconnect) # return the fresh session object return session transport.connect_attempts += 1 d = txaio.as_future( self._connect_transport, reactor, transport, create_session, done, ) def on_error(err): """ this may seem redundant after looking at _connect_transport, but it will handle a case where something goes wrong in _connect_transport itself -- as the only connect our caller has is the 'done' future """ transport.connect_failures += 1 # something bad has happened, and maybe didn't get caught # upstream yet if not txaio.is_called(done): txaio.reject(done, err) txaio.add_callbacks(d, None, on_error) return done
def onMessage(self, msg): """ Implements :func:`autobahn.wamp.interfaces.ITransportHandler.onMessage` """ if self._session_id is None: # the first message must be WELCOME, ABORT or CHALLENGE .. if isinstance(msg, message.Welcome): self._session_id = msg.session details = SessionDetails(self._realm, self._session_id, msg.authid, msg.authrole, msg.authmethod) d = txaio.as_future(self.onJoin, details) def _error(e): return self._swallow_error(e, "While firing onJoin") 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 = txaio.as_future(self.onLeave, details) def _error(e): return self._swallow_error(e, "While firing onLeave") txaio.add_callbacks(d, None, _error) elif isinstance(msg, message.Challenge): challenge = types.Challenge(msg.method, msg.extra) d = txaio.as_future(self.onChallenge, challenge) def success(signature): reply = message.Authenticate(signature) self._transport.send(reply) def error(err): reply = message.Abort(u"wamp.error.cannot_authenticate", u"{0}".format(err.value)) self._transport.send(reply) # fire callback and close the transport details = types.CloseDetails(reply.reason, reply.message) d = txaio.as_future(self.onLeave, details) def _error(e): return self._swallow_error(e, "While firing onLeave") txaio.add_callbacks(d, None, _error) # switching to the callback chain, effectively # cancelling error (which we've now handled) return d txaio.add_callbacks(d, success, error) else: raise ProtocolError("Received {0} message, and session is not yet established".format(msg.__class__)) else: # self._session_id != None (aka "session established") if isinstance(msg, message.Goodbye): if not self._goodbye_sent: # the peer wants to close: send GOODBYE reply reply = message.Goodbye() self._transport.send(reply) self._session_id = None # fire callback and close the transport details = types.CloseDetails(msg.reason, msg.message) 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) txaio.add_callbacks(d, None, _error) elif isinstance(msg, message.Event): if msg.subscription in self._subscriptions: # fire all event handlers on subscription .. for subscription in self._subscriptions[msg.subscription]: handler = subscription.handler invoke_args = (handler.obj,) if handler.obj else tuple() if msg.args: invoke_args = invoke_args + tuple(msg.args) invoke_kwargs = msg.kwargs if msg.kwargs else dict() if handler.details_arg: invoke_kwargs[handler.details_arg] = types.EventDetails(publication=msg.publication, publisher=msg.publisher, topic=msg.topic) def _error(e): errmsg = 'While firing {0} subscribed under {1}.'.format( handler.fn, msg.subscription) 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)) elif isinstance(msg, message.Published): if msg.request in self._publish_reqs: # get and pop outstanding publish request publish_request = self._publish_reqs.pop(msg.request) # create a new publication object publication = Publication(msg.publication) # resolve deferred/future for publishing successfully txaio.resolve(publish_request.on_reply, publication) else: raise ProtocolError("PUBLISHED received for non-pending request ID {0}".format(msg.request)) elif isinstance(msg, message.Subscribed): if msg.request in self._subscribe_reqs: # get and pop outstanding subscribe request request = self._subscribe_reqs.pop(msg.request) # create new handler subscription list for subscription ID if not yet tracked if msg.subscription not in self._subscriptions: self._subscriptions[msg.subscription] = [] subscription = Subscription(msg.subscription, self, request.handler) # add handler to existing subscription self._subscriptions[msg.subscription].append(subscription) # resolve deferred/future for subscribing successfully txaio.resolve(request.on_reply, subscription) else: raise ProtocolError("SUBSCRIBED received for non-pending request ID {0}".format(msg.request)) elif isinstance(msg, message.Unsubscribed): if msg.request in self._unsubscribe_reqs: # get and pop outstanding subscribe request request = self._unsubscribe_reqs.pop(msg.request) # if the subscription still exists, mark as inactive and remove .. if request.subscription_id in self._subscriptions: for subscription in self._subscriptions[request.subscription_id]: subscription.active = False del self._subscriptions[request.subscription_id] # resolve deferred/future for unsubscribing successfully txaio.resolve(request.on_reply, 0) else: raise ProtocolError("UNSUBSCRIBED received for non-pending request ID {0}".format(msg.request)) elif isinstance(msg, message.Result): if msg.request in self._call_reqs: if msg.progress: # progressive result call_request = self._call_reqs[msg.request] if call_request.options.on_progress: kw = msg.kwargs or dict() args = msg.args or tuple() try: # XXX what if on_progress returns a Deferred/Future? call_request.options.on_progress(*args, **kw) except Exception as e: try: self.onUserError(e, "While firing on_progress") except: pass else: # silently ignore progressive results pass else: # final result # call_request = self._call_reqs.pop(msg.request) on_reply = call_request.on_reply if msg.kwargs: if msg.args: res = types.CallResult(*msg.args, **msg.kwargs) else: res = types.CallResult(**msg.kwargs) txaio.resolve(on_reply, res) else: if msg.args: if len(msg.args) > 1: res = types.CallResult(*msg.args) txaio.resolve(on_reply, res) else: txaio.resolve(on_reply, msg.args[0]) else: txaio.resolve(on_reply, None) else: raise ProtocolError("RESULT received for non-pending request ID {0}".format(msg.request)) elif isinstance(msg, message.Invocation): if msg.request in self._invocations: raise ProtocolError("INVOCATION received for request ID {0} already invoked".format(msg.request)) else: if msg.registration not in self._registrations: raise ProtocolError("INVOCATION received for non-registered registration ID {0}".format(msg.registration)) else: registration = self._registrations[msg.registration] endpoint = registration.endpoint if endpoint.obj: invoke_args = (endpoint.obj,) else: invoke_args = tuple() if msg.args: invoke_args = invoke_args + tuple(msg.args) invoke_kwargs = msg.kwargs if msg.kwargs else dict() if endpoint.details_arg: if msg.receive_progress: def progress(*args, **kwargs): progress_msg = message.Yield(msg.request, args=args, kwargs=kwargs, progress=True) self._transport.send(progress_msg) else: progress = None invoke_kwargs[endpoint.details_arg] = types.CallDetails(progress, caller=msg.caller, procedure=msg.procedure) on_reply = txaio.as_future(endpoint.fn, *invoke_args, **invoke_kwargs) def success(res): del self._invocations[msg.request] if isinstance(res, types.CallResult): reply = message.Yield(msg.request, args=res.results, kwargs=res.kwresults) else: reply = message.Yield(msg.request, args=[res]) try: self._transport.send(reply) except SerializationError as e: # the application-level payload returned from the invoked procedure can't be serialized reply = message.Error(message.Invocation.MESSAGE_TYPE, msg.request, ApplicationError.INVALID_PAYLOAD, args=[u'success return value from invoked procedure "{0}" could not be serialized: {1}'.format(registration.procedure, e)]) self._transport.send(reply) def error(err): errmsg = 'Failure while invoking procedure {0} registered under "{1}".'.format(endpoint.fn, registration.procedure) try: self.onUserError(err, errmsg) except: pass formatted_tb = None if self.traceback_app: # if asked to marshal the traceback within the WAMP error message, extract it # noinspection PyCallingNonCallable tb = StringIO() err.printTraceback(file=tb) formatted_tb = tb.getvalue().splitlines() del self._invocations[msg.request] if hasattr(err, 'value'): exc = err.value else: exc = err reply = self._message_from_exception(message.Invocation.MESSAGE_TYPE, msg.request, exc, formatted_tb) try: self._transport.send(reply) except SerializationError as e: # the application-level payload returned from the invoked procedure can't be serialized reply = message.Error(message.Invocation.MESSAGE_TYPE, msg.request, ApplicationError.INVALID_PAYLOAD, args=[u'error return value from invoked procedure "{0}" could not be serialized: {1}'.format(registration.procedure, e)]) self._transport.send(reply) # we have handled the error, so we eat it return None self._invocations[msg.request] = InvocationRequest(msg.request, on_reply) txaio.add_callbacks(on_reply, success, error) elif isinstance(msg, message.Interrupt): if msg.request not in self._invocations: raise ProtocolError("INTERRUPT received for non-pending invocation {0}".format(msg.request)) else: # noinspection PyBroadException try: self._invocations[msg.request].cancel() except Exception as e: # XXX can .cancel() return a Deferred/Future? try: self.onUserError(e, "While cancelling call.") except: pass finally: del self._invocations[msg.request] elif isinstance(msg, message.Registered): if msg.request in self._register_reqs: # get and pop outstanding register request request = self._register_reqs.pop(msg.request) # create new registration if not yet tracked if msg.registration not in self._registrations: registration = Registration(self, msg.registration, request.procedure, request.endpoint) self._registrations[msg.registration] = registration else: raise ProtocolError("REGISTERED received for already existing registration ID {0}".format(msg.registration)) txaio.resolve(request.on_reply, registration) else: raise ProtocolError("REGISTERED received for non-pending request ID {0}".format(msg.request)) elif isinstance(msg, message.Unregistered): if msg.request in self._unregister_reqs: # get and pop outstanding subscribe request request = self._unregister_reqs.pop(msg.request) # if the registration still exists, mark as inactive and remove .. if request.registration_id in self._registrations: self._registrations[request.registration_id].active = False del self._registrations[request.registration_id] # resolve deferred/future for unregistering successfully txaio.resolve(request.on_reply) else: raise ProtocolError("UNREGISTERED received for non-pending request ID {0}".format(msg.request)) elif isinstance(msg, message.Error): # remove outstanding request and get the reply deferred/future on_reply = None # ERROR reply to CALL if msg.request_type == message.Call.MESSAGE_TYPE and msg.request in self._call_reqs: on_reply = self._call_reqs.pop(msg.request).on_reply # ERROR reply to PUBLISH elif msg.request_type == message.Publish.MESSAGE_TYPE and msg.request in self._publish_reqs: on_reply = self._publish_reqs.pop(msg.request).on_reply # ERROR reply to SUBSCRIBE elif msg.request_type == message.Subscribe.MESSAGE_TYPE and msg.request in self._subscribe_reqs: on_reply = self._subscribe_reqs.pop(msg.request).on_reply # ERROR reply to UNSUBSCRIBE elif msg.request_type == message.Unsubscribe.MESSAGE_TYPE and msg.request in self._unsubscribe_reqs: on_reply = self._unsubscribe_reqs.pop(msg.request).on_reply # ERROR reply to REGISTER elif msg.request_type == message.Register.MESSAGE_TYPE and msg.request in self._register_reqs: on_reply = self._register_reqs.pop(msg.request).on_reply # ERROR reply to UNREGISTER elif msg.request_type == message.Unregister.MESSAGE_TYPE and msg.request in self._unregister_reqs: on_reply = self._unregister_reqs.pop(msg.request).on_reply if on_reply: 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)) else: raise ProtocolError("Unexpected message {0}".format(msg.__class__))
def send(self, msg): """ Implements :func:`autobahn.wamp.interfaces.ITransport.send` """ if isinstance(msg, message.Hello): self._router = self._routerFactory.get(msg.realm) # fake session ID assignment (normally done in WAMP opening handshake) self._session._session_id = util.id() # set fixed/trusted authentication information self._session._authid = self._trusted_authid self._session._authrole = self._trusted_authrole self._session._authmethod = None # FIXME: the following does blow up # self._session._authmethod = u'trusted' self._session._authprovider = None self._session._authextra = None # add app session to router self._router.attach(self._session) # fake app session open details = SessionDetails(self._session._realm, self._session._session_id, self._session._authid, self._session._authrole, self._session._authmethod, self._session._authprovider, self._session._authextra) # have to fire the 'join' notification ourselves, as we're # faking out what the protocol usually does. d = self._session.fire('join', self._session, details) d.addErrback(lambda fail: self._log_error(fail, "While notifying 'join'")) # now fire onJoin (since _log_error returns None, we'll be # back in the callback chain even on errors from 'join' d.addCallback(lambda _: txaio.as_future(self._session.onJoin, details)) d.addErrback(lambda fail: self._swallow_error(fail, "While firing onJoin")) d.addCallback(lambda _: self._session.fire('ready', self._session)) d.addErrback(lambda fail: self._log_error(fail, "While notifying 'ready'")) # app-to-router # elif isinstance(msg, (message.Publish, message.Subscribe, message.Unsubscribe, message.Call, message.Yield, message.Register, message.Unregister, message.Cancel)) or \ (isinstance(msg, message.Error) and msg.request_type == message.Invocation.MESSAGE_TYPE): # deliver message to router # self._router.process(self._session, msg) # router-to-app # elif isinstance(msg, (message.Event, message.Invocation, message.Result, message.Published, message.Subscribed, message.Unsubscribed, message.Registered, message.Unregistered)) or \ (isinstance(msg, message.Error) and (msg.request_type in { message.Call.MESSAGE_TYPE, message.Cancel.MESSAGE_TYPE, message.Register.MESSAGE_TYPE, message.Unregister.MESSAGE_TYPE, message.Publish.MESSAGE_TYPE, message.Subscribe.MESSAGE_TYPE, message.Unsubscribe.MESSAGE_TYPE})): # deliver message to app session # self._session.onMessage(msg) # ignore messages # elif isinstance(msg, message.Goodbye): details = types.CloseDetails(msg.reason, msg.message) session = self._session @inlineCallbacks def do_goodbye(): try: yield session.onLeave(details) except Exception: self._log_error(Failure(), "While firing onLeave") if session._transport: session._transport.close() try: yield session.fire('leave', session, details) except Exception: self._log_error(Failure(), "While notifying 'leave'") try: yield session.fire('disconnect', session) except Exception: self._log_error(Failure(), "While notifying 'disconnect'") if self._router._realm.session: yield self._router._realm.session.publish( u'wamp.session.on_leave', session._session_id, ) d = do_goodbye() d.addErrback(lambda fail: self._log_error(fail, "Internal error")) else: # should not arrive here # raise Exception("RouterApplicationSession.send: unhandled message {0}".format(msg))
def onMessage(self, msg): """ Implements :func:`autobahn.wamp.interfaces.ITransportHandler.onMessage` """ if self._session_id is None: if not self._pending_session_id: self._pending_session_id = util.id() def welcome(realm, authid=None, authrole=None, authmethod=None, authprovider=None, authextra=None, custom=None): self._realm = realm self._session_id = self._pending_session_id self._pending_session_id = None self._goodbye_sent = False self._router = self._router_factory.get(realm) if not self._router: # should not arrive here raise Exception("logic error (no realm at a stage were we should have one)") self._authid = authid self._authrole = authrole self._authmethod = authmethod self._authprovider = authprovider roles = self._router.attach(self) msg = message.Welcome(self._session_id, roles, realm=realm, authid=authid, authrole=authrole, authmethod=authmethod, authprovider=authprovider, authextra=authextra, custom=custom) self._transport.send(msg) self.onJoin(SessionDetails(self._realm, self._session_id, self._authid, self._authrole, self._authmethod, self._authprovider, self._authextra)) # the first message MUST be HELLO if isinstance(msg, message.Hello): self._session_roles = msg.roles details = types.HelloDetails(realm=msg.realm, authmethods=msg.authmethods, authid=msg.authid, authrole=msg.authrole, authextra=msg.authextra, session_roles=msg.roles, pending_session=self._pending_session_id) d = txaio.as_future(self.onHello, msg.realm, details) def success(res): msg = None if isinstance(res, types.Accept): custom = { # FIXME: # u'x_cb_node_id': self._router_factory._node_id u'x_cb_node_id': None } welcome(res.realm, res.authid, res.authrole, res.authmethod, res.authprovider, res.authextra, custom) elif isinstance(res, types.Challenge): msg = message.Challenge(res.method, res.extra) elif isinstance(res, types.Deny): msg = message.Abort(res.reason, res.message) else: pass if msg: self._transport.send(msg) txaio.add_callbacks(d, success, self._swallow_error_and_abort) elif isinstance(msg, message.Authenticate): d = txaio.as_future(self.onAuthenticate, msg.signature, {}) def success(res): msg = None if isinstance(res, types.Accept): custom = { # FIXME: # u'x_cb_node_id': self._router_factory._node_id u'x_cb_node_id': None } welcome(res.realm, res.authid, res.authrole, res.authmethod, res.authprovider, res.authextra, custom) elif isinstance(res, types.Deny): msg = message.Abort(res.reason, res.message) else: pass if msg: self._transport.send(msg) txaio.add_callbacks(d, success, self._swallow_error_and_abort) elif isinstance(msg, message.Abort): # fire callback and close the transport self.onLeave(types.CloseDetails(msg.reason, msg.message)) self._session_id = None self._pending_session_id = None # self._transport.close() else: # raise ProtocolError(u"PReceived {0} message while session is not joined".format(msg.__class__)) # self.log.warn('Protocol state error - received {message} while session is not joined') # swallow all noise like still getting PUBLISH messages from log event forwarding - maybe FIXME pass else: if isinstance(msg, message.Hello): raise ProtocolError(u"HELLO message received, while session is already established") elif isinstance(msg, message.Goodbye): if not self._goodbye_sent: # The peer wants to close: answer with GOODBYE reply. # Note: We MUST NOT send any WAMP message _after_ GOODBYE reply = message.Goodbye() self._transport.send(reply) self._goodbye_sent = True else: # This is the peer's GOODBYE reply to our own earlier GOODBYE pass # We need to first detach the session from the router before # erasing the session ID below .. try: self._router.detach(self) except Exception: self.log.failure("Internal error") # In order to send wamp.session.on_leave properly # (i.e. *with* the proper session_id) we save it previous_session_id = self._session_id # At this point, we've either sent GOODBYE already earlier, # or we have just responded with GOODBYE. In any case, we MUST NOT # send any WAMP message from now on: # clear out session ID, so that anything that might be triggered # in the onLeave below is prohibited from sending WAMP stuff. # E.g. the client might have been subscribed to meta events like # wamp.session.on_leave - and we must not send that client's own # leave to itself! self._session_id = None self._pending_session_id = None # publish event, *after* self._session_id is None so # that we don't publish to ourselves as well (if this # session happens to be subscribed to wamp.session.on_leave) if self._service_session: self._service_session.publish( u'wamp.session.on_leave', previous_session_id, ) # fire callback and close the transport self.onLeave(types.CloseDetails(msg.reason, msg.message)) # don't close the transport, as WAMP allows to reattach a session # to the same or a different realm without closing the transport # self._transport.close() else: self._router.process(self, msg)