Finger 演化:给 finger 服务器添加新的特性 (The Evolution of Finger: adding features to the finger service) -- dreamingk [2004-08-09 02:08:21]

介绍 (Introduction)

这是《Twisted 入门 - Finger 演化》的第二部分。

This is the second part of the Twisted tutorial Twisted from Scratch, or The Evolution of Finger.

在教程的这个部分,我们的 finger 服务器将继续进化出新的特性:用户设置 finger 公告的能力;通过 finger 服务向 web 上、向 IRC 上、或通过 XML-RPC 向外界发布这些公告的能力。

In this section of the tutorial, our finger server will continue to sprout features: the ability for users to set finger announces, and using our finger service to send those announcements on the web, on IRC and over XML-RPC.

让本地用户来设置信息 (Setting Message By Local Users)

现在,1079端口空闲出来了;也许我们可以用它运行另外一个服务器,让人们可以设置他们的信息内容。初步设想,它不进行访问控制,所以任何可以登录到这台机器的人都可以设置任何信息──假设这就是我们计划中的需求 :)

如果要测试服务,可以输入下面的命令:

Now that port 1079 is free, maybe we can run on it a different server, one which will let people set their messages. It does no access control, so anyone who can login to the machine can set any message. We assume this is the desired behavior in our case. Testing it can be done by simply:

% nc localhost 1079   # 如果你不知道nc是什么,哪么就 telnet localhost 1079
moshez
Giving a tutorial now, sorry!
^D

   1 # 让我们试试设置公告信息,怎么样?
   2 # But let's try and fix setting away messages, shall we?
   3 from twisted.application import internet, service
   4 from twisted.internet import protocol, reactor, defer
   5 from twisted.protocols import basic
   6 class FingerProtocol(basic.LineReceiver):
   7     def lineReceived(self, user):
   8         self.factory.getUser(user
   9         ).addErrback(lambda _: "Internal error in server"
  10         ).addCallback(lambda m:
  11                       (self.transport.write(m+"\r\n"),
  12                        self.transport.loseConnection()))
  13 
  14 class FingerFactory(protocol.ServerFactory):
  15     protocol = FingerProtocol
  16     def __init__(self, **kwargs): self.users = kwargs
  17     def getUser(self, user):
  18         return defer.succeed(self.users.get(user, "No such user"))
  19 
  20 class FingerSetterProtocol(basic.LineReceiver):
  21     def connectionMade(self): self.lines = []
  22     def lineReceived(self, line): self.lines.append(line)
  23     def connectionLost(self, reason):
  24         self.factory.setUser(*self.lines[:2])
  25         # first line: user    second line: status
  26 
  27 class FingerSetterFactory(protocol.ServerFactory):
  28     protocol = FingerSetterProtocol
  29     def __init__(self, ff): self.setUser = ff.users.__setitem__
  30 
  31 ff = FingerFactory(moshez='Happy and well')
  32 fsf = FingerSetterFactory(ff)
  33 
  34 application = service.Application('finger', uid=1, gid=1)
  35 serviceCollection = service.IServiceCollection(application)
  36 internet.TCPServer(79,ff).setServiceParent(serviceCollection)
  37 internet.TCPServer(1079,fsf).setServiceParent(serviceCollection)

源程序 - listings/finger/finger12.py

用服务让彼此间的依赖健壮 (Use Services to Make Dependencies Sane)

上一个版本里我们在 finger 工厂里建立了一个设置属性用的“钩子”。一般来说,这么做不太好;现在我们将通过把两个工厂视为一个对象来实现它们的均衡。当一个对象不再关联于一个特定的网络服务器时,服务(Service)将会大有用处。现在我们把两个创建工厂的所有职责都转移到服务里。注意,我们并不是使用建立子类的方法:这个服务只是简单的把原来工厂里的方法和属性照搬进来。这样的架构在协议设计方面比原来更优秀了:我们用于协议的两个类都不必做任何修改;而且直到这个教程的最后也不用再变动什么东西了。

