Make Iq exceptions more discoverable and simpler to use.

IqError and IqTimeout now extend XMPPError, so if you don't care
about the difference, you can use:

    try:
        self.do_something_with_iqs()
    except XMPPError:
        # Error? Timeout? I don't care!
        pass

If you do need to distinguish between timeouts and error replies,
you can still continue to use:

    try:
        self.do_somethin_with_iqs()
    except IqError as err:
        pass
    except IqTimeout:
        pass

If you don't catch any Iq errors and you're processing a stanza
then an error response will be sent, just like normal if you raise
XMPPError or any other exception, except that the error messages
will be generic to prevent leaking too much information.
This commit is contained in:
Lance Stout 2011-08-19 00:08:47 -07:00
parent b98555c512
commit f92f96325a
3 changed files with 64 additions and 13 deletions

View file

@ -13,9 +13,9 @@ import copy
import logging import logging
import sleekxmpp import sleekxmpp
from sleekxmpp import plugins from sleekxmpp import plugins, roster
from sleekxmpp.exceptions import IqError, IqTimeout
import sleekxmpp.roster as roster
from sleekxmpp.stanza import Message, Presence, Iq, Error, StreamError from sleekxmpp.stanza import Message, Presence, Iq, Error, StreamError
from sleekxmpp.stanza.roster import Roster from sleekxmpp.stanza.roster import Roster
from sleekxmpp.stanza.nick import Nick from sleekxmpp.stanza.nick import Nick
@ -743,6 +743,29 @@ class BaseXMPP(XMLStream):
self.event("changed_status", presence) self.event("changed_status", presence)
def exception(self, exception):
"""
Process any uncaught exceptions, notably IqError and
IqTimeout exceptions.
Overrides XMLStream.exception.
Arguments:
exception -- An unhandled exception object.
"""
if isinstance(exception, IqError):
iq = exception.iq
log.error('%s: %s' % (iq['error']['condition'],
iq['error']['text']))
log.warning('You should catch IqError exceptions')
elif isinstance(exception, IqTimeout):
iq = exception.iq
log.error('Request timed out: %s' % iq)
log.warning('You should catch IqTimeout exceptions')
else:
log.exception(exception)
# Restore the old, lowercased name for backwards compatibility. # Restore the old, lowercased name for backwards compatibility.
basexmpp = BaseXMPP basexmpp = BaseXMPP

View file

@ -20,9 +20,9 @@ class XMPPError(Exception):
Meant for use in SleekXMPP plugins and applications using SleekXMPP. Meant for use in SleekXMPP plugins and applications using SleekXMPP.
""" """
def __init__(self, condition='undefined-condition', text=None, etype=None, def __init__(self, condition='undefined-condition', text=None,
extension=None, extension_ns=None, extension_args=None, etype='cancel', extension=None, extension_ns=None,
clear=True): extension_args=None, clear=True):
""" """
Create a new XMPPError exception. Create a new XMPPError exception.
@ -31,8 +31,10 @@ class XMPPError(Exception):
Arguments: Arguments:
condition -- The XMPP defined error condition. condition -- The XMPP defined error condition.
Defaults to 'undefined-condition'.
text -- Human readable text describing the error. text -- Human readable text describing the error.
etype -- The XMPP error type, such as cancel or modify. etype -- The XMPP error type, such as cancel or modify.
Defaults to 'cancel'.
extension -- Tag name of the extension's XML content. extension -- Tag name of the extension's XML content.
extension_ns -- XML namespace of the extensions' XML content. extension_ns -- XML namespace of the extensions' XML content.
extension_args -- Content and attributes for the extension extension_args -- Content and attributes for the extension
@ -54,7 +56,7 @@ class XMPPError(Exception):
self.extension_args = extension_args self.extension_args = extension_args
class IqTimeout(Exception): class IqTimeout(XMPPError):
""" """
An exception which indicates that an IQ request response has not been An exception which indicates that an IQ request response has not been
@ -62,10 +64,13 @@ class IqTimeout(Exception):
""" """
def __init__(self, iq): def __init__(self, iq):
super(IqTimeout, self).__init__(
condition='remote-server-timeout',
etype='cancel')
self.iq = iq self.iq = iq
class IqError(XMPPError):
class IqError(Exception):
""" """
An exception raised when an Iq stanza of type 'error' is received An exception raised when an Iq stanza of type 'error' is received
@ -73,4 +78,9 @@ class IqError(Exception):
""" """
def __init__(self, iq): def __init__(self, iq):
super(IqError, self).__init__(
condition=iq['error']['condition'],
text=iq['error']['text'],
etype=iq['error']['type'])
self.iq = iq self.iq = iq

View file

@ -10,7 +10,7 @@ import logging
import traceback import traceback
import sys import sys
from sleekxmpp.exceptions import XMPPError from sleekxmpp.exceptions import XMPPError, IqError, IqTimeout
from sleekxmpp.stanza import Error from sleekxmpp.stanza import Error
from sleekxmpp.xmlstream import ET, StanzaBase, register_stanza_plugin from sleekxmpp.xmlstream import ET, StanzaBase, register_stanza_plugin
@ -43,23 +43,41 @@ class RootStanza(StanzaBase):
Arguments: Arguments:
e -- Exception object e -- Exception object
""" """
if isinstance(e, XMPPError): if isinstance(e, IqError):
self.reply(clear=e.clear) # We received an Iq error reply, but it wasn't caught
# locally. Using the condition/text from that error
# response could leak too much information, so we'll
# only use a generic error here.
self.reply()
self['error']['condition'] = 'undefined-condition'
self['error']['text'] = 'External error'
self['error']['type'] = 'cancel'
log.warning('You should catch IqError exceptions')
self.send()
elif isinstance(e, IqTimeout):
self.reply()
self['error']['condition'] = 'remote-server-timeout'
self['error']['type'] = 'wait'
log.warning('You should catch IqTimeout exceptions')
self.send()
elif isinstance(e, XMPPError):
# We raised this deliberately # We raised this deliberately
self.reply(clear=e.clear)
self['error']['condition'] = e.condition self['error']['condition'] = e.condition
self['error']['text'] = e.text self['error']['text'] = e.text
self['error']['type'] = e.etype
if e.extension is not None: if e.extension is not None:
# Extended error tag # Extended error tag
extxml = ET.Element("{%s}%s" % (e.extension_ns, e.extension), extxml = ET.Element("{%s}%s" % (e.extension_ns, e.extension),
e.extension_args) e.extension_args)
self['error'].append(extxml) self['error'].append(extxml)
self['error']['type'] = e.etype
self.send() self.send()
else: else:
self.reply()
# We probably didn't raise this on purpose, so send an error stanza # We probably didn't raise this on purpose, so send an error stanza
self.reply()
self['error']['condition'] = 'undefined-condition' self['error']['condition'] = 'undefined-condition'
self['error']['text'] = "SleekXMPP got into trouble." self['error']['text'] = "SleekXMPP got into trouble."
self['error']['type'] = 'cancel'
self.send() self.send()
# log the error # log the error
log.exception('Error handling {%s}%s stanza' % log.exception('Error handling {%s}%s stanza' %