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.