The previous version had the setter poke at the innards of the finger factory. It's usually not a good idea: this version makes both factories symmetric by making them both look at a single object. Services are useful for when an object is needed which is not related to a specific network server. Here, we moved all responsibility for manufacturing factories into the service. Note that we stopped subclassing: the service simply puts useful methods and attributes inside the factories. We are getting better at protocol design: none of our protocol classes had to be changed, and neither will have to change until the end of the tutorial.

   1 # 修正不对称的设计
   2 # Fix asymmetry
   3 from twisted.application import internet, service
   4 from twisted.internet import protocol, reactor, defer
   5 from twisted.protocols import basic
   6 
   7 class FingerProtocol(basic.LineReceiver):
   8     def lineReceived(self, user):
   9         self.factory.getUser(user
  10         ).addErrback(lambda _: "Internal error in server"
  11         ).addCallback(lambda m:
  12                       (self.transport.write(m+"\r\n"),
  13                        self.transport.loseConnection()))
  14 
  15 class FingerSetterProtocol(basic.LineReceiver):
  16     def connectionMade(self): self.lines = []
  17     def lineReceived(self, line): self.lines.append(line)
  18     def connectionLost(self,reason): self.factory.setUser(*self.lines[:2])
  19     # first line: user   second line: status
  20 
  21 class FingerService(service.Service):
  22     def __init__(self, *args, **kwargs):
  23         self.parent.__init__(self, *args)
  24         self.users = kwargs
  25     def getUser(self, user):
  26         return defer.succeed(self.users.get(user, "No such user"))
  27     def getFingerFactory(self):
  28         f = protocol.ServerFactory()
  29         f.protocol, f.getUser = FingerProtocol, self.getUser
  30         return f
  31     def getFingerSetterFactory(self):
  32         f = protocol.ServerFactory()
  33         f.protocol, f.setUser = FingerSetterProtocol, self.users.__setitem__
  34         return f
  35 
  36 application = service.Application('finger', uid=1, gid=1)
  37 f = FingerService('finger', moshez='Happy and well')
  38 serviceCollection = service.IServiceCollection(application)
  39 internet.TCPServer(79,f.getFingerFactory()
  40                    ).setServiceParent(serviceCollection)
  41 internet.TCPServer(1079,f.getFingerSetterFactory()
  42                    ).setServiceParent(serviceCollection)

源程序 - listings/finger/finger13.py

读取状态文件 (Read Status File)

这一版本展示了如何从一个集中管理的文件中读取用户消息,而不仅仅是让用户设定它们。我们缓存结果,并每30秒刷新一次。服务对于这种调度性质的任务非常有用。

This version shows how, instead of just letting users set their messages, we can read those from a centrally managed file. We cache results, and every 30 seconds we refresh it. Services are useful for such scheduled tasks.

moshez: happy and well
shawn: alive

/etc/users 示例文件 - listings/finger/etc.users

   1 # 从文件读取资料
   2 # Read from file
   3 from twisted.application import internet, service
   4 from twisted.internet import protocol, reactor, defer
   5 from twisted.protocols import basic
   6 
   7 class FingerProtocol(basic.LineReceiver):
   8     def lineReceived(self, user):
   9         self.factory.getUser(user
  10         ).addErrback(lambda _: "Internal error in server"
  11         ).addCallback(lambda m:
  12                       (self.transport.write(m+"\r\n"),
  13                        self.transport.loseConnection()))
  14 
  15 class FingerService(service.Service):
  16     def __init__(self, filename):
  17         self.users = {}
  18         self.filename = filename
  19     def _read(self):
  20         for line in file(self.filename):
  21             user, status = line.split(':', 1)
  22             user = user.strip()
  23             status = status.strip()
  24             self.users[user] = status
  25         self.call = reactor.callLater(30, self._read)
  26     def startService(self):
  27         self._read()
  28         service.Service.startService(self)
  29     def stopService(self):
  30         service.Service.stopService(self)
  31         self.call.cancel()
  32     def getUser(self, user):
  33         return defer.succeed(self.users.get(user, "No such user"))
  34     def getFingerFactory(self):
  35         f = protocol.ServerFactory()
  36         f.protocol, f.getUser = FingerProtocol, self.getUser
  37         return f
  38 
  39 application = service.Application('finger', uid=1, gid=1)
  40 f = FingerService('/etc/users')
  41 finger = internet.TCPServer(79, f.getFingerFactory())
  42 
  43 finger.setServiceParent(service.IServiceCollection(application))
  44 f.setServiceParent(service.IServiceCollection(application))

