def subscribe(self, handler, topic=None, options=None): """ Implements :func:`autobahn.wamp.interfaces.ISubscriber.subscribe` """ if six.PY2 and type(topic) == str: topic = six.u(topic) assert (topic is None or type(topic) == six.text_type) assert ((callable(handler) and topic is not None) or hasattr(handler, '__class__')) assert (options is None or isinstance(options, dict)) if not self._transport: raise exception.TransportLost() if callable(handler): # subscribe a single handler return self._subscribe(None, handler, topic, options) else: # subscribe all methods on an object decorated with "wamp.subscribe" on_replies = [] for k in inspect.getmembers(handler.__class__, is_method_or_function): proc = k[1] wampuris = filter(lambda x: x.is_handler(), proc.__dict__.get("_wampuris")) or () for pat in wampuris: subopts = pat.options or options or types.SubscribeOptions( match=u"wildcard" if pat.uri_type == uri.Pattern. URI_TYPE_WILDCARD else u"exact").message_attr() on_replies.append( self._subscribe(handler, proc, pat.uri(), subopts)) return txaio.gather(on_replies, consume_exceptions=True)
def test_subscribe(self): handler = ApplicationSession() transport = MockTransport(handler) def on_event(*args, **kwargs): print("got event", args, kwargs) subscription = yield handler.subscribe(on_event, 'com.myapp.topic1') self.assertTrue(type(subscription.id) in (int, long)) subscription = yield handler.subscribe(on_event, 'com.myapp.topic1', options = types.SubscribeOptions(match = 'wildcard')) self.assertTrue(type(subscription.id) in (int, long))
def forward_subscriber(self, func_path, topic, options=None): def wrapped(*args, **kwargs): payload = { 'func_path': func_path, 'topic': topic, 'args': args, 'kwargs': kwargs, } channel_name = 'wamp.events' Channel(channel_name).send(payload) self.log.info("registered subscriber for '{}'".format(topic)) if options is None: options = types.SubscribeOptions() return self.subscribe(wrapped, topic, options=options)
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', ))
async def _register_subscribers(self): for name, method, options in ScopeInspector.subscribers(self.scope): topic = self.scope.get_routing(method.subscribed).format( self.scope) options = types.SubscribeOptions(**options) try: subscribed = await self.session.subscribe( method, topic, options=options, ) self.subscriptions.append(subscribed) self.session.log.debug("subscriber `{topic}` registered", topic=topic) except Exception as e: self.session.log.error(topic) self.session.log.error( "could not register subscriber to `{uri}`: {e!r}", uri=topic, e=e) self.session.log.error(no_format(traceback.format_exc()))
async def _register_hooks(self): for name, method, options in ScopeInspector.hooks(self.scope): global_topic = '{}.webhooks.{}'.format( settings.ROOT_TOPIC, method.hooked).format(self.scope) options = types.SubscribeOptions(**options) try: subscribed = await self.session.subscribe( method, global_topic, options=options, ) self.subscriptions.append(subscribed) self.session.log.debug("hook `{topic}` registered", topic=global_topic) except Exception as e: self.session.log.error(global_topic) self.session.log.error( "could not register hook to `{topic}`: {e!r}", topic=global_topic, e=e) self.session.log.error(no_format(traceback.format_exc()))
async def on_brain_join(session, details): brain = Brain(robot_info, planner_name, episode_dir, session) logger.info('brain subscribing to {}'.format(robot_name)) session.subscribe(brain.on_signal, 'gp.robots.{}'.format(robot_name), options=ab_types.SubscribeOptions(match='prefix')), asyncio.get_event_loop().create_task(brain.main_brain_loop())
async def on_join(session, details): logger.info('wamp component: joining') session.subscribe(callback, channel, options=ab_types.SubscribeOptions(match='prefix'))
def test_roundrobin_proxy(request, reactor, virtualenv): """ Confirm that a proxy with two connections does connections to both backends. Two nodes each with a router-worker for 'realm1' Each node rlink-connects to the other. One node has a proxy """ tempdir = _create_temp(request) # burn in hard-coded keys so we can refer to the public parts in # configs more easily. node_keys = [ (node0_pubkey, node0_privkey), (node1_pubkey, node1_privkey), (node2_pubkey, node2_privkey), (node3_pubkey, node3_privkey), ] for node_num in range(4): node_dir = join(tempdir, "node{}".format(node_num)) os.mkdir(node_dir) pub, priv = node_keys[node_num] with open(join(node_dir, "key.pub"), "w") as f: f.write(pub) with open(join(node_dir, "key.priv"), "w") as f: f.write(priv) # we start the nodes in parallel because we don't know which one # will "win" and connect first node_setup = [ (node0_config, join(tempdir, "node0")), (node1_config, join(tempdir, "node1")), (node2_config, join(tempdir, "node2")), (node3_config, join(tempdir, "node3")), ] node_starts = [] for node_config, node_dir in node_setup: node_d = start_node(request, reactor, virtualenv, node_config, node_dir) node_starts.append(node_d) print("-" * 80) print(node_starts) results = yield DeferredList(node_starts) print("-" * 80) print(results) print("-" * 80) nodes = [] for ok, res in results: if not ok: raise res nodes.append(res) protocol0, protocol1, protocol2, protocol3 = nodes print("Started rlink'd nodes:") print(" 0: {}".format(protocol0)) print(" 1: {}".format(protocol1)) print(" 2: {}".format(protocol2)) print(" 3: {}".format(protocol3)) print("-" * 80) # we could wait to see text of each node successfully connecting # to the other .. or we just wait a bit. yield sleep(5) subscribed_d = Deferred() rpc_call_d = Deferred() print("start alice") # run alice first alice = Component( transports=[ {"url": "ws://localhost:7070/ws", "type": "websocket"}, # proxy0 ], realm="realm1", ) @alice.on_join @inlineCallbacks def alice_join(session, details): print("\n\nalice joined\n") def a_thing(*args, **kw): print("received: a_thing: args={} kw={}".format(args, kw)) reactor.callLater(3, session.leave) yield session.subscribe(a_thing, "test.a_thing") def rpc(*args, **kw): print("call: rpc: args={} kw={}".format(args, kw)) reactor.callLater(1, rpc_call_d.callback, None) return "rpc return" yield session.register(rpc, "test.rpc") # XXX we don't know when the rlink registration goes all the way through... reactor.callLater(2.0, subscribed_d.callback, None) alice_done = alice.start(reactor) # wait until Alice actually subscribes (and thus is also registered) before starting bob yield subscribed_d print("alice is subscribed + registered") print("start bob") bob = Component( transports=[{ "url": "ws://localhost:7070/ws", # node0 XXX should be node1 "type": "websocket", }], realm="realm1", ) @bob.on_join @inlineCallbacks def bob_join(session, details): print("bob joined: PID={x_cb_pid}".format(**details.authextra)) print("publishing 'test.a_thing'") p = yield session.publish("test.a_thing", 3, 2, 1, options=types.PublishOptions(acknowledge=True)) print("published {}".format(p)) res = yield session.call("test.rpc", 1, 2, 3) print("test.rpc returned: {}".format(res)) reactor.callLater(2, session.leave) bob_done = bob.start(reactor) print("bob is starting", bob_done, alice_done) yield rpc_call_d yield bob_done yield alice_done # do a bunch of pubs in different sessions to prove we're hitting # different proxies and different router processes. received = [] connects = [] carol = Component( transports=[{ "url": "ws://*****:*****@carol.subscribe("multiverse", types.SubscribeOptions(details=True)) def _(*args, **kwargs): print("SUB: {}".format(kwargs.get('details', None))) received.append((args, kwargs)) carol_ready = Deferred() carol.on('ready', carol_ready.callback) carol.start() yield sleep(3) yield carol_ready GROUPS = 10 CONNECTS = 5 for g in range(GROUPS): group = [] for m in range(CONNECTS): client = Component( transports=[{ "url": "ws://localhost:7070/ws", # proxy0 "type": "websocket", }], realm="realm1", ) @client.on_join @inlineCallbacks def _(session, details): connects.append(details) yield session.publish( u"multiverse", group=g, member=m, options=types.PublishOptions(acknowledge=True) ) yield session.leave() group.append(client.start()) res = yield DeferredList(group) for ok, value in res: if not ok: raise value print("-" * 80) print("Received {} events".format(len(received))) for r in received: print(r[1]['details']) # some client should get each publish() that we sent # FIXME: AssertionError: assert 49 == (10 * 5) assert len(received) == GROUPS * CONNECTS print("-" * 80) # figure out which nodes and proxies we've contacted workers = set() proxies = set() for c in connects: workers.add(c.authextra['x_cb_worker']) proxies.add(c.authextra['x_cb_proxy_worker']) print(c.authextra['x_cb_worker']) print("workers: {}".format(workers)) print("proxies: {}".format(proxies)) print("-" * 80) assert workers == set([ "node0_worker0", "node1_worker0", "node2_worker0", "node3_worker0", ]) assert proxies == set(["node0_proxy0"])
def onJoin(self, details): self.channel_layer = channel_layers["default"] Channel('wamp.join').send({'session': details.session}) while self.is_connected(): yield self.publish('com.example.bonjour', 2, options=types.PublishOptions(exclude_me=False)) self.call('com.example.hello', 'hey!') channel_name, result = self.channel_layer.receive_many( self.channels) if channel_name is None: yield sleep(SLEEP_TIME) continue elif channel_name == 'wamp.call': uri = result['uri'] args = result.get('args', []) kwargs = result.get('kwargs', {}) options = result.get('options', {}) if options: kwargs['options'] = types.CallOptions(**options) registration = self.call(uri, *args, **kwargs) elif channel_name == 'wamp.publish': topic = result['topic'] args = result.get('args', []) kwargs = result.get('kwargs', {}) options = result.get('options', {}) if options: kwargs['options'] = types.PublishOptions(**options) self.publish(topic, *args, **kwargs) elif channel_name == 'wamp.subscribe': func_path = result['func_path'] topic = result['topic'] options = result.get('options', {}) or {} subscribe_options = types.SubscribeOptions(**options) subscription = yield self.forward_subscriber( func_path, topic, subscribe_options) self.forward_subscription(result['reply_channel'], subscription) elif channel_name == 'wamp.unsubscribe': subscription_id = result['subscription_id'] subscription = self.subscriptions.pop(subscription_id) yield subscription.unsubscribe() elif channel_name == 'wamp.register': func_path = result['func_path'] uri = result['uri'] options = result.get('options', {}) or {} register_options = types.RegisterOptions(**options) registration = yield self.forward_procedure( func_path, uri, register_options) self.forward_registration(result['reply_channel'], registration) elif channel_name == 'wamp.unregister': registration_id = result['registration_id'] registration = self.subscriptions.pop(registration_id) yield registration.unregister() elif channel_name in self.reply_channels: self.reply_channels[channel_name].callback( *result['args'], **result['kwargs']) yield sleep(SLEEP_TIME) self.log.info('disconnected!') Channel('wamp.disconnect').send({ 'reason': 'not connected', }) self.disconnect() reactor.stop()