def deferredwrapper(self, tup, jidstr, id, msg, callname, callargs): """deferredwrapper() -- callback for deferred handlers. This is used by handlecall(), for the case where an RPC handler wants to undertake a deferral operation. You should not call it, or even try to understand it. """ try: try: res = sched.Deferred.extract(tup) except CallNotFound: res = self.notfound(msg, callname, callargs) respnod = rpcdata.makeresponse(res) except rpcdata.RPCResponse, ex: respnod = rpcdata.makeresponse(ex.value)
class RPCService(service.Service): """RPCService: A high-level Jabber facility for responding to Jabber-RPC calls from other entities. (Service label: 'rpcservice'.) The RPCService is a generic wrapper for RPC calls. It receives the RPC stanza, translates the Jabber-RPC arguments into Python values, and then passes everything on to its opset. You supply the opset when you create the RPCService; it contains the code which actually handles each RPC. (See the Opset class for more information.) The code in the opset has several options: * Return a Python value. This will become the RPC response value. * Return None. This will cause an RPC response with a boolean True value. * Raise an RPCResponse(value). This is equivalent to returning the value -- it causes an RPC response with that value. * Raise an RPCFault(code, string). This will cause an RPC fault reply, with the given *code* and *string*. * Raise a CallNotFound. This will become a stanza-level 'item-not-found' error, indicating that the method does not exist. * Raise an interface.StanzaError. This will cause the RPC to be replied to with the given stanza-level error. * Raise some other kind of Exception. This will become a stanza-level 'internal-server-error' error. * Raise a sched.Deferred(op). This is a promise that you will eventually invoke the Deferred object. That will result in *op* being called; and the *op* should then do one of the things on this list. If you use an RPCService, you should add the string interface.NS_RPC ('jabber:iq:rpc') to your DiscoService features. (It would be better if this happened automatically, but it doesn't.) RPCService(opset=None, notfoundhandler=None) -- constructor. If you don't supply an opset, a bare Opset instance will be used. This is a minimal handler -- it doesn't accept *any* calls, and always raises CallNotFound. You can change the opset later with the setopset() method. The *notfoundhandler* is a function which is called when an RPC method name is not recognized by the opset. The function is invoked as notfoundhandler(msg, callname, callargs) where *msg* is a Node tree, *callname* is a string, and *callargs* is a tuple of values. The function must return a value or raise RPCResponse, RPCFault, or a StanzaError. If you do not supply a function, the service will raise StanzaItemNotFound for unrecognized RPCs. Public methods: setopset(opset) -- change the service's opset. getopset() -- return the service's current opset. Internal methods: attach() -- attach this Service to a JabberStream. handlecall() -- RPC stanza handler. deferredwrapper() -- callback for deferred handlers. """ label = 'rpcservice' logprefix = 'zymb.jabber.rpc' def __init__(self, opset=None, notfoundhandler=None): service.Service.__init__(self) if (not opset): opset = Opset() self.opset = opset self.notfoundhandler = notfoundhandler def attach(self, agent): """attach() -- internal method to attach this Service to a JabberStream. Do not call this directly. Instead, call jstream.addservice(service). This calls the inherited class method, and then sets up the stanza dispatcher which catches incoming RPC calls. """ service.Service.attach(self, agent) self.agent.adddispatcher(self.handlecall, name='iq', type='set') def setopset(self, opset): """setopset(opset) -> None Change the service's opset. An RPCService has exactly one opset at a time. (But opsets can contain other opsets. This method sets the top-level opset. See the Opset class for details.) """ self.opset = opset def getopset(self): """getopset() -> Opset Return the service's current opset. """ return self.opset def handlecall(self, msg): """handlecall() -- RPC stanza handler. Do not call. This parses the Jabber stanza, to extract the method name and arguments. It translates the arguments into Python objects. Then it invokes the service opset, and takes care of the various responses and exceptions which that may generate. """ qnod = msg.getchild('query') if (not qnod): return if (qnod.getnamespace() != interface.NS_RPC): return jidstr = msg.getattr('from') id = msg.getattr('id') nod = qnod.getchild('methodCall') if (not nod): raise interface.StanzaBadRequest('no methodCall') try: (callname, callargs) = rpcdata.parsecall(nod) except Exception, ex: self.log.warning('Ill-formed RPC call from <%s>, id %s', jidstr, id, exc_info=True) raise interface.StanzaBadRequest(str(ex)) self.log.debug('RPC %s %s from <%s>, id %s', callname, callargs, jidstr, id) try: if (not self.opset): raise CallNotFound jid = interface.JID(jidstr) try: res = self.opset(jid, callname, *callargs) except CallNotFound: res = self.notfound(msg, callname, callargs) respnod = rpcdata.makeresponse(res) except sched.Deferred, ex: ex.addcontext(self.deferredwrapper, jidstr, id, msg, callname, callargs) raise
try: if (not self.opset): raise CallNotFound jid = interface.JID(jidstr) try: res = self.opset(jid, callname, *callargs) except CallNotFound: res = self.notfound(msg, callname, callargs) respnod = rpcdata.makeresponse(res) except sched.Deferred, ex: ex.addcontext(self.deferredwrapper, jidstr, id, msg, callname, callargs) raise except rpcdata.RPCResponse, ex: respnod = rpcdata.makeresponse(ex.value) except rpcdata.RPCFault, ex: respnod = rpcdata.makefaultresponse(ex.code, ex.string) msg = interface.Node('iq', attrs={ 'to': jidstr, 'type': 'result', 'id': id }) qnod = msg.setchild('query', namespace=interface.NS_RPC) qnod.addchild(respnod) self.agent.send(msg) raise interface.StanzaHandled