Finger演化: 创建一个简单的finger服务 -- dreamingk [2004-08-09 02:06:10]

1. 介绍(Introduction)

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

这是《Twisted入门--Finger演化》教程的第一章。

By the end of this section of the tutorial, our finger server will answer TCP finger requests on port 1079, and will read data from the web.

在本章的结尾,我们的finger服务器会在1079端口响应TCP的finger请求,并且从web上读取数据。

2. 拒绝连接(Refuse Connections)

   1 #Source listing - listings/finger/finger01.py
   2 
   3 from twisted.internet import reactor
   4 reactor.run()

This example only runs the reactor. Nothing at all will happen until we interrupt the program. It will consume almost no CPU resources. Not very useful, perhaps -- but this is the skeleton inside which the Twisted program will grow.

这个例子只运行了反应器(reactor)。所以什么也不会发生。它几乎不会耗费任何处理器(cpu)资源。也许没什么用,不过这是Twisted程序成长的骨架。

2.1. 反应器(Reactor)

You don't call Twisted, Twisted calls you. The reactor is Twisted's main event loop. There is exactly one reactor in any running Twisted application. Once started it loops over and over again, responding to network events, and making scheduled calls to code.

不是你的程序调用Twisted,而是Twisted来调用你的程序。反应器是Twisted的主事件循环。每一个运行着的Twisted程序都有一个反应器,一旦启动它,它就不停循环,响应网络事件,然后执行预定的调用。

3. 什么也不做(Do Nothing)

   1 #Source listing - listings/finger/finger02.py
   2 
   3 from twisted.internet import protocol, reactor
   4 class FingerProtocol(protocol.Protocol):
   5     pass
   6 class FingerFactory(protocol.ServerFactory):
   7     protocol = FingerProtocol
   8 reactor.listenTCP(1079, FingerFactory())
   9 reactor.run()

Here, we start listening on port 1079. The 1079 is a reminder that eventually, we want to run on port 79, the standard port for finger servers. We define a protocol which does not respond to any events. Thus, connections to 1079 will be accepted, but the input ignored.

这里,我们开始监听1079端口。1079端口只是临时使用,最终我们会使用79端口,79端口是finger服务器的标准端口。我们定义了一个协议(protocol),它不响应任何事件。因此可以连接到1079端口,但是任何输入都得不到响应。

4. 断开连接(Drop Connections)

   1 #Source listing - listings/finger/finger03.py
   2 
   3 from twisted.internet import protocol, reactor
   4 class FingerProtocol(protocol.Protocol):
   5     def connectionMade(self):
   6         self.transport.loseConnection()
   7 class FingerFactory(protocol.ServerFactory):
   8     protocol = FingerProtocol
   9 reactor.listenTCP(1079, FingerFactory())
  10 reactor.run()

Here we add to the protocol the ability to respond to the event of beginning a connection -- by terminating it. Perhaps not an interesting behavior, but it is already close to behaving according to the letter of the protocol. After all, there is no requirement to send any data to the remote connection in the standard. The only problem, as far as the standard is concerned, is that we terminate the connection too soon. A client which is slow enough will see his send() of the username result in an error.

现在我们改进了协议(protocol), 使它有能力对建立连接的事件作出反应——断开连接。也许不是一个有趣的做法,但是反应器已经能够根据协议(protocol)来作出反应。毕竟,标准中也没有规定一定要在建立连接的时候向远端发数据。唯一的问题是,我们关闭连接太快。一个很慢的客户端会发现它用send()函数发送用户名时会出现错误。

5. 读用户名,断开连接(Read Username, Drop Connections)

   1 #Source listing - listings/finger/finger04.py
   2 
   3 from twisted.internet import protocol, reactor
   4 from twisted.protocols import basic
   5 class FingerProtocol(basic.LineReceiver):
   6     def lineReceived(self, user):
   7         self.transport.loseConnection()
   8 class FingerFactory(protocol.ServerFactory):
   9     protocol = FingerProtocol
  10 reactor.listenTCP(1079, FingerFactory())
  11 reactor.run()

Here we make FingerProtocol inherit from LineReceiver, so that we get data-based events on a line-by-line basis. We respond to the event of receiving the line with shutting down the connection.

现在我们从LineReveiver类继承了一个新类叫FingerProtocol, 这样当数据一行一行到达时,就会产生事件,而我们响应事件,然后关闭连接。

Congratulations, this is the first standard-compliant version of the code. However, usually people actually expect some data about users to be transmitted.

恭喜你,这是第一个符合finger标准的代码版本。但是,人们通常希望能得到用户的有关信息。

6. 读用户名,输出错误信息,断开连接(Read Username, Output Error, Drop Connections)

   1 #Source listing - listings/finger/finger05.py
   2 
   3 from twisted.internet import protocol, reactor
   4 from twisted.protocols import basic
   5 class FingerProtocol(basic.LineReceiver):
   6     def lineReceived(self, user):
   7         self.transport.write("No such user\r\n")
   8         self.transport.loseConnection()
   9 class FingerFactory(protocol.ServerFactory):
  10     protocol = FingerProtocol
  11 reactor.listenTCP(1079, FingerFactory())
  12 reactor.run()

