1
# -*- test-case-name: twisted.words.test.test_xmlstream -*-
3
# Copyright (c) 2001-2008 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(self, 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}, L{unicode} and L{str}. The first two will be
129
properly serialized and/or encoded. L{str} objects must be in UTF-8
132
Note: because it is easy to make mistakes in maintaining a properly
133
encoded L{str} object, it is advised to use L{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 L{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)