diff --git a/docs/getting_started/index.rst b/docs/getting_started/index.rst index 97c34a22..cbad1be8 100644 --- a/docs/getting_started/index.rst +++ b/docs/getting_started/index.rst @@ -9,7 +9,5 @@ Getting Started (with examples) component presence muc - proxy scheduler iq - diff --git a/docs/getting_started/proxy.rst b/docs/getting_started/proxy.rst deleted file mode 100644 index 22439d4e..00000000 --- a/docs/getting_started/proxy.rst +++ /dev/null @@ -1,40 +0,0 @@ -.. _proxy: - -========================= -Enable HTTP Proxy Support -========================= - -.. note:: - - If you have any issues working through this quickstart guide - join the chat room at `slixmpp@muc.poez.io - `_. - -In some instances, you may wish to route XMPP traffic through -an HTTP proxy, probably to get around restrictive firewalls. -Slixmpp provides support for basic HTTP proxying with DIGEST -authentication. - -Enabling proxy support is done in two steps. The first is to instruct Slixmpp -to use a proxy, and the second is to configure the proxy details: - -.. code-block:: python - - xmpp = ClientXMPP(...) - xmpp.use_proxy = True - xmpp.proxy_config = { - 'host': 'proxy.example.com', - 'port': 5555, - 'username': 'example_user', - 'password': '******' - } - -The ``'username'`` and ``'password'`` fields are optional if the proxy does not -require authentication. - - -The Final Product ------------------ - -.. include:: ../../examples/proxy_echo_client.py - :literal: diff --git a/examples/proxy_echo_client.py b/examples/proxy_echo_client.py deleted file mode 100755 index 566f5957..00000000 --- a/examples/proxy_echo_client.py +++ /dev/null @@ -1,135 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -""" - Slixmpp: The Slick XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of Slixmpp. - - See the file LICENSE for copying permission. -""" - -import logging -from getpass import getpass -from argparse import ArgumentParser - -import slixmpp - - -class EchoBot(slixmpp.ClientXMPP): - - """ - A simple Slixmpp bot that will echo messages it - receives, along with a short thank you message. - """ - - def __init__(self, jid, password): - slixmpp.ClientXMPP.__init__(self, jid, password) - - # The session_start event will be triggered when - # the bot establishes its connection with the server - # and the XML streams are ready for use. We want to - # listen for this event so that we we can initialize - # our roster. - self.add_event_handler("session_start", self.start) - - # The message event is triggered whenever a message - # stanza is received. Be aware that that includes - # MUC messages and error messages. - self.add_event_handler("message", self.message) - - async def start(self, event): - """ - Process the session_start event. - - Typical actions for the session_start event are - requesting the roster and broadcasting an initial - presence stanza. - - Arguments: - event -- An empty dictionary. The session_start - event does not provide any additional - data. - """ - self.send_presence() - await self.get_roster() - - def message(self, msg): - """ - Process incoming message stanzas. Be aware that this also - includes MUC messages and error messages. It is usually - a good idea to check the messages's type before processing - or sending replies. - - Arguments: - msg -- The received message stanza. See the documentation - for stanza objects and the Message stanza to see - how it may be used. - """ - msg.reply("Thanks for sending\n%(body)s" % msg).send() - - -if __name__ == '__main__': - # Setup the command line arguments. - parser = ArgumentParser() - - # Output verbosity options. - parser.add_argument("-q", "--quiet", help="set logging to ERROR", - action="store_const", dest="loglevel", - const=logging.ERROR, default=logging.INFO) - parser.add_argument("-d", "--debug", help="set logging to DEBUG", - action="store_const", dest="loglevel", - const=logging.DEBUG, default=logging.INFO) - - # JID and password options. - parser.add_argument("-j", "--jid", dest="jid", - help="JID to use") - parser.add_argument("-p", "--password", dest="password", - help="password to use") - parser.add_argument("--phost", dest="proxy_host", - help="Proxy hostname") - parser.add_argument("--pport", dest="proxy_port", - help="Proxy port") - parser.add_argument("--puser", dest="proxy_user", - help="Proxy username") - parser.add_argument("--ppass", dest="proxy_pass", - help="Proxy password") - - args = parser.parse_args() - - # Setup logging. - logging.basicConfig(level=args.loglevel, - format='%(levelname)-8s %(message)s') - - if args.jid is None: - args.jid = input("Username: ") - if args.password is None: - args.password = getpass("Password: ") - if args.proxy_host is None: - args.proxy_host = input("Proxy host: ") - if args.proxy_port is None: - args.proxy_port = input("Proxy port: ") - if args.proxy_user is None: - args.proxy_user = input("Proxy username: ") - if args.proxy_pass is None and args.proxy_user: - args.proxy_pass = getpass("Proxy password: ") - - # Setup the EchoBot and register plugins. Note that while plugins may - # have interdependencies, the order in which you register them does - # not matter. - xmpp = EchoBot(args.jid, args.password) - xmpp.register_plugin('xep_0030') # Service Discovery - xmpp.register_plugin('xep_0004') # Data Forms - xmpp.register_plugin('xep_0060') # PubSub - xmpp.register_plugin('xep_0199') # XMPP Ping - - xmpp.use_proxy = True - xmpp.proxy_config = { - 'host': args.proxy_host, - 'port': int(args.proxy_port), - 'username': args.proxy_user, - 'password': args.proxy_pass} - - # Connect to the XMPP server and start processing XMPP stanzas. - xmpp.connect() - xmpp.process() diff --git a/slixmpp/xmlstream/matcher/xpath.py b/slixmpp/xmlstream/matcher/xpath.py index 31ab1b8c..0edef5a9 100644 --- a/slixmpp/xmlstream/matcher/xpath.py +++ b/slixmpp/xmlstream/matcher/xpath.py @@ -19,15 +19,6 @@ class MatchXPath(MatcherBase): The XPath matcher selects stanzas whose XML contents matches a given XPath expression. - .. warning:: - - Using this matcher may not produce expected behavior when using - attribute selectors. For Python 2.6 and 3.1, the ElementTree - :meth:`~xml.etree.ElementTree.Element.find()` method does - not support the use of attribute selectors. If you need to - support Python 2.6 or 3.1, it might be more useful to use a - :class:`~slixmpp.xmlstream.matcher.stanzapath.StanzaPath` matcher. - If the value of :data:`IGNORE_NS` is set to ``True``, then XPath expressions will be matched without using namespaces. """ @@ -42,12 +33,6 @@ class MatchXPath(MatcherBase): If the value of :data:`IGNORE_NS` is set to ``True``, then XPath expressions will be matched without using namespaces. - .. warning:: - - In Python 2.6 and 3.1 the ElementTree - :meth:`~xml.etree.ElementTree.Element.find()` method does not - support attribute selectors in the XPath expression. - :param xml: The :class:`~slixmpp.xmlstream.stanzabase.ElementBase` stanza to compare against. """ diff --git a/slixmpp/xmlstream/xmlstream.py b/slixmpp/xmlstream/xmlstream.py index b80c55d3..e6af3faa 100644 --- a/slixmpp/xmlstream/xmlstream.py +++ b/slixmpp/xmlstream/xmlstream.py @@ -16,10 +16,12 @@ from typing import ( Any, Callable, Iterable, + Iterator, List, Optional, Set, Union, + Tuple, ) import functools @@ -88,7 +90,9 @@ class XMLStream(asyncio.BaseProtocol): # The socket that is used internally by the transport object self.socket = None - self.connect_loop_wait = 0 + # The backoff of the connect routine (increases exponentially + # after each failure) + self._connect_loop_wait = 0 self.parser = None self.xml_depth = 0 @@ -157,10 +161,6 @@ class XMLStream(asyncio.BaseProtocol): #: non-SSL traffic and another for SSL traffic. self.use_ssl = False - #: If set to ``True``, attempt to connect through an HTTP - #: proxy based on the settings in :attr:`proxy_config`. - self.use_proxy = False - #: If set to ``True``, attempt to use IPv6. self.use_ipv6 = True @@ -173,13 +173,6 @@ class XMLStream(asyncio.BaseProtocol): #: to ``False``. self.use_cdata = False - #: An optional dictionary of proxy settings. It may provide: - #: :host: The host offering proxy services. - #: :port: The port for the proxy service. - #: :username: Optional username for accessing the proxy. - #: :password: Optional password for accessing the proxy. - self.proxy_config = {} - #: The default namespace of the stream content, not of the #: stream wrapper itself. self.default_ns = '' @@ -221,7 +214,7 @@ class XMLStream(asyncio.BaseProtocol): self._current_connection_attempt = None #: A list of DNS results that have not yet been tried. - self.dns_answers = None + self._dns_answers: Optional[Iterator[Tuple[str, str, int]]] = None #: The service name to check with DNS SRV records. For #: example, setting this to ``'xmpp-client'`` would query the @@ -295,7 +288,7 @@ class XMLStream(asyncio.BaseProtocol): self.disconnect_reason = None self.cancel_connection_attempt() - self.connect_loop_wait = 0 + self._connect_loop_wait = 0 if host and port: self.address = (host, int(port)) try: @@ -320,11 +313,11 @@ class XMLStream(asyncio.BaseProtocol): 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) + 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) + 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] @@ -333,7 +326,7 @@ class XMLStream(asyncio.BaseProtocol): else: # No DNS records left, stop iterating # and try (host, port) as a last resort - self.dns_answers = None + self._dns_answers = None if self.use_ssl: ssl_context = self.get_ssl_context() @@ -348,7 +341,7 @@ class XMLStream(asyncio.BaseProtocol): self.address[1], ssl=ssl_context, server_hostname=self.default_domain if self.use_ssl else None) - self.connect_loop_wait = 0 + self._connect_loop_wait = 0 except Socket.gaierror as e: self.event('connection_failed', 'No DNS record available for %s' % self.default_domain) @@ -357,7 +350,7 @@ class XMLStream(asyncio.BaseProtocol): self.event("connection_failed", e) if self._current_connection_attempt is None: return - self.connect_loop_wait = self.connect_loop_wait * 2 + 1 + self._connect_loop_wait = self._connect_loop_wait * 2 + 1 self._current_connection_attempt = asyncio.ensure_future( self._connect_routine(), loop=self.loop, @@ -401,7 +394,7 @@ class XMLStream(asyncio.BaseProtocol): self._current_connection_attempt = None self.init_parser() self.send_raw(self.stream_header) - self.dns_answers = None + self._dns_answers = None def data_received(self, data): """Called when incoming data is received on the socket. @@ -786,7 +779,7 @@ class XMLStream(asyncio.BaseProtocol): idx += 1 return False - async def get_dns_records(self, domain, port=None): + async def get_dns_records(self, domain: str, port: Optional[int] = None) -> List[Tuple[str, str, int]]: """Get the DNS records for a domain. :param domain: The domain in question. @@ -806,7 +799,7 @@ class XMLStream(asyncio.BaseProtocol): loop=self.loop) return result - async def pick_dns_answer(self, domain, port=None): + async def _pick_dns_answer(self, domain: str, port: Optional[int] = None) -> Optional[Tuple[str, str, int]]: """Pick a server and port from DNS answers. Gets DNS answers if none available. @@ -815,12 +808,12 @@ class XMLStream(asyncio.BaseProtocol): :param domain: The domain in question. :param port: If the results don't include a port, use this one. """ - if self.dns_answers is None: + if self._dns_answers is None: dns_records = await self.get_dns_records(domain, port) - self.dns_answers = iter(dns_records) + self._dns_answers = iter(dns_records) try: - return next(self.dns_answers) + return next(self._dns_answers) except StopIteration: return