OLD | NEW |
| (Empty) |
1 # -*- test-case-name: twisted.words.test.test_xmlstream -*- | |
2 # | |
3 # Copyright (c) 2001-2007 Twisted Matrix Laboratories. | |
4 # See LICENSE for details. | |
5 | |
6 """ | |
7 XML Stream processing. | |
8 | |
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). | |
12 | |
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. | |
15 | |
16 Maintainer: U{Ralph Meijer<mailto:twisted@ralphm.ik.nu>} | |
17 """ | |
18 | |
19 from twisted.internet import protocol | |
20 from twisted.words.xish import domish, utility | |
21 | |
22 STREAM_CONNECTED_EVENT = intern("//event/stream/connected") | |
23 STREAM_START_EVENT = intern("//event/stream/start") | |
24 STREAM_END_EVENT = intern("//event/stream/end") | |
25 STREAM_ERROR_EVENT = intern("//event/stream/error") | |
26 | |
27 class XmlStream(protocol.Protocol, utility.EventDispatcher): | |
28 """ Generic Streaming XML protocol handler. | |
29 | |
30 This protocol handler will parse incoming data as XML and dispatch events | |
31 accordingly. Incoming stanzas can be handled by registering observers using | |
32 XPath-like expressions that are matched against each stanza. See | |
33 L{utility.EventDispatcher} for details. | |
34 """ | |
35 def __init__(self): | |
36 utility.EventDispatcher.__init__(self) | |
37 self.stream = None | |
38 self.rawDataOutFn = None | |
39 self.rawDataInFn = None | |
40 | |
41 def _initializeStream(self): | |
42 """ Sets up XML Parser. """ | |
43 self.stream = domish.elementStream() | |
44 self.stream.DocumentStartEvent = self.onDocumentStart | |
45 self.stream.ElementEvent = self.onElement | |
46 self.stream.DocumentEndEvent = self.onDocumentEnd | |
47 | |
48 ### -------------------------------------------------------------- | |
49 ### | |
50 ### Protocol events | |
51 ### | |
52 ### -------------------------------------------------------------- | |
53 | |
54 def connectionMade(self): | |
55 """ Called when a connection is made. | |
56 | |
57 Sets up the XML parser and dispatches the L{STREAM_CONNECTED_EVENT} | |
58 event indicating the connection has been established. | |
59 """ | |
60 self._initializeStream() | |
61 self.dispatch(self, STREAM_CONNECTED_EVENT) | |
62 | |
63 def dataReceived(self, data): | |
64 """ Called whenever data is received. | |
65 | |
66 Passes the data to the XML parser. This can result in calls to the | |
67 DOM handlers. If a parse error occurs, the L{STREAM_ERROR_EVENT} event | |
68 is called to allow for cleanup actions, followed by dropping the | |
69 connection. | |
70 """ | |
71 try: | |
72 if self.rawDataInFn: | |
73 self.rawDataInFn(data) | |
74 self.stream.parse(data) | |
75 except domish.ParserError: | |
76 self.dispatch(self, STREAM_ERROR_EVENT) | |
77 self.transport.loseConnection() | |
78 | |
79 def connectionLost(self, reason): | |
80 """ Called when the connection is shut down. | |
81 | |
82 Dispatches the L{STREAM_END_EVENT}. | |
83 """ | |
84 self.dispatch(self, STREAM_END_EVENT) | |
85 self.stream = None | |
86 | |
87 ### -------------------------------------------------------------- | |
88 ### | |
89 ### DOM events | |
90 ### | |
91 ### -------------------------------------------------------------- | |
92 | |
93 def onDocumentStart(self, rootElement): | |
94 """ Called whenever the start tag of a root element has been received. | |
95 | |
96 Dispatches the L{STREAM_START_EVENT}. | |
97 """ | |
98 self.dispatch(self, STREAM_START_EVENT) | |
99 | |
100 def onElement(self, element): | |
101 """ Called whenever a direct child element of the root element has | |
102 been received. | |
103 | |
104 Dispatches the received element. | |
105 """ | |
106 self.dispatch(element) | |
107 | |
108 def onDocumentEnd(self): | |
109 """ Called whenever the end tag of the root element has been received. | |
110 | |
111 Closes the connection. This causes C{connectionLost} being called. | |
112 """ | |
113 self.transport.loseConnection() | |
114 | |
115 def setDispatchFn(self, fn): | |
116 """ Set another function to handle elements. """ | |
117 self.stream.ElementEvent = fn | |
118 | |
119 def resetDispatchFn(self): | |
120 """ Set the default function (C{onElement}) to handle elements. """ | |
121 self.stream.ElementEvent = self.onElement | |
122 | |
123 def send(self, obj): | |
124 """ Send data over the stream. | |
125 | |
126 Sends the given C{obj} over the connection. C{obj} may be instances of | |
127 L{domish.Element}, L{unicode} and L{str}. The first two will be | |
128 properly serialized and/or encoded. L{str} objects must be in UTF-8 | |
129 encoding. | |
130 | |
131 Note: because it is easy to make mistakes in maintaining a properly | |
132 encoded L{str} object, it is advised to use L{unicode} objects | |
133 everywhere when dealing with XML Streams. | |
134 | |
135 @param obj: Object to be sent over the stream. | |
136 @type obj: L{domish.Element}, L{domish} or L{str} | |
137 | |
138 """ | |
139 if domish.IElement.providedBy(obj): | |
140 obj = obj.toXml() | |
141 | |
142 if isinstance(obj, unicode): | |
143 obj = obj.encode('utf-8') | |
144 | |
145 if self.rawDataOutFn: | |
146 self.rawDataOutFn(obj) | |
147 | |
148 self.transport.write(obj) | |
149 | |
150 | |
151 class XmlStreamFactoryMixin(object): | |
152 """ | |
153 XmlStream factory mixin that takes care of event handlers. | |
154 | |
155 To make sure certain event observers are set up before incoming data is | |
156 processed, you can set up bootstrap event observers using C{addBootstrap}. | |
157 | |
158 The C{event} and C{fn} parameters correspond with the C{event} and | |
159 C{observerfn} arguments to L{utility.EventDispatcher.addObserver}. | |
160 """ | |
161 | |
162 def __init__(self, *args, **kwargs): | |
163 self.bootstraps = [] | |
164 self.args = args | |
165 self.kwargs = kwargs | |
166 | |
167 def buildProtocol(self, addr): | |
168 """ | |
169 Create an instance of XmlStream. | |
170 | |
171 The returned instance will have bootstrap event observers registered | |
172 and will proceed to handle input on an incoming connection. | |
173 """ | |
174 xs = self.protocol(*self.args, **self.kwargs) | |
175 xs.factory = self | |
176 for event, fn in self.bootstraps: | |
177 xs.addObserver(event, fn) | |
178 return xs | |
179 | |
180 def addBootstrap(self, event, fn): | |
181 """ | |
182 Add a bootstrap event handler. | |
183 """ | |
184 self.bootstraps.append((event, fn)) | |
185 | |
186 def removeBootstrap(self, event, fn): | |
187 """ | |
188 Remove a bootstrap event handler. | |
189 """ | |
190 self.bootstraps.remove((event, fn)) | |
191 | |
192 | |
193 class XmlStreamFactory(XmlStreamFactoryMixin, | |
194 protocol.ReconnectingClientFactory): | |
195 """ | |
196 Factory for XmlStream protocol objects as a reconnection client. | |
197 """ | |
198 | |
199 protocol = XmlStream | |
200 | |
201 def buildProtocol(self, addr): | |
202 """ | |
203 Create a protocol instance. | |
204 | |
205 Overrides L{XmlStreamFactoryMixin.buildProtocol} to work with | |
206 a L{ReconnectingClientFactory}. As this is called upon having an | |
207 connection established, we are resetting the delay for reconnection | |
208 attempts when the connection is lost again. | |
209 """ | |
210 self.resetDelay() | |
211 return XmlStreamFactoryMixin.buildProtocol(self, addr) | |
OLD | NEW |