source: wokkel/subprotocols.py @ 4:e8e7d5543a6f

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

Reverse how handlers are associated with the stream manager. Kick version.

File size: 8.7 KB
Line 
1# -*- test-case-name: wokkel.test.test_subprotocols -*-
2#
3# Copyright (c) 2001-2007 Twisted Matrix Laboratories.
4# See LICENSE for details.
5
6"""
7XMPP subprotocol support.
8"""
9
10from zope.interface import implements
11
12from twisted.internet import defer
13from twisted.python import log
14from twisted.words.protocols.jabber import error, xmlstream
15from twisted.words.xish import xpath
16from twisted.words.xish.domish import IElement
17
18from wokkel.iwokkel import IXMPPHandler, IXMPPHandlerCollection
19
20class XMPPHandler(object):
21    implements(IXMPPHandler)
22
23    def setHandlerParent(self, parent):
24        self.parent = parent
25        self.parent.addHandler(self)
26
27    def disownHandlerParent(self, parent):
28        self.parent.removeHandler(self)
29        self.parent = None
30
31    def makeConnection(self, xs):
32        self.xmlstream = xs
33        self.connectionMade()
34
35    def connectionMade(self):
36        pass
37
38    def connectionInitialized(self):
39        pass
40
41    def connectionLost(self, reason):
42        self.xmlstream = None
43
44    def send(self, obj):
45        """
46        Send data over the managed XML stream.
47
48        @note: The stream manager maintains a queue for data sent using this
49               method when there is no current initialized XML stream. This
50               data is then sent as soon as a new stream has been established
51               and initialized. Subsequently, L{connectionInitialized} will be
52               called again. If this queueing is not desired, use C{send} on
53               C{self.xmlstream}.
54
55        @param obj: data to be sent over the XML stream. This is usually an
56                    object providing L{domish.IElement}, or serialized XML. See
57                    L{xmlstream.XmlStream} for details.
58        """
59        self.parent.send(obj)
60
61
62class XMPPHandlerCollection(object):
63    """
64    Collection of XMPP subprotocol handlers.
65
66    This allows for grouping of subprotocol handlers, but is not an
67    L{XMPPHandler} itself, so this is not recursive.
68
69    @ivar xmlstream: Currently managed XML stream.
70    @type xmlstream: L{XmlStream}
71    @ivar handlers: List of protocol handlers.
72    @type handlers: L{list} of objects providing
73                      L{IXMPPHandler}
74    """
75
76    implements(IXMPPHandlerCollection)
77
78    def __init__(self):
79        self.handlers = []
80        self.xmlstream = None
81        self._initialized = False
82
83    def __iter__(self):
84        """
85        Act as a container for handlers.
86        """
87        return iter(self.handlers)
88
89    def addHandler(self, handler):
90        """
91        Add protocol handler.
92
93        Protocol handlers are expected to provide L{IXMPPHandler}.
94
95        When an XML stream has already been established, the handler's
96        C{connectionInitialized} will be called to get it up to speed.
97        """
98
99        self.handlers.append(handler)
100
101        # get protocol handler up to speed when a connection has already
102        # been established
103        if self.xmlstream and self._initialized:
104            handler.makeConnection(self.xmlstream)
105            handler.connectionInitialized()
106
107    def removeHandler(self, handler):
108        """
109        Remove protocol handler.
110        """
111
112        self.handlers.remove(handler)
113
114class StreamManager(XMPPHandlerCollection):
115    """
116    Business logic representing a managed XMPP connection.
117
118    This maintains a single XMPP connection and provides facilities for packet
119    routing and transmission. Business logic modules are objects providing
120    L{IXMPPHandler} (like subclasses of L{XMPPHandler}), and added
121    using L{addHandler}.
122
123    @ivar logTraffic: if true, log all traffic.
124    @type logTraffic: L{bool}
125    @ivar _packetQueue: internal buffer of unsent data. See L{send} for details.
126    @type _packetQueue: L{list}
127    """
128
129    logTraffic = False
130
131    def __init__(self, factory):
132        self.handlers = []
133        self.xmlstream = None
134        self._packetQueue = []
135        self._initialized = False
136
137        factory.addBootstrap(xmlstream.STREAM_CONNECTED_EVENT, self._connected)
138        factory.addBootstrap(xmlstream.STREAM_AUTHD_EVENT, self._authd)
139        factory.addBootstrap(xmlstream.INIT_FAILED_EVENT,
140                             self.initializationFailed)
141        factory.addBootstrap(xmlstream.STREAM_END_EVENT, self._disconnected)
142        self.factory = factory
143
144    def _connected(self, xs):
145        def logDataIn(buf):
146            log.msg("RECV: %r" % buf)
147
148        def logDataOut(buf):
149            log.msg("SEND: %r" % buf)
150
151        if self.logTraffic:
152            xs.rawDataInFn = logDataIn
153            xs.rawDataOutFn = logDataOut
154
155        self.xmlstream = xs
156
157        for e in self:
158            e.makeConnection(xs)
159
160    def _authd(self, xs):
161        # Flush all pending packets
162        for p in self._packetQueue:
163            xs.send(p)
164        self._packetQueue = []
165        self._initialized = True
166
167        # Notify all child services which implement
168        # the IService interface
169        for e in self:
170            e.connectionInitialized()
171
172    def initializationFailed(self, reason):
173        """
174        Called when stream initialization has failed.
175
176        Stream initialization has halted, with the reason indicated by
177        C{reason}. It may be retried by calling the authenticator's
178        C{initializeStream}. See the respective authenticators for details.
179
180        @param reason: A failure instance indicating why stream initialization
181                       failed.
182        @type reason: L{failure.Failure}
183        """
184
185    def _disconnected(self, _):
186        self.xmlstream = None
187        self._initialized = False
188
189        # Notify all child services which implement
190        # the IService interface
191        for e in self:
192            e.xmlstream = None
193            e.connectionLost(None)
194
195    def send(self, obj):
196        """
197        Send data over the XML stream.
198
199        When there is no established XML stream, the data is queued and sent
200        out when a new XML stream has been established and initialized.
201
202        @param obj: data to be sent over the XML stream. See
203                    L{xmlstream.XmlStream.send} for details.
204        """
205
206        if self._initialized:
207            self.xmlstream.send(obj)
208        else:
209            self._packetQueue.append(obj)
210
211
212class IQHandlerMixin(object):
213    """
214    XMPP subprotocol mixin for handle incoming IQ stanzas.
215
216    This matches up the iq with XPath queries to call methods on itself,
217    wrapping the call so that exceptions result in proper error responses,
218    or, when succesful will reply with a response with optional payload.
219
220    Derivatives of this class must provide an
221    L{XmlStream<twisted.words.protocols.jabber.xmlstream.XmlStream>} instance
222    in its C{xmlstream} attribute.
223
224    The optional payload is taken from the result of the handler and is
225    expected to be a child or a list of childs.
226
227    If an exception is raised, or the deferred has its errback called,
228    the exception is checked for being a L{error.StanzaError}. If so,
229    an error response is sent. Any other exception will cause a error
230    response of C{internal-server-error} to be sent.
231
232    @cvar iqHandlers: Mapping from XPath queries (as a string) to the method
233                      name that will handle requests that match the query.
234    @type iqHandlers: L{dict}
235    """
236
237    iqHandlers = None
238
239    def handleRequest(self, iq):
240        """
241        Find a handler and wrap the call for sending a response stanza.
242        """
243        def toResult(result, iq):
244            response = xmlstream.toResponse(iq, 'result')
245
246            if result:
247                if IElement.providedBy(result):
248                    response.addChild(result)
249                else:
250                    for element in result:
251                        response.addChild(element)
252
253            return response
254
255        def checkNotImplemented(failure):
256            failure.trap(NotImplementedError)
257            raise error.StanzaError('feature-not-implemented')
258
259        def fromStanzaError(failure, iq):
260            e = failure.trap(error.StanzaError)
261            return failure.value.toResponse(iq)
262
263        def fromOtherError(failure, iq):
264            log.msg("Unhandled error in iq handler:", isError=True)
265            log.err(failure)
266            return error.StanzaError('internal-server-error').toResponse(iq)
267
268        handler = None
269        for queryString, method in self.iqHandlers.iteritems():
270            if xpath.internQuery(queryString).matches(iq):
271                handler = getattr(self, method)
272
273        if handler:
274            d = defer.maybeDeferred(handler, iq)
275        else:
276            d = defer.fail(NotImplementedError())
277
278        d.addCallback(toResult, iq)
279        d.addErrback(checkNotImplemented)
280        d.addErrback(fromStanzaError, iq)
281        d.addErrback(fromOtherError, iq)
282
283        d.addCallback(self.send)
284
285        iq.handled = True
Note: See TracBrowser for help on using the repository browser.