1 # -*- test-case-name: twisted.words.test.test_xmlstream -*-
3 # Copyright (c) Twisted Matrix Laboratories.
4 # See LICENSE for details.
9 An XML Stream is defined as a connection over which two XML documents are
10 exchanged during the lifetime of the connection, one for each direction. The
11 unit of interaction is a direct child element of the root element (stanza).
13 The most prominent use of XML Streams is Jabber, but this module is generically
14 usable. See Twisted Words for Jabber specific protocol support.
16 Maintainer: Ralph Meijer
19 from twisted.python import failure
20 from twisted.internet import protocol
21 from twisted.words.xish import domish, utility
23 STREAM_CONNECTED_EVENT = intern("//event/stream/connected")
24 STREAM_START_EVENT = intern("//event/stream/start")
25 STREAM_END_EVENT = intern("//event/stream/end")
26 STREAM_ERROR_EVENT = intern("//event/stream/error")
28 class XmlStream(protocol.Protocol, utility.EventDispatcher):
29 """ Generic Streaming XML protocol handler.
31 This protocol handler will parse incoming data as XML and dispatch events
32 accordingly. Incoming stanzas can be handled by registering observers using
33 XPath-like expressions that are matched against each stanza. See
34 L{utility.EventDispatcher} for details.
37 utility.EventDispatcher.__init__(self)
39 self.rawDataOutFn = None
40 self.rawDataInFn = None
42 def _initializeStream(self):
43 """ Sets up XML Parser. """
44 self.stream = domish.elementStream()
45 self.stream.DocumentStartEvent = self.onDocumentStart
46 self.stream.ElementEvent = self.onElement
47 self.stream.DocumentEndEvent = self.onDocumentEnd
49 ### --------------------------------------------------------------
53 ### --------------------------------------------------------------
55 def connectionMade(self):
56 """ Called when a connection is made.
58 Sets up the XML parser and dispatches the L{STREAM_CONNECTED_EVENT}
59 event indicating the connection has been established.
61 self._initializeStream()
62 self.dispatch(self, STREAM_CONNECTED_EVENT)
64 def dataReceived(self, data):
65 """ Called whenever data is received.
67 Passes the data to the XML parser. This can result in calls to the
68 DOM handlers. If a parse error occurs, the L{STREAM_ERROR_EVENT} event
69 is called to allow for cleanup actions, followed by dropping the
74 self.rawDataInFn(data)
75 self.stream.parse(data)
76 except domish.ParserError:
77 self.dispatch(failure.Failure(), STREAM_ERROR_EVENT)
78 self.transport.loseConnection()
80 def connectionLost(self, reason):
81 """ Called when the connection is shut down.
83 Dispatches the L{STREAM_END_EVENT}.
85 self.dispatch(reason, STREAM_END_EVENT)
88 ### --------------------------------------------------------------
92 ### --------------------------------------------------------------
94 def onDocumentStart(self, rootElement):
95 """ Called whenever the start tag of a root element has been received.
97 Dispatches the L{STREAM_START_EVENT}.
99 self.dispatch(self, STREAM_START_EVENT)
101 def onElement(self, element):
102 """ Called whenever a direct child element of the root element has
105 Dispatches the received element.
107 self.dispatch(element)
109 def onDocumentEnd(self):
110 """ Called whenever the end tag of the root element has been received.
112 Closes the connection. This causes C{connectionLost} being called.
114 self.transport.loseConnection()
116 def setDispatchFn(self, fn):
117 """ Set another function to handle elements. """
118 self.stream.ElementEvent = fn
120 def resetDispatchFn(self):
121 """ Set the default function (C{onElement}) to handle elements. """
122 self.stream.ElementEvent = self.onElement
125 """ Send data over the stream.
127 Sends the given C{obj} over the connection. C{obj} may be instances of
128 L{domish.Element}, C{unicode} and C{str}. The first two will be
129 properly serialized and/or encoded. C{str} objects must be in UTF-8
132 Note: because it is easy to make mistakes in maintaining a properly
133 encoded C{str} object, it is advised to use C{unicode} objects
134 everywhere when dealing with XML Streams.
136 @param obj: Object to be sent over the stream.
137 @type obj: L{domish.Element}, L{domish} or C{str}
140 if domish.IElement.providedBy(obj):
143 if isinstance(obj, unicode):
144 obj = obj.encode('utf-8')
146 if self.rawDataOutFn:
147 self.rawDataOutFn(obj)
149 self.transport.write(obj)
153 class BootstrapMixin(object):
155 XmlStream factory mixin to install bootstrap event observers.
157 This mixin is for factories providing
158 L{IProtocolFactory<twisted.internet.interfaces.IProtocolFactory>} to make
159 sure bootstrap event observers are set up on protocols, before incoming
160 data is processed. Such protocols typically derive from
161 L{utility.EventDispatcher}, like L{XmlStream}.
163 You can set up bootstrap event observers using C{addBootstrap}. The
164 C{event} and C{fn} parameters correspond with the C{event} and
165 C{observerfn} arguments to L{utility.EventDispatcher.addObserver}.
168 @ivar bootstraps: The list of registered bootstrap event observers.
169 @type bootstrap: C{list}
176 def installBootstraps(self, dispatcher):
178 Install registered bootstrap observers.
180 @param dispatcher: Event dispatcher to add the observers to.
181 @type dispatcher: L{utility.EventDispatcher}
183 for event, fn in self.bootstraps:
184 dispatcher.addObserver(event, fn)
187 def addBootstrap(self, event, fn):
189 Add a bootstrap event handler.
191 @param event: The event to register an observer for.
192 @type event: C{str} or L{xpath.XPathQuery}
193 @param fn: The observer callable to be registered.
195 self.bootstraps.append((event, fn))
198 def removeBootstrap(self, event, fn):
200 Remove a bootstrap event handler.
202 @param event: The event the observer is registered for.
203 @type event: C{str} or L{xpath.XPathQuery}
204 @param fn: The registered observer callable.
206 self.bootstraps.remove((event, fn))
210 class XmlStreamFactoryMixin(BootstrapMixin):
212 XmlStream factory mixin that takes care of event handlers.
214 All positional and keyword arguments passed to create this factory are
215 passed on as-is to the protocol.
217 @ivar args: Positional arguments passed to the protocol upon instantiation.
218 @type args: C{tuple}.
219 @ivar kwargs: Keyword arguments passed to the protocol upon instantiation.
220 @type kwargs: C{dict}.
223 def __init__(self, *args, **kwargs):
224 BootstrapMixin.__init__(self)
229 def buildProtocol(self, addr):
231 Create an instance of XmlStream.
233 The returned instance will have bootstrap event observers registered
234 and will proceed to handle input on an incoming connection.
236 xs = self.protocol(*self.args, **self.kwargs)
238 self.installBootstraps(xs)
243 class XmlStreamFactory(XmlStreamFactoryMixin,
244 protocol.ReconnectingClientFactory):
246 Factory for XmlStream protocol objects as a reconnection client.
251 def buildProtocol(self, addr):
253 Create a protocol instance.
255 Overrides L{XmlStreamFactoryMixin.buildProtocol} to work with
256 a L{ReconnectingClientFactory}. As this is called upon having an
257 connection established, we are resetting the delay for reconnection
258 attempts when the connection is lost again.
261 return XmlStreamFactoryMixin.buildProtocol(self, addr)