def __init__(self, tg: anyio.abc.TaskGroup, client_id=None, config=None, codec=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 distmqtt.utils import gen_client_id self.client_id = gen_client_id() self.logger.debug("Using generated client ID : %s", self.client_id) self.session = None self._tg = tg self._handler = None self._disconnect_task = None self._connected_state = anyio.create_event() self._no_more_connections = anyio.create_event() self.extra_headers = {} self.codec = get_codec(codec, config=self.config) self._subscriptions = None # Init plugins manager context = ClientContext(self.config) self.plugins_manager = PluginManager(tg, "distmqtt.client.plugins", context) self.client_task = None
async def from_stream( cls, reader: StreamAdapter, 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, adapter: BaseAdapter): # Wait for connection available on listener remote_address, remote_port = adapter.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( adapter, self.plugins_manager ) except DistMQTTException 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 adapter.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 != "": await 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 %r", self._sessions[client_session.client_id]) client_session = self._sessions[client_session.client_id][0] client_session.parent = 1 else: client_session.parent = 0 if not client_session.parent: await client_session.start(self) if client_session.keep_alive > 0 and not client_session.parent: # MQTT 3.1.2.10: one and a half keepalive times, plus configurable grace client_session.keep_alive += ( client_session.keep_alive / 2 + self.config["timeout-disconnect-delay"] ) self.logger.debug("Keep-alive timeout=%d", client_session.keep_alive) await handler.attach(client_session, adapter) self._sessions[client_session.client_id] = (client_session, handler) authenticated = await self.authenticate(client_session) if not authenticated: await adapter.close() return while True: try: client_session.transitions.connect() break except (MachineError, ValueError) as exc: # 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, exc_info=exc, ) # Wait a bit may be client is reconnecting too fast await anyio.sleep(1) 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() if self._do_retain: 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) async with anyio.create_task_group() as tg: async def handle_unsubscribe(): while True: unsubscription = await handler.get_next_pending_unsubscription() self.logger.debug("%s handling unsubscription", client_session.client_id) 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"]) async def handle_subscribe(): while True: subscriptions = await handler.get_next_pending_subscription() self.logger.debug("%s handling subscription", client_session.client_id) 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], ) if self._do_retain: await self.publish_retained_messages_for_subscription( subscription, client_session ) self.logger.debug(repr(self._subscriptions)) await tg.spawn(handle_unsubscribe) await tg.spawn(handle_subscribe) try: await handler.wait_disconnect() self.logger.debug( "%s wait_diconnect: %sclean", client_session.client_id, "" if handler.clean_disconnect else "un", ) if not handler.clean_disconnect: # Connection closed anormally, send will message self.logger.debug("Will flag: %s", client_session.will_flag) if client_session.will_flag: if self.logger.isEnabledFor(logging.DEBUG): self.logger.debug( "Client %s disconnected abnormally, sending will message", format_client_message(session=client_session), ) await self.broadcast_message( client_session, client_session.will_topic, client_session.will_message, client_session.will_qos, retain=client_session.will_retain, ) 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 ) finally: async with anyio.fail_after(2, shield=True): await tg.cancel_scope.cancel() pass # end taskgroup self.logger.debug("%s Client disconnected", client_session.client_id)