Finger 的演变:制作 Finger 库

简介

这是 Twisted 教程的第十部分 从头开始使用 Twisted,或 Finger 的演变

在本部分中,我们将启动 Finger 服务的应用程序代码与定义 Finger 服务的库代码分离,并将应用程序放置在 Twisted 应用程序配置(.tac)文件中。我们还将配置(例如 HTML 模板)移到单独的文件中。使用 .tac 和 twistd 进行配置和部署在 使用 Twisted 应用程序框架 中介绍。

组织

现在这段代码虽然非常模块化且设计良好,但组织不当。application= 以上的所有内容都应该放在一个模块中,而 HTML 模板应该放在单独的文件中。

我们可以使用 templateFiletemplateDirectory 属性来指示每个页面的 HTML 模板文件以及在哪里查找它。

organized-finger.tac

# organized-finger.tac
# eg:  twistd -ny organized-finger.tac

import finger

from twisted.application import internet, service, strports
from twisted.internet import defer, endpoints, protocol, reactor
from twisted.python import log
from twisted.spread import pb
from twisted.web import resource, server

application = service.Application("finger", uid=1, gid=1)
f = finger.FingerService("/etc/users")
serviceCollection = service.IServiceCollection(application)
strports.service("tcp:79", finger.IFingerFactory(f)).setServiceParent(serviceCollection)

site = server.Site(resource.IResource(f))
strports.service(
    "tcp:8000",
    site,
).setServiceParent(serviceCollection)

strports.service(
    "ssl:port=443:certKey=cert.pem:privateKey=key.pem", site
).setServiceParent(serviceCollection)

i = finger.IIRCClientFactory(f)
i.nickname = "fingerbot"
internet.ClientService(
    endpoints.clientFromString(reactor, "tcp:irc.freenode.org:6667"), i
).setServiceParent(serviceCollection)

strports.service(
    "tcp:8889", pb.PBServerFactory(finger.IPerspectiveFinger(f))
).setServiceParent(serviceCollection)

请注意,我们的程序现在已经完全分离。我们有

  • 代码(在模块中)

  • 配置(上面的文件)

  • 演示(模板)

  • 内容(/etc/users

  • 部署(twistd)

原型不需要这种级别的分离,因此我们之前的示例都捆绑在一起。但是,真正的应用程序需要。值得庆幸的是,如果我们正确编写代码,很容易实现良好的部分分离。

简单配置

我们还可以使用 makeService 方法为常见情况提供简单配置,这也有助于以后构建 .tac 文件

finger_config.py

# Easy configuration
# makeService from finger module


def makeService(config):
    # finger on port 79
    s = service.MultiService()
    f = FingerService(config["file"])
    h = strports.service("tcp:79", IFingerFactory(f))
    h.setServiceParent(s)

    # website on port 8000
    r = resource.IResource(f)
    r.templateDirectory = config["templates"]
    site = server.Site(r)
    j = strports.service("tcp:8000", site)
    j.setServiceParent(s)

    # ssl on port 443
    if config.get("ssl"):
        k = strports.service("ssl:port=443:certKey=cert.pem:privateKey=key.pem", site)
        k.setServiceParent(s)

    # irc fingerbot
    if "ircnick" in config:
        i = IIRCClientFactory(f)
        i.nickname = config["ircnick"]
        ircserver = config["ircserver"]
        b = internet.ClientService(
            endpoints.HostnameEndpoint(reactor, ircserver, 6667), i
        )
        b.setServiceParent(s)

    # Pespective Broker on port 8889
    if "pbport" in config:
        m = internet.StreamServerEndpointService(
            endpoints.TCP4ServerEndpoint(reactor, int(config["pbport"])),
            pb.PBServerFactory(IPerspectiveFinger(f)),
        )
        m.setServiceParent(s)

    return s

现在我们可以编写更简单的文件

simple-finger.tac

# simple-finger.tac
# eg:  twistd -ny simple-finger.tac

import finger

from twisted.application import service

options = {
    "file": "/etc/users",
    "templates": "/usr/share/finger/templates",
    "ircnick": "fingerbot",
    "ircserver": "irc.freenode.net",
    "pbport": 8889,
    "ssl": "ssl=0",
}

ser = finger.makeService(options)
application = service.Application("finger", uid=1, gid=1)
ser.setServiceParent(service.IServiceCollection(application))
% twistd -ny simple-finger.tac

注意:Finger 用户仍然拥有最终的权力:他们可以使用 makeService,或者如果他们有特殊需求,可以使用更低级的接口(也许是在其他端口上的 IRC 服务器?也许我们希望非 SSL Web 服务器仅在本地监听?等等)。这是一个重要的设计原则:永远不要强制使用抽象层;而是允许使用抽象层。

关于设计的意大利面理论

意大利面

每段代码都与其他每段代码交互(可以使用 GOTO、函数、对象实现)。

千层面

代码具有精心设计的层级结构。理论上,每一层都是独立的。但是,低级层通常难以使用,而高级层依赖于低级层。

意大利饺子

代码的每个部分本身都是有用的。各个部分之间有一层薄薄的接口(酱汁)。每个部分都可以用在其他地方。

…但有时,用户只想点“意大利饺子”,因此在所有这些之上添加一层粗粒度且易于定义的抽象层可能是有用的。