Otra de las grandes cosas de Twisted es que tiene varias opciones de
RPC. De éstas, la que me parece más pythonesca es la que dieron en
llamar Perspective
Broker.
Con esta parte del framework sólo tenemos que heredar de un par de
clases, usar un par de factories ya listas para usar, y ponerle
remote_ a los métodos que queremos exportar:
from twisted.spread import pb
from twisted.internet import reactor, defer
class MyServer (pb.Root):
# ...
def remote_hello (self, greeting):
print "client says %s" % greeting
return "why, hello there!"
s= MyServer ()
reactor.listenTCP (port, pb.PBServerFactory (s))
reactor.run ()
Éste es un server que exporta un método remoto hello. El cliente no es
mucho más difícil:
def answered (answer):
print "server said %s" % answer
def connected (root):
root.callRemote ('hello', 'hi server!').addCallback (answered)
factory= pb.PBClientFactory ()
reactor.connectTCP (name, port, factory)
factory.getRootObject ().addCallback (connected)
Lo primero que tenemos que pedir antes de poder hacer nada es el "objeto
raíz". Éste es el objeto que nos va a permitir acceder a todos los
servicios remotos ofrecidos por el server. Este objecto es el s que
creamos en el server y que pasamos como objeto raíz a la
pb.PBServerFactory. getRootObject() nos devuelve un Deferred.
Una vez obtenido el objeto raíz, podemos empezar a llamarle métodos
remotos. Notar que callRemote() recibe el nombre del método remoto
como un string, y el resto de los parámetros que pasamos son los
parámetros con los que va a ser llamado el método remoto en el server.
Como es de esperarse, una llamada a un método remoto no vuelve
inmediatamente, sino que nos da también un Deferred.
Ahora bien, vemos que los Deferreds son la estrella del framework, y
que los usamos en todos lados... ¿Qué pasa cuando un método remoto no
puede devolver inmediatamente el resultado? Pues, aunque parezca raro y
obvio al mismo tiempo, ¡devuelve un Deferred!:
# ... en MyServer
def bye (self):
return defer.succeed (True)
Acá estoy usando defer.suceed(), que es una función que devuelve un
Deferred con su valor ya disponible. Esto es útil para cuando en
algunos casos podemos calcular el valor de la respuesta ya, pero en
otros casos los tenemos que ir a buscar en otro lado. También existe
defer.fail().
Ahora bien, acá entra un poco la magia de Twisted. Es obvio que este
Deferred no es el que es devuelto por callRemote(), porque ése es el
de haber llamado a un método remoto, no el que devuelve ese método por
no tener la respuesta inmediatamente. Lo que no es tan obvio es que este
Deferred nunca viaja por la red. Perspective Broker se da cuenta de
que el valor no está disponible aún y por lo tanto no retorna nada por
la red. Esto es posible porque del otro lado ya hay un Deferred
"esperando". Cuando el resultado esté disponible en el server, podemos
llamar al método callback() de la promesa con el valor calculado y
recién entonces se manda un mensaje por la red y se resuelve la promesa
del lado del cliente:
# ... MyServer
mxCalc= relaymanager.MXCalculator ()
def remote_resolveMX (self, hostname):
promise= defer.Deferred ()
mxCalc.getMX (hostname).addCalback (self.resolvedMX, promise)
return promise
def resolvedMX (self, mxRecord, promise):
if mxRecord is not None:
answer= mwRecord.name
else:
answer= None
promise.callback (answer)
Acá estoy usando otro detalle que creo que no había presentado antes:
estoy llamando a addCallback() no sólo con un callback, sino que le
agrego otro parámetro, una referencia a la promesa que devolví. Cuando
getMX() resuelve su valor, el callback es llamado con ese resultado
como primer parámetro y los otros parámetros que pasé a addCallback()
después. Esto me permite juntar el resiltado con la promesa devuelta
anteriormente.
Hay varias cosas más en el PB. Hay objetos que se pueden referenciar remotamente, que se pueden copiar (hay dos tipos, pero aún no les agarro la mano) y un par de cosas más. Además, Twisted permite RPC usando XML-RPC (buzzword!) y también acceso a/servido de webservices (buzzword!) a través de SOAP y REST (buzzword, buzzword!).
Vamos viendo que Twisted tiene muchas cosas pensadas, y que los conceptos que maneja no son muchos ni muy complicados. Si puedo decir que en la forma en que se van 'partiendo' las soluciones en métodos (estados) no ayuda a la legibilidad posterior del código. Por ejemplo, vean cómo empezamos a tener métodos que representan estados y que son varios para algo que de otra forma nos parecería más sencillo:
# ... MyServer
def remote_resolveMX (self, hostname):
return mxCalc.getMX (hostname)
# ...
# ... cliente
root= factory.getRootObject ()
mx= root.callRemote ('resolveMX', 'decode.com.ar')
# mandar un mail a pyar...
Tengo algunas ideas de cómo simplificar estas cosas. Básicamente se basa en partir el conjunto de máquinas de estados representado en una sola clase a una clase maestra que coordina todo y varias clasesitas que manipulan las distintas maquinitas de estados. Después les cuento bien.