Package spade :: Package xmpp :: Module dispatcher
[hide private]
[frames] | no frames]

Source Code for Module spade.xmpp.dispatcher

  1  ##   transports.py 
  2  ## 
  3  ##   Copyright (C) 2003-2005 Alexey "Snake" Nezhdanov 
  4  ## 
  5  ##   This program is free software; you can redistribute it and/or modify 
  6  ##   it under the terms of the GNU General Public License as published by 
  7  ##   the Free Software Foundation; either version 2, or (at your option) 
  8  ##   any later version. 
  9  ## 
 10  ##   This program is distributed in the hope that it will be useful, 
 11  ##   but WITHOUT ANY WARRANTY; without even the implied warranty of 
 12  ##   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 13  ##   GNU General Public License for more details. 
 14   
 15  # $Id: dispatcher.py,v 1.40 2006/01/18 19:26:43 normanr Exp $ 
 16   
 17  """ 
 18  Main xmpppy mechanism. Provides library with methods to assign different handlers 
 19  to different XMPP stanzas. 
 20  Contains one tunable attribute: DefaultTimeout (25 seconds by default). It defines time that  
 21  Dispatcher.SendAndWaitForResponce method will wait for reply stanza before giving up. 
 22  """ 
 23   
 24  import simplexml,time,sys 
 25  from protocol import * 
 26  from client import PlugIn 
 27   
 28  DefaultTimeout=25 
 29  ID=0 
 30   
