source: ralphm-patches/response-tracking.patch @ 79:0752a1cca356

Last change on this file since 79:0752a1cca356 was 79:0752a1cca356, checked in by Ralph Meijer <ralphm@…>, 4 years ago

Split out stanza module and response tracking patches, fix other patches.

File size: 8.2 KB
  • wokkel/subprotocols.py

    # HG changeset patch
    # Parent  6c643f639dd4e7a188e69b9ade67a80719152505
    
    diff --git a/wokkel/subprotocols.py b/wokkel/subprotocols.py
    a b  
    2525from twisted.words.xish import xpath
    2626from twisted.words.xish.domish import IElement
    2727
     28from wokkel.stanza import Stanza
     29
    2830deprecatedModuleAttribute(
    2931        Version("Wokkel", 0, 7, 0),
    3032        "Use twisted.words.protocols.jabber.xmlstream.XMPPHandlerCollection "
     
    287289        """
    288290        Handle iq response by firing associated deferred.
    289291        """
     292        stanza = Stanza.fromElement(iq)
    290293        try:
    291             d = self._iqDeferreds[iq["id"]]
     294            d = self._iqDeferreds[(stanza.sender, stanza.stanzaID)]
    292295        except KeyError:
    293296            return
    294297
    295         del self._iqDeferreds[iq["id"]]
     298        del self._iqDeferreds[(stanza.sender, stanza.stanzaID)]
    296299        iq.handled = True
    297         if iq['type'] == 'error':
     300        if stanza.stanzaType == 'error':
    298301            d.errback(error.exceptionFromStanza(iq))
    299302        else:
    300303            d.callback(iq)
     
    358361
    359362        # Set up iq response tracking
    360363        d = defer.Deferred()
    361         self._iqDeferreds[element['id']] = d
     364        self._iqDeferreds[(request.recipient, request.stanzaID)] = d
    362365
    363366        timeout = getattr(request, 'timeout', self.timeout)
    364367
    365368        if timeout is not None:
    366369            def onTimeout():
    367                 del self._iqDeferreds[element['id']]
     370                del self._iqDeferreds[(request.recipient, request.stanzaID)]
    368371                d.errback(xmlstream.TimeoutError("IQ timed out"))
    369372
    370373            call = self._reactor.callLater(timeout, onTimeout)
     
    410413
    411414    The return value of the wrapped observer is used to set the C{handled}
    412415    attribute, so that handlers may choose to ignore processing the same
    413     stanza.
     416    stanza. This is expected to be of type C{boolean} or a deferred. In the
     417    latter case, the C{handled} attribute is set to C{True}.
    414418
    415419    If an exception is raised, or the deferred has its errback called, the
    416420    exception is checked for being a L{error.StanzaError}. If so, an error
     
    459463            result.addErrback(trapStanzaError)
    460464            result.addErrback(log.err)
    461465
    462         element.handled = result
     466        element.handled = bool(result)
    463467    return observe
    464468
    465469
  • wokkel/test/test_subprotocols.py

    diff --git a/wokkel/test/test_subprotocols.py b/wokkel/test/test_subprotocols.py
    a b  
    1414from twisted.python import failure
    1515from twisted.words.xish import domish
    1616from twisted.words.protocols.jabber import error, ijabber, xmlstream
     17from twisted.words.protocols.jabber.jid import JID
    1718
    1819from wokkel import generic, subprotocols
    1920
     
    202203        self.transport = proto_helpers.StringTransport()
    203204        self.xmlstream.transport = self.transport
    204205
    205         self.request = IQGetStanza()
     206        self.request = IQGetStanza(recipient=JID('other@example.org'),
     207                                   sender=JID('user@example.org'))
     208
    206209
    207210    def _streamStarted(self):
    208211        """
     
    562565        self._streamStarted()
    563566
    564567        self.streamManager.request(self.request)
    565         expected = u"<iq type='get' id='%s'/>" % self.request.stanzaID
     568        expected = (u"<iq to='other@example.org' from='user@example.org'"
     569                       u" id='%s' type='get'/>" % self.request.stanzaID)
    566570        self.assertEquals(expected, self.transport.value())
    567571
    568572
     
    575579        self.request.stanzaID = None
    576580        self.streamManager.request(self.request)
    577581        self.assertNotIdentical(None, self.request.stanzaID)
    578         expected = u"<iq type='get' id='%s'/>" % self.request.stanzaID
     582        expected = (u"<iq to='other@example.org' from='user@example.org'"
     583                       u" id='%s' type='get'/>" % self.request.stanzaID)
    579584        self.assertEquals(expected, self.transport.value())
    580585
    581586
     
    587592        self.streamManager.addHandler(handler)
    588593
    589594        self.streamManager.request(self.request)
    590         expected = u"<iq type='get' id='test'/>"
     595        expected = (u"<iq to='other@example.org' from='user@example.org'"
     596                       u" id='test' type='get'/>")
    591597
    592598        xs = self.xmlstream
    593599        self.assertEquals("", xs.transport.value())
     
    616622        d.addCallback(cb)
    617623
    618624        xs = self.xmlstream
    619         xs.dataReceived("<iq type='result' id='test'/>")
     625        xs.dataReceived("<iq from='other@example.org' "
     626                            "type='result' id='test'/>")
    620627        return d
    621628
    622629
     
    629636        self.assertFailure(d, error.StanzaError)
    630637
    631638        xs = self.xmlstream
    632         xs.dataReceived("<iq type='error' id='test'/>")
     639        xs.dataReceived("<iq from='other@example.org' "
     640                            "type='error' id='test'/>")
    633641        return d
    634642
    635643
     
    656664        self.assertFalse(getattr(dispatched[-1], 'handled', False))
    657665
    658666
     667    def test_requestUnrelatedSenderResponse(self):
     668        """
     669        Responses with same id, but different sender are ignored.
     670
     671        As stanza identifiers are unique per entity, iq responses must be
     672        tracked by both the stanza ID and the entity the request was sent to.
     673        If a response with the same id as the request is received, but from
     674        a different entity, it is to be ignored.
     675        """
     676        # Set up a fallback handler that checks the stanza's handled attribute.
     677        # If that is set to True, the iq tracker claims to have handled the
     678        # response.
     679        dispatched = []
     680        def cb(iq):
     681            dispatched.append(iq)
     682
     683        self._streamStarted()
     684        self.xmlstream.addObserver("/iq", cb, -1)
     685
     686        d = self.streamManager.request(self.request)
     687
     688        # Receive an untracked iq response
     689        self.xmlstream.dataReceived("<iq from='foo@example.org' "
     690                                        "type='result' id='test'/>")
     691        self.assertEquals(1, len(dispatched))
     692        self.assertFalse(getattr(dispatched[-1], 'handled', False))
     693
     694        # Receive expected response
     695        self.xmlstream.dataReceived("<iq from='other@example.org' "
     696                                        "type='result' id='test'/>")
     697
     698        return d
     699
     700
    659701    def test_requestCleanup(self):
    660702        """
    661703        Test if the deferred associated with an iq request is removed
     
    665707        self._streamStarted()
    666708        d = self.streamManager.request(self.request)
    667709        xs = self.xmlstream
    668         xs.dataReceived("<iq type='result' id='test'/>")
     710        xs.dataReceived("<iq from='other@example.org' "
     711                            "type='result' id='test'/>")
    669712        self.assertNotIn('test', self.streamManager._iqDeferreds)
    670713        return d
    671714
     
    721764        self.request.timeout = 60
    722765        d = self.streamManager.request(self.request)
    723766        self.clock.callLater(1, self.xmlstream.dataReceived,
    724                              "<iq type='result' id='test'/>")
     767                             "<iq from='other@example.org' "
     768                                 "type='result' id='test'/>")
    725769        self.clock.pump([1, 1])
    726770        self.assertFalse(self.clock.calls)
    727771        return d
     
    853897        self.assertFalse(self.output)
    854898
    855899
     900    def test_syncTruthy(self):
     901        """
     902        A truthy value results in element marked as handled.
     903        """
     904        @subprotocols.asyncObserver
     905        def observer(self, element):
     906            return 3
     907
     908        element = domish.Element((None, u'message'))
     909
     910        observer(self, element)
     911        self.assertTrue(element.handled)
     912        self.assertIsInstance(element.handled, bool)
     913
     914
     915    def test_syncNonTruthy(self):
     916        """
     917        A non-truthy value results in element marked as not handled.
     918        """
     919        @subprotocols.asyncObserver
     920        def observer(self, element):
     921            return None
     922
     923        element = domish.Element((None, u'message'))
     924
     925        observer(self, element)
     926        self.assertFalse(element.handled)
     927        self.assertIsInstance(element.handled, bool)
     928
     929
    856930    def test_syncNotImplemented(self):
    857931        """
    858932        NotImplementedError causes a service-unavailable response.
Note: See TracBrowser for help on using the repository browser.