diff --git a/examples/ping.py b/examples/ping.py index a476e1d..70066e3 100755 --- a/examples/ping.py +++ b/examples/ping.py @@ -28,15 +28,15 @@ if sys.version_info < (3, 0): class PingTest(sleekxmpp.ClientXMPP): """ - A simple SleekXMPP bot that will echo messages it - receives, along with a short thank you message. + A simple SleekXMPP bot that will send a ping request + to a given JID. """ def __init__(self, jid, password, pingjid): sleekxmpp.ClientXMPP.__init__(self, jid, password) if pingjid is None: pingjid = self.jid - self.pingjid = pingjid + self.pingjid = pingjid # The session_start event will be triggered when # the bot establishes its connection with the server @@ -59,14 +59,16 @@ class PingTest(sleekxmpp.ClientXMPP): data. """ self.sendPresence() - result = self.plugin['xep_0199'].sendPing(self.pingjid, timeout=10, errorfalse=True) + result = self['xep_0199'].send_ping(self.pingjid, + timeout=10, + errorfalse=True) logging.info("Pinging...") if result is False: logging.info("Couldn't ping.") self.disconnect() sys.exit(1) else: - logging.info("Success!") + logging.info("Success! RTT: %s" % str(result)) self.disconnect() @@ -85,7 +87,8 @@ if __name__ == '__main__': action='store_const', dest='loglevel', const=5, default=logging.INFO) optp.add_option('-t', '--pingto', help='set jid to ping', - action='store', type='string', dest='pingjid', default=None) + action='store', type='string', dest='pingjid', + default=None) # JID and password options. optp.add_option("-j", "--jid", dest="jid", @@ -107,10 +110,10 @@ if __name__ == '__main__': # have interdependencies, the order in which you register them does # not matter. xmpp = PingTest(opts.jid, opts.password, opts.pingjid) - xmpp.registerPlugin('xep_0030') # Service Discovery - xmpp.registerPlugin('xep_0004') # Data Forms - xmpp.registerPlugin('xep_0060') # PubSub - xmpp.registerPlugin('xep_0199') # XMPP Ping + 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 # If you are working with an OpenFire server, you may need # to adjust the SSL version used: diff --git a/setup.py b/setup.py index d6d8d6d..ae8cf68 100644 --- a/setup.py +++ b/setup.py @@ -51,6 +51,7 @@ packages = [ 'sleekxmpp', 'sleekxmpp/plugins/xep_0030/stanza', 'sleekxmpp/plugins/xep_0059', 'sleekxmpp/plugins/xep_0092', + 'sleekxmpp/plugins/xep_0199', ] if sys.version_info < (3, 0): diff --git a/sleekxmpp/plugins/xep_0199.py b/sleekxmpp/plugins/xep_0199.py deleted file mode 100644 index e7ec5c4..0000000 --- a/sleekxmpp/plugins/xep_0199.py +++ /dev/null @@ -1,63 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" -from xml.etree import cElementTree as ET -from . import base -import time -import logging - - -log = logging.getLogger(__name__) - - -class xep_0199(base.base_plugin): - """XEP-0199 XMPP Ping""" - - def plugin_init(self): - self.description = "XMPP Ping" - self.xep = "0199" - self.xmpp.add_handler("" % self.xmpp.default_ns, self.handler_ping, name='XMPP Ping') - if self.config.get('keepalive', True): - self.xmpp.add_event_handler('session_start', self.handler_pingserver, threaded=True) - - def post_init(self): - base.base_plugin.post_init(self) - self.xmpp.plugin['xep_0030'].add_feature('urn:xmpp:ping') - - def handler_pingserver(self, xml): - self.xmpp.schedule("xep-0119 ping", float(self.config.get('frequency', 300)), self.scheduled_ping, repeat=True) - - def scheduled_ping(self): - log.debug("pinging...") - if self.sendPing(self.xmpp.boundjid.host, self.config.get('timeout', 30)) is False: - log.debug("Did not recieve ping back in time. Requesting Reconnect.") - self.xmpp.reconnect() - - def handler_ping(self, xml): - iq = self.xmpp.makeIqResult(xml.get('id', 'unknown')) - iq.attrib['to'] = xml.get('from', self.xmpp.boundjid.domain) - self.xmpp.send(iq) - - def sendPing(self, jid, timeout = 30, errorfalse=False): - """ sendPing(jid, timeout) - Sends a ping to the specified jid, returning the time (in seconds) - to receive a reply, or None if no reply is received in timeout seconds. - """ - id = self.xmpp.getNewId() - iq = self.xmpp.makeIq(id) - iq.attrib['type'] = 'get' - iq.attrib['to'] = jid - ping = ET.Element('{urn:xmpp:ping}ping') - iq.append(ping) - startTime = time.clock() - #pingresult = self.xmpp.send(iq, self.xmpp.makeIq(id), timeout) - pingresult = iq.send() - endTime = time.clock() - if pingresult == False or (errorfalse and pingresult['type'] == 'error'): - #self.xmpp.disconnect(reconnect=True) - return False - return endTime - startTime diff --git a/sleekxmpp/plugins/xep_0199/__init__.py b/sleekxmpp/plugins/xep_0199/__init__.py new file mode 100644 index 0000000..3444fe9 --- /dev/null +++ b/sleekxmpp/plugins/xep_0199/__init__.py @@ -0,0 +1,10 @@ +""" + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2010 Nathanael C. Fritz + This file is part of SleekXMPP. + + See the file LICENSE for copying permission. +""" + +from sleekxmpp.plugins.xep_0199.stanza import Ping +from sleekxmpp.plugins.xep_0199.ping import xep_0199 diff --git a/sleekxmpp/plugins/xep_0199/ping.py b/sleekxmpp/plugins/xep_0199/ping.py new file mode 100644 index 0000000..cde2f82 --- /dev/null +++ b/sleekxmpp/plugins/xep_0199/ping.py @@ -0,0 +1,162 @@ +""" + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2010 Nathanael C. Fritz + This file is part of SleekXMPP. + + See the file LICENSE for copying permission. +""" + +import time +import logging + +import sleekxmpp +from sleekxmpp import Iq +from sleekxmpp.xmlstream import register_stanza_plugin +from sleekxmpp.xmlstream.matcher import StanzaPath +from sleekxmpp.xmlstream.handler import Callback +from sleekxmpp.plugins.base import base_plugin +from sleekxmpp.plugins.xep_0199 import stanza, Ping + + +log = logging.getLogger(__name__) + + +class xep_0199(base_plugin): + + """ + XEP-0199: XMPP Ping + + Given that XMPP is based on TCP connections, it is possible for the + underlying connection to be terminated without the application's + awareness. Ping stanzas provide an alternative to whitespace based + keepalive methods for detecting lost connections. + + Also see . + + Attributes: + keepalive -- If True, periodically send ping requests + to the server. If a ping is not answered, + the connection will be reset. + frequency -- Time in seconds between keepalive pings. + Defaults to 300 seconds. + timeout -- Time in seconds to wait for a ping response. + Defaults to 30 seconds. + Methods: + send_ping -- Send a ping to a given JID, returning the + round trip time. + """ + + def plugin_init(self): + """ + Start the XEP-0199 plugin. + """ + self.description = 'XMPP Ping' + self.xep = '0199' + self.stanza = stanza + + # Backwards compatibility for names + self.sendPing = self.send_ping + + self.keepalive = self.config.get('keepalive', True) + self.frequency = float(self.config.get('frequency', 300)) + self.timeout = self.config.get('timeout', 30) + + register_stanza_plugin(Iq, Ping) + + self.xmpp.register_handler( + Callback('Ping', + StanzaPath('iq@type=get/ping'), + self._handle_ping)) + + if self.keepalive: + self.xmpp.add_event_handler('session_start', + self._handle_keepalive, + threaded=True) + + def post_init(self): + """Handle cross-plugin dependencies.""" + base_plugin.post_init(self) + self.xmpp['xep_0030'].add_feature(Ping.namespace) + + def _handle_keepalive(self, event): + """ + Begin periodic pinging of the server. If a ping is not + answered, the connection will be restarted. + + The pinging interval can be adjused using self.frequency + before beginning processing. + + Arguments: + event -- The session_start event. + """ + def scheduled_ping(): + """Send ping request to the server.""" + log.debug("Pinging...") + resp = self.send_ping(self.xmpp.boundjid.host, self.timeout) + if not resp: + log.debug("Did not recieve ping back in time." + \ + "Requesting Reconnect.") + self.xmpp.reconnect() + + self.xmpp.schedule('Ping Keep Alive', + self.frequency, + scheduled_ping, + repeat=True) + + def _handle_ping(self, iq): + """ + Automatically reply to ping requests. + + Arguments: + iq -- The ping request. + """ + log.debug("Pinged by %s" % iq['from']) + iq.reply().enable('ping').send() + + def send_ping(self, jid, timeout=None, errorfalse=False, + ifrom=None, block=True, callback=None): + """ + Send a ping request and calculate the response time. + + Arguments: + jid -- The JID that will receive the ping. + timeout -- Time in seconds to wait for a response. + Defaults to self.timeout. + errorfalse -- Indicates if False should be returned + if an error stanza is received. Defaults + to False. + ifrom -- Specifiy the sender JID. + block -- Indicate if execution should block until + a pong response is received. Defaults + to True. + callback -- Optional handler to execute when a pong + is received. Useful in conjunction with + the option block=False. + """ + log.debug("Pinging %s" % jid) + if timeout is None: + timeout = self.timeout + + iq = self.xmpp.Iq() + iq['type'] = 'get' + iq['to'] = jid + if ifrom: + iq['from'] = ifrom + iq.enable('ping') + + start_time = time.clock() + resp = iq.send(block=block, + timeout=timeout, + callback=callback) + end_time = time.clock() + + delay = end_time - start_time + + if not block: + return None + + if not resp or resp['type'] == 'error': + return False + + log.debug("Pong: %s %f" % (jid, delay)) + return delay diff --git a/sleekxmpp/plugins/xep_0199/stanza.py b/sleekxmpp/plugins/xep_0199/stanza.py new file mode 100644 index 0000000..6586a76 --- /dev/null +++ b/sleekxmpp/plugins/xep_0199/stanza.py @@ -0,0 +1,36 @@ +""" + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2010 Nathanael C. Fritz + This file is part of SleekXMPP. + + See the file LICENSE for copying permission. +""" + +import sleekxmpp +from sleekxmpp.xmlstream import ElementBase + + +class Ping(ElementBase): + + """ + Given that XMPP is based on TCP connections, it is possible for the + underlying connection to be terminated without the application's + awareness. Ping stanzas provide an alternative to whitespace based + keepalive methods for detecting lost connections. + + Example ping stanza: + + + + + Stanza Interface: + None + + Methods: + None + """ + + name = 'ping' + namespace = 'urn:xmpp:ping' + plugin_attrib = 'ping' + interfaces = set()