XML-RPC and SOAP both transfer data using XML, which makes them language- and platform-neutral. This is an advantage most of the time. But what if you want to communicate between two Twisted applications without having to interoperate with anything else? In that case, the cross-platform advantages of XML could introduce a lot of work, as you have to convert your objects into formats that can be serialized as XML elements. A better approach might be to use Twisted Spread , a system for RPC that uses serialized Python objects.
5.8.1. How Do I Do That?
The core of Twisted Spread is Perspective Broker, which manages connections and handles calls to and from remote objects. The Perspective Broker module is named pb, which has resulted in a large number of peanut-butter-sandwich-related puns and module names (two of the other modules in Spread are banana and jelly).
Perspective Broker is conservative about what it exposes to the network. To avoid security holes, you have to specify exactly which objects should be able to be accessed remotely, and which methods you want to make available over the network. The pb module contains several different classes that you can inherit from to provide remotely accessible objects and methods. It also provides a PBServerFactory class for managing a Perspective Broker server. Example 5-7 shows how to run a server that shares Wiki data using Perspective Broker.
Example 5-7. pb_wiki.py
from twisted.spread import pb import wiki class WikiPerspective(pb.Root): def _ _init_ _(self, wikiData): self.wikiData = wikiData def remote_hasPage(self, path): return self.wikiData.hasPage(path) def remote_listPages(self): return self.wikiData.listPages( ) def remote_getPage(self, path): return self.wikiData.getPage(path) def remote_getRenderedPage(self, path): return self.wikiData.getRenderedPage(path) def remote_setPage(self, path, data): self.wikiData.setPage(path, data) def remote_getReporter(self): return RemoteWikiReporter(self.wikiData) class WikiReporter(pb.Referenceable): "I do simple reporting tasks in the wiki" def _ _init_ _(self, wikiData): self.wikiData = wikiData def listNeededPages(self): "return a list of pages that are linked to but don't exist yet" allPages = [self.wikiData.getPage(p) for p in self.wikiData.listPages( )] neededPages =  for page in allPages: wikiWords = [match for match in wiki.reWikiWord.findall(page)] for pageName in wikiWords: if not self.wikiData.hasPage(pageName): neededPages.append(pageName) return neededPages if __name__ == "_ _main_ _": import sys from twisted.internet import reactor datafile = sys.argv wikiData = wiki.WikiData(datafile) wikiPerspective = WikiPerspective(wikiData) reactor.listenTCP(8789, pb.PBServerFactory(wikiPerspective)) reactor.run( )
Run pb_server.py with one argument, the name of the Wiki datafile:
$ python pb_server.py wiki.dat
Now you need a client to connect to the server. Example 5-8 shows a basic Perspective Broker client.
Example 5-8. pb_client.py
from twisted.spread import pb from twisted.internet import reactor class PbTester(object): def _ _init_ _(self): self.wiki = None def runTests(self): self.connect( ).addCallback( lambda _: self.listPages( )).addCallback( lambda _: self.createTestPage( )).addCallback( lambda _: self.getTestPage( )).addCallback( lambda _: self.runReports( )).addErrback( self._catchFailure).addCallback( lambda _: reactor.stop( )) def connect(self): factory = pb.PBClientFactory( ) reactor.connectTCP("localhost", 8789, factory) return factory.getRootObject( ).addCallback(self._connected) def _connected(self, rootObj): self.wiki = rootObj def listPages(self): print "Getting page list..." return self.wiki.callRemote('listPages').addCallback( self._gotList) def _gotList(self, pages): print "Got page list:", pages def createTestPage(self): print "Creating test page PbTest..." pageData = "This is a test of PerspectiveBroker" return self.wiki.callRemote('setPage', 'PbTest', pageData) def getTestPage(self): print "Getting test page content..." return self.wiki.callRemote( 'getRenderedPage', 'PbTest').addCallback( self._gotTestPage) def _gotTestPage(self, content): print "Got page content:" print content return self.listPages( ) def runReports(self): print "Running report..." return self.wiki.callRemote('getReporter').addCallback( self._gotReporter) def _gotReporter(self, reporter): print "Got remote reporter object" return reporter.callRemote('listNeededPages').addCallback( self._gotNeededPages) def _gotNeededPages(self, pages): print "These pages are needed:", pages def _catchFailure(self, failure): print "Error:", failure.getErrorMessage( ) t = PbTester( ) t.runTests( ) reactor.run( )
Run pb_client.py, and it will call functions on the server using the Perspective Broker protocol:
$ python pb_client.py Getting page list... Got page list: ['WikiHome', 'CurlTest', 'SoapTest', 'XmlRpcTest', 'WgetTest', 'RestTest', 'PythonXmlRpcTest'] Creating test page PbTest... Getting test page content... Got page content: This is a test of <a href="PerspectiveBroker">PerspectiveBroker</a> Getting page list... Got page list: ['WikiHome', 'CurlTest', 'SoapTest', 'XmlRpcTest', 'WgetTest', 'RestTest', 'PbTest', 'PythonXmlRpcTest'] Running report... Got remote reporter object These pages are needed: ['AnotherWikiPage', 'SoapRpc', 'XmlRpc', 'HttpPut', 'PerspectiveBroker', 'XmlRpc']
5.8.2. How Does That Work?
The pb_server.py server creates subclasses of classes in the twisted.spread.pb module. WikiPerspective is a subclass of pb.Root, and WikiReporter is a subclass of pb.Referenceable. Inheriting from these special classes tells Perspective Broker that these objects should be made available for remote access. Methods that should be available remotely must be named with a remote_ prefix.
WikiPerspective inherits from pb.Root, and is passed to the PBServerFactory when it gets created. This makes a WikiPerspective instance available to any client that establishes a connection.
The WikiReporter class in Example 5-7 is an example of how you could expose a second object through Perspective Broker. To make this object available over the network, it inherits from pb.Referenceable. WikiReporter has one method, remote_listNeededPages, which crawls through the Wiki data and returns a list of pages that are mentioned in other pages but have yet to be created.
On the client side, pb_client.py opens a connection to the server. The first thing it has to do is call getrootObject on a PBClientFactory. This returns a Deferred that will call back with a reference to the root object on the remote server once the connection has been established. Once you have the root object, you can call remote methods with the same callRemote syntax used in the XML-RPC and SOAP clients earlier in this chapter.
The server method remote_getReporter is interesting because instead of returning a simple Python type it returns a WikiReporter object. Since WikiReporter inherits from pb.Referenceable, the server knows to give the client a reference to the server-side object. Once it receives this reference, the client can use its callRemote method to call methods on the WikiReporter object that was created on the server.
Building Simple Clients and Servers
Web Services and RPC
NNTP Clients and Servers
Services, Processes, and Logging