31 -class Dispatcher(PlugIn):
32 """ Ancestor of PlugIn class. Handles XMPP stream, i.e. aware of stream headers. 33 Can be plugged out/in to restart these headers (used for SASL f.e.). """
34 - def __init__(self):
35 PlugIn.__init__(self) 36 DBG_LINE='dispatcher' 37 self.handlers={} 38 self._expected={} 39 self._defaultHandler=None 40 self._pendingExceptions=[] 41 self._eventHandler=None 42 self._cycleHandlers=[] 43 self._exported_methods=[self.Process,self.RegisterHandler,self.RegisterDefaultHandler,\ 44 self.RegisterEventHandler,self.UnregisterCycleHandler,self.RegisterCycleHandler,\ 45 self.RegisterHandlerOnce,self.UnregisterHandler,self.RegisterProtocol,\ 46 self.WaitForResponse,self.SendAndWaitForResponse,self.send,self.disconnect,\ 47 self.SendAndCallForResponse, ]
48
49 - def dumpHandlers(self):
50 """ Return set of user-registered callbacks in it's internal format. 51 Used within the library to carry user handlers set over Dispatcher replugins. """ 52 return self.handlers
53 - def restoreHandlers(self,handlers):
54 """ Restores user-registered callbacks structure from dump previously obtained via dumpHandlers. 55 Used within the library to carry user handlers set over Dispatcher replugins. """ 56 self.handlers=handlers
57
58 - def _init(self):
59 """ Registers default namespaces/protocols/handlers. Used internally. """ 60 self.RegisterNamespace('unknown') 61 self.RegisterNamespace(NS_STREAMS) 62 self.RegisterNamespace(self._owner.defaultNamespace) 63 self.RegisterProtocol('iq',Iq) 64 self.RegisterProtocol('presence',Presence) 65 self.RegisterProtocol('message',Message) 66 self.RegisterDefaultHandler(self.returnStanzaHandler) 67 self.RegisterHandler('error',self.streamErrorHandler,xmlns=NS_STREAMS)
68
69 - def plugin(self, owner):
70 """ Plug the Dispatcher instance into Client class instance and send initial stream header. Used internally.""" 71 self._init() 72 for method in self._old_owners_methods: 73 if method.__name__=='send': self._owner_send=method; break 74 self._owner.lastErrNode=None 75 self._owner.lastErr=None 76 self._owner.lastErrCode=None 77 self.StreamInit()
78
79 - def plugout(self):
80 """ Prepares instance to be destructed. """ 81 self.Stream.dispatch=None 82 self.Stream.DEBUG=None 83 self.Stream.features=None 84 self.Stream.destroy()
85
86 - def StreamInit(self):
87 """ Send an initial stream header. """ 88 self.Stream=simplexml.NodeBuilder() 89 self.Stream._dispatch_depth=2 90 self.Stream.dispatch=self.dispatch 91 self.Stream.stream_header_received=self._check_stream_start 92 self._owner.debug_flags.append(simplexml.DBG_NODEBUILDER) 93 self.Stream.DEBUG=self._owner.DEBUG 94 self.Stream.features=None 95 self._metastream=Node('stream:stream') 96 self._metastream.setNamespace(self._owner.Namespace) 97 self._metastream.setAttr('version','1.0') 98 self._metastream.setAttr('xmlns:stream',NS_STREAMS) 99 self._metastream.setAttr('to',self._owner.Server) 100 self._owner.send("<?xml version='1.0'?>%s>"%str(self._metastream)[:-2])
101
102 - def _check_stream_start(self,ns,tag,attrs):
103 if ns<>NS_STREAMS or tag<>'stream': 104 raise ValueError('Incorrect stream start: (%s,%s). Terminating.'%(tag,ns))
105
106 - def Process(self, timeout=0):
107 """ Check incoming stream for data waiting. If "timeout" is positive - block for as max. this time. 108 Returns: 109 1) length of processed data if some data were processed; 110 2) '0' string if no data were processed but link is alive; 111 3) 0 (zero) if underlying connection is closed. 112 Take note that in case of disconnection detect during Process() call 113 disconnect handlers are called automatically. 114 """ 115 for handler in self._cycleHandlers: handler(self) 116 if len(self._pendingExceptions) > 0: 117 _pendingException = self._pendingExceptions.pop() 118 raise _pendingException[0], _pendingException[1], _pendingException[2] 119 if self._owner.Connection.pending_data(timeout): 120 try: data=self._owner.Connection.receive() 121 except IOError: return 0 122 self._owner._data = data 123 self.Stream.Parse(data) 124 if len(self._pendingExceptions) > 0: 125 _pendingException = self._pendingExceptions.pop() 126 raise _pendingException[0], _pendingException[1], _pendingException[2] 127 if data: return len(data) 128 return '0' # It means that nothing is received but link is alive.
129
130 - def RegisterNamespace(self,xmlns,order='info'):
131 """ Creates internal structures for newly registered namespace. 132 You can register handlers for this namespace afterwards. By default one namespace 133 already registered (jabber:client or jabber:component:accept depending on context. """ 134 self.DEBUG('Registering namespace "%s"'%xmlns,order) 135 self.handlers[xmlns]={} 136 self.RegisterProtocol('unknown',Protocol,xmlns=xmlns) 137 self.RegisterProtocol('default',Protocol,xmlns=xmlns)
138
139 - def RegisterProtocol(self,tag_name,Proto,xmlns=None,order='info'):
140 """ Used to declare some top-level stanza name to dispatcher. 141 Needed to start registering handlers for such stanzas. 142 Iq, message and presence protocols are registered by default. """ 143 if not xmlns: xmlns=self._owner.defaultNamespace 144 self.DEBUG('Registering protocol "%s" as %s(%s)'%(tag_name,Proto,xmlns), order) 145 self.handlers[xmlns][tag_name]={type:Proto, 'default':[]}
146
147 - def RegisterNamespaceHandler(self,xmlns,handler,typ='',ns='', makefirst=0, system=0):
148 """ Register handler for processing all stanzas for specified namespace. """ 149 self.RegisterHandler('default', handler, typ, ns, xmlns, makefirst, system)
150
151 - def RegisterHandler(self,name,handler,typ='',ns='',xmlns=None, makefirst=0, system=0):
152 """Register user callback as stanzas handler of declared type. Callback must take 153 (if chained, see later) arguments: dispatcher instance (for replying), incomed 154 return of previous handlers. 155 The callback must raise xmpp.NodeProcessed just before return if it want preven 156 callbacks to be called with the same stanza as argument _and_, more importantly 157 library from returning stanza to sender with error set (to be enabled in 0.2 ve 158 Arguments: 159 "name" - name of stanza. F.e. "iq". 160 "handler" - user callback. 161 "typ" - value of stanza's "type" attribute. If not specified any value match 162 "ns" - namespace of child that stanza must contain. 163 "chained" - chain together output of several handlers. 164 "makefirst" - insert handler in the beginning of handlers list instead of 165 adding it to the end. Note that more common handlers (i.e. w/o "typ" and " 166 will be called first nevertheless. 167 "system" - call handler even if NodeProcessed Exception were raised already. 168 """ 169 if not xmlns: xmlns=self._owner.defaultNamespace 170 self.DEBUG('Registering handler %s for "%s" type->%s ns->%s(%s)'%(handler,name,typ,ns,xmlns), 'info') 171 if not typ and not ns: typ='default' 172 if not self.handlers.has_key(xmlns): self.RegisterNamespace(xmlns,'warn') 173 if not self.handlers[xmlns].has_key(name): self.RegisterProtocol(name,Protocol,xmlns,'warn') 174 if not self.handlers[xmlns][name].has_key(typ+ns): self.handlers[xmlns][name][typ+ns]=[] 175 if makefirst: self.handlers[xmlns][name][typ+ns].insert(0,{'func':handler,'system':system}) 176 else: self.handlers[xmlns][name][typ+ns].append({'func':handler,'system':system})
177
178 - def RegisterHandlerOnce(self,name,handler,typ='',ns='',xmlns=None,makefirst=0, system=0):
179 """ Unregister handler after first call (not implemented yet). """ 180 if not xmlns: xmlns=self._owner.defaultNamespace 181 self.RegisterHandler(name, handler, typ, ns, xmlns, makefirst, system)
182
183 - def UnregisterHandler(self,name,handler,typ='',ns='',xmlns=None):
184 """ Unregister handler. "typ" and "ns" must be specified exactly the same as with registering.""" 185 if not xmlns: xmlns=self._owner.defaultNamespace 186 if not self.handlers.has_key(xmlns): xmlns=self._owner.defaultNamespace 187 if not typ and not ns: typ='default' 188 if self.handlers[xmlns].has_key(name): 189 for pack in self.handlers[xmlns][name][typ+ns]: 190 if handler==pack['func']: break 191 else: pack=None 192 try: self.handlers[xmlns][name][typ+ns].remove(pack) 193 except ValueError: pass
194 195
196 - def RegisterDefaultHandler(self,handler):
197 """ Specify the handler that will be used if no NodeProcessed exception were raised. 198 This is returnStanzaHandler by default. """ 199 self._defaultHandler=handler
200
201 - def RegisterEventHandler(self,handler):
202 """ Register handler that will process events. F.e. "FILERECEIVED" event. """ 203 self._eventHandler=handler
204
205 - def returnStanzaHandler(self,conn,stanza):
206 """ Return stanza back to the sender with <feature-not-implemennted/> error set. """ 207 if stanza.getType() in ['get','set']: 208 conn.send(Error(stanza,ERR_FEATURE_NOT_IMPLEMENTED))
209
210 - def streamErrorHandler(self,conn,error):
211 name,text='error',error.getData() 212 for tag in error.getChildren(): 213 if tag.getNamespace()==NS_XMPP_STREAMS: 214 if tag.getName()=='text': text=tag.getData() 215 else: name=tag.getName() 216 if name in stream_exceptions.keys(): exc=stream_exceptions[name] 217 else: exc=StreamError 218 raise exc((name,text))
219
220 - def RegisterCycleHandler(self,handler):
221 """ Register handler that will be called on every Dispatcher.Process() call. """ 222 if handler not in self._cycleHandlers: self._cycleHandlers.append(handler)
223
224 - def UnregisterCycleHandler(self,handler):
225 """ Unregister handler that will is called on every Dispatcher.Process() call.""" 226 if handler in self._cycleHandlers: self._cycleHandlers.remove(handler)
227
228 - def Event(self,realm,event,data):
229 """ Raise some event. Takes three arguments: 230 1) "realm" - scope of event. Usually a namespace. 231 2) "event" - the event itself. F.e. "SUCESSFULL SEND". 232 3) data that comes along with event. Depends on event.""" 233 if self._eventHandler: self._eventHandler(realm,event,data)
234
235 - def dispatch(self,stanza,session=None,direct=0):
236 """ Main procedure that performs XMPP stanza recognition and calling apppropriate handlers for it. 237 Called internally. """ 238 if not session: session=self 239 session.Stream._mini_dom=None 240 name=stanza.getName() 241 242 if not direct and self._owner._component: 243 if name == 'route': 244 if stanza.getAttr('error') == None: 245 if len(stanza.getChildren()) == 1: 246 stanza = stanza.getChildren()[0] 247 name=stanza.getName() 248 else: 249 for each in stanza.getChildren(): 250 self.dispatch(each,session,direct=1) 251 return 252 elif name == 'presence': 253 pass #return 254 elif name in ('features','bind'): 255 pass 256 else: 257 raise UnsupportedStanzaType(name) 258 if name=='features': session.Stream.features=stanza 259 260 xmlns=stanza.getNamespace() 261 if not self.handlers.has_key(xmlns): 262 self.DEBUG("Unknown namespace: " + xmlns,'warn') 263 xmlns='unknown' 264 if not self.handlers[xmlns].has_key(name): 265 self.DEBUG("Unknown stanza: " + name,'warn') 266 name='unknown' 267 else: 268 self.DEBUG("Got %s/%s stanza"%(xmlns,name), 'ok') 269 270 if stanza.__class__.__name__=='Node': stanza=self.handlers[xmlns][name][type](node=stanza) 271 272 typ=stanza.getType() 273 if not typ: typ='' 274 stanza.props=stanza.getProperties() 275 ID=stanza.getID() 276 277 session.DEBUG("Dispatching %s stanza with type->%s props->%s id->%s"%(name,typ,stanza.props,ID),'ok') 278 279 list=['default'] # we will use all handlers: 280 if self.handlers[xmlns][name].has_key(typ): list.append(typ) # from very common... 281 for prop in stanza.props: 282 if self.handlers[xmlns][name].has_key(prop): list.append(prop) 283 if typ and self.handlers[xmlns][name].has_key(typ+prop): list.append(typ+prop) # ...to very particular 284 285 chain=self.handlers[xmlns]['default']['default'] 286 for key in list: 287 if key: chain = chain + self.handlers[xmlns][name][key] 288 289 output='' 290 if session._expected.has_key(ID): 291 user=0 292 if type(session._expected[ID])==type(()): 293 cb,args=session._expected[ID] 294 session.DEBUG("Expected stanza arrived. Callback %s(%s) found!"%(cb,args),'ok') 295 try: cb(session,stanza,**args) 296 except Exception, typ: 297 if typ.__class__.__name__<>'NodeProcessed': raise 298 else: 299 session.DEBUG("Expected stanza arrived!",'ok') 300 session._expected[ID]=stanza 301 else: user=1 302 for handler in chain: 303 if user or handler['system']: 304 try: 305 handler['func'](session,stanza) 306 except Exception, typ: 307 if typ.__class__.__name__<>'NodeProcessed': 308 self._pendingExceptions.insert(0, sys.exc_info()) 309 return 310 user=0 311 if user and self._defaultHandler: self._defaultHandler(session,stanza)
312
313 - def WaitForResponse(self, ID, timeout=DefaultTimeout):
314 """ Block and wait until stanza with specific "id" attribute will come. 315 If no such stanza is arrived within timeout, return None. 316 If operation failed for some reason then owner's attributes 317 lastErrNode, lastErr and lastErrCode are set accordingly. """ 318 self._expected[ID]=None 319 has_timed_out=0 320 abort_time=time.time() + timeout 321 self.DEBUG("Waiting for ID:%s with timeout %s..." % (ID,timeout),'wait') 322 while not self._expected[ID]: 323 if not self.Process(0.04): 324 self._owner.lastErr="Disconnect" 325 return None 326 if time.time() > abort_time: 327 self._owner.lastErr="Timeout" 328 return None 329 response=self._expected[ID] 330 del self._expected[ID] 331 if response.getErrorCode(): 332 self._owner.lastErrNode=response 333 self._owner.lastErr=response.getError() 334 self._owner.lastErrCode=response.getErrorCode() 335 return response
336
337 - def SendAndWaitForResponse(self, stanza, timeout=DefaultTimeout):
338 """ Put stanza on the wire and wait for recipient's response to it. """ 339 return self.WaitForResponse(self.send(stanza),timeout)
340
341 - def SendAndCallForResponse(self, stanza, func, args={}):
342 """ Put stanza on the wire and call back when recipient replies. 343 Additional callback arguments can be specified in args. """ 344 self._expected[self.send(stanza)]=(func,args)
345
346 - def send(self,stanza):
347 """ Serialise stanza and put it on the wire. Assign an unique ID to it before send. 348 Returns assigned ID.""" 349 if type(stanza) in [type(''), type(u'')]: return self._owner_send(stanza) 350 if not isinstance(stanza,Protocol): _ID=None 351 elif not stanza.getID(): 352 global ID 353 ID+=1 354 _ID=`ID` 355 stanza.setID(_ID) 356 else: _ID=stanza.getID() 357 if self._owner._registered_name and not stanza.getAttr('from'): stanza.setAttr('from',self._owner._registered_name) 358 if self._owner._component and stanza.getName()!='bind': 359 to=self._owner.Server 360 if stanza.getTo() and stanza.getTo().getDomain(): 361 to=stanza.getTo().getDomain() 362 frm=stanza.getFrom() 363 if frm.getDomain(): 364 frm=frm.getDomain() 365 route=Protocol('route',to=to,frm=frm,payload=[stanza]) 366 stanza=route 367 stanza.setNamespace(self._owner.Namespace) 368 stanza.setParent(self._metastream) 369 self._owner_send(stanza) 370 return _ID
371
372 - def disconnect(self):
373 """ Send a stream terminator and and handle all incoming stanzas before stream closure. """ 374 self._owner_send('</stream:stream>') 375 while self.Process(1): pass
376