def test_timed_request(self): nc = NATS() msgs = [] counter = 0 @asyncio.coroutine def worker_handler(msg): nonlocal counter counter += 1 msgs.append(msg) yield from nc.publish(msg.reply, 'Reply:{}'.format(counter).encode()) @asyncio.coroutine def slow_worker_handler(msg): yield from asyncio.sleep(0.5, loop=self.loop) yield from nc.publish(msg.reply, b'timeout by now...') yield from nc.connect(io_loop=self.loop) yield from nc.subscribe("help", cb=worker_handler) yield from nc.subscribe("slow.help", cb=slow_worker_handler) response = yield from nc.timed_request("help", b'please', timeout=1) self.assertEqual(b'Reply:1', response.data) response = yield from nc.timed_request("help", b'please', timeout=1) self.assertEqual(b'Reply:2', response.data) with self.assertRaises(ErrTimeout): yield from nc.timed_request("slow.help", b'please', timeout=0.1) yield from asyncio.sleep(1, loop=self.loop) yield from nc.close()
def run(loop): nc = Nats() yield from nc.connect(io_loop=loop) # Send a request and expect a single response and trigger timeout if not # faster than 50 ms. try: response = yield from nc.timed_request("conf.host", b'host', 0.050) print("Received response: {message}".format(message=response.data.decode())) except ErrTimeout: print("Request timed out") yield from nc.publish("log.info", b'initializing') yield from nc.publish("log.info", b'scraping item 1') @asyncio.coroutine def help_request(msg): subject = msg.subject reply = msg.reply data = msg.data.decode() print("Received a message on '{subject} {reply}': {data}".format( subject=subject, reply=reply, data=data ) ) yield from nc.publish(reply, b'I can help') # Use queue named 'workers' for distributing requests among subscribers. yield from nc.subscribe("cmd.help", "workers", help_request) yield from asyncio.sleep(20, loop=loop) yield from nc.close()
def go(loop): nc = NATS() try: yield from nc.connect(io_loop=loop) except: pass @asyncio.coroutine def message_handler(msg): print("[Received on '{}']: {}".format(msg.subject, msg.data.decode())) try: # Interested in receiving 2 messages from the 'discover' subject. sid = yield from nc.subscribe("discover", "", message_handler) yield from nc.auto_unsubscribe(sid, 2) yield from nc.publish("discover", b'hello') yield from nc.publish("discover", b'world') # Following 2 messages won't be received. yield from nc.publish("discover", b'again') yield from nc.publish("discover", b'!!!!!') except ErrConnectionClosed: print("Connection closed prematurely") @asyncio.coroutine def request_handler(msg): print("[Request on '{} {}']: {}".format(msg.subject, msg.reply, msg.data.decode())) yield from nc.publish(msg.reply, b'OK') if nc.is_connected: # Subscription using a 'workers' queue so that only a single subscriber # gets a request at a time. yield from nc.subscribe("help", "workers", cb=request_handler) try: # Make a request expecting a single response within 500 ms, # otherwise raising a timeout error. msg = yield from nc.timed_request("help", b'help please', 0.500) print("[Response]: {}".format(msg.data)) # Make a roundtrip to the server to ensure messages # that sent messages have been processed already. yield from nc.flush(0.500) except ErrTimeout: print("[Error] Timeout!") # Wait a bit for message to be dispatched... yield from asyncio.sleep(1, loop=loop) # Detach from the server. yield from nc.close() if nc.last_error is not None: print("Last Error: {}".format(nc.last_error)) if nc.is_closed: print("Disconnected.")
def run(loop): nc = NATS() ssl_ctx = ssl.create_default_context(purpose=ssl.Purpose.SERVER_AUTH) ssl_ctx.protocol = ssl.PROTOCOL_TLSv1_2 ssl_ctx.load_verify_locations('../tests/certs/ca.pem') ssl_ctx.load_cert_chain(certfile='../tests/certs/client-cert.pem', keyfile='../tests/certs/client-key.pem') yield from nc.connect(io_loop=loop, tls=ssl_ctx) @asyncio.coroutine def message_handler(msg): subject = msg.subject reply = msg.reply data = msg.data.decode() print("Received a message on '{subject} {reply}': {data}".format( subject=subject, reply=reply, data=data)) # Simple publisher and async subscriber via coroutine. sid = yield from nc.subscribe("foo", cb=message_handler) # Stop receiving after 2 messages. yield from nc.auto_unsubscribe(sid, 2) yield from nc.publish("foo", b'Hello') yield from nc.publish("foo", b'World') yield from nc.publish("foo", b'!!!!!') @asyncio.coroutine def help_request(msg): subject = msg.subject reply = msg.reply data = msg.data.decode() print("Received a message on '{subject} {reply}': {data}".format( subject=subject, reply=reply, data=data)) yield from nc.publish(reply, b'I can help') # Use queue named 'workers' for distributing requests # among subscribers. yield from nc.subscribe("help", "workers", help_request) # Send a request and expect a single response # and trigger timeout if not faster than 50 ms. try: response = yield from nc.timed_request("help", b'help me', 0.050) print("Received response: {message}".format( message=response.data.decode())) except ErrTimeout: print("Request timed out") yield from asyncio.sleep(1, loop=loop) yield from nc.close()
def run(loop): nc = NATS() ssl_ctx = ssl.create_default_context(purpose=ssl.Purpose.SERVER_AUTH) ssl_ctx.protocol = ssl.PROTOCOL_TLSv1_2 ssl_ctx.load_verify_locations('../tests/certs/ca.pem') ssl_ctx.load_cert_chain(certfile='../tests/certs/client-cert.pem', keyfile='../tests/certs/client-key.pem') yield from nc.connect(io_loop=loop, tls=ssl_ctx) @asyncio.coroutine def message_handler(msg): subject = msg.subject reply = msg.reply data = msg.data.decode() print("Received a message on '{subject} {reply}': {data}".format( subject=subject, reply=reply, data=data)) # Simple publisher and async subscriber via coroutine. sid = yield from nc.subscribe("foo", cb=message_handler) # Stop receiving after 2 messages. yield from nc.auto_unsubscribe(sid, 2) yield from nc.publish("foo", b'Hello') yield from nc.publish("foo", b'World') yield from nc.publish("foo", b'!!!!!') @asyncio.coroutine def help_request(msg): subject = msg.subject reply = msg.reply data = msg.data.decode() print("Received a message on '{subject} {reply}': {data}".format( subject=subject, reply=reply, data=data)) yield from nc.publish(reply, b'I can help') # Use queue named 'workers' for distributing requests # among subscribers. yield from nc.subscribe("help", "workers", help_request) # Send a request and expect a single response # and trigger timeout if not faster than 50 ms. try: response = yield from nc.timed_request("help", b'help me', 0.050) print("Received response: {message}".format(message=response.data.decode())) except ErrTimeout: print("Request timed out") yield from asyncio.sleep(1, loop=loop) yield from nc.close()
def main(loop): parser = argparse.ArgumentParser() parser.add_argument('-n', '--iterations', default=DEFAULT_ITERATIONS, type=int) parser.add_argument('-S', '--subject', default='test') parser.add_argument('--servers', default=[], action='append') args = parser.parse_args() servers = args.servers if len(args.servers) < 1: servers = ["nats://127.0.0.1:4222"] opts = {"servers": servers} # Make sure we're connected to a server first... nc = NATS() try: yield from nc.connect(**opts) except Exception as e: sys.stderr.write("ERROR: {0}".format(e)) show_usage_and_die() @asyncio.coroutine def handler(msg): yield from nc.publish(msg.reply, b'') yield from nc.subscribe(args.subject, cb=handler) # Start the benchmark start = time.monotonic() to_send = args.iterations print("Sending {0} request/responses on [{1}]".format( args.iterations, args.subject)) while to_send > 0: to_send -= 1 if to_send == 0: break yield from nc.timed_request(args.subject, b'') if (to_send % HASH_MODULO) == 0: sys.stdout.write("#") sys.stdout.flush() duration = time.monotonic() - start ms = "%.3f" % ((duration / args.iterations) * 1000) print("\nTest completed : {0} ms avg request/response latency".format(ms)) yield from nc.close()
def run(loop): nc = NATS() yield from nc.connect(io_loop=loop) @asyncio.coroutine def message_handler(msg): subject = msg.subject reply = msg.reply data = msg.data.decode() print("Received a message on '{subject} {reply}': {data}".format( subject=subject, reply=reply, data=data)) # Simple publisher and async subscriber via coroutine. sid = yield from nc.subscribe("foo", cb=message_handler) # Stop receiving after 2 messages. yield from nc.auto_unsubscribe(sid, 2) yield from nc.publish("foo", b'Hello') yield from nc.publish("foo", b'World') yield from nc.publish("foo", b'!!!!!') @asyncio.coroutine def help_request(msg): subject = msg.subject reply = msg.reply data = msg.data.decode() print("Received a message on '{subject} {reply}': {data}".format( subject=subject, reply=reply, data=data)) yield from nc.publish(reply, b'I can help') # Use queue named 'workers' for distributing requests # among subscribers. yield from nc.subscribe("help", "workers", help_request) # Send a request and expect a single response # and trigger timeout if not faster than 50 ms. try: response = yield from nc.timed_request("help", b'help me', 0.050) print("Received response: {message}".format( message=response.data.decode())) except ErrTimeout: print("Request timed out") yield from asyncio.sleep(1, loop=loop) yield from nc.close()
def run(loop): nc = NATS() yield from nc.connect(io_loop=loop) @asyncio.coroutine def message_handler(msg): subject = msg.subject reply = msg.reply data = msg.data.decode() print("Received a message on '{subject} {reply}': {data}".format( subject=subject, reply=reply, data=data)) # Simple publisher and async subscriber via coroutine. sid = yield from nc.subscribe("foo", cb=message_handler) # Stop receiving after 2 messages. yield from nc.auto_unsubscribe(sid, 2) yield from nc.publish("foo", b'Hello') yield from nc.publish("foo", b'World') yield from nc.publish("foo", b'!!!!!') @asyncio.coroutine def help_request(msg): subject = msg.subject reply = msg.reply data = msg.data.decode() print("Received a message on '{subject} {reply}': {data}".format( subject=subject, reply=reply, data=data)) yield from nc.publish(reply, b'I can help') # Use queue named 'workers' for distributing requests # among subscribers. yield from nc.subscribe("help", "workers", help_request) # Send a request and expect a single response # and trigger timeout if not faster than 50 ms. try: response = yield from nc.timed_request("help", b'help me', 0.050) print("Received response: {message}".format(message=response.data.decode())) except ErrTimeout: print("Request timed out") yield from asyncio.sleep(1, loop=loop) yield from nc.close()
def main(loop): parser = argparse.ArgumentParser() parser.add_argument('-n', '--iterations', default=DEFAULT_ITERATIONS, type=int) parser.add_argument('-S', '--subject', default='test') parser.add_argument('--servers', default=[], action='append') args = parser.parse_args() servers = args.servers if len(args.servers) < 1: servers = ["nats://127.0.0.1:4222"] opts = { "servers": servers } # Make sure we're connected to a server first... nc = NATS() try: yield from nc.connect(**opts) except Exception as e: sys.stderr.write("ERROR: {0}".format(e)) show_usage_and_die() @asyncio.coroutine def handler(msg): yield from nc.publish(msg.reply, b'') yield from nc.subscribe(args.subject, cb=handler) # Start the benchmark start = time.monotonic() to_send = args.iterations print("Sending {0} request/responses on [{1}]".format( args.iterations, args.subject)) while to_send > 0: to_send -= 1 if to_send == 0: break yield from nc.timed_request(args.subject, b'') if (to_send % HASH_MODULO) == 0: sys.stdout.write("#") sys.stdout.flush() duration = time.monotonic() - start ms = "%.3f" % ((duration/args.iterations) * 1000) print("\nTest completed : {0} ms avg request/response latency".format(ms)) yield from nc.close()
def test_reconnect_with_auth_token(self): nc = NATS() disconnected_count = 0 reconnected_count = 0 closed_count = 0 err_count = 0 @asyncio.coroutine def disconnected_cb(): nonlocal disconnected_count disconnected_count += 1 @asyncio.coroutine def reconnected_cb(): nonlocal reconnected_count reconnected_count += 1 @asyncio.coroutine def closed_cb(): nonlocal closed_count closed_count += 1 counter = 0 @asyncio.coroutine def worker_handler(msg): nonlocal counter counter += 1 if msg.reply != "": yield from nc.publish(msg.reply, 'Reply:{}'.format(counter).encode()) options = { 'servers': [ "nats://[email protected]:4223", "nats://[email protected]:4224", ], 'disconnected_cb': disconnected_cb, 'closed_cb': closed_cb, 'reconnected_cb': reconnected_cb, 'dont_randomize': True, 'io_loop': self.loop } yield from nc.connect(**options) yield from nc.subscribe("test", cb=worker_handler) self.assertIn('auth_required', nc._server_info) self.assertTrue(nc.is_connected) # Trigger a reconnnect yield from self.loop.run_in_executor(None, self.server_pool[0].stop) yield from asyncio.sleep(1, loop=self.loop) yield from nc.subscribe("test", cb=worker_handler) response = yield from nc.timed_request("test", b'data', timeout=1) self.assertEqual(b'Reply:1', response.data) yield from nc.close() self.assertTrue(nc.is_closed) self.assertFalse(nc.is_connected) self.assertEqual(1, closed_count) self.assertEqual(2, disconnected_count) self.assertEqual(1, reconnected_count)
def test_auth_reconnect(self): nc = NATS() disconnected_count = 0 reconnected_count = 0 closed_count = 0 err_count = 0 @asyncio.coroutine def disconnected_cb(): nonlocal disconnected_count disconnected_count += 1 @asyncio.coroutine def reconnected_cb(): nonlocal reconnected_count reconnected_count += 1 @asyncio.coroutine def closed_cb(): nonlocal closed_count closed_count += 1 @asyncio.coroutine def err_cb(e): nonlocal err_count err_count += 1 counter = 0 @asyncio.coroutine def worker_handler(msg): nonlocal counter counter += 1 if msg.reply != "": yield from nc.publish(msg.reply, 'Reply:{}'.format(counter).encode()) options = { 'servers': [ "nats://*****:*****@127.0.0.1:4223", "nats://*****:*****@127.0.0.1:4224" ], 'io_loop': self.loop, 'disconnected_cb': disconnected_cb, 'closed_cb': closed_cb, 'reconnected_cb': reconnected_cb, 'error_cb': err_cb, 'dont_randomize': True, } yield from nc.connect(**options) self.assertTrue(nc.is_connected) yield from nc.subscribe("one", cb=worker_handler) yield from nc.subscribe("two", cb=worker_handler) yield from nc.subscribe("three", cb=worker_handler) response = yield from nc.timed_request("one", b'Help!', timeout=1) self.assertEqual(b'Reply:1', response.data) # Stop the first server and connect to another one asap. yield from self.loop.run_in_executor(None, self.server_pool[0].stop) # FIXME: Find better way to wait for the server to be stopped. yield from asyncio.sleep(0.5, loop=self.loop) response = yield from nc.timed_request("three", b'Help!', timeout=1) self.assertEqual('Reply:2'.encode(), response.data) yield from asyncio.sleep(0.5, loop=self.loop) yield from nc.close() self.assertEqual(1, nc.stats['reconnects']) self.assertEqual(1, closed_count) self.assertEqual(2, disconnected_count) self.assertEqual(1, reconnected_count) self.assertEqual(1, err_count)
def test_tls_reconnect(self): nc = NATS() disconnected_count = 0 reconnected_count = 0 closed_count = 0 err_count = 0 @asyncio.coroutine def disconnected_cb(): nonlocal disconnected_count disconnected_count += 1 @asyncio.coroutine def reconnected_cb(): nonlocal reconnected_count reconnected_count += 1 @asyncio.coroutine def closed_cb(): nonlocal closed_count closed_count += 1 @asyncio.coroutine def err_cb(e): nonlocal err_count err_count += 1 counter = 0 @asyncio.coroutine def worker_handler(msg): nonlocal counter counter += 1 if msg.reply != "": yield from nc.publish(msg.reply, 'Reply:{}'.format(counter).encode()) options = { 'servers': [ "nats://*****:*****@localhost:4223", "nats://*****:*****@localhost:4224" ], 'io_loop': self.loop, 'disconnected_cb': disconnected_cb, 'closed_cb': closed_cb, 'reconnected_cb': reconnected_cb, 'error_cb': err_cb, 'dont_randomize': True, 'tls': self.ssl_ctx } yield from nc.connect(**options) self.assertTrue(nc.is_connected) yield from nc.subscribe("example", cb=worker_handler) response = yield from nc.timed_request("example", b'Help!', timeout=1) self.assertEqual(b'Reply:1', response.data) # Trigger a reconnnect and should be fine yield from self.loop.run_in_executor(None, self.server_pool[0].stop) yield from asyncio.sleep(1, loop=self.loop) yield from nc.subscribe("example", cb=worker_handler) response = yield from nc.timed_request("example", b'Help!', timeout=1) self.assertEqual(b'Reply:2', response.data) yield from nc.close() self.assertTrue(nc.is_closed) self.assertFalse(nc.is_connected) self.assertEqual(1, nc.stats['reconnects']) self.assertEqual(1, closed_count) self.assertEqual(2, disconnected_count) self.assertEqual(1, reconnected_count) self.assertEqual(1, err_count)
class NatsIo(object): """ asyncio NATS client """ def __init__(self, host='127.0.0.1', port='4222', streaming_id=None, cluster='svom-cluster'): self.nc = NATS() self.server = host self.is_connected = False self.streaming_id = streaming_id self.cluster = cluster self.sc = None self.loop = None try: self.loop = asyncio.get_event_loop() except RuntimeError: LOG.info( 'Failed to retrieve asyncio event loop. Creating a new one...') self.loop = asyncio.new_event_loop() self.loop.run_until_complete(self._connect(servers=[self.server])) if streaming_id is not None: if has_streaming is True: self.sc = STAN() self.loop.run_until_complete(self._stan_connect()) else: LOG.error("Can't run client without asyncio-nats-streaming") sys.exit(1) self.nats_server = NATSClient(self.nc, self.loop) self.nats_server.start() def is_streaming(self): return True if self.streaming_id is not None else False def _connect(self, servers): """connect""" while not self.is_connected: try: LOG.info('Attempting connection to nats server ' + servers[0]) yield from self.nc.connect(servers=servers, io_loop=self.loop) yield from self.nc.flush() except Exception: LOG.exception('Connection failed.') LOG.info('Retrying connection in 5s.') yield from asyncio.sleep(5) else: self.is_connected = True LOG.info('Connected.') if self.streaming_id is not None: self.is_connected = False @asyncio.coroutine def _stan_connect(self): """connect""" while not self.is_connected: try: LOG.info('Attempting connection to server cluster \'' + self.cluster + '\' with clientID \'' + self.streaming_id + '\'') yield from self.sc.connect(self.cluster, self.streaming_id, nats=self.nc) except Exception: LOG.exception('Connection failed.') LOG.info('Retrying streaming server connection in 5s.') yield from asyncio.sleep(5) else: self.is_connected = True LOG.info('Connected to streaming server.') def subscribe(self, subject, handler=None): """launch subscription as async task""" if not self.is_connected: LOG.info("Awaiting to be connected to subscribe") self.loop.call_later(5, self.subscribe, subject, handler) if handler is None: handler = self._default_handler if self.streaming_id is None: asyncio.run_coroutine_threadsafe(self._subscribe(subject, handler), loop=self.loop) else: asyncio.run_coroutine_threadsafe(self._stan_subscribe( subject, handler), loop=self.loop) @asyncio.coroutine def _subscribe(self, subject, handler): """new subscription or handler change""" LOG.info('Subscribing to subject \'' + subject + '\'') try: yield from self.nc.subscribe(subject, cb=handler) except Exception: LOG.exception('Subscription failed.') else: LOG.info('Subscribed to subject \'' + subject + '\'') @asyncio.coroutine def _stan_subscribe(self, subject, handler): """new subscription or handler change""" LOG.info('Subscribing to subject \'' + subject + '\'') try: # No need to custom durable name since 'clientID+durable name' is checked yield from self.sc.subscribe(subject, durable_name=handler.__name__, cb=handler) except Exception as e: LOG.exception('Subscription to \'%s\' failed' % subject) else: LOG.info('Handler \'%s\' subscribed to subject \'%s\'' % (handler.__name__, subject)) def publish(self, subject, data, with_reply=False): """launch publish as async task""" if isinstance(data, str): data = data.encode() if with_reply is False: if self.streaming_id is None: asyncio.run_coroutine_threadsafe(self._publish(subject, data), loop=self.loop) else: asyncio.run_coroutine_threadsafe(self._stan_publish(subject,\ data), loop=self.loop) else: if self.streaming_id is None: future = asyncio.run_coroutine_threadsafe(\ self._publish(subject,\ data, with_reply=True), loop=self.loop) try: result = future.result(1) if result is None: LOG.error('Message received no response') return None except Exception: LOG.exception('Timed request publishing exception') else: return result else: LOG.error('NATS Streaming publish cannot handle replies') @asyncio.coroutine def _publish(self, subject, data, with_reply=False): """Nats transport data : bytes or str""" try: if with_reply is False: yield from self.nc.publish(subject, data) else: yield from self.nc.timed_request(subject, data) except Exception as e: LOG.exception('Publication failed.') @asyncio.coroutine def _stan_publish(self, subject, data): """Nats transport data : bytes or str""" try: yield from self.sc.publish(subject,\ data, ack_handler=self._ack_handler) except Exception as e: LOG.exception('Publication failed, is \'' + \ subject + '\' a valid channel?') @asyncio.coroutine def _default_handler(self, msg): """Application handler data : str""" LOG.debug("--- Received: " + msg.subject + msg.data.decode()) if self.streaming_id is not None: yield from self.sc.ack(msg) @asyncio.coroutine def ack(self, msg): yield from self.sc.ack(msg) @asyncio.coroutine def _ack_handler(self, ack): LOG.debug('Delivery ack received: {}'.format(ack.guid)) def stop(self): LOG.debug('Trying to stop properly NatsIo: ') asyncio.run_coroutine_threadsafe(self.nc.flush(), loop=self.loop) asyncio.run_coroutine_threadsafe(self.nc.close(), loop=self.loop) try: time.sleep(1) self.loop.call_soon_threadsafe(self.loop.stop) except Exception as e: LOG.debug(e) try: time.sleep(1) if self.loop.is_running(): LOG.debug('loop.is_running: ' + str(self.loop.is_running())) self.loop.call_soon_threadsafe(self.loop.close()) except Exception as e: LOG.debug(e) self.nats_server.join()
def test_auth_reconnect(self): nc = NATS() disconnected_count = 0 reconnected_count = 0 closed_count = 0 err_count = 0 @asyncio.coroutine def disconnected_cb(): nonlocal disconnected_count disconnected_count += 1 @asyncio.coroutine def reconnected_cb(): nonlocal reconnected_count reconnected_count += 1 @asyncio.coroutine def closed_cb(): nonlocal closed_count closed_count += 1 @asyncio.coroutine def err_cb(): nonlocal err_count err_count += 1 counter = 0 @asyncio.coroutine def worker_handler(msg): nonlocal counter counter += 1 if msg.reply != "": yield from nc.publish(msg.reply, 'Reply:{}'.format(counter).encode()) options = { 'servers': [ "nats://*****:*****@127.0.0.1:4223", "nats://*****:*****@127.0.0.1:4224" ], 'io_loop': self.loop, 'disconnected_cb': disconnected_cb, 'closed_cb': closed_cb, 'reconnected_cb': reconnected_cb, 'error_cb': err_cb, 'dont_randomize': True, } yield from nc.connect(**options) self.assertTrue(nc.is_connected) yield from nc.subscribe("one", cb=worker_handler) yield from nc.subscribe("two", cb=worker_handler) yield from nc.subscribe("three", cb=worker_handler) response = yield from nc.timed_request("one", b'Help!', timeout=1) self.assertEqual(b'Reply:1', response.data) # Stop the first server and connect to another one asap. yield from self.loop.run_in_executor(None, self.server_pool[0].stop) # FIXME: Find better way to wait for the server to be stopped. yield from asyncio.sleep(0.5, loop=self.loop) response = yield from nc.timed_request("three", b'Help!', timeout=1) self.assertEqual('Reply:2'.encode(), response.data) yield from asyncio.sleep(0.5, loop=self.loop) yield from nc.close() self.assertEqual(1, nc.stats['reconnects']) self.assertEqual(1, closed_count) self.assertEqual(2, disconnected_count) self.assertEqual(1, reconnected_count) self.assertEqual(0, err_count)