Introduction
This is the sixth part of the Twisted tutorial Twisted from Scratch, or The Evolution of Finger.
In this part, we demonstrate adding a web frontend using simple twisted.web.resource.Resource
objects: UserStatusTree
, which will produce a listing of all
users at the base URL (/
) of our site; UserStatus
, which gives the status of each user at the
locaton /username
; and UserStatusXR
,
which exposes an XMLRPC interface to getUser
and
getUsers
functions at the URL /RPC2
.
In this example we construct HTML segments manually. If the web interface was less trivial, we would want to use more sophisticated web templating and design our system so that HTML rendering and logic were clearly separated.
# Do everything properly, and componentize from twisted.application import internet, service from twisted.internet import protocol, reactor, defer from twisted.words.protocols import irc from twisted.protocols import basic from twisted.python import components from twisted.web import resource, server, static, xmlrpc, microdom from zope.interface import Interface, implements import cgi class IFingerService(Interface): def getUser(user): """Return a deferred returning a string""" def getUsers(): """Return a deferred returning a list of strings""" class IFingerSetterService(Interface): def setUser(user, status): """Set the user's status to something""" def catchError(err): return "Internal error in server" class FingerProtocol(basic.LineReceiver): def lineReceived(self, user): d = self.factory.getUser(user) d.addErrback(catchError) def writeValue(value): self.transport.write(value+'\r\n') self.transport.loseConnection() d.addCallback(writeValue) class IFingerFactory(Interface): def getUser(user): """Return a deferred returning a string""" def buildProtocol(addr): """Return a protocol returning a string""" class FingerFactoryFromService(protocol.ServerFactory): implements(IFingerFactory) protocol = FingerProtocol def __init__(self, service): self.service = service def getUser(self, user): return self.service.getUser(user) components.registerAdapter(FingerFactoryFromService, IFingerService, IFingerFactory) class FingerSetterProtocol(basic.LineReceiver): def connectionMade(self): self.lines = [] def lineReceived(self, line): self.lines.append(line) def connectionLost(self, reason): if len(self.lines) == 2: self.factory.setUser(*self.lines) class IFingerSetterFactory(Interface): def setUser(user, status): """Return a deferred returning a string""" def buildProtocol(addr): """Return a protocol returning a string""" class FingerSetterFactoryFromService(protocol.ServerFactory): implements(IFingerSetterFactory) protocol = FingerSetterProtocol def __init__(self, service): self.service = service def setUser(self, user, status): self.service.setUser(user, status) components.registerAdapter(FingerSetterFactoryFromService, IFingerSetterService, IFingerSetterFactory) class IRCReplyBot(irc.IRCClient): def connectionMade(self): self.nickname = self.factory.nickname irc.IRCClient.connectionMade(self) def privmsg(self, user, channel, msg): user = user.split('!')[0] if self.nickname.lower() == channel.lower(): d = self.factory.getUser(msg) d.addErrback(catchError) d.addCallback(lambda m: "Status of %s: %s" % (msg, m)) d.addCallback(lambda m: self.msg(user, m)) class IIRCClientFactory(Interface): """ @ivar nickname """ def getUser(user): """Return a deferred returning a string""" def buildProtocol(addr): """Return a protocol""" class IRCClientFactoryFromService(protocol.ClientFactory): implements(IIRCClientFactory) protocol = IRCReplyBot nickname = None def __init__(self, service): self.service = service def getUser(self, user): return self.service.getUser(user) components.registerAdapter(IRCClientFactoryFromService, IFingerService, IIRCClientFactory) class UserStatusTree(resource.Resource): def __init__(self, service): resource.Resource.__init__(self) self.service=service # add a specific child for the path "RPC2" self.putChild("RPC2", UserStatusXR(self.service)) # need to do this for resources at the root of the site self.putChild("", self) def _cb_render_GET(self, users, request): userOutput = ''.join(["<li><a href=\"%s\">%s</a></li>" % (user, user) for user in users]) request.write(""" <html><head><title>Users</title></head><body> <h1>Users</h1> <ul> %s </ul></body></html>""" % userOutput) request.finish() def render_GET(self, request): d = self.service.getUsers() d.addCallback(self._cb_render_GET, request) # signal that the rendering is not complete return server.NOT_DONE_YET def getChild(self, path, request): return UserStatus(user=path, service=self.service) components.registerAdapter(UserStatusTree, IFingerService, resource.IResource) class UserStatus(resource.Resource): def __init__(self, user, service): resource.Resource.__init__(self) self.user = user self.service = service def _cb_render_GET(self, status, request): request.write("""<html><head><title>%s</title></head> <body><h1>%s</h1> <p>%s</p> </body></html>""" % (self.user, self.user, status)) request.finish() def render_GET(self, request): d = self.service.getUser(self.user) d.addCallback(self._cb_render_GET, request) # signal that the rendering is not complete return server.NOT_DONE_YET class UserStatusXR(xmlrpc.XMLRPC): def __init__(self, service): xmlrpc.XMLRPC.__init__(self) self.service = service def xmlrpc_getUser(self, user): return self.service.getUser(user) def xmlrpc_getUsers(self): return self.service.getUsers() class FingerService(service.Service): implements(IFingerService) def __init__(self, filename): self.filename = filename self._read() def _read(self): self.users = {} for line in file(self.filename): user, status = line.split(':', 1) user = user.strip() status = status.strip() self.users[user] = status self.call = reactor.callLater(30, self._read) def getUser(self, user): return defer.succeed(self.users.get(user, "No such user")) def getUsers(self): return defer.succeed(self.users.keys()) application = service.Application('finger', uid=1, gid=1) f = FingerService('/etc/users') serviceCollection = service.IServiceCollection(application) internet.TCPServer(79, IFingerFactory(f) ).setServiceParent(serviceCollection) internet.TCPServer(8000, server.Site(resource.IResource(f)) ).setServiceParent(serviceCollection) i = IIRCClientFactory(f) i.nickname = 'fingerbot' internet.TCPClient('irc.freenode.org', 6667, i ).setServiceParent(serviceCollection)