Finger 的演变:可插拔后端¶
简介¶
这是 Twisted 教程 从头开始使用 Twisted,或 Finger 的演变 的第五部分。
在本部分中,我们将使用在 Finger 的演变:迁移到基于组件的架构 中开发的基于组件的架构,为我们的 Finger 服务添加几个新的后端。这将展示当我们迁移到基于组件的架构时,实现新后端是多么方便。请注意,这里我们还使用之前编写的接口 FingerSetterFactory
,通过支持单个方法来实现。我们设法保持了服务对网络的无知。
另一个后端¶
import pwd
from twisted.internet import defer, protocol, reactor, utils
# Another back-end
@implementer(IFingerService)
class LocalFingerService(service.Service):
def getUser(self, user):
# need a local finger daemon running for this to work
return utils.getProcessOutput("finger", [user])
def getUsers(self):
return defer.succeed([])
f = LocalFingerService()
完整的源代码在这里
# Do everything properly, and componentize
import html
import pwd
from zope.interface import Interface, implementer
from twisted.application import internet, service, strports
from twisted.internet import defer, endpoints, protocol, reactor, utils
from twisted.protocols import basic
from twisted.python import components
from twisted.web import resource, server, static, xmlrpc
from twisted.words.protocols import irc
class IFingerService(Interface):
def getUser(user):
"""
Return a deferred returning L{bytes}.
"""
def getUsers():
"""
Return a deferred returning a L{list} of L{bytes}.
"""
class IFingerSetterService(Interface):
def setUser(user, status):
"""
Set the user's status to something.
"""
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 + b"\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.
"""
@implementer(IFingerFactory)
class FingerFactoryFromService(protocol.ServerFactory):
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 L{bytes}.
"""
def buildProtocol(addr):
"""
Return a protocol returning L{bytes}.
"""
@implementer(IFingerSetterFactory)
class FingerSetterFactoryFromService(protocol.ServerFactory):
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: f"Status of {msg}: {m}")
d.addCallback(lambda m: self.msg(user, m))
class IIRCClientFactory(Interface):
"""
@ivar nickname
"""
def getUser(user):
"""
Return a deferred returning L{bytes}.
"""
def buildProtocol(addr):
"""
Return a protocol.
"""
@implementer(IIRCClientFactory)
class IRCClientFactoryFromService(protocol.ClientFactory):
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
)
@implementer(resource.IResource)
class UserStatusTree(resource.Resource):
def __init__(self, service):
resource.Resource.__init__(self)
self.service = service
self.putChild("RPC2", UserStatusXR(self.service))
def render_GET(self, request):
d = self.service.getUsers()
def formatUsers(users):
l = [f'<li><a href="{user}">{user}</a></li>' for user in users]
return "<ul>" + "".join(l) + "</ul>"
d.addCallback(formatUsers)
d.addCallback(request.write)
d.addCallback(lambda _: request.finish())
return server.NOT_DONE_YET
def getChild(self, path, request):
if path == "":
return UserStatusTree(self.service)
else:
return UserStatus(path, 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 render_GET(self, request):
d = self.service.getUser(self.user)
d.addCallback(html.escape)
d.addCallback(lambda m: "<h1>%s</h1>" % self.user + "<p>%s</p>" % m)
d.addCallback(request.write)
d.addCallback(lambda _: request.finish())
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)
@implementer(IFingerService)
class FingerService(service.Service):
def __init__(self, filename):
self.filename = filename
self.users = {}
def _read(self):
self.users.clear()
with open(self.filename, "rb") as f:
for line in f:
user, status = line.split(b":", 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, b"No such user"))
def getUsers(self):
return defer.succeed(list(self.users.keys()))
def startService(self):
self._read()
service.Service.startService(self)
def stopService(self):
service.Service.stopService(self)
self.call.cancel()
# Another back-end
@implementer(IFingerService)
class LocalFingerService(service.Service):
def getUser(self, user):
# need a local finger daemon running for this to work
return utils.getProcessOutput(b"finger", [user])
def getUsers(self):
return defer.succeed([])
application = service.Application("finger", uid=1, gid=1)
f = LocalFingerService()
serviceCollection = service.IServiceCollection(application)
strports.service("tcp:79", IFingerFactory(f)).setServiceParent(serviceCollection)
strports.service("tcp:8000", server.Site(resource.IResource(f))).setServiceParent(
serviceCollection
)
i = IIRCClientFactory(f)
i.nickname = "fingerbot"
internet.ClientService(
endpoints.clientFromString(reactor, "tcp:irc.freenode.org:6667"), i
).setServiceParent(serviceCollection)
我们已经编写了这个,但现在我们用更少的努力获得了更多:网络代码与后端完全分离。
另一个后端:执行标准操作¶
import os
import pwd
from twisted.internet import defer, protocol, reactor, utils
# Yet another back-end
@implementer(IFingerService)
class LocalFingerService(service.Service):
def getUser(self, user):
user = user.strip()
try:
entry = pwd.getpwnam(user)
except KeyError:
return defer.succeed("No such user")
try:
f = open(os.path.join(entry[5], ".plan"))
except OSError:
return defer.succeed("No such user")
with f:
data = f.read()
data = data.strip()
return defer.succeed(data)
def getUsers(self):
return defer.succeed([])
f = LocalFingerService()
完整的源代码在这里
# Do everything properly, and componentize
import html
import os
import pwd
from zope.interface import Interface, implementer
from twisted.application import internet, service, strports
from twisted.internet import defer, endpoints, protocol, reactor, utils
from twisted.protocols import basic
from twisted.python import components
from twisted.web import resource, server, static, xmlrpc
from twisted.words.protocols import irc
class IFingerService(Interface):
def getUser(user):
"""
Return a deferred returning L{bytes}.
"""
def getUsers():
"""
Return a deferred returning a L{list} of L{bytes}.
"""
class IFingerSetterService(Interface):
def setUser(user, status):
"""
Set the user's status to something.
"""
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 + b"\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.
"""
@implementer(IFingerFactory)
class FingerFactoryFromService(protocol.ServerFactory):
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.
"""
@implementer(IFingerSetterFactory)
class FingerSetterFactoryFromService(protocol.ServerFactory):
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.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: f"Status of {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.
"""
@implementer(IIRCClientFactory)
class IRCClientFactoryFromService(protocol.ClientFactory):
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
)
@implementer(resource.IResource)
class UserStatusTree(resource.Resource):
def __init__(self, service):
resource.Resource.__init__(self)
self.service = service
self.putChild("RPC2", UserStatusXR(self.service))
def render_GET(self, request):
d = self.service.getUsers()
def formatUsers(users):
l = [f'<li><a href="{user}">{user}</a></li>' for user in users]
return "<ul>" + "".join(l) + "</ul>"
d.addCallback(formatUsers)
d.addCallback(request.write)
d.addCallback(lambda _: request.finish())
return server.NOT_DONE_YET
def getChild(self, path, request):
if path == "":
return UserStatusTree(self.service)
else:
return UserStatus(path, 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 render_GET(self, request):
d = self.service.getUser(self.user)
d.addCallback(html.escape)
d.addCallback(lambda m: "<h1>%s</h1>" % self.user + "<p>%s</p>" % m)
d.addCallback(request.write)
d.addCallback(lambda _: request.finish())
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)
@implementer(IFingerService)
class FingerService(service.Service):
def __init__(self, filename):
self.filename = filename
self.users = {}
def _read(self):
self.users.clear()
with open(self.filename, "rb") as f:
for line in f:
user, status = line.split(b":", 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, b"No such user"))
def getUsers(self):
return defer.succeed(list(self.users.keys()))
def startService(self):
self._read()
service.Service.startService(self)
def stopService(self):
service.Service.stopService(self)
self.call.cancel()
# Yet another back-end
@implementer(IFingerService)
class LocalFingerService(service.Service):
def getUser(self, user):
user = user.strip()
try:
entry = pwd.getpwnam(user)
except KeyError:
return defer.succeed(b"No such user")
try:
f = open(os.path.join(entry[5], ".plan"))
except OSError:
return defer.succeed(b"No such user")
with f:
data = f.read()
data = data.strip()
return defer.succeed(data)
def getUsers(self):
return defer.succeed([])
application = service.Application("finger", uid=1, gid=1)
f = LocalFingerService()
serviceCollection = service.IServiceCollection(application)
strports.service("tcp:79", IFingerFactory(f)).setServiceParent(serviceCollection)
strports.service("tcp:8000", server.Site(resource.IResource(f))).setServiceParent(
serviceCollection
)
i = IIRCClientFactory(f)
i.nickname = "fingerbot"
internet.ClientService(
endpoints.clientFromString(reactor, "tcp:irc.freenode.org:6667"), i
).setServiceParent(serviceCollection)
没什么好说的,除了现在我们可以疯狂地生成后端。想为 Advogato 做一个后端吗?找出 Twisted 中的 XML-RPC 客户端支持,开始工作吧!