def test_double_subscribe(self): handler = ApplicationSession() MockTransport(handler) event0 = Deferred() event1 = Deferred() subscription0 = yield handler.subscribe( lambda: event0.callback(42), u'com.myapp.topic1') subscription1 = yield handler.subscribe( lambda: event1.callback('foo'), u'com.myapp.topic1') # same topic, same ID self.assertTrue(subscription0.id == subscription1.id) # do a publish (MockTransport fakes the acknowledgement # message) and then do an actual publish event. The IDs # are the same, so we just do one Event. publish = yield handler.publish( u'com.myapp.topic1', options=types.PublishOptions(acknowledge=True, exclude_me=False), ) handler.onMessage(message.Event(subscription0.id, publish.id)) # ensure we actually got both callbacks self.assertTrue(event0.called, "Missing callback") self.assertTrue(event1.called, "Missing callback")
def test_publish_callback_exception(self): """ Ensure we handle an exception from the user code. """ handler = ApplicationSession() MockTransport(handler) error_instance = RuntimeError("we have a problem") got_err_d = Deferred() def observer(e, msg): if error_instance == e.value: got_err_d.callback(True) handler.onUserError = observer def boom(): raise error_instance sub = yield handler.subscribe(boom, u'com.myapp.topic1') # MockTransport gives us the ack reply and then we do our # own event message publish = yield handler.publish( u'com.myapp.topic1', options=types.PublishOptions(acknowledge=True, exclude_me=False), ) msg = message.Event(sub.id, publish.id) handler.onMessage(msg) # we know it worked if our observer worked and did # .callback on our Deferred above. self.assertTrue(got_err_d.called)
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 []
def generate_test_messages(): return [ message.Hello(u"realm1", [role.RoleBrokerFeatures()]), message.Goodbye(), message.Heartbeat(123, 456), message.Yield(123456), message.Yield(123456, args = [1, 2, 3], kwargs = {u'foo': 23, u'bar': u'hello'}), message.Yield(123456, progress = True), message.Interrupt(123456), message.Interrupt(123456, mode = message.Interrupt.KILL), message.Invocation(123456, 789123), message.Invocation(123456, 789123, args = [1, 2, 3], kwargs = {u'foo': 23, u'bar': u'hello'}), message.Invocation(123456, 789123, timeout = 10000), message.Result(123456), message.Result(123456, args = [1, 2, 3], kwargs = {u'foo': 23, u'bar': u'hello'}), message.Result(123456, progress = True), message.Cancel(123456), message.Cancel(123456, mode = message.Cancel.KILL), message.Call(123456, u'com.myapp.procedure1'), message.Call(123456, u'com.myapp.procedure1', args = [1, 2, 3], kwargs = {u'foo': 23, u'bar': u'hello'}), message.Call(123456, u'com.myapp.procedure1', timeout = 10000), message.Unregistered(123456), message.Unregister(123456, 789123), message.Registered(123456, 789123), message.Register(123456, u'com.myapp.procedure1'), message.Register(123456, u'com.myapp.procedure1', pkeys = [10, 11, 12]), message.Event(123456, 789123), message.Event(123456, 789123, args = [1, 2, 3], kwargs = {u'foo': 23, u'bar': u'hello'}), message.Event(123456, 789123, publisher = 300), message.Published(123456, 789123), message.Publish(123456, u'com.myapp.topic1'), message.Publish(123456, u'com.myapp.topic1', args = [1, 2, 3], kwargs = {u'foo': 23, u'bar': u'hello'}), message.Publish(123456, u'com.myapp.topic1', excludeMe = False, exclude = [300], eligible = [100, 200, 300], discloseMe = True), message.Unsubscribed(123456), message.Unsubscribe(123456, 789123), message.Subscribed(123456, 789123), message.Subscribe(123456, u'com.myapp.topic1'), message.Subscribe(123456, u'com.myapp.topic1', match = message.Subscribe.MATCH_PREFIX), message.Error(message.Call.MESSAGE_TYPE, 123456, u'com.myapp.error1'), message.Error(message.Call.MESSAGE_TYPE, 123456, u'com.myapp.error1', args = [1, 2, 3], kwargs = {u'foo': 23, u'bar': u'hello'}), message.Call(123456, u'com.myapp.\u4f60\u597d\u4e16\u754c', args=[1, 2, 3]), message.Result(123456, args=[1, 2, 3], kwargs={u'en': u'Hello World', u'jp': u'\u3053\u3093\u306b\u3061\u306f\u4e16\u754c'}) ]
def generate_test_messages_binary(): """ Generate WAMP test messages which contain binary app payloads. With the JSON serializer, this currently only works on Python 3 (both CPython3 and PyPy3), because even on Python 3, we need to patch the stdlib JSON, and on Python 2, the patching would be even hackier. """ msgs = [] for binary in [b'', b'\x00', b'\30', os.urandom(4), os.urandom(16), os.urandom(128), os.urandom(256), os.urandom(512), os.urandom(1024)]: msgs.append(message.Event(123456, 789123, args=[binary])) msgs.append(message.Event(123456, 789123, args=[binary], kwargs={'foo': binary})) return [(True, msg) for msg in msgs]
def test_double_subscribe_double_unsubscribe(self): ''' If we subscribe twice, and unsubscribe twice, we should then get an Unsubscribed message. ''' handler = ApplicationSession() MockTransport(handler) # monkey-patch ApplicationSession to ensure we get our message unsubscribed_d = Deferred() def onMessage(msg): if isinstance(msg, message.Unsubscribed): unsubscribed_d.callback(msg) return ApplicationSession.onMessage(handler, msg) handler.onMessage = onMessage event0 = Deferred() event1 = Deferred() subscription0 = yield handler.subscribe( lambda: event0.callback(42), u'com.myapp.topic1') subscription1 = yield handler.subscribe( lambda: event1.callback('foo'), u'com.myapp.topic1') self.assertTrue(subscription0.id == subscription1.id) yield subscription0.unsubscribe() yield subscription1.unsubscribe() # after the second unsubscribe, we should have gotten an # Unsubscribed message assert unsubscribed_d.called # do a publish (MockTransport fakes the acknowledgement # message) and then do an actual publish event. Sending # the Event should be an error, as we have no # subscriptions left. publish = yield handler.publish( u'com.myapp.topic1', options=types.PublishOptions(acknowledge=True, exclude_me=False), ) try: handler.onMessage(message.Event(subscription0.id, publish.id)) self.fail("Expected ProtocolError") except ProtocolError: pass # since we unsubscribed the second event handler, we # should NOT have called its callback self.assertTrue(not event0.called, "First callback fired.") self.assertTrue(not event1.called, "Second callback fired.")
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 []
def test_double_subscribe_errors(self): """ Test various error-conditions when we try to add a second subscription-handler (its signature must match any existing handlers). """ handler = ApplicationSession() MockTransport(handler) event0 = Deferred() event1 = Deferred() def second(*args, **kw): # our EventDetails should have been passed as the # "boom" kwarg; see "details_arg=" below self.assertTrue('boom' in kw) self.assertTrue(isinstance(kw['boom'], types.EventDetails)) event1.callback(args) subscription0 = yield handler.subscribe( lambda arg: event0.callback(arg), u'com.myapp.topic1') subscription1 = yield handler.subscribe( second, u'com.myapp.topic1', types.SubscribeOptions(details_arg='boom'), ) # same topic, same ID self.assertTrue(subscription0.id == subscription1.id) # MockTransport gives us the ack reply and then we do our # own event message. publish = yield handler.publish( u'com.myapp.topic1', options=types.PublishOptions(acknowledge=True, exclude_me=False), ) # note that the protocol serializer converts all sequences # to lists, so we pass "args" as a list, not a tuple on # purpose. handler.onMessage( message.Event(subscription0.id, publish.id, args=['arg0'])) # each callback should have gotten called, each with its # own args (we check the correct kwarg in second() above) self.assertTrue(event0.called) self.assertTrue(event1.called) self.assertEqual(event0.result, 'arg0') self.assertEqual(event1.result, ('arg0', ))
def test_publish_callback_exception(self): """ Ensure we handle an exception from the user code. """ handler = ApplicationSession() MockTransport(handler) error_instance = RuntimeError("we have a problem") got_err_d = Deferred() def observer(kw): if kw['isError'] and 'failure' in kw: fail = kw['failure'] fail.trap(RuntimeError) if error_instance == fail.value: got_err_d.callback(True) log.addObserver(observer) def boom(): raise error_instance try: sub = yield handler.subscribe(boom, u'com.myapp.topic1') # MockTransport gives us the ack reply and then we do our # own event message publish = yield handler.publish( u'com.myapp.topic1', options=types.PublishOptions(acknowledge=True, exclude_me=False), ) msg = message.Event(sub.id, publish.id) handler.onMessage(msg) # we know it worked if our observer worked and did # .callback on our Deferred above. self.assertTrue(got_err_d.called) # ...otherwise trial will fail the test anyway self.flushLoggedErrors() finally: log.removeObserver(observer)
def test_double_subscribe_single_unsubscribe(self): ''' Make sure we correctly deal with unsubscribing one of our handlers from the same topic. ''' handler = ApplicationSession() MockTransport(handler) # monkey-patch ApplicationSession to ensure we DO NOT get # an Unsubscribed message -- since we only unsubscribe # from ONE of our handlers for com.myapp.topic1 def onMessage(msg): assert not isinstance(msg, message.Unsubscribed) return ApplicationSession.onMessage(handler, msg) handler.onMessage = onMessage event0 = Deferred() event1 = Deferred() subscription0 = yield handler.subscribe( lambda: event0.callback(42), u'com.myapp.topic1') subscription1 = yield handler.subscribe( lambda: event1.callback('foo'), u'com.myapp.topic1') self.assertTrue(subscription0.id == subscription1.id) yield subscription1.unsubscribe() # do a publish (MockTransport fakes the acknowledgement # message) and then do an actual publish event. Note the # IDs are the same, so there's only one Event. publish = yield handler.publish( u'com.myapp.topic1', options=types.PublishOptions(acknowledge=True, exclude_me=False), ) handler.onMessage(message.Event(subscription0.id, publish.id)) # since we unsubscribed the second event handler, we # should NOT have called its callback self.assertTrue(event0.called, "Missing callback") self.assertTrue(not event1.called, "Second callback fired.")
def test_basic(self): messages = [ message.Event(123456, 789123, args=[1, 2, 3], kwargs={ 'foo': 23, 'bar': 'hello' }, publisher=666, retained=True), message.Publish(123456, 'com.example.topic1', args=[1, 2, 3], kwargs={ 'foo': 23, 'bar': 'hello' }, retain=True) ] ser = serializer.FlatBuffersSerializer() # from pprint import pprint for msg in messages: # serialize message payload, binary = ser.serialize(msg) # unserialize message again msg2 = ser.unserialize(payload, binary)[0] # pprint(msg.marshal()) # pprint(msg2.marshal()) # must be equal: message roundtrips via the serializer self.assertEqual(msg, msg2)
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 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 generate_test_messages(): """ List of WAMP test message used for serializers. Expand this if you add more options or messages. This list of WAMP message does not contain any binary app payloads! """ some_bytes = os.urandom(32) some_unicode = '\u3053\u3093\u306b\u3061\u306f\u4e16\u754c' some_uri = 'com.myapp.foobar' some_unicode_uri = 'com.myapp.\u4f60\u597d\u4e16\u754c.baz' some_args = [1, 2, 3, 'hello', some_bytes, some_unicode, {'foo': 23, 'bar': 'hello', 'baz': some_bytes, 'moo': some_unicode}] some_kwargs = {'foo': 23, 'bar': 'hello', 'baz': some_bytes, 'moo': some_unicode, 'arr': some_args} msgs = [ message.Hello("realm1", {'subscriber': role.RoleSubscriberFeatures()}), message.Hello("realm1", {'publisher': role.RolePublisherFeatures()}), message.Hello("realm1", {'caller': role.RoleCallerFeatures()}), message.Hello("realm1", {'callee': role.RoleCalleeFeatures()}), message.Hello("realm1", { 'subscriber': role.RoleSubscriberFeatures(), 'publisher': role.RolePublisherFeatures(), 'caller': role.RoleCallerFeatures(), 'callee': role.RoleCalleeFeatures(), }), message.Goodbye(), message.Yield(123456), message.Yield(123456, args=some_args), message.Yield(123456, args=[], kwargs=some_kwargs), message.Yield(123456, args=some_args, kwargs=some_kwargs), message.Yield(123456, progress=True), message.Interrupt(123456), message.Interrupt(123456, mode=message.Interrupt.KILL), message.Invocation(123456, 789123), message.Invocation(123456, 789123, args=some_args), message.Invocation(123456, 789123, args=[], kwargs=some_kwargs), message.Invocation(123456, 789123, args=some_args, kwargs=some_kwargs), message.Invocation(123456, 789123, timeout=10000), message.Result(123456), message.Result(123456, args=some_args), message.Result(123456, args=[], kwargs=some_kwargs), message.Result(123456, args=some_args, kwargs=some_kwargs), message.Result(123456, progress=True), message.Cancel(123456), message.Cancel(123456, mode=message.Cancel.KILL), message.Call(123456, some_uri), message.Call(123456, some_uri, args=some_args), message.Call(123456, some_uri, args=[], kwargs=some_kwargs), message.Call(123456, some_uri, args=some_args, kwargs=some_kwargs), message.Call(123456, some_uri, timeout=10000), message.Call(123456, some_unicode_uri), message.Call(123456, some_unicode_uri, args=some_args), message.Call(123456, some_unicode_uri, args=[], kwargs=some_kwargs), message.Call(123456, some_unicode_uri, args=some_args, kwargs=some_kwargs), message.Call(123456, some_unicode_uri, timeout=10000), message.Unregistered(123456), message.Unregister(123456, 789123), message.Registered(123456, 789123), message.Register(123456, some_uri), message.Register(123456, some_uri, match='prefix'), message.Register(123456, some_uri, invoke='roundrobin'), message.Register(123456, some_unicode_uri), message.Register(123456, some_unicode_uri, match='prefix'), message.Register(123456, some_unicode_uri, invoke='roundrobin'), message.Event(123456, 789123), message.Event(123456, 789123, args=some_args), message.Event(123456, 789123, args=[], kwargs=some_kwargs), message.Event(123456, 789123, args=some_args, kwargs=some_kwargs), message.Event(123456, 789123, publisher=300), message.Published(123456, 789123), message.Publish(123456, some_uri), message.Publish(123456, some_uri, args=some_args), message.Publish(123456, some_uri, args=[], kwargs=some_kwargs), message.Publish(123456, some_uri, args=some_args, kwargs=some_kwargs), message.Publish(123456, some_uri, exclude_me=False, exclude=[300], eligible=[100, 200, 300]), message.Publish(123456, some_unicode_uri), message.Publish(123456, some_unicode_uri, args=some_args), message.Publish(123456, some_unicode_uri, args=[], kwargs=some_kwargs), message.Publish(123456, some_unicode_uri, args=some_args, kwargs=some_kwargs), message.Publish(123456, some_unicode_uri, exclude_me=False, exclude=[300], eligible=[100, 200, 300]), message.Unsubscribed(123456), message.Unsubscribe(123456, 789123), message.Subscribed(123456, 789123), message.Subscribe(123456, some_uri), message.Subscribe(123456, some_uri, match=message.Subscribe.MATCH_PREFIX), message.Subscribe(123456, some_unicode_uri), message.Subscribe(123456, some_unicode_uri, match=message.Subscribe.MATCH_PREFIX), message.Error(message.Call.MESSAGE_TYPE, 123456, some_uri), message.Error(message.Call.MESSAGE_TYPE, 123456, some_uri, args=some_args), message.Error(message.Call.MESSAGE_TYPE, 123456, some_uri, args=[], kwargs=some_kwargs), message.Error(message.Call.MESSAGE_TYPE, 123456, some_uri, args=some_args, kwargs=some_kwargs), message.Error(message.Call.MESSAGE_TYPE, 123456, some_unicode_uri), message.Error(message.Call.MESSAGE_TYPE, 123456, some_unicode_uri, args=some_args), message.Error(message.Call.MESSAGE_TYPE, 123456, some_unicode_uri, args=[], kwargs=some_kwargs), message.Error(message.Call.MESSAGE_TYPE, 123456, some_unicode_uri, args=some_args, kwargs=some_kwargs), message.Result(123456), message.Result(123456, args=some_args), message.Result(123456, args=some_args, kwargs=some_kwargs), ] return [(False, msg) for msg in msgs]
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 send(self, msg): if self._log: print("req") print(msg) reply = None if isinstance(msg, message.Publish): if msg.topic in self._subscription_topics.keys(): pubID = util.id() def published(): self._s(message.Published(msg.request, pubID)) reg = self._subscription_topics[msg.topic] reply = message.Event(reg, pubID, args=msg.args, kwargs=msg.kwargs) if msg.acknowledge: reactor.callLater(0, published) elif len(msg.topic) == 0: reply = message.Error(message.Publish.MESSAGE_TYPE, msg.request, u'wamp.error.invalid_uri') else: reply = message.Error(message.Publish.MESSAGE_TYPE, msg.request, u'wamp.error.not_authorized') elif isinstance(msg, message.Error): # Convert an invocation error into a call error if msg.request_type == 68: msg.request_type = 48 reply = msg elif isinstance(msg, message.Call): if msg.procedure in self._registrations: request = util.id() registration = self._registrations[msg.procedure] self._invocations[msg.request] = msg.request def invoke(): self._s(message.Invocation(msg.request, registration, args=msg.args, kwargs=msg.kwargs)) reactor.callLater(0, invoke) else: reply = message.Error(message.Call.MESSAGE_TYPE, msg.request, u'wamp.error.no_such_procedure') elif isinstance(msg, message.Yield): if msg.request in self._invocations: request = self._invocations[msg.request] reply = message.Result(request, args=msg.args, kwargs=msg.kwargs) elif isinstance(msg, message.Subscribe): topic = msg.topic if topic in self._subscription_topics: reply_id = self._subscription_topics[topic] else: reply_id = util.id() self._subscription_topics[topic] = reply_id reply = message.Subscribed(msg.request, reply_id) elif isinstance(msg, message.Unsubscribe): reply = message.Unsubscribed(msg.request) elif isinstance(msg, message.Register): registration = util.id() self._registrations[msg.procedure] = registration reply = message.Registered(msg.request, registration) elif isinstance(msg, message.Unregister): reply = message.Unregistered(msg.request) if reply: self._s(reply)
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 generate_test_messages(): """ List of WAMP test message used for serializers. Expand this if you add more options or messages. This list of WAMP message does not contain any binary app payloads! """ msgs = [ message.Hello(u"realm1", {u'subscriber': role.RoleSubscriberFeatures()}), message.Goodbye(), message.Yield(123456), message.Yield(123456, args=[1, 2, 3], kwargs={ u'foo': 23, u'bar': u'hello' }), message.Yield(123456, args=[u'hello']), message.Yield(123456, progress=True), message.Interrupt(123456), message.Interrupt(123456, mode=message.Interrupt.KILL), message.Invocation(123456, 789123), message.Invocation(123456, 789123, args=[1, 2, 3], kwargs={ u'foo': 23, u'bar': u'hello' }), message.Invocation(123456, 789123, timeout=10000), message.Result(123456), message.Result(123456, args=[1, 2, 3], kwargs={ u'foo': 23, u'bar': u'hello' }), message.Result(123456, progress=True), message.Cancel(123456), message.Cancel(123456, mode=message.Cancel.KILL), message.Call(123456, u'com.myapp.procedure1'), message.Call(123456, u'com.myapp.procedure1', args=[1, 2, 3], kwargs={ u'foo': 23, u'bar': u'hello' }), message.Call(123456, u'com.myapp.procedure1', timeout=10000), message.Unregistered(123456), message.Unregister(123456, 789123), message.Registered(123456, 789123), message.Register(123456, u'com.myapp.procedure1'), message.Register(123456, u'com.myapp.procedure1', match=u'prefix'), message.Register(123456, u'com.myapp.procedure1', invoke=u'roundrobin'), message.Event(123456, 789123), message.Event(123456, 789123, args=[1, 2, 3], kwargs={ u'foo': 23, u'bar': u'hello' }), message.Event(123456, 789123, publisher=300), message.Published(123456, 789123), message.Publish(123456, u'com.myapp.topic1'), message.Publish(123456, u'com.myapp.topic1', args=[1, 2, 3], kwargs={ u'foo': 23, u'bar': u'hello' }), message.Publish(123456, u'com.myapp.topic1', exclude_me=False, exclude=[300], eligible=[100, 200, 300]), message.Unsubscribed(123456), message.Unsubscribe(123456, 789123), message.Subscribed(123456, 789123), message.Subscribe(123456, u'com.myapp.topic1'), message.Subscribe(123456, u'com.myapp.topic1', match=message.Subscribe.MATCH_PREFIX), message.Error(message.Call.MESSAGE_TYPE, 123456, u'com.myapp.error1'), message.Error(message.Call.MESSAGE_TYPE, 123456, u'com.myapp.error1', args=[1, 2, 3], kwargs={ u'foo': 23, u'bar': u'hello' }), message.Call(123456, u'com.myapp.\u4f60\u597d\u4e16\u754c', args=[1, 2, 3]), message.Result(123456, args=[1, 2, 3], kwargs={ u'en': u'Hello World', u'jp': u'\u3053\u3093\u306b\u3061\u306f\u4e16\u754c' }) ] return [(False, msg) for msg in msgs]
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_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)