编写 twistd 插件

本文档介绍了如何向 twistd 命令添加子命令,作为简化应用程序部署的一种方式。

本文档的目标读者是那些已经开发了 Twisted 应用程序并需要基于命令行的部署机制的人。

理解本文档需要一些先决条件

  • 需要对 Twisted 插件系统(即 twisted.plugin 模块)有一个基本的了解,但是会提供逐步说明。建议阅读 Twisted 插件系统,特别是“扩展现有程序”部分。

  • twistd 插件中使用了 应用程序 基础设施;特别是,您应该了解如何将程序的功能公开为服务。

  • 为了解析命令行参数,twistd 插件机制依赖于 twisted.python.usage,该机制在 使用 usage.Options 中有介绍。

目标

阅读完本文档后,读者应该能够将使用服务的应用程序公开为 twistd 的子命令,并考虑命令行中传递的任何内容。

twistd 插件的替代方案

twistd 插件机制的主要替代方案是 .tac 文件,它是一个简单的脚本,可与 twistd 的 -y/--python 参数一起使用。twistd 插件机制旨在为您的应用程序提供更可扩展的基于命令行的界面。有关 .tac 文件的更多信息,请参阅文档 使用 Twisted 应用程序框架

创建插件

假设您的项目的目录结构如下

  • MyProject - 顶级目录

    • myproject - Python 包

      • __init__.py

在开发项目期间,Twisted 插件可以从项目中的一个特殊目录加载,假设您的顶级目录最终位于 sys.path 中。创建一个名为 twisted 的目录,其中包含一个名为 plugins 的目录,并将一个名为 myproject_plugin.py 的文件添加到其中。此文件将包含您的插件。请注意,您不能在此目录结构中添加任何 __init__.py 文件,并且插件文件不能命名为 myproject.py(因为这会与您的项目的模块名称冲突)。

在此文件中,定义一个提供 twisted.plugin.IPlugintwisted.application.service.IServiceMaker 接口的对象。

您的 IServiceMaker 提供者的 tapname 属性将用作类似 twistd [subcommand] [args...] 命令中的子命令名称,而 options 属性(应该是 usage.Options 子类)将用于解析给定的参数。

from zope.interface import implementer

from twisted.python import usage
from twisted.plugin import IPlugin
from twisted.application.service import IServiceMaker
from twisted.application import internet

from myproject import MyFactory


class Options(usage.Options):
    optParameters = [["port", "p", 1235, "The port number to listen on."]]


@implementer(IServiceMaker, IPlugin)
class MyServiceMaker(object):
    tapname = "myproject"
    description = "Run this! It'll make your dog happy."
    options = Options

    def makeService(self, options):
        """
        Construct a TCPServer from a factory defined in myproject.
        """
        return internet.TCPServer(int(options["port"]), MyFactory())


# Now construct an object which *provides* the relevant interfaces
# The name of this variable is irrelevant, as long as there is *some*
# name bound to a provider of IPlugin and IServiceMaker.
serviceMaker = MyServiceMaker()

现在运行 twistd --help 应该在可用子命令列表中打印 myproject,后跟我们在插件中指定的描述。假设我们在 myproject 中定义了一个 MyFactory 工厂,那么 twistd -n myproject 将在端口 1235 上启动一个使用该工厂的监听服务器。

在 TAP 中使用 cred

Twisted 附带了一个强大的身份验证框架,可用于您的应用程序。如果您的服务器需要身份验证功能,并且您还没有阅读有关 twisted.cred 的内容,请先阅读。

如果您正在构建 twistd 插件,并且希望支持各种身份验证模式,Twisted 为您的 Options 子类提供了一个易于使用的 mixin:strcred.AuthOptionMixin。以下代码是使用此 mixin 的示例

from twisted.cred import credentials, portal, strcred
from twisted.python import usage
from twisted.plugin import IPlugin
from twisted.application.service import IServiceMaker
from myserver import myservice


class ServerOptions(usage.Options, strcred.AuthOptionMixin):
    # This part is optional; it tells AuthOptionMixin what
    # kinds of credential interfaces the user can give us.
    supportedInterfaces = (credentials.IUsernamePassword,)

    optParameters = [
        ["port", "p", 1234, "Server port number"],
        ["host", "h", "localhost", "Server hostname"]]


@implementer(IServiceMaker, IPlugin)
class MyServerServiceMaker(object):
    tapname = "myserver"
    description = "This server does nothing productive."
    options = ServerOptions

    def makeService(self, options):
        """Construct a service object."""
        # The realm is a custom object that your server defines.
        realm = myservice.MyServerRealm(options["host"])

        # The portal is something Cred can provide, as long as
        # you have a list of checkers that you'll support. This
        # list is provided my AuthOptionMixin.
        portal = portal.Portal(realm, options["credCheckers"])

        # OR, if you know you might get multiple interfaces, and
        # only want to give your application one of them, you
        # also have that option with AuthOptionMixin:
        interface = credentials.IUsernamePassword
        portal = portal.Portal(realm, options["credInterfaces"][interface])

        # The protocol factory is, like the realm, something you implement.
        factory = myservice.ServerFactory(realm, portal)

        # Finally, return a service that will listen for connections.
        return internet.TCPServer(int(options["port"]), factory)


# As in our example above, we have to construct an object that
# provides the IPlugin and IServiceMaker interfaces.
serviceMaker = MyServerServiceMaker()

现在您已经将 TAP 配置为支持我们可能遇到的任何身份验证,您就可以使用它了。以下是如何使用 /etc/passwd 文件进行身份验证来启动服务器的示例。(显然,这在使用 shadow 密码的服务器上不起作用。)

$ twistd myserver --auth passwd:/etc/passwd

有关支持的完整 cred 插件列表,请参阅 twisted.plugins,或使用命令行帮助

$ twistd myserver --help-auth
$ twistd myserver --help-auth-type passwd

使用 Python 包部署您的应用程序

要部署您的应用程序,一种方法是将其打包成 Python 包。为此,您需要编写一个名为 setup.py 的特殊文件,其中包含包的元数据。您需要像这样扩展文件布局

  • MyProject - 顶级目录

    • setup.py - 包的描述文件

      • myproject - Python 包

        • __init__.py

      • twisted

        • 插件

          • myproject_plugins.py - 包含实际插件的 Dropin 文件

from setuptools import setup, find_packages

setup(
    name='MyApplication',
    version='0.1dev',
    # it is necesary to extend the found package list with the twisted.plugin
    # directory. It cannot be automatically detected, because it should not
    # contain a __init__.py file.
    packages=find_packages() + ['twisted.plugins'],
    install_requires=[
        'twisted',
    ],
)

要从目录创建 Python 包,可以使用标准的设置工具

$ python3 setup.py sdist

此命令会在您的项目文件夹中创建一个名为 dist 的目录,其中包含压缩的存档文件 MyApplication-0.1dev.tar.gz。 此存档包含所有代码和指定的其他文件。 此文件可以复制并用于部署。

要安装应用程序,只需使用 pip。 它还会安装 setup.py 中指定的所有需求。

$ pip install MyApplication-0.1dev.tar.gz

有关 Python 中打包的更多信息,请查看 官方 Python 打包用户指南打包的搭便车指南

结论

您现在应该能够

  • 创建一个 twistd 插件

  • 将身份验证整合到您的插件中

  • 从您的开发环境中使用它

  • 正确安装它并在部署中使用它