def __init__(self, client_id=None, config=None, loop=None): self.logger = logging.getLogger(__name__) self.config = copy.deepcopy(_defaults) if config is not None: self.config.update(config) if client_id is not None: self.client_id = client_id else: from amqtt.utils import gen_client_id self.client_id = gen_client_id() self.logger.debug("Using generated client ID : %s" % self.client_id) if loop is not None: self._loop = loop else: self._loop = asyncio.get_event_loop() self.session = None self._handler = None self._disconnect_task = None self._connected_state = asyncio.Event(loop=self._loop) self._no_more_connections = asyncio.Event(loop=self._loop) self.extra_headers = {} # Init plugins manager context = ClientContext() context.config = self.config self.plugins_manager = PluginManager("amqtt.client.plugins", context, loop=self._loop) self.client_tasks = deque()
async def from_stream( cls, reader: ReaderAdapter, fixed_header: MQTTFixedHeader, variable_header: ConnectVariableHeader, ): payload = cls() # Client identifier try: payload.client_id = await decode_string(reader) except NoDataException: payload.client_id = None if payload.client_id is None or payload.client_id == "": # A Server MAY allow a Client to supply a ClientId that has a length of zero bytes # [MQTT-3.1.3-6] payload.client_id = gen_client_id() # indicator to trow exception in case CLEAN_SESSION_FLAG is set to False payload.client_id_is_random = True # Read will topic, username and password if variable_header.will_flag: try: payload.will_topic = await decode_string(reader) payload.will_message = await decode_data_with_length(reader) except NoDataException: payload.will_topic = None payload.will_message = None if variable_header.username_flag: try: payload.username = await decode_string(reader) except NoDataException: payload.username = None if variable_header.password_flag: try: payload.password = await decode_string(reader) except NoDataException: payload.password = None return payload
async def client_connected( self, listener_name, reader: ReaderAdapter, writer: WriterAdapter ): # Wait for connection available on listener server = self._servers.get(listener_name, None) if not server: raise BrokerException("Invalid listener name '%s'" % listener_name) await server.acquire_connection() remote_address, remote_port = writer.get_peer_info() self.logger.info( "Connection from %s:%d on listener '%s'" % (remote_address, remote_port, listener_name) ) # Wait for first packet and expect a CONNECT try: handler, client_session = await BrokerProtocolHandler.init_from_connect( reader, writer, self.plugins_manager, loop=self._loop ) except HBMQTTException as exc: self.logger.warning( "[MQTT-3.1.0-1] %s: Can't read first packet an CONNECT: %s" % (format_client_message(address=remote_address, port=remote_port), exc) ) # await writer.close() self.logger.debug("Connection closed") return except MQTTException as me: self.logger.error( "Invalid connection from %s : %s" % (format_client_message(address=remote_address, port=remote_port), me) ) await writer.close() self.logger.debug("Connection closed") return if client_session.clean_session: # Delete existing session and create a new one if client_session.client_id is not None and client_session.client_id != "": self.delete_session(client_session.client_id) else: client_session.client_id = gen_client_id() client_session.parent = 0 else: # Get session from cache if client_session.client_id in self._sessions: self.logger.debug( "Found old session %s" % repr(self._sessions[client_session.client_id]) ) (client_session, h) = self._sessions[client_session.client_id] client_session.parent = 1 else: client_session.parent = 0 if client_session.keep_alive > 0: client_session.keep_alive += self.config["timeout-disconnect-delay"] self.logger.debug("Keep-alive timeout=%d" % client_session.keep_alive) handler.attach(client_session, reader, writer) self._sessions[client_session.client_id] = (client_session, handler) authenticated = await self.authenticate( client_session, self.listeners_config[listener_name] ) if not authenticated: await writer.close() server.release_connection() # Delete client from connections list return while True: try: client_session.transitions.connect() break except (MachineError, ValueError): # Backwards compat: MachineError is raised by transitions < 0.5.0. self.logger.warning( "Client %s is reconnecting too quickly, make it wait" % client_session.client_id ) # Wait a bit may be client is reconnecting too fast await asyncio.sleep(1, loop=self._loop) await handler.mqtt_connack_authorize(authenticated) await self.plugins_manager.fire_event( EVENT_BROKER_CLIENT_CONNECTED, client_id=client_session.client_id ) self.logger.debug("%s Start messages handling" % client_session.client_id) await handler.start() self.logger.debug( "Retained messages queue size: %d" % client_session.retained_messages.qsize() ) await self.publish_session_retained_messages(client_session) # Init and start loop for handling client messages (publish, subscribe/unsubscribe, disconnect) disconnect_waiter = asyncio.ensure_future( handler.wait_disconnect(), loop=self._loop ) subscribe_waiter = asyncio.ensure_future( handler.get_next_pending_subscription(), loop=self._loop ) unsubscribe_waiter = asyncio.ensure_future( handler.get_next_pending_unsubscription(), loop=self._loop ) wait_deliver = asyncio.ensure_future( handler.mqtt_deliver_next_message(), loop=self._loop ) connected = True while connected: try: done, pending = await asyncio.wait( [ disconnect_waiter, subscribe_waiter, unsubscribe_waiter, wait_deliver, ], return_when=asyncio.FIRST_COMPLETED, loop=self._loop, ) if disconnect_waiter in done: result = disconnect_waiter.result() self.logger.debug( "%s Result from wait_diconnect: %s" % (client_session.client_id, result) ) if result is None: self.logger.debug("Will flag: %s" % client_session.will_flag) # Connection closed anormally, send will message if client_session.will_flag: self.logger.debug( "Client %s disconnected abnormally, sending will message" % format_client_message(client_session) ) await self._broadcast_message( client_session, client_session.will_topic, client_session.will_message, client_session.will_qos, ) if client_session.will_retain: self.retain_message( client_session, client_session.will_topic, client_session.will_message, client_session.will_qos, ) self.logger.debug( "%s Disconnecting session" % client_session.client_id ) await self._stop_handler(handler) client_session.transitions.disconnect() await self.plugins_manager.fire_event( EVENT_BROKER_CLIENT_DISCONNECTED, client_id=client_session.client_id, ) connected = False if unsubscribe_waiter in done: self.logger.debug( "%s handling unsubscription" % client_session.client_id ) unsubscription = unsubscribe_waiter.result() for topic in unsubscription["topics"]: self._del_subscription(topic, client_session) await self.plugins_manager.fire_event( EVENT_BROKER_CLIENT_UNSUBSCRIBED, client_id=client_session.client_id, topic=topic, ) await handler.mqtt_acknowledge_unsubscription( unsubscription["packet_id"] ) unsubscribe_waiter = asyncio.Task( handler.get_next_pending_unsubscription(), loop=self._loop ) if subscribe_waiter in done: self.logger.debug( "%s handling subscription" % client_session.client_id ) subscriptions = subscribe_waiter.result() return_codes = [] for subscription in subscriptions["topics"]: result = await self.add_subscription( subscription, client_session ) return_codes.append(result) await handler.mqtt_acknowledge_subscription( subscriptions["packet_id"], return_codes ) for index, subscription in enumerate(subscriptions["topics"]): if return_codes[index] != 0x80: await self.plugins_manager.fire_event( EVENT_BROKER_CLIENT_SUBSCRIBED, client_id=client_session.client_id, topic=subscription[0], qos=subscription[1], ) await self.publish_retained_messages_for_subscription( subscription, client_session ) subscribe_waiter = asyncio.Task( handler.get_next_pending_subscription(), loop=self._loop ) self.logger.debug(repr(self._subscriptions)) if wait_deliver in done: if self.logger.isEnabledFor(logging.DEBUG): self.logger.debug( "%s handling message delivery" % client_session.client_id ) app_message = wait_deliver.result() if not app_message.topic: self.logger.warning( "[MQTT-4.7.3-1] - %s invalid TOPIC sent in PUBLISH message, closing connection" % client_session.client_id ) break if "#" in app_message.topic or "+" in app_message.topic: self.logger.warning( "[MQTT-3.3.2-2] - %s invalid TOPIC sent in PUBLISH message, closing connection" % client_session.client_id ) break await self.plugins_manager.fire_event( EVENT_BROKER_MESSAGE_RECEIVED, client_id=client_session.client_id, message=app_message, ) await self._broadcast_message( client_session, app_message.topic, app_message.data ) if app_message.publish_packet.retain_flag: self.retain_message( client_session, app_message.topic, app_message.data, app_message.qos, ) wait_deliver = asyncio.Task( handler.mqtt_deliver_next_message(), loop=self._loop ) except asyncio.CancelledError: self.logger.debug("Client loop cancelled") break disconnect_waiter.cancel() subscribe_waiter.cancel() unsubscribe_waiter.cancel() wait_deliver.cancel() self.logger.debug("%s Client disconnected" % client_session.client_id) server.release_connection()
def test_client_id(): client_id = utils.gen_client_id() assert isinstance(client_id, str) assert client_id.startswith("amqtt/")