源程序 - listings/finger/finger14.py

同时在 web 上发布 (Announce on Web, Too)

同样类型的服务对于其他协议也可以提供有价值的东西。例如,在 twisted.web 中,工厂本身(site)几乎从来不被用来生成子类。取而代之的是,它提供了一种资源,可以通过网络地址(URL)的方式来表现可用的资源树。这种层次结构由 site 来操控,并且可以通过 getChild 来动态重载它。

The same kind of service can also produce things useful for other protocols. For example, in twisted.web, the factory itself (the site) is almost never subclassed -- instead, it is given a resource, which represents the tree of resources available via URLs. That hierarchy is navigated by site, and overriding it dynamically is possible with getChild.

   1 # 从文件读取资料,并把它们发布在 web 上!
   2 # Read from file, announce on the web!
   3 from twisted.application import internet, service
   4 from twisted.internet import protocol, reactor, defer
   5 from twisted.protocols import basic
   6 from twisted.web import resource, server, static
   7 import cgi
   8 
   9 class FingerProtocol(basic.LineReceiver):
  10     def lineReceived(self, user):
  11         self.factory.getUser(user
  12         ).addErrback(lambda _: "Internal error in server"
  13         ).addCallback(lambda m:
  14                       (self.transport.write(m+"\r\n"),
  15                        self.transport.loseConnection()))
  16 
  17 class MotdResource(resource.Resource):
  18 
  19     def __init__(self, users):
  20         self.users = users
  21         resource.Resource.__init__(self)
  22 
  23     # we treat the path as the username
  24     def getChild(self, username, request):
  25         motd = self.users.get(username)
  26         username = cgi.escape(username)
  27         if motd is not None:
  28             motd = cgi.escape(motd)
  29             text = '<h1>%s</h1><p>%s</p>' % (username,motd)
  30         else:
  31             text = '<h1>%s</h1><p>No such user</p>' % username
  32         return static.Data(text, 'text/html')
  33 
  34 class FingerService(service.Service):
  35     def __init__(self, filename):
  36         self.filename = filename
  37         self._read()
  38     def _read(self):
  39         self.users = {}
  40         for line in file(self.filename):
  41             user, status = line.split(':', 1)
  42             user = user.strip()
  43             status = status.strip()
  44             self.users[user] = status
  45         self.call = reactor.callLater(30, self._read)
  46     def getUser(self, user):
  47         return defer.succeed(self.users.get(user, "No such user"))
  48     def getFingerFactory(self):
  49         f = protocol.ServerFactory()
  50         f.protocol, f.getUser = FingerProtocol, self.getUser
  51         f.startService = self.startService
  52         return f
  53 
  54     def getResource(self):
  55         r = MotdResource(self.users)
  56         return r
  57 
  58 application = service.Application('finger', uid=1, gid=1)
  59 f = FingerService('/etc/users')
  60 serviceCollection = service.IServiceCollection(application)
  61 internet.TCPServer(79, f.getFingerFactory()
  62                    ).setServiceParent(serviceCollection)
  63 internet.TCPServer(8000, server.Site(f.getResource())
  64                    ).setServiceParent(serviceCollection)

源程序 - listings/finger/finger15.py

同时在 IRC 上发布 (Announce on IRC, Too)

这是教程里头一回有客户端的代码。IRC 客户端也常常扮演服务器的角色:对网上的事件作出应答。重联客户工厂(reconnecting client factory)可以确保中断的联接被重新建立,它使用智能加强幂指数支持算法(呵呵,这是什么算法,这么长的名字:)。IRC 客户本身很简单:它唯一真正做得事情就是在 connectionMade 函数里从工厂获得昵称。

