Network Programming with Twisted
- Andrew Bennetts
- andrew-twisted@puzzling.org
(page 1)
Network Programming with Twisted
- Introduction
- Trivial servers
- Hello, World!
- Echo server: TCP and UDP
- All of the above
- Some basics
- Protocols and Factories
- The Reactor
- Deferreds
- LineReceiver
- Clients
- Twisted Web
- XML-RPC
- A simple web server
- Remote Objects with Perspective Broker
- Simple PB server
- Simple PB client
- Referenceable, Copyable, Cacheable, oh my!
(page 2)
Introduction
- Single-threaded asynchronous
- Event-driven
- Nice abstractions
- Lots of pre-written protocols
(page 3)
Hello, World!
from twisted.internet.protocol import Protocol, Factory
from twisted.internet import reactor
class Hello(Protocol):
def connectionMade(self):
self.transport.write("Hello, World!\n")
self.transport.loseConnection()
class HelloFactory(Factory):
protocol = Hello
if __name__ == '__main__':
reactor.listenTCP(9999, HelloFactory())
reactor.run()
(page 4)
Echo Server -- TCP
from twisted.internet.protocol import Protocol, Factory
from twisted.internet import reactor
class Echo(Protocol):
def dataReceived(self, data):
self.transport.write(data)
class EchoFactory(Factory):
protocol = Echo
if __name__ == '__main__':
reactor.listenTCP(9999, EchoFactory())
reactor.run()
(page 5)
Echo Server -- UDP
from twisted.internet.protocol import DatagramProtocol
from twisted.internet import reactor
class EchoUDP(DatagramProtocol):
def datagramReceived(self, data, (host, port)):
self.transport.write(data, (host, port))
if __name__ == '__main__':
reactor.listenUDP(9999, EchoUDP())
reactor.run()
(page 6)
All of the above
from hello_serv import HelloFactory
from echo_serv_tcp import EchoFactory
from echo_serv_udp import EchoUDP
from twisted.internet import reactor
if __name__ == '__main__':
reactor.listenTCP(9001, HelloFactory())
reactor.listenTCP(9002, EchoFactory())
reactor.listenUDP(9002, EchoUDP())
reactor.run()
(page 7)
Protocols and Factories
- Protocol
- connectionMade
- connectionLost
- dataReceived
- Factory
- startFactory
- stopFactory
- buildProtocol
(page 8)
The Reactor
- The "core" of the Twisted framework
- Noteable methods:
- run()
- listenTCP(port, factory)
- listenUDP(port, protocol)
- listenUNIX(path, factory)
- callLater(60, someFunction)
(page 9)
Deferred Execution
- Some things take time (e.g. DB queries), but Twisted is asynchronous, so your can't block while it waits
- This means using callbacks. Lots of them.
Deferreds make this easy
(page 10)
Deferreds and deferToThread
from twisted.internet.protocol import Protocol, Factory
from twisted.internet import reactor, threads
class QOTD(Protocol):
def connectionMade(self):
d = threads.deferToThread(getQuoteFromDB)
d.addCallback(self._cb)
d.addErrback(self._eb)
def _cb(self, quote):
self.transport.write(quote + '\n')
self.transport.loseConnection()
def _eb(self, error):
self.transport.write("Internal server error!")
self.transport.loseConnection()
(page 11)
Deferreds and deferToThread (cont'd)
def getQuoteFromDB():
# TODO: Connect to DB here
return 'I am a fish!'
class QOTDFactory(Factory):
protocol = QOTD
if __name__ == '__main__':
reactor.listenTCP(9999, QOTDFactory())
reactor.run()
(page 12)
LineReceiver
- twisted.protocols.basic.LineReceiver is one of the simpler Protocol subclasses in twisted.protocols.*
- HTTP, SMTP, FTP and many other protocols inherit from this
- Instead of overriding dataReceived(self, data), you override lineReceived(self, line)
- Instead of calling self.transport.write(data), you call self.sendLine(line)
- For other details, e.g. switching to binary mode, see API docs
(page 13)
Echo client
from twisted.internet.protocol import ClientFactory
from twisted.protocols.basic import LineReceiver
from twisted.internet import reactor
class EchoClient(LineReceiver):
end="Bye-bye!"
def connectionMade(self):
self.sendLine("Hello, world!")
self.sendLine("What a fine day it is.")
self.sendLine(self.end)
def lineReceived(self, line):
print "receive:", line
if line==self.end:
self.transport.loseConnection()
(page 14)
Echo client (cont'd)
class EchoClientFactory(ClientFactory):
protocol = EchoClient
def clientConnectionFailed(self, connector, reason):
print 'connection failed:', reason.getErrorMessage()
reactor.stop()
def clientConnectionLost(self, connector, reason):
print 'connection lost:', reason.getErrorMessage()
reactor.stop()
factory = EchoClientFactory()
reactor.connectTCP('localhost', 8000, factory)
reactor.run()
(page 15)
XML-RPC server using Twisted Web
from twisted.web import server, xmlrpc
from twisted.internet import defer, reactor
class Echoer(xmlrpc.XMLRPC):
def xmlrpc_echo(self, *args):
"""Return all passed args."""
return args
def xmlrpc_hello(self):
"""Return 'hello, world'."""
return 'hello, world!'
(page 16)
XML-RPC (cont'd)
def xmlrpc_defer(self):
"""Show how xmlrpc methods can return Deferred."""
return defer.succeed("hello")
def xmlrpc_defer_fail(self):
"""Show how xmlrpc methods can return failed Deferred."""
return defer.fail(12)
def xmlrpc_fail(self):
"""Show how we can return a failure code."""
return xmlrpc.Fault(7, "Out of cheese.")
if __name__ == '__main__':
resource = Echoer()
reactor.listenTCP(7080, server.Site(resource))
reactor.run()
(page 17)
A simple web server
from twisted.web import server, static
from twisted.internet import reactor
root = static.Data("hello world", "text/plain")
root.putChild('', root)
site = server.Site(root)
reactor.listenTCP(10998, site)
reactor.run()
(page 18)
Simple PB server
from twisted.spread import pb
from twisted.internet import app
from twisted.cred.authorizer import DefaultAuthorizer
class DefinedError(pb.Error):
pass
class SimplePerspective(pb.Perspective):
def perspective_echo(self, text):
print 'echoing',text
return text
def perspective_error(self):
raise DefinedError("exception!")
(page 19)
Simple PB server (cont'd)
class SimpleService(pb.Service):
def getPerspectiveNamed(self, name):
p = SimplePerspective(name)
p.setService(self)
return p
if __name__ == '__main__':
appl = app.Application("pbecho")
auth = DefaultAuthorizer(appl)
service = SimpleService("pbecho", appl, auth)
service.getPerspectiveNamed("guest").makeIdentity("guest")
appl.listenTCP(pb.portno, pb.BrokerFactory(pb.AuthRoot(auth)))
appl.run(save=0)
(page 20)
Simple PB client
from twisted.internet import reactor
from twisted.spread import pb
from pbecho import DefinedError
def success(message):
print "Message received:",message
# reactor.stop()
def failure(error):
t = error.trap(DefinedError)
print "error received:", t
reactor.stop()
(page 21)
Simple PB client (cont'd)
def connected(perspective):
d = perspective.callRemote('echo', "hello world")
d.addCallbacks(success, failure)
perspective.callRemote('error').addCallbacks(success, failure)
print "connected."
pb.connect("localhost", pb.portno,
"guest", "guest",
"pbecho", "guest", 30).addCallbacks(connected, failure)
reactor.run()
(page 22)
Referenceable, Copyable, Cacheable, oh my!
- Different "flavours" of remote objects
- Referenceable
- Copyable
- Cacheable
(page 23)
Questions?
(page 24)