Esempio n. 1
0
    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)
Esempio n. 2
0
    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,
        )
Esempio n. 3
0
    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
Esempio n. 4
0
    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
Esempio n. 5
0
    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()
Esempio n. 6
0
    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
Esempio n. 7
0
    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
Esempio n. 8
0
    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,
        )
Esempio n. 9
0
    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
Esempio n. 10
0
    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,
            )
Esempio n. 11
0
    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,
        )
Esempio n. 12
0
    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,
            )