Finally, a useful version. Granted, the usefulness is somewhat limited by the fact that this version only prints out a No such user message. It could be used for devastating effect in honey-pots, of course.

最终我们得到了一个有用的版本。但是这个版本仅仅返回“没有这个用户”的信息,所以不是很有用。当然它也许可以用来搞恶作剧。

7. 从一个空的工厂(Factory)中输出(Output From Empty Factory)

   1 #Source listing - listings/finger/finger06.py
   2 
   3 # Read username, output from empty factory, drop connections
   4 from twisted.internet import protocol, reactor
   5 from twisted.protocols import basic
   6 class FingerProtocol(basic.LineReceiver):
   7     def lineReceived(self, user):
   8         self.transport.write(self.factory.getUser(user)+"\r\n")
   9         self.transport.loseConnection()
  10 class FingerFactory(protocol.ServerFactory):
  11     protocol = FingerProtocol
  12     def getUser(self, user): return "No such user"
  13 reactor.listenTCP(1079, FingerFactory())
  14 reactor.run()

The same behavior, but finally we see what usefulness the factory has: as something that does not get constructed for every connection, it can be in charge of the user database. In particular, we won't have to change the protocol if the user database back-end changes.

这个例子和上面的例子有同样的行为,但是最终我们会看到工厂(Factory)有什么用处:它不需要为每次连接都创建一次,它可以来管理用户数据库。尤其是当后台用户数据库发生了变化时,我们不用改变协议(protocol)。

8. 从一个非空的工厂(Factory)中输出(Output from Non-empty Factory)

   1 #Source listing - listings/finger/finger07.py
   2 
   3 # Read username, output from non-empty factory, drop connections
   4 from twisted.internet import protocol, reactor
   5 from twisted.protocols import basic
   6 class FingerProtocol(basic.LineReceiver):
   7     def lineReceived(self, user):
   8         self.transport.write(self.factory.getUser(user)+"\r\n")
   9         self.transport.loseConnection()
  10 class FingerFactory(protocol.ServerFactory):
  11     protocol = FingerProtocol
  12     def __init__(self, **kwargs): self.users = kwargs
  13     def getUser(self, user):
  14         return self.users.get(user, "No such user")
  15 reactor.listenTCP(1079, FingerFactory(moshez='Happy and well'))
  16 reactor.run()

Finally, a really useful finger database. While it does not supply information about logged in users, it could be used to distribute things like office locations and internal office numbers. As hinted above, the factory is in charge of keeping the user database: note that the protocol instance has not changed. This is starting to look good: we really won't have to keep tweaking our protocol.

最终,一个有用的finger数据库出现了。虽然它不能提供登录用户的任何信息,但是可以发布一些类似办公地点,内部办公室编号的信息。 就像上面指出的那样,工厂(factory)负责管理用户数据库,而协议(protocol)实例却没有发生改变。这看起来不错,因为我们真的不用老是改协议(protocol)了。

9. 使用 Deferreds(Use Deferreds)

   1 #Source listing - listings/finger/finger08.py
   2 
   3 # Read username, output from non-empty factory, drop connections
   4 # Use deferreds, to minimize synchronicity assumptions
   5 from twisted.internet import protocol, reactor, defer
   6 from twisted.protocols import basic
   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 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 reactor.listenTCP(1079, FingerFactory(moshez='Happy and well'))
  20 reactor.run()

But, here we tweak it just for the hell of it. Yes, while the previous version worked, it did assume the result of getUser is always immediately available. But what if instead of an in memory database, we would have to fetch result from a remote Oracle? Or from the web? Or, or...

但是,现在我们改了它(协议), 仅仅是为了好玩。是的,尽管上一个版本可以工作,但那是在假设“getUser”操作总是能立即执行完的情况下。假如我们需要从另一台机器上运行的Oracle数据库取数据,或者是从web上和其它地方,我们该怎么办呢?

10. 在本地运行'finger'(Run 'finger' Locally)

   1 #Source listing - listings/finger/finger09.py
   2 
   3 # Read username, output from factory interfacing to OS, drop connections
   4 from twisted.internet import protocol, reactor, defer, utils
   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 class FingerFactory(protocol.ServerFactory):
  14     protocol = FingerProtocol
  15     def getUser(self, user):
  16         return utils.getProcessOutput("finger", [user])
  17 reactor.listenTCP(1079, FingerFactory())
  18 reactor.run()

...from running a local command? Yes, this version runs finger locally with whatever arguments it is given, and returns the standard output. This is probably insecure, so you probably don't want a real server to do this without a lot more validation of the user input. This will do exactly what the standard version of the finger server does.

