def disconnect(self, wait: float = 2.0, reason: Optional[str] = None, ignore_send_queue: bool = False) -> None: """Close the XML stream and wait for an acknowldgement from the server for at most `wait` seconds. After the given number of seconds has passed without a response from the server, or when the server successfully responds with a closure of its own stream, abort() is called. If wait is 0.0, this will call abort() directly without closing the stream. Does nothing if we are not connected. :param wait: Time to wait for a response from the server. """ # Compat: docs/getting_started/sendlogout.rst has been promoting # `disconnect(wait=True)` for ages. This doesn't mean anything to the # schedule call below. It would fortunately be converted to `1` later # down the call chain. Praise the implicit casts lord. if wait == True: wait = 2.0 if self.transport: if self.waiting_queue.empty() or ignore_send_queue: self.disconnect_reason = reason self.cancel_connection_attempt() if wait > 0.0: self.send_raw(self.stream_footer) self.schedule('Disconnect wait', wait, self.abort, repeat=False) else: asyncio.ensure_future( self._consume_send_queue_before_disconnecting(reason, wait), loop=self.loop, ) else: self.event("disconnected", reason)
def connect(self, host='', port=0, use_ssl=False, force_starttls=True, disable_starttls=False): """Create a new socket and connect to the server. :param host: The name of the desired server for the connection. :param port: Port to connect to on the server. :param use_ssl: Flag indicating if SSL should be used by connecting directly to a port using SSL. If it is False, the connection will be upgraded to SSL/TLS later, using STARTTLS. Only use this value for old servers that have specific port for SSL/TLS :param force_starttls: If True, the connection will be aborted if the server does not initiate a STARTTLS negotiation. If None, the connection will be upgraded to TLS only if the server initiate the STARTTLS negotiation, otherwise it will connect in clear. If False it will never upgrade to TLS, even if the server provides it. Use this for example if you’re on localhost """ if self._run_filters is None: self._run_filters = asyncio.ensure_future( self.run_filters(), loop=self.loop, ) self.disconnect_reason = None self.cancel_connection_attempt() self.connect_loop_wait = 0 if host and port: self.address = (host, int(port)) try: Socket.inet_aton(self.address[0]) except (Socket.error, ssl.SSLError): self.default_domain = self.address[0] # Respect previous TLS usage. if use_ssl is not None: self.use_ssl = use_ssl if force_starttls is not None: self.force_starttls = force_starttls if disable_starttls is not None: self.disable_starttls = disable_starttls self.event("connecting") self._current_connection_attempt = asyncio.ensure_future( self._connect_routine(), loop=self.loop, )
def run(self, payload, instream=False): """Execute the callback function with the matched stanza payload. :param payload: The matched :class:`~slixmpp.xmlstream.stanzabase.ElementBase` object. :param bool instream: Force the handler to execute during stream processing. This should only be used by :meth:`prerun()`. Defaults to ``False``. """ if not self._instream or instream: asyncio.ensure_future(self._pointer(payload)) if self._once: self._destroy = True del self._pointer
def run(self, payload, instream=False): """Execute the callback function with the matched stanza payload. :param payload: The matched :class:`~slixmpp.xmlstream.stanzabase.ElementBase` object. :param bool instream: Force the handler to execute during stream processing. This should only be used by :meth:`prerun()`. Defaults to ``False``. """ if not self._instream or instream: asyncio.ensure_future(self._pointer(payload)) if self._once: self._destroy = True del self._pointer
async def run_filters(self): """ Background loop that processes stanzas to send. """ while True: (data, use_filters) = await self.waiting_queue.get() try: if isinstance(data, ElementBase): if use_filters: already_run_filters = set() for filter in self.__filters['out']: already_run_filters.add(filter) if iscoroutinefunction(filter): task = asyncio.create_task(filter(data)) completed, pending = await wait( {task}, timeout=1, ) if pending: self.slow_tasks.append(task) asyncio.ensure_future( self._continue_slow_send( task, already_run_filters ), loop=self.loop, ) raise Exception("Slow coro, rescheduling") data = task.result() else: data = filter(data) if data is None: raise ContinueQueue('Empty stanza') if isinstance(data, ElementBase): if use_filters: for filter in self.__filters['out_sync']: data = filter(data) if data is None: raise ContinueQueue('Empty stanza') str_data = tostring(data.xml, xmlns=self.default_ns, stream=self, top_level=True) self.send_raw(str_data) else: self.send_raw(data) except ContinueQueue as exc: log.debug('Stanza in send queue not sent: %s', exc) except Exception: log.error('Exception raised in send queue:', exc_info=True) self.waiting_queue.task_done()
def event(self, name, data={}): """Manually trigger a custom event. :param name: The name of the event to trigger. :param data: Data that will be passed to each event handler. Defaults to an empty dictionary, but is usually a stanza object. """ log.debug("Event triggered: %s", name) handlers = self.__event_handlers.get(name, []) for handler in handlers: handler_callback, disposable = handler old_exception = getattr(data, 'exception', None) # If the callback is a coroutine, schedule it instead of # running it directly if iscoroutinefunction(handler_callback): async def handler_callback_routine(cb): try: await cb(data) except Exception as e: if old_exception: old_exception(e) else: self.exception(e) asyncio.ensure_future( handler_callback_routine(handler_callback), loop=self.loop, ) else: try: handler_callback(data) except Exception as e: if old_exception: old_exception(e) else: self.exception(e) if disposable: # If the handler is disposable, we will go ahead and # remove it now instead of waiting for it to be # processed in the queue. try: self.__event_handlers[name].remove(handler) except ValueError: pass
def event(self, name, data={}): """Manually trigger a custom event. :param name: The name of the event to trigger. :param data: Data that will be passed to each event handler. Defaults to an empty dictionary, but is usually a stanza object. """ log.debug("Event triggered: %s", name) handlers = self.__event_handlers.get(name, []) for handler in handlers: handler_callback, disposable = handler old_exception = getattr(data, 'exception', None) # If the callback is a coroutine, schedule it instead of # running it directly if asyncio.iscoroutinefunction(handler_callback): async def handler_callback_routine(cb): try: await cb(data) except Exception as e: if old_exception: old_exception(e) else: self.exception(e) asyncio.ensure_future( handler_callback_routine(handler_callback), loop=self.loop, ) else: try: handler_callback(data) except Exception as e: if old_exception: old_exception(e) else: self.exception(e) if disposable: # If the handler is disposable, we will go ahead and # remove it now instead of waiting for it to be # processed in the queue. try: self.__event_handlers[name].remove(handler) except ValueError: pass
def wrap(self, coroutine: Coroutine[Any, Any, Any]) -> Future: """Make a Future out of a coroutine with the current loop. :param coroutine: The coroutine to wrap. """ return asyncio.ensure_future( coroutine, loop=self.loop, )
def disconnect(self, wait: Union[float, int] = 2.0, reason: Optional[str] = None, ignore_send_queue: bool = False) -> Future: """Close the XML stream and wait for an acknowldgement from the server for at most `wait` seconds. After the given number of seconds has passed without a response from the server, or when the server successfully responds with a closure of its own stream, abort() is called. If wait is 0.0, this will call abort() directly without closing the stream. Does nothing but trigger the disconnected event if we are not connected. :param wait: Time to wait for a response from the server. :param reason: An optional reason for the disconnect. :param ignore_send_queue: Boolean to toggle if we want to ignore the in-flight stanzas and disconnect immediately. :return: A future that ends when all code involved in the disconnect has ended """ # Compat: docs/getting_started/sendlogout.rst has been promoting # `disconnect(wait=True)` for ages. This doesn't mean anything to the # schedule call below. It would fortunately be converted to `1` later # down the call chain. Praise the implicit casts lord. if wait == True: wait = 2.0 if self.transport: self.disconnect_reason = reason if self.waiting_queue.empty() or ignore_send_queue: self.cancel_connection_attempt() return asyncio.ensure_future( self._end_stream_wait(wait, reason=reason), loop=self.loop, ) else: return asyncio.ensure_future( self._consume_send_queue_before_disconnecting(reason, wait), loop=self.loop, ) else: self._set_disconnected_future() self.event("disconnected", reason) future = Future() future.set_result(None) return future
async def _connect_routine(self): self.event_when_connected = "connected" if self.connect_loop_wait > 0: self.event('reconnect_delay', self.connect_loop_wait) await asyncio.sleep(self.connect_loop_wait, loop=self.loop) record = await self.pick_dns_answer(self.default_domain) if record is not None: host, address, dns_port = record port = dns_port if dns_port else self.address[1] self.address = (address, port) self._service_name = host else: # No DNS records left, stop iterating # and try (host, port) as a last resort self.dns_answers = None if self.use_ssl: ssl_context = self.get_ssl_context() else: ssl_context = None if self._current_connection_attempt is None: return try: await self.loop.create_connection( lambda: self, self.address[0], self.address[1], ssl=ssl_context, server_hostname=self.default_domain if self.use_ssl else None) self.connect_loop_wait = 0 except Socket.gaierror as e: self.event('connection_failed', 'No DNS record available for %s' % self.default_domain) except OSError as e: log.debug('Connection failed: %s', e) self.event("connection_failed", e) if self._current_connection_attempt is None: return self.connect_loop_wait = self.connect_loop_wait * 2 + 1 self._current_connection_attempt = asyncio.ensure_future( self._connect_routine(), loop=self.loop, )
def connect(self, host='', port=0, use_ssl=False, force_starttls=True, disable_starttls=False): """Create a new socket and connect to the server. :param host: The name of the desired server for the connection. :param port: Port to connect to on the server. :param use_ssl: Flag indicating if SSL should be used by connecting directly to a port using SSL. If it is False, the connection will be upgraded to SSL/TLS later, using STARTTLS. Only use this value for old servers that have specific port for SSL/TLS TODO fix the comment :param force_starttls: If True, the connection will be aborted if the server does not initiate a STARTTLS negotiation. If None, the connection will be upgraded to TLS only if the server initiate the STARTTLS negotiation, otherwise it will connect in clear. If False it will never upgrade to TLS, even if the server provides it. Use this for example if you’re on localhost """ self.disconnect_reason = None self.cancel_connection_attempt() if host and port: self.address = (host, int(port)) try: Socket.inet_aton(self.address[0]) except (Socket.error, ssl.SSLError): self.default_domain = self.address[0] # Respect previous TLS usage. if use_ssl is not None: self.use_ssl = use_ssl if force_starttls is not None: self.force_starttls = force_starttls if disable_starttls is not None: self.disable_starttls = disable_starttls self.event("connecting") self._current_connection_attempt = asyncio.ensure_future( self._connect_routine(), loop=self.loop, )
async def _connect_routine(self): self.event_when_connected = "connected" record = await self.pick_dns_answer(self.default_domain) if record is not None: host, address, dns_port = record port = dns_port if dns_port else self.address[1] self.address = (address, port) self._service_name = host else: # No DNS records left, stop iterating # and try (host, port) as a last resort self.dns_answers = None if self.use_ssl: ssl_context = self.get_ssl_context() else: ssl_context = None await asyncio.sleep(self.connect_loop_wait, loop=self.loop) if self._current_connection_attempt is None: return try: await self.loop.create_connection(lambda: self, self.address[0], self.address[1], ssl=ssl_context, server_hostname=self.default_domain if self.use_ssl else None) self.connect_loop_wait = 0 except Socket.gaierror as e: self.event('connection_failed', 'No DNS record available for %s' % self.default_domain) except OSError as e: log.debug('Connection failed: %s', e) self.event("connection_failed", e) if self._current_connection_attempt is None: return self.connect_loop_wait = self.connect_loop_wait * 2 + 1 self._current_connection_attempt = asyncio.ensure_future( self._connect_routine(), loop=self.loop, )