def processCall(self, session, call): """ Implements :func:`autobahn.wamp.interfaces.IDealer.processCall` """ assert (session in self._session_to_registrations) ## check procedure URI ## if (not self._option_uri_strict and not _URI_PAT_LOOSE_NON_EMPTY.match(call.procedure)) or \ ( self._option_uri_strict and not _URI_PAT_STRICT_NON_EMPTY.match(call.procedure)): reply = message.Error( message.Register.MESSAGE_TYPE, call.request, ApplicationError.INVALID_URI, [ "call with invalid procedure URI '{}'".format( call.procedure) ]) session._transport.send(reply) else: if call.procedure in self._procs_to_regs: registration_id, endpoint_session, discloseCaller = self._procs_to_regs[ call.procedure] request_id = util.id() if discloseCaller or call.discloseMe: caller = session._session_id authid = session._authid authrole = session._authrole authmethod = session._authmethod else: caller = None authid = None authrole = None authmethod = None invocation = message.Invocation( request_id, registration_id, args=call.args, kwargs=call.kwargs, timeout=call.timeout, receive_progress=call.receive_progress, caller=caller, authid=authid, authrole=authrole, authmethod=authmethod) self._invocations[request_id] = (call, session) endpoint_session._transport.send(invocation) else: reply = message.Error( message.Call.MESSAGE_TYPE, call.request, ApplicationError.NO_SUCH_PROCEDURE, ["no procedure '{}' registered".format(call.procedure)]) session._transport.send(reply)
def processSubscribe(self, session, subscribe): """ Implements :func:`autobahn.wamp.interfaces.IBroker.processSubscribe` """ #assert(session in self._session_to_subscriptions) ## check topic URI ## if (not self._option_uri_strict and not _URI_PAT_LOOSE_NON_EMPTY.match(subscribe.topic)) or \ ( self._option_uri_strict and not _URI_PAT_STRICT_NON_EMPTY.match(subscribe.topic)): reply = message.Error(message.Subscribe.MESSAGE_TYPE, subscribe.request, ApplicationError.INVALID_URI, ["subscribe for invalid topic URI '{0}'".format(subscribe.topic)]) session._transport.send(reply) else: ## authorize action ## d = self._as_future(self._router.authorize, session, subscribe.topic, IRouter.ACTION_SUBSCRIBE) def on_authorize_success(authorized): if not authorized: reply = message.Error(message.Subscribe.MESSAGE_TYPE, subscribe.request, ApplicationError.NOT_AUTHORIZED, ["session is not authorized to subscribe to topic '{0}'".format(subscribe.topic)]) else: if not subscribe.topic in self._topic_to_sessions: subscription = util.id() self._topic_to_sessions[subscribe.topic] = (subscription, set()) subscription, subscribers = self._topic_to_sessions[subscribe.topic] if not session in subscribers: subscribers.add(session) if not subscription in self._subscription_to_sessions: self._subscription_to_sessions[subscription] = (subscribe.topic, set()) _, subscribers = self._subscription_to_sessions[subscription] if not session in subscribers: subscribers.add(session) if not subscription in self._session_to_subscriptions[session]: self._session_to_subscriptions[session].add(subscription) reply = message.Subscribed(subscribe.request, subscription) session._transport.send(reply) def on_authorize_error(err): reply = message.Error(message.Subscribe.MESSAGE_TYPE, subscribe.request, ApplicationError.AUTHORIZATION_FAILED, ["failed to authorize session for subscribing to topic URI '{0}': {1}".format(subscribe.topic, err.value)]) session._transport.send(reply) self._add_future_callbacks(d, on_authorize_success, on_authorize_error)
def _mqtt_topicfilter_to_wamp(topic): """ Convert a MQTT topic as used in MQTT Subscribe (and hence ptoentially containing special characters "+" and "#") to a WAMP URI and a match policy. """ if type(topic) != six.text_type: raise TypeError('invalid type "{}" for MQTT topic filter'.format(type(topic))) if u'+' in topic: # check topic filter containing single-level wildcard character # this is a restriction following from WAMP! we cannot have both # wildcard and prefix matching combined. if u'#' in topic: raise TypeError('MQTT topic filter "{}" contains both single-level and multi-level wildcards, and this cannot be mapped to WAMP'.format(topic)) for c in topic.split(u'/'): if c != u'+' and u'+' in c: raise TypeError('invalid MQTT filter "{}": single-level wildcard characters must stand on their own in components'.format(topic)) _match = u'wildcard' _topic = topic.replace(u'+', u'') elif u'#' in topic: # check topic filter containing multi-level wildcard character # there can be only one occurence, and it must be at the end if topic.find('#') != len(topic) - 1: raise TypeError('invalid MQTT topic filter "{}": # multi-level wildcard character must only appear as last character'.format(topic)) if len(topic) > 1: if topic[-2] != u'/': raise TypeError('invalid MQTT topic filter "{}": # multi-level wildcard character must either appear solely, or be preceded by a / topic separator character'.format(topic)) _match = u'prefix' _topic = topic[:-1] else: _match = u'exact' _topic = topic[:] # MQTT spec 4.7.1.1: "The use of the topic level separator is significant when either # of the two wildcard characters is encountered in the Topic Filters specified by subscribing Clients." # # FIXME: However, we still cannot leave the "/" character untouched and uninterpreted # when no "+" or "#" was encountered if True or _match != u'exact': # replace MQTT level separator "/" with WAMP level separator "." _topic = u'.'.join(_topic.split(u'/')) if (_match == u'exact' and not _URI_PAT_LOOSE_NON_EMPTY.match(_topic)) or \ (_match == u'prefix' and not _URI_PAT_LOOSE_LAST_EMPTY.match(_topic)) or \ (_match == u'wildcard' and not _URI_PAT_LOOSE_EMPTY.match(_topic)): raise TypeError('invalid WAMP URI "{}" (match="{}") after conversion from MQTT topic filter "{}"'.format(_topic, _match, topic)) return _topic, _match
def processSubscribe(self, session, subscribe): """ Implements :func:`autobahn.wamp.interfaces.IBroker.processSubscribe` """ assert (session in self._session_to_subscriptions) ## check topic URI ## if (not self._option_uri_strict and not _URI_PAT_LOOSE_NON_EMPTY.match(subscribe.topic)) or \ ( self._option_uri_strict and not _URI_PAT_STRICT_NON_EMPTY.match(subscribe.topic)): reply = message.Error( message.Subscribe.MESSAGE_TYPE, subscribe.request, ApplicationError.INVALID_URI, [ "subscribe for invalid topic URI '{}'".format( subscribe.topic) ]) else: if not subscribe.topic in self._topic_to_sessions: subscription = util.id() self._topic_to_sessions[subscribe.topic] = (subscription, set()) subscription, subscribers = self._topic_to_sessions[ subscribe.topic] if not session in subscribers: subscribers.add(session) if not subscription in self._subscription_to_sessions: self._subscription_to_sessions[subscription] = ( subscribe.topic, set()) _, subscribers = self._subscription_to_sessions[subscription] if not session in subscribers: subscribers.add(session) if not subscription in self._session_to_subscriptions[session]: self._session_to_subscriptions[session].add(subscription) reply = message.Subscribed(subscribe.request, subscription) session._transport.send(reply)
def processRegister(self, session, register): """ Implements :func:`autobahn.wamp.interfaces.IDealer.processRegister` """ assert(session in self._session_to_registrations) ## check procedure URI ## if (not self._option_uri_strict and not _URI_PAT_LOOSE_NON_EMPTY.match(register.procedure)) or \ ( self._option_uri_strict and not _URI_PAT_STRICT_NON_EMPTY.match(register.procedure)): reply = message.Error(message.Register.MESSAGE_TYPE, register.request, ApplicationError.INVALID_URI, ["register for invalid procedure URI '{0}'".format(register.procedure)]) session._transport.send(reply) else: if not register.procedure in self._procs_to_regs: ## authorize action ## d = self._as_future(self._router.authorize, session, register.procedure, IRouter.ACTION_REGISTER) def on_authorize_success(authorized): if authorized: registration_id = util.id() self._procs_to_regs[register.procedure] = (registration_id, session, register.discloseCaller, register.discloseCallerTransport) self._regs_to_procs[registration_id] = register.procedure self._session_to_registrations[session].add(registration_id) reply = message.Registered(register.request, registration_id) else: reply = message.Error(message.Register.MESSAGE_TYPE, register.request, ApplicationError.NOT_AUTHORIZED, ["session is not authorized to register procedure '{0}'".format(register.procedure)]) session._transport.send(reply) def on_authorize_error(err): reply = message.Error(message.Register.MESSAGE_TYPE, register.request, ApplicationError.AUTHORIZATION_FAILED, ["failed to authorize session for registering procedure '{0}': {1}".format(register.procedure, err.value)]) session._transport.send(reply) self._add_future_callbacks(d, on_authorize_success, on_authorize_error) else: reply = message.Error(message.Register.MESSAGE_TYPE, register.request, ApplicationError.PROCEDURE_ALREADY_EXISTS, ["register for already registered procedure '{0}'".format(register.procedure)]) session._transport.send(reply)
def _mqtt_topicname_to_wamp(topic): """ Convert a MQTT topic as used in MQTT Publish to a WAMP URI. """ if type(topic) != six.text_type: raise TypeError('invalid type "{}" for MQTT topic name'.format(type(topic))) if u'#' in topic or u'+' in topic: raise TypeError('invalid MQTT topic name "{}" - contains wildcard characters'.format(topic)) if u'/' in topic: _topic = u'.'.join(topic.split(u'/')) else: _topic = topic if not _URI_PAT_LOOSE_NON_EMPTY.match(_topic): raise TypeError('invalid WAMP URI "{}" after conversion from MQTT topic name "{}"'.format(_topic, topic)) return _topic
def processRegister(self, session, register): """ Implements :func:`autobahn.wamp.interfaces.IDealer.processRegister` """ assert (session in self._session_to_registrations) ## check procedure URI ## if (not self._option_uri_strict and not _URI_PAT_LOOSE_NON_EMPTY.match(register.procedure)) or \ ( self._option_uri_strict and not _URI_PAT_STRICT_NON_EMPTY.match(register.procedure)): reply = message.Error( message.Register.MESSAGE_TYPE, register.request, ApplicationError.INVALID_URI, [ "register for invalid procedure URI '{}'".format( register.procedure) ]) else: if not register.procedure in self._procs_to_regs: registration_id = util.id() self._procs_to_regs[register.procedure] = ( registration_id, session, register.discloseCaller) self._regs_to_procs[registration_id] = register.procedure self._session_to_registrations[session].add(registration_id) reply = message.Registered(register.request, registration_id) else: reply = message.Error( message.Register.MESSAGE_TYPE, register.request, ApplicationError.PROCEDURE_ALREADY_EXISTS, [ "register for already registered procedure URI '{}'". format(register.procedure) ]) session._transport.send(reply)
def processCall(self, session, call): """ Implements :func:`autobahn.wamp.interfaces.IDealer.processCall` """ assert(session in self._session_to_registrations) ## check procedure URI ## if (not self._option_uri_strict and not _URI_PAT_LOOSE_NON_EMPTY.match(call.procedure)) or \ ( self._option_uri_strict and not _URI_PAT_STRICT_NON_EMPTY.match(call.procedure)): reply = message.Error(message.Call.MESSAGE_TYPE, call.request, ApplicationError.INVALID_URI, ["call with invalid procedure URI '{0}'".format(call.procedure)]) session._transport.send(reply) else: if call.procedure in self._procs_to_regs: ## validate payload ## 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, ["call of procedure '{0}' with invalid application payload: {1}".format(call.procedure, e)]) session._transport.send(reply) return ## authorize action ## d = self._as_future(self._router.authorize, session, call.procedure, IRouter.ACTION_CALL) def on_authorize_success(authorized): if authorized: registration_id, endpoint_session, discloseCaller, discloseCallerTransport = self._procs_to_regs[call.procedure] request_id = util.id() if discloseCaller or call.discloseMe: caller = session._session_id caller_transport = None authid = session._authid authrole = session._authrole authmethod = session._authmethod if discloseCallerTransport and hasattr(session._transport, '_transport_info'): caller_transport = session._transport._transport_info else: caller = None caller_transport = None authid = None authrole = None authmethod = None invocation = message.Invocation(request_id, registration_id, args = call.args, kwargs = call.kwargs, timeout = call.timeout, receive_progress = call.receive_progress, caller = caller, caller_transport = caller_transport, authid = authid, authrole = authrole, authmethod = authmethod) self._invocations[request_id] = (call, session) endpoint_session._transport.send(invocation) else: reply = message.Error(message.Call.MESSAGE_TYPE, call.request, ApplicationError.NOT_AUTHORIZED, ["session is not authorized to call procedure '{0}'".format(call.procedure)]) session._transport.send(reply) def on_authorize_error(err): reply = message.Error(message.Call.MESSAGE_TYPE, call.request, ApplicationError.AUTHORIZATION_FAILED, ["failed to authorize session for calling procedure '{0}': {1}".format(call.procedure, err.value)]) session._transport.send(reply) self._add_future_callbacks(d, on_authorize_success, on_authorize_error) else: reply = message.Error(message.Call.MESSAGE_TYPE, call.request, ApplicationError.NO_SUCH_PROCEDURE, ["no procedure '{0}' registered".format(call.procedure)]) session._transport.send(reply)
def processPublish(self, session, publish): """ Implements :func:`autobahn.wamp.interfaces.IBroker.processPublish` """ #assert(session in self._session_to_subscriptions) ## check topic URI ## if (not self._option_uri_strict and not _URI_PAT_LOOSE_NON_EMPTY.match(publish.topic)) or \ ( self._option_uri_strict and not _URI_PAT_STRICT_NON_EMPTY.match(publish.topic)): if publish.acknowledge: reply = message.Error( message.Publish.MESSAGE_TYPE, publish.request, ApplicationError.INVALID_URI, [ "publish with invalid topic URI '{0}'".format( publish.topic) ]) session._transport.send(reply) return if publish.topic in self._topic_to_sessions or publish.acknowledge: ## validate payload ## try: self._router.validate('event', publish.topic, publish.args, publish.kwargs) except Exception as e: reply = message.Error( message.Publish.MESSAGE_TYPE, publish.request, ApplicationError.INVALID_ARGUMENT, [ "publish to topic URI '{0}' with invalid application payload: {1}" .format(publish.topic, e) ]) session._transport.send(reply) return ## authorize action ## d = self._as_future(self._router.authorize, session, publish.topic, IRouter.ACTION_PUBLISH) def on_authorize_success(authorized): if not authorized: if publish.acknowledge: reply = message.Error( message.Publish.MESSAGE_TYPE, publish.request, ApplicationError.NOT_AUTHORIZED, [ "session not authorized to publish to topic '{0}'" .format(publish.topic) ]) session._transport.send(reply) else: ## continue processing if either a) there are subscribers to the topic or b) the publish is to be acknowledged ## if publish.topic in self._topic_to_sessions and self._topic_to_sessions[ publish.topic]: ## initial list of receivers are all subscribers .. ## subscription, receivers = self._topic_to_sessions[ publish.topic] ## filter by "eligible" receivers ## if publish.eligible: eligible = [] for s in publish.eligible: if s in self._session_id_to_session: eligible.append( self._session_id_to_session[s]) receivers = set(eligible) & receivers ## remove "excluded" receivers ## if publish.exclude: exclude = [] for s in publish.exclude: if s in self._session_id_to_session: exclude.append( self._session_id_to_session[s]) if exclude: receivers = receivers - set(exclude) ## remove publisher ## if publish.excludeMe is None or publish.excludeMe: # receivers.discard(session) # bad: this would modify our actual subscriber list me_also = False else: me_also = True else: subscription, receivers, me_also = None, [], False publication = util.id() ## send publish acknowledge when requested ## if publish.acknowledge: msg = message.Published(publish.request, publication) session._transport.send(msg) ## if receivers is non-empty, dispatch event .. ## if receivers: if publish.discloseMe: publisher = session._session_id else: publisher = None msg = message.Event(subscription, publication, args=publish.args, kwargs=publish.kwargs, publisher=publisher) for receiver in receivers: if me_also or receiver != session: ## the subscribing session might have been lost in the meantime .. if receiver._transport: receiver._transport.send(msg) def on_authorize_error(err): if publish.acknowledge: reply = message.Error( message.Publish.MESSAGE_TYPE, publish.request, ApplicationError.AUTHORIZATION_FAILED, [ "failed to authorize session for publishing to topic URI '{0}': {1}" .format(publish.topic, err.value) ]) session._transport.send(reply) self._add_future_callbacks(d, on_authorize_success, on_authorize_error)
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)]) self._router.send(session, reply) return # disallow publication to topics 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 = publish.topic.startswith(u"wamp.") or publish.topic.startswith(u"crossbar.") if is_restricted: 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)]) self._router.send(session, reply) return # get subscriptions active on the topic published to # subscriptions = self._subscription_map.match_observations(publish.topic) # check if the event is being persisted by checking if we ourself are among the observers # on _any_ matching subscription # we've been previously added to observer lists on subscriptions ultimately from # node configuration and during the broker starts up. store_event = False if self._event_store: for subscription in subscriptions: if self._event_store in subscription.observers: store_event = True break if store_event: self.log.debug('Persisting event on topic "{topic}"', topic=publish.topic) # check if the event is to be retained by inspecting the 'retain' flag retain_event = False if publish.retain: retain_event = True # go on if (otherwise there isn't anything to do anyway): # # - there are any active subscriptions OR # - the publish is to be acknowledged OR # - the event is to be persisted OR # - the event is to be retained # if subscriptions or publish.acknowledge or store_event or retain_event: # If it's a MQTT publish, we need to adjust the arguments. if getattr(publish, "_mqtt_publish", False): from crossbar.adapter.mqtt.wamp import mqtt_payload_transform tfd = mqtt_payload_transform(self._router._mqtt_payload_format, publish.payload) if not tfd: # If we have no message to give, drop it entirely return else: publish.payload = None publish.args, publish.kwargs = tfd # validate payload # if publish.payload is None: 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)]) self._router.send(session, reply) return # authorize PUBLISH action # d = self._router.authorize(session, publish.topic, u'publish') def on_authorize_success(authorization): # the call to authorize the action _itself_ succeeded. now go on depending on whether # the action was actually authorized or not .. # if not authorization[u'allow']: 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)]) self._router.send(session, reply) else: # new ID for the publication # publication = util.id() # persist event (this is done only once, regardless of the number of subscriptions # the event matches on) # if store_event: self._event_store.store_event(session._session_id, publication, publish.topic, publish.args, publish.kwargs) # retain event on the topic # if retain_event: observation = self._subscription_map.get_observation(publish.topic) if not observation: # No observation, lets make a new one observation = self._subscription_map.create_observation(publish.topic, extra=SubscriptionExtra()) if observation.extra.retained_events: if not publish.eligible and not publish.exclude: observation.extra.retained_events = [publish] else: observation.extra.retained_events.append(publish) else: observation.extra.retained_events = [publish] # send publish acknowledge immediately when requested # if publish.acknowledge: reply = message.Published(publish.request, publication) self._router.send(session, reply) # publisher disclosure # if authorization[u'disclose']: disclose = True elif (publish.topic.startswith(u"wamp.") or publish.topic.startswith(u"crossbar.")): disclose = True else: disclose = False if disclose: publisher = session._session_id publisher_authid = session._authid publisher_authrole = session._authrole else: publisher = None publisher_authid = None publisher_authrole = 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: # persist event history, but check if it is persisted on the individual subscription! # if store_event and self._event_store in subscription.observers: self._event_store.store_event_history(publication, subscription.id) # 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 .. # receivers_cnt = len(receivers) - (1 if self in receivers else 0) if receivers_cnt: # 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 if publish.payload: msg = message.Event(subscription.id, publication, payload=publish.payload, publisher=publisher, publisher_authid=publisher_authid, publisher_authrole=publisher_authrole, topic=topic, enc_algo=publish.enc_algo, enc_key=publish.enc_key, enc_serializer=publish.enc_serializer) else: msg = message.Event(subscription.id, publication, args=publish.args, kwargs=publish.kwargs, publisher=publisher, publisher_authid=publisher_authid, publisher_authrole=publisher_authrole, topic=topic) for receiver in receivers: if (me_also or receiver != session) and receiver != self._event_store: # the receiving subscriber session # might have no transport, or no # longer be joined if receiver._session_id and receiver._transport: self._router.send(receiver, 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("Authorization failed", 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)] ) self._router.send(session, reply) txaio.add_callbacks(d, on_authorize_success, on_authorize_error)
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(u'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 = self._router.authorize(session, call.procedure, u'call', options=call.marshal_options()) def on_authorize_success(authorization): # the call to authorize the action _itself_ succeeded. now go on depending on whether # the action was actually authorized or not .. # if not authorization[u'allow']: 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: self._call(session, call, registration, authorization) 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("Authorization of 'call' for '{uri}' failed", uri=call.procedure, 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 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, ["subscribe for invalid topic URI '{0}'".format(subscribe.topic)]) session._transport.send(reply) return # authorize action # d = self._as_future(self._router.authorize, session, subscribe.topic, IRouter.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, ["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) # reply = message.Error(message.Subscribe.MESSAGE_TYPE, subscribe.request, ApplicationError.AUTHORIZATION_FAILED, ["failed to authorize session for subscribing to topic URI '{0}': {1}".format(subscribe.topic, err.value)]) session._transport.send(reply) self._add_future_callbacks(d, on_authorize_success, on_authorize_error)
def processSubscribe(self, session, subscribe): """ Implements :func:`crossbar.router.interfaces.IBroker.processSubscribe` """ if self._router.is_traced: if not subscribe.correlation_id: subscribe.correlation_id = self._router.new_correlation_id() subscribe.correlation_is_anchor = True subscribe.correlation_is_last = False if not subscribe.correlation_uri: subscribe.correlation_uri = subscribe.topic # check topic URI: for SUBSCRIBE, must be valid URI (either strict or loose), and all # URI components must be non-empty for normal subscriptions, may be empty for # wildcard subscriptions and must be non-empty for all but the last component for # prefix subscriptions # if self._option_uri_strict: if subscribe.match == u"wildcard": uri_is_valid = _URI_PAT_STRICT_EMPTY.match(subscribe.topic) elif subscribe.match == u"prefix": uri_is_valid = _URI_PAT_STRICT_LAST_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) elif subscribe.match == u"prefix": uri_is_valid = _URI_PAT_LOOSE_LAST_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) ]) reply.correlation_id = subscribe.correlation_id reply.correlation_uri = subscribe.topic reply.correlation_is_anchor = False reply.correlation_is_last = True self._router.send(session, reply) return # authorize SUBSCRIBE action # d = self._router.authorize(session, subscribe.topic, u'subscribe', options=subscribe.marshal_options()) def on_authorize_success(authorization): if not authorization[u'allow']: # error reply since session is not authorized to subscribe # replies = [ message.Error( message.Subscribe.MESSAGE_TYPE, subscribe.request, ApplicationError.NOT_AUTHORIZED, [ u"session is not authorized to subscribe to topic '{0}'" .format(subscribe.topic) ]) ] replies[0].correlation_id = subscribe.correlation_id replies[0].correlation_uri = subscribe.topic replies[0].correlation_is_anchor = False replies[0].correlation_is_last = True 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, extra=SubscriptionExtra()) if not was_already_subscribed: self._session_to_subscriptions[session].add(subscription) # publish WAMP meta events, if we have a service session, but # not for the meta API itself! # if self._router._realm and \ self._router._realm.session and \ not subscription.uri.startswith(u'wamp.') and \ (is_first_subscriber or not was_already_subscribed): has_follow_up_messages = True def _publish(): service_session = self._router._realm.session options = types.PublishOptions( correlation_id=subscribe.correlation_id, correlation_is_anchor=False, correlation_is_last=False, ) if is_first_subscriber: subscription_details = { u'id': subscription.id, u'created': subscription.created, u'uri': subscription.uri, u'match': subscription.match, } service_session.publish( u'wamp.subscription.on_create', session._session_id, subscription_details, options=options, ) if not was_already_subscribed: options.correlation_is_last = True service_session.publish( u'wamp.subscription.on_subscribe', session._session_id, subscription.id, options=options, ) # we postpone actual sending of meta events until we return to this client session self._reactor.callLater(0, _publish) else: has_follow_up_messages = False # check for retained events # def _get_retained_event(): if subscription.extra.retained_events: retained_events = list( subscription.extra.retained_events) retained_events.reverse() for retained_event in retained_events: authorized = False if not retained_event.publish.exclude and not retained_event.publish.eligible: authorized = True elif session._session_id in retained_event.publish.eligible and session._session_id not in retained_event.publish.exclude: authorized = True if authorized: publication = util.id() if retained_event.publish.payload: msg = message.Event( subscription.id, publication, payload=retained_event.publish.payload, enc_algo=retained_event.publish. enc_algo, enc_key=retained_event.publish.enc_key, enc_serializer=retained_event.publish. enc_serializer, publisher=retained_event.publisher, publisher_authid=retained_event. publisher_authid, publisher_authrole=retained_event. publisher_authrole, retained=True) else: msg = message.Event( subscription.id, publication, args=retained_event.publish.args, kwargs=retained_event.publish.kwargs, publisher=retained_event.publisher, publisher_authid=retained_event. publisher_authid, publisher_authrole=retained_event. publisher_authrole, retained=True) msg.correlation_id = subscribe.correlation_id msg.correlation_uri = subscribe.topic msg.correlation_is_anchor = False msg.correlation_is_last = False return [msg] return [] # acknowledge subscribe with subscription ID # replies = [ message.Subscribed(subscribe.request, subscription.id) ] replies[0].correlation_id = subscribe.correlation_id replies[0].correlation_uri = subscribe.topic replies[0].correlation_is_anchor = False replies[0].correlation_is_last = False if subscribe.get_retained: replies.extend(_get_retained_event()) replies[-1].correlation_is_last = not has_follow_up_messages # send out reply to subscribe requestor # [self._router.send(session, reply) for reply in replies] 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("Authorization of 'subscribe' for '{uri}' failed", uri=subscribe.topic, 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) ]) reply.correlation_id = subscribe.correlation_id reply.correlation_uri = subscribe.topic reply.correlation_is_anchor = False reply.correlation_is_last = True self._router.send(session, reply) txaio.add_callbacks(d, on_authorize_success, on_authorize_error)
def processPublish(self, session, publish): """ Implements :func:`crossbar.router.interfaces.IBroker.processPublish` """ if self._router.is_traced: if not publish.correlation_id: publish.correlation_id = self._router.new_correlation_id() publish.correlation_is_anchor = True if not publish.correlation_uri: publish.correlation_uri = publish.topic # 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) ]) reply.correlation_id = publish.correlation_id reply.correlation_uri = publish.topic reply.correlation_is_anchor = False reply.correlation_is_last = True self._router.send(session, reply) return # disallow publication to topics starting with "wamp." other than for # trusted sessions (that are sessions built into Crossbar.io routing core) # if session._authrole is not None and session._authrole != u"trusted": is_restricted = publish.topic.startswith(u"wamp.") if is_restricted: 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) ]) reply.correlation_id = publish.correlation_id reply.correlation_uri = publish.topic reply.correlation_is_anchor = False reply.correlation_is_last = True self._router.send(session, reply) return # get subscriptions active on the topic published to # subscriptions = self._subscription_map.match_observations( publish.topic) # check if the event is being persisted by checking if we ourself are among the observers # on _any_ matching subscription # we've been previously added to observer lists on subscriptions ultimately from # node configuration and during the broker starts up. store_event = False if self._event_store: for subscription in subscriptions: if self._event_store in subscription.observers: store_event = True break if store_event: self.log.debug('Persisting event on topic "{topic}"', topic=publish.topic) # check if the event is to be retained by inspecting the 'retain' flag retain_event = False if publish.retain: retain_event = True # go on if (otherwise there isn't anything to do anyway): # # - there are any active subscriptions OR # - the publish is to be acknowledged OR # - the event is to be persisted OR # - the event is to be retained # if not (subscriptions or publish.acknowledge or store_event or retain_event): # the received PUBLISH message is the only one received/sent # for this WAMP action, so mark it as "last" (there is another code path below!) if self._router.is_traced and publish.correlation_is_last is None: publish.correlation_is_last = True else: # validate payload # if publish.payload is None: 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) ]) reply.correlation_id = publish.correlation_id reply.correlation_uri = publish.topic reply.correlation_is_anchor = False reply.correlation_is_last = True self._router.send(session, reply) return # authorize PUBLISH action # d = self._router.authorize(session, publish.topic, u'publish', options=publish.marshal_options()) def on_authorize_success(authorization): # the call to authorize the action _itself_ succeeded. now go on depending on whether # the action was actually authorized or not .. # if not authorization[u'allow']: 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) ]) reply.correlation_id = publish.correlation_id reply.correlation_uri = publish.topic reply.correlation_is_anchor = False reply.correlation_is_last = True self._router.send(session, reply) else: # new ID for the publication # publication = util.id() # publisher disclosure # if authorization[u'disclose']: disclose = True elif (publish.topic.startswith(u"wamp.") or publish.topic.startswith(u"crossbar.")): disclose = True else: disclose = False if disclose: publisher = session._session_id publisher_authid = session._authid publisher_authrole = session._authrole else: publisher = None publisher_authid = None publisher_authrole = None # skip publisher # if publish.exclude_me is None or publish.exclude_me: me_also = False else: me_also = True # persist event (this is done only once, regardless of the number of subscriptions # the event matches on) # if store_event: self._event_store.store_event(session._session_id, publication, publish.topic, publish.args, publish.kwargs) # retain event on the topic # if retain_event: retained_event = RetainedEvent(publish, publisher, publisher_authid, publisher_authrole) observation = self._subscription_map.get_observation( publish.topic) if not observation: # No observation, lets make a new one observation = self._subscription_map.create_observation( publish.topic, extra=SubscriptionExtra()) else: # this can happen if event-history is # enabled on the topic: the event-store # creates an observation before any client # could possible hit the code above if observation.extra is None: observation.extra = SubscriptionExtra() elif not isinstance(observation.extra, SubscriptionExtra): raise Exception( "incorrect 'extra' for '{}'".format( publish.topic)) if observation.extra.retained_events: if not publish.eligible and not publish.exclude: observation.extra.retained_events = [ retained_event ] else: observation.extra.retained_events.append( retained_event) else: observation.extra.retained_events = [ retained_event ] subscription_to_receivers = {} total_receivers_cnt = 0 # iterate over all subscriptions and determine actual receivers of the event # under the respective subscription. also persist events (independent of whether # there is any actual receiver right now on the subscription) # for subscription in subscriptions: # persist event history, but check if it is persisted on the individual subscription! # if store_event and self._event_store in subscription.observers: self._event_store.store_event_history( publication, subscription.id) # initial list of receivers are all subscribers on a subscription .. # receivers = subscription.observers receivers = self._filter_publish_receivers( receivers, publish) # if receivers is non-empty, dispatch event .. # receivers_cnt = len(receivers) - (1 if self in receivers else 0) if receivers_cnt: total_receivers_cnt += receivers_cnt subscription_to_receivers[subscription] = receivers # send publish acknowledge before dispatching # if publish.acknowledge: if self._router.is_traced: publish.correlation_is_last = False reply = message.Published(publish.request, publication) reply.correlation_id = publish.correlation_id reply.correlation_uri = publish.topic reply.correlation_is_anchor = False reply.correlation_is_last = total_receivers_cnt == 0 self._router.send(session, reply) else: if self._router.is_traced and publish.correlation_is_last is None: if total_receivers_cnt == 0: publish.correlation_is_last = True else: publish.correlation_is_last = False # now actually dispatch the events! # for chunked dispatching, this will be filled with deferreds for each chunk # processed. when the complete list of deferreds is done, that means the # event has been sent out to all applicable receivers all_dl = [] if total_receivers_cnt: # list of receivers that should have received the event, but we could not # send the event, since the receiver has disappeared in the meantime vanished_receivers = [] for subscription, receivers in subscription_to_receivers.items( ): self.log.debug( 'dispatching for subscription={subscription}', subscription=subscription) # 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 if publish.payload: msg = message.Event( subscription.id, publication, payload=publish.payload, publisher=publisher, publisher_authid=publisher_authid, publisher_authrole=publisher_authrole, topic=topic, enc_algo=publish.enc_algo, enc_key=publish.enc_key, enc_serializer=publish.enc_serializer) else: msg = message.Event( subscription.id, publication, args=publish.args, kwargs=publish.kwargs, publisher=publisher, publisher_authid=publisher_authid, publisher_authrole=publisher_authrole, topic=topic) # if the publish message had a correlation ID, this will also be the # correlation ID of the event message sent out msg.correlation_id = publish.correlation_id msg.correlation_uri = publish.topic msg.correlation_is_anchor = False msg.correlation_is_last = False chunk_size = self._options.event_dispatching_chunk_size if chunk_size and len(receivers) > chunk_size: self.log.debug( 'chunked dispatching to {receivers_size} with chunk_size={chunk_size}', receivers_size=len(receivers), chunk_size=chunk_size) else: self.log.debug( 'unchunked dispatching to {receivers_size} receivers', receivers_size=len(receivers)) # note that we're using one code-path for both chunked and unchunked # dispatches; the *first* chunk is always done "synchronously" (before # the first call-later) so "un-chunked mode" really just means we know # we'll be done right now and NOT do a call_later... # a Deferred that fires when all chunks are done all_d = txaio.create_future() all_dl.append(all_d) # all the event messages are the same except for the last one, which # needs to have the "is_last" flag set if we're doing a trace if self._router.is_traced: last_msg = copy.deepcopy(msg) last_msg.correlation_id = msg.correlation_id last_msg.correlation_uri = msg.correlation_uri last_msg.correlation_is_anchor = False last_msg.correlation_is_last = True def _notify_some(receivers): # we do a first pass over the proposed chunk of receivers # because not all of them will have a transport, and if this # will be the last chunk of receivers we need to figure out # which event is last... receivers_this_chunk = [] for receiver in receivers[:chunk_size]: if (me_also or receiver != session ) and receiver != self._event_store: # the receiving subscriber session might have no transport, # or no longer be joined if receiver._session_id and receiver._transport: receivers_this_chunk.append( receiver) else: vanished_receivers.append(receiver) receivers = receivers[chunk_size:] # XXX note there's still going to be some edge-cases here .. if # we are NOT the last chunk, but all the next chunk's receivers # (could be only 1 in that chunk!) vanish before we run our next # batch, then a "last" event will never go out ... # we now actually do the deliveries, but now we know which # receiver is the last one if receivers or not self._router.is_traced: # NOT the last chunk (or we're not traced so don't care) for receiver in receivers_this_chunk: self._router.send(receiver, msg) else: # last chunk, so last receiver gets the different message for receiver in receivers_this_chunk[:-1]: self._router.send(receiver, msg) # we might have zero valid receivers if receivers_this_chunk: self._router.send( receivers_this_chunk[-1], last_msg) if receivers: # still more to do .. return txaio.call_later( 0, _notify_some, receivers) else: # all done! resolve all_d, which represents all receivers # to a single subscription matching the event txaio.resolve(all_d, None) _notify_some(list(receivers)) return txaio.gather(all_dl) 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("Authorization failed", 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) ]) reply.correlation_id = publish.correlation_id reply.correlation_uri = publish.topic reply.correlation_is_anchor = False self._router.send(session, reply) txaio.add_callbacks(d, on_authorize_success, on_authorize_error)
def processPublish(self, session, publish): """ Implements :func:`autobahn.wamp.interfaces.IBroker.processPublish` """ assert (session in self._session_to_subscriptions) ## check topic URI ## if (not self._option_uri_strict and not _URI_PAT_LOOSE_NON_EMPTY.match(publish.topic)) or \ ( self._option_uri_strict and not _URI_PAT_STRICT_NON_EMPTY.match(publish.topic)): if publish.acknowledge: reply = message.Error( message.Publish.MESSAGE_TYPE, publish.request, ApplicationError.INVALID_URI, [ "publish with invalid topic URI '{}'".format( publish.topic) ]) session._transport.send(reply) return if publish.topic in self._topic_to_sessions and self._topic_to_sessions[ publish.topic]: ## initial list of receivers are all subscribers .. ## subscription, receivers = self._topic_to_sessions[publish.topic] ## filter by "eligible" receivers ## if publish.eligible: eligible = [] for s in publish.eligible: if s in self._session_id_to_session: eligible.append(self._session_id_to_session[s]) if eligible: receivers = set(eligible) & receivers ## remove "excluded" receivers ## if publish.exclude: exclude = [] for s in publish.exclude: if s in self._session_id_to_session: exclude.append(self._session_id_to_session[s]) if exclude: receivers = receivers - set(exclude) ## remove publisher ## if publish.excludeMe is None or publish.excludeMe: # receivers.discard(session) # bad: this would modify our actual subscriber list me_also = False else: me_also = True else: subscription, receivers, me_also = None, [], False publication = util.id() ## send publish acknowledge when requested ## if publish.acknowledge: msg = message.Published(publish.request, publication) session._transport.send(msg) ## if receivers is non-empty, dispatch event .. ## if receivers: if publish.discloseMe: publisher = session._session_id else: publisher = None msg = message.Event(subscription, publication, args=publish.args, kwargs=publish.kwargs, publisher=publisher) for receiver in receivers: if me_also or receiver != session: ## the subscribing session might have been lost in the meantime .. if receiver._transport: receiver._transport.send(msg)
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(u'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 = self._router.authorize(session, call.procedure, u'call') def on_authorize_success(authorization): # the call to authorize the action _itself_ succeeded. now go on depending on whether # the action was actually authorized or not .. # if not authorization[u'allow']: 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 authorization[u'disclose']: caller = session._session_id caller_authid = session._authid caller_authrole = session._authrole else: caller = None caller_authid = None caller_authrole = 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, caller_authid=caller_authid, caller_authrole=caller_authrole, 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, caller_authid=caller_authid, caller_authrole=caller_authrole, 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 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) elif register.match == u"prefix": uri_is_valid = _URI_PAT_STRICT_LAST_EMPTY.match( register.procedure) elif register.match == u"exact": uri_is_valid = _URI_PAT_STRICT_NON_EMPTY.match( register.procedure) else: # should not arrive here raise Exception("logic error") else: if register.match == u"wildcard": uri_is_valid = _URI_PAT_LOOSE_EMPTY.match(register.procedure) elif register.match == u"prefix": uri_is_valid = _URI_PAT_LOOSE_LAST_EMPTY.match( register.procedure) elif register.match == u"exact": uri_is_valid = _URI_PAT_LOOSE_NON_EMPTY.match( register.procedure) else: # should not arrive here raise Exception("logic error") 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." other than for # trusted sessions (that are sessions built into Crossbar.io routing core) # if session._authrole is not None and session._authrole != u"trusted": is_restricted = register.procedure.startswith(u"wamp.") 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 REGISTER action # d = self._router.authorize(session, register.procedure, u'register', options=register.marshal_options()) def on_authorize_success(authorization): if not authorization[u'allow']: # 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_callee_extra = RegistrationCalleeExtra( register.concurrency) registration, was_already_registered, is_first_callee = self._registration_map.add_observer( session, register.procedure, register.match, registration_extra, registration_callee_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 = { u'id': registration.id, u'created': registration.created, u'uri': registration.uri, u'match': registration.match, u'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("Authorization of 'register' for '{uri}' failed", uri=register.procedure, 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 test_assumptions(): from autobahn.wamp.message import _URI_PAT_LOOSE_NON_EMPTY assert _URI_PAT_LOOSE_NON_EMPTY.match(".") is None
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 for normal subscriptions, may be empty for # wildcard subscriptions and must be non-empty for all but the last component for # prefix subscriptions # if self._option_uri_strict: if subscribe.match == u"wildcard": uri_is_valid = _URI_PAT_STRICT_EMPTY.match(subscribe.topic) elif subscribe.match == u"prefix": uri_is_valid = _URI_PAT_STRICT_LAST_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) elif subscribe.match == u"prefix": uri_is_valid = _URI_PAT_LOOSE_LAST_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) ]) self._router.send(session, reply) return # authorize SUBSCRIBE action # d = self._router.authorize(session, subscribe.topic, u'subscribe') def on_authorize_success(authorization): if not authorization[u'allow']: # 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 = { u'id': subscription.id, u'created': subscription.created, u'uri': subscription.uri, u'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 # 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) """ # 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) ]) self._router.send(session, reply) txaio.add_callbacks(d, on_authorize_success, on_authorize_error)
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 sessions (that are sessions built into Crossbar.io) # if session._authrole is not None and session._authrole != u"trusted": is_restricted = publish.topic.startswith( u"wamp.") or publish.topic.startswith(u"crossbar.") if is_restricted: 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) # check if the event is being persisted by checking if we ourself are among the observers. # we've been added to observer lists on subscriptions ultimately from node configuration # and during the broker starts up. store_event = False if self._event_store: for s in subscriptions: if self._event_store in s.observers: store_event = True break if store_event: self.log.debug("event on topic '{topic}'' is being persisted", topic=publish.topic) # go on if (otherwise there isn't anything to do anyway): # # - there are any active subscriptions OR # - the publish is to be acknowledged OR # - the event is to be persistet # if subscriptions or publish.acknowledge or store_event: # 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() # persist event # if store_event: self._event_store.store_event(session._session_id, publication, publish.topic, publish.args, publish.kwargs) # 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: # persist event history # if store_event: self._event_store.store_event_history( publication, subscription.id) # 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 .. # receivers_cnt = len(receivers) - (1 if self in receivers else 0) if receivers_cnt: # 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 ) and receiver != self._event_store: # the receiving subscriber session # might have no transport, or no # longer be joined if receiver._session_id and 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 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 for normal subscriptions, may be empty for # wildcard subscriptions and must be non-empty for all but the last component for # prefix subscriptions # if self._option_uri_strict: if subscribe.match == u"wildcard": uri_is_valid = _URI_PAT_STRICT_EMPTY.match(subscribe.topic) elif subscribe.match == u"prefix": uri_is_valid = _URI_PAT_STRICT_LAST_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) elif subscribe.match == u"prefix": uri_is_valid = _URI_PAT_LOOSE_LAST_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)]) self._router.send(session, reply) return # authorize SUBSCRIBE action # d = self._router.authorize(session, subscribe.topic, u'subscribe') def on_authorize_success(authorization): if not authorization[u'allow']: # error reply since session is not authorized to subscribe # replies = [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, extra=SubscriptionExtra()) 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 = { u'id': subscription.id, u'created': subscription.created, u'uri': subscription.uri, u'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) # check for retained events # def _get_retained_event(): if subscription.extra.retained_events: retained_events = list(subscription.extra.retained_events) retained_events.reverse() for publish in retained_events: authorised = False if not publish.exclude and not publish.eligible: authorised = True elif session._session_id in publish.eligible and session._session_id not in publish.exclude: authorised = True if authorised: publication = util.id() if publish.payload: msg = message.Event(subscription.id, publication, payload=publish.payload, retained=True, enc_algo=publish.enc_algo, enc_key=publish.enc_key, enc_serializer=publish.enc_serializer) else: msg = message.Event(subscription.id, publication, args=publish.args, kwargs=publish.kwargs, retained=True) return [msg] return [] # acknowledge subscribe with subscription ID # replies = [message.Subscribed(subscribe.request, subscription.id)] if subscribe.get_retained: replies.extend(_get_retained_event()) # send out reply to subscribe requestor # [self._router.send(session, reply) for reply in replies] 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("Authorization failed", 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)] ) self._router.send(session, reply) txaio.add_callbacks(d, on_authorize_success, on_authorize_error)
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) ]) self._router.send(session, reply) return # disallow publication to topics starting with "wamp." other than for # trusted sessions (that are sessions built into Crossbar.io routing core) # if session._authrole is not None and session._authrole != u"trusted": is_restricted = publish.topic.startswith(u"wamp.") if is_restricted: 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) ]) self._router.send(session, reply) return # get subscriptions active on the topic published to # subscriptions = self._subscription_map.match_observations( publish.topic) # check if the event is being persisted by checking if we ourself are among the observers # on _any_ matching subscription # we've been previously added to observer lists on subscriptions ultimately from # node configuration and during the broker starts up. store_event = False if self._event_store: for subscription in subscriptions: if self._event_store in subscription.observers: store_event = True break if store_event: self.log.debug('Persisting event on topic "{topic}"', topic=publish.topic) # check if the event is to be retained by inspecting the 'retain' flag retain_event = False if publish.retain: retain_event = True # go on if (otherwise there isn't anything to do anyway): # # - there are any active subscriptions OR # - the publish is to be acknowledged OR # - the event is to be persisted OR # - the event is to be retained # if subscriptions or publish.acknowledge or store_event or retain_event: # validate payload # if publish.payload is None: 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) ]) self._router.send(session, reply) return # authorize PUBLISH action # d = self._router.authorize(session, publish.topic, u'publish') def on_authorize_success(authorization): # the call to authorize the action _itself_ succeeded. now go on depending on whether # the action was actually authorized or not .. # if not authorization[u'allow']: 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) ]) self._router.send(session, reply) else: # new ID for the publication # publication = util.id() # send publish acknowledge immediately when requested # if publish.acknowledge: reply = message.Published(publish.request, publication) self._router.send(session, reply) # publisher disclosure # if authorization[u'disclose']: disclose = True elif (publish.topic.startswith(u"wamp.") or publish.topic.startswith(u"crossbar.")): disclose = True else: disclose = False if disclose: publisher = session._session_id publisher_authid = session._authid publisher_authrole = session._authrole else: publisher = None publisher_authid = None publisher_authrole = None # skip publisher # if publish.exclude_me is None or publish.exclude_me: me_also = False else: me_also = True # persist event (this is done only once, regardless of the number of subscriptions # the event matches on) # if store_event: self._event_store.store_event(session._session_id, publication, publish.topic, publish.args, publish.kwargs) # retain event on the topic # if retain_event: retained_event = RetainedEvent(publish, publisher, publisher_authid, publisher_authrole) observation = self._subscription_map.get_observation( publish.topic) if not observation: # No observation, lets make a new one observation = self._subscription_map.create_observation( publish.topic, extra=SubscriptionExtra()) else: # this can happen if event-history is # enabled on the topic: the event-store # creates an observation before any client # could possible hit the code above if observation.extra is None: observation.extra = SubscriptionExtra() elif not isinstance(observation.extra, SubscriptionExtra): raise Exception( "incorrect 'extra' for '{}'".format( publish.topic)) if observation.extra.retained_events: if not publish.eligible and not publish.exclude: observation.extra.retained_events = [ retained_event ] else: observation.extra.retained_events.append( retained_event) else: observation.extra.retained_events = [ retained_event ] all_dl = [] # iterate over all subscriptions .. # for subscription in subscriptions: self.log.debug( 'dispatching for subscription={subscription}', subscription=subscription) # persist event history, but check if it is persisted on the individual subscription! # if store_event and self._event_store in subscription.observers: self._event_store.store_event_history( publication, subscription.id) # initial list of receivers are all subscribers on a subscription .. # receivers = subscription.observers receivers = self._filter_publish_receivers( receivers, publish) # if receivers is non-empty, dispatch event .. # receivers_cnt = len(receivers) - (1 if self in receivers else 0) if receivers_cnt: # 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 if publish.payload: msg = message.Event( subscription.id, publication, payload=publish.payload, publisher=publisher, publisher_authid=publisher_authid, publisher_authrole=publisher_authrole, topic=topic, enc_algo=publish.enc_algo, enc_key=publish.enc_key, enc_serializer=publish.enc_serializer) else: msg = message.Event( subscription.id, publication, args=publish.args, kwargs=publish.kwargs, publisher=publisher, publisher_authid=publisher_authid, publisher_authrole=publisher_authrole, topic=topic) chunk_size = self._options.event_dispatching_chunk_size if chunk_size: self.log.debug( 'chunked dispatching to {receivers_size} with chunk_size={chunk_size}', receivers_size=len(receivers), chunk_size=chunk_size) # a Deferred that fires when all chunks are done all_d = txaio.create_future() all_dl.append(all_d) def _notify_some(receivers): for receiver in receivers[:chunk_size]: if ( me_also or receiver != session ) and receiver != self._event_store: # the receiving subscriber session # might have no transport, or no # longer be joined if receiver._session_id and receiver._transport: self._router.send( receiver, msg) receivers = receivers[chunk_size:] if len(receivers) > 0: # still more to do .. return txaio.call_later( 0, _notify_some, receivers) else: # all done! resolve all_d, which represents all receivers # to a single subscription matching the event txaio.resolve(all_d, None) _notify_some(list(receivers)) else: self.log.debug( 'unchunked dispatching to {receivers_size} receivers', receivers_size=len(receivers)) for receiver in receivers: if (me_also or receiver != session ) and receiver != self._event_store: # the receiving subscriber session # might have no transport, or no # longer be joined if receiver._session_id and receiver._transport: self._router.send(receiver, msg) return txaio.gather(all_dl) 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("Authorization failed", 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) ]) self._router.send(session, reply) txaio.add_callbacks(d, on_authorize_success, on_authorize_error)