...来自于一个本地运行的命令(程序)? 是的,这个版本在本地运行系统提供的finger服务,并且传给它任何参数,然后返回它的标准输出。这也许不够安全,所以你可能不会要一个真正运行的服务器在不进行大量的用户输入检查的情况下就提供这样的服务。这个版本确实做到了标准版的finger服务器应该做的了。

11. 从web上读取信息(Read Status from the Web)

The web. That invention which has infiltrated homes around the world finally gets through to our invention. Here we use the built-in Twisted web client, which also returns a deferred. Finally, we manage to have examples of three different database back-ends, which do not change the protocol class. In fact, we will not have to change the protocol again until the end of this tutorial: we have achieved, here, one truly usable class.

web,这个渗透到全世界千千万万家庭中的发明最终结合到我们的程序中来了,这里我们使用了Twisted内置(built in)的web客户端组件,它同样也返回一个deferred对象。 最后,我们展示了如何在不修改协议(protocol)类的情况下,实现三个不同的数据库后端。实际上,直到这个教程结束,我们都不用修改协议(protocol)类,我们已经有了一个真正可用的类。

   1 #Source listing - listings/finger/finger10.py
   2 
   3 # Read username, output from factory interfacing to web, drop connections
   4 from twisted.internet import protocol, reactor, defer, utils
   5 from twisted.protocols import basic
   6 from twisted.web import client
   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 class FingerFactory(protocol.ServerFactory):
  15     protocol = FingerProtocol
  16     def __init__(self, prefix): self.prefix=prefix
  17     def getUser(self, user):
  18         return client.getPage(self.prefix+user)
  19 reactor.listenTCP(1079, FingerFactory(prefix='http://livejournal.com/~'))
  20 reactor.run()

12. 使用Application对象(Use Application)

Up until now, we faked. We kept using port 1079, because really, who wants to run a finger server with root privileges? Well, the common solution is privilege shedding: after binding to the network, become a different, less privileged user. We could have done it ourselves, but Twisted has a built-in way to do it. We will create a snippet as above, but now we will define an application object. That object will have uid and gid attributes. When running it (later we will see how) it will bind to ports, shed privileges and then run.

直到现在我们一直在使用1079端口,而不是标准的79端口,是因为没有谁希望在root权限下运行一个finger服务器。通常的解决办法是:在绑定到端口以后,成为另一个权限较低的用户。我们可以自己做这件事,但是Twisted提供了一种内置(built in)的方法。我们可以使用上面那段代码,但是现在我们会定义一个Application对象,它有uid和gid的属性。当我们运行这个Appliction对象的时候,它会绑定(bind)到端口,改变用户权限然后运行。

After saving the next example (finger11.py) as finger.tac, read on to find out how to run this code using the twistd utility.

把下一个例子程序另存为finger.tac,接着往下读,你会知道如何用Twisted提供的工具来运行这个文件。

   1 #Source listing - listings/finger/finger11.py
   2 
   3 # Read username, output from non-empty factory, drop connections
   4 # Use deferreds, to minimize synchronicity assumptions
   5 # Write application. Save in 'finger.tpy'
   6 from twisted.application import internet, service
   7 from twisted.internet import protocol, reactor, defer
   8 from twisted.protocols import basic
   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 class FingerFactory(protocol.ServerFactory):
  17     protocol = FingerProtocol
  18     def __init__(self, **kwargs): self.users = kwargs
  19     def getUser(self, user):
  20         return defer.succeed(self.users.get(user, "No such user"))
  21 
  22 application = service.Application('finger', uid=1, gid=1)
  23 factory = FingerFactory(moshez='Happy and well')
  24 internet.TCPServer(79, factory).setServiceParent(
  25     service.IServiceCollection(application))

13. twistd(twisted)

This is how to run Twisted Applications -- files which define an 'application'. twistd (TWISTed Daemonizer) does everything a daemon can be expected to -- shuts down stdin/stdout/stderr, disconnects from the terminal and can even change runtime directory, or even the root filesystems. In short, it does everything so the Twisted application developer can concentrate on writing his networking code.

这段代码展示了如何用twisted来运行定义了Twisted Application对象的.tac文件。twisted(Twisted式的daemon)实现了一个daemon应该做的全部工作――关闭stdin/stdout/stderr,断开与终端的连接,甚至可以改变运行时目录和根(root)文件系统。总之有它在,你就只管写你的网络部分代码吧。

root% twistd -ny finger.tac # 和以前一样
root% twistd -y finger.tac # 守护进程化,保存pid到twistd.pid文件中去
root% twistd -y finger.tac --pidfile=finger.pid
root% twistd -y finger.tac --rundir=/
root% twistd -y finger.tac --chroot=/var
root% twistd -y finger.tac -l /var/log/finger.log
root% twistd -y finger.tac --syslog # 转向log到syslog中
root% twistd -y finger.tac --syslog --prefix=twistedfinger # 使用指定的路径

Version: 1.3.0

return index-->TwistedTUT