This is the first time there is client code. IRC clients often act a lot like servers: responding to events from the network. The reconnecting client factory will make sure that severed links will get re-established, with intelligent tweaked exponential back-off algorithms. The IRC client itself is simple: the only real hack is getting the nickname from the factory in connectionMade.

   1 # 从文件读取数据,发布到 web 和 IRC
   2 # Read from file, announce on the web, irc
   3 from twisted.application import internet, service
   4 from twisted.internet import protocol, reactor, defer
   5 from twisted.protocols import basic, irc
   6 from twisted.web import resource, server, static
   7 import cgi
   8 class FingerProtocol(basic.LineReceiver):
   9     def lineReceived(self, user):
  10         self.factory.getUser(user
  11         ).addErrback(lambda _: "Internal error in server"
  12         ).addCallback(lambda m:
  13                       (self.transport.write(m+"\r\n"),
  14                        self.transport.loseConnection()))
  15 class FingerSetterProtocol(basic.LineReceiver):
  16     def connectionMade(self): self.lines = []
  17     def lineReceived(self, line): self.lines.append(line)
  18     def connectionLost(self,reason): self.factory.setUser(*self.lines[:2])
  19 class IRCReplyBot(irc.IRCClient):
  20     def connectionMade(self):
  21         self.nickname = self.factory.nickname
  22         irc.IRCClient.connectionMade(self)
  23     def privmsg(self, user, channel, msg):
  24         user = user.split('!')[0]
  25         if self.nickname.lower() == channel.lower():
  26             self.factory.getUser(msg
  27             ).addErrback(lambda _: "Internal error in server"
  28             ).addCallback(lambda m: irc.IRCClient.msg(self, user, msg+': '+m))
  29 
  30 class FingerService(service.Service):
  31     def __init__(self, filename):
  32         self.filename = filename
  33         self._read()
  34     def _read(self):
  35         self.users = {}
  36         for line in file(self.filename):
  37             user, status = line.split(':', 1)
  38             user = user.strip()
  39             status = status.strip()
  40             self.users[user] = status
  41         self.call = reactor.callLater(30, self._read)
  42     def getUser(self, user):
  43         return defer.succeed(self.users.get(user, "No such user"))
  44     def getFingerFactory(self):
  45         f = protocol.ServerFactory()
  46         f.protocol, f.getUser = FingerProtocol, self.getUser
  47         return f
  48     def getResource(self):
  49         r = resource.Resource()
  50         r.getChild = (lambda path, request:
  51                       static.Data('<h1>%s</h1><p>%s</p>' %
  52                       tuple(map(cgi.escape,
  53                       [path,self.users.get(path,
  54                       "No such user <p/> usage: site/user")])),
  55                       'text/html'))
  56         return r
  57 
  58     def getIRCBot(self, nickname):
  59         f = protocol.ReconnectingClientFactory()
  60         f.protocol,f.nickname,f.getUser = IRCReplyBot,nickname,self.getUser
  61         return f
  62 
  63 application = service.Application('finger', uid=1, gid=1)
  64 f = FingerService('/etc/users')
  65 serviceCollection = service.IServiceCollection(application)
  66 internet.TCPServer(79, f.getFingerFactory()
  67                    ).setServiceParent(serviceCollection)
  68 internet.TCPServer(8000, server.Site(f.getResource())
  69                    ).setServiceParent(serviceCollection)
  70 internet.TCPClient('irc.freenode.org', 6667, f.getIRCBot('fingerbot')
  71                    ).setServiceParent(serviceCollection)

源程序 - listings/finger/finger16.py

添加 XML-RPC 支持 (Add XML-RPC Support)

在 twisted 中,只是把对 XML-RPC 支持当作另外一个资源来处理。这个资源通过 render() 方法仍然可以支持 GET 访问,但这个方法一般都预留为一个还没有实现的接口函数。注意:有可能会从 XML-RPC 方法中返回延期对象。当然,客户端是只有等到这个延期对象被触发才会获得应答的。

In Twisted, XML-RPC support is handled just as though it was another resource. That resource will still support GET calls normally through render(), but that is usually left unimplemented. Note that it is possible to return deferreds from XML-RPC methods. The client, of course, will not get the answer until the deferred is triggered.

   1 # 从文件读取资料,发布到 web、IRC、XML-RPC
   2 # Read from file, announce on the web, irc, xml-rpc
   3 from twisted.application import internet, service
   4 from twisted.internet import protocol, reactor, defer
   5 from twisted.protocols import basic, irc
   6 from twisted.web import resource, server, static, xmlrpc
   7 import cgi
   8 class FingerProtocol(basic.LineReceiver):
   9     def lineReceived(self, user):
  10         self.factory.getUser(user
  11         ).addErrback(lambda _: "Internal error in server"
  12         ).addCallback(lambda m:
  13                       (self.transport.write(m+"\r\n"),
  14                        self.transport.loseConnection()))
  15 class FingerSetterProtocol(basic.LineReceiver):
  16     def connectionMade(self): self.lines = []
  17     def lineReceived(self, line): self.lines.append(line)
  18     def connectionLost(self,reason): self.factory.setUser(*self.lines[:2])
  19 class IRCReplyBot(irc.IRCClient):
  20     def connectionMade(self):
  21         self.nickname = self.factory.nickname
  22         irc.IRCClient.connectionMade(self)
  23     def privmsg(self, user, channel, msg):
  24         user = user.split('!')[0]
  25         if self.nickname.lower() == channel.lower():
  26             self.factory.getUser(msg
  27             ).addErrback(lambda _: "Internal error in server"
  28             ).addCallback(lambda m: irc.IRCClient.msg(self, user, msg+': '+m))
  29 
  30 class FingerService(service.Service):
  31     def __init__(self, filename):
  32         self.filename = filename
  33         self._read()
  34     def _read(self):
  35         self.users = {}
  36         for line in file(self.filename):
  37             user, status = line.split(':', 1)
  38             user = user.strip()
  39             status = status.strip()
  40             self.users[user] = status
  41         self.call = reactor.callLater(30, self._read)
  42     def getUser(self, user):
  43         return defer.succeed(self.users.get(user, "No such user"))
  44     def getFingerFactory(self):
  45         f = protocol.ServerFactory()
  46         f.protocol, f.getUser = FingerProtocol, self.getUser
  47         return f
  48     def getResource(self):
  49         r = resource.Resource()
  50         r.getChild = (lambda path, request:
  51                       static.Data('<h1>%s</h1><p>%s</p>' %
  52                       tuple(map(cgi.escape,
  53                       [path,self.users.get(path, "No such user")])),
  54                       'text/html'))
  55         x = xmlrpc.XMLRPC()
  56         x.xmlrpc_getUser = self.getUser
  57         r.putChild('RPC2', x)
  58         return r
  59     def getIRCBot(self, nickname):
  60         f = protocol.ReconnectingClientFactory()
  61         f.protocol,f.nickname,f.getUser = IRCReplyBot,nickname,self.getUser
  62         return f
  63 
  64 application = service.Application('finger', uid=1, gid=1)
  65 f = FingerService('/etc/users')
  66 serviceCollection = service.IServiceCollection(application)
  67 internet.TCPServer(79, f.getFingerFactory()
  68                    ).setServiceParent(serviceCollection)
  69 internet.TCPServer(8000, server.Site(f.getResource())
  70                    ).setServiceParent(serviceCollection)
  71 internet.TCPClient('irc.freenode.org', 6667, f.getIRCBot('fingerbot')
  72                    ).setServiceParent(serviceCollection)

源程序 - listings/finger/finger17.py

一个用来测试 XMLRPC finger 的简易客户端 (A simple client to test the XMLRPC finger:)

   1 # 测试 xmlrpc finger
   2 # testing xmlrpc finger
   3 
   4 import xmlrpclib
   5 server = xmlrpclib.Server('http://127.0.0.1:8000/RPC2')
   6 print server.getUser('moshez')

源程序 - listings/finger/fingerXRclient.py

返回目录 --> TwistedTUT

twistedTUT02 (last edited 2009-12-25 07:14:37 by localhost)