配置和使用 Twisted Web 服务器

Twisted Web 开发

Twisted Web 提供实现 IResource 接口的 Python 对象。

../../_images/web-process.png

主要概念

  • 站点对象 负责创建 HTTPChannel 实例以解析 HTTP 请求并开始对象查找过程。它们包含根资源,该资源表示站点上的 URL /

  • 资源 对象表示单个 URL 段。该 IResource 接口描述了资源对象必须实现的方法才能参与对象发布过程。

  • 资源树 是资源对象的排列,形成资源树。从根资源对象开始,资源对象的树定义了将有效的 URL。

  • .rpy 脚本 是 twisted.web 静态文件服务器将执行的 Python 脚本,类似于 CGI。但是,与 CGI 不同,它们必须创建一个资源对象,该对象将在访问 URL 时呈现。

  • 资源渲染 发生在 Twisted Web 找到叶子资源对象时。资源可以返回 html 字符串或写入请求对象。

  • 会话 对象允许您在多个请求之间存储信息。每个使用该系统的浏览器都有一个唯一的会话实例。

Twisted Web 服务器通过 Twisted Daemonizer 启动,如下所示

% twistd web

站点对象

站点对象充当监听 HTTP 请求的端口与根资源对象之间的粘合剂。

使用 twistd -n web --path /foo/bar/baz 时,将创建一个站点对象,其根资源从给定路径提供文件。

您也可以手动创建一个 Site 实例,并向其传递一个 Resource 对象,该对象将用作站点的根。

from twisted.web import server, resource
from twisted.internet import reactor, endpoints

class Simple(resource.Resource):
    isLeaf = True
    def render_GET(self, request):
        return b"<html>Hello, world!</html>"

site = server.Site(Simple())
endpoint = endpoints.TCP4ServerEndpoint(reactor, 8080)
endpoint.listen(site)
reactor.run()

资源对象

Resource 对象表示站点的单个 URL 段。在 URL 解析期间,将对当前 Resource 调用 getChild 以生成下一个 Resource 对象。

当到达叶子资源时,无论是由于没有更多 URL 段还是资源的 isLeaf 设置为 True,都会通过调用 render(request) 来呈现叶子资源。有关此内容的更多信息,请参见下面的“资源渲染”。

在资源定位过程中,已处理的 URL 段和尚未处理的 URL 段在 request.prepathrequest.postpath 中可用。

资源可以通过查看 request.prepath(一个 URL 段字符串列表)来了解其在 URL 树中的位置。

资源可以通过查看 request.postpath 来了解在其之后将处理哪些路径段。

如果 URL 以斜杠结尾,例如 http://example.com/foo/bar/,则最终 URL 段将为空字符串。因此,资源可以知道它们是使用或不使用最终斜杠请求的。

这是一个简单的资源对象

from twisted.web.resource import Resource

class Hello(Resource):
    isLeaf = True
    def getChild(self, name, request):
        if name == '':
            return self
        return Resource.getChild(self, name, request)

    def render_GET(self, request):
        output = "Hello, world! I am located at {}.".format(request.prepath)
        return output.encode("utf8")

resource = Hello()

资源树

可以使用 putChild 将资源排列成树。 putChild 将资源实例放入另一个资源实例中,使其在给定的路径段名称处可用

root = Hello()
root.putChild(b'fred', Hello())
root.putChild(b'bob', Hello())

如果此根资源作为站点实例的根提供服务,则以下 URL 将全部有效

  • http://example.com/

  • http://example.com/fred

  • http://example.com/bob

  • http://example.com/fred/

  • http://example.com/bob/

.rpy 脚本

扩展名为 .rpy 的文件是 Python 脚本,当放置在 Twisted Web 提供服务的目录中时,将在通过 Web 访问时执行。

一个 .rpy 脚本必须定义一个变量 resource,它是将呈现请求的资源对象。

.rpy 文件非常适合快速开发和原型设计。由于它们在每个 Web 请求时都会执行,因此在 .rpy 中定义资源子类将使您只需刷新页面即可查看对类的更改结果

from twisted.web.resource import Resource

class MyResource(Resource):
    def render_GET(self, request):
        return b"<html>Hello, world!</html>"

resource = MyResource()

但是,通常最好在 Python 模块中定义资源子类。为了使模块中的更改可见,您必须重新启动 Python 进程或重新加载模块

import myresource

## Comment out this line when finished debugging
reload(myresource)

resource = myresource.MyResource()

创建一个提供服务的 Twisted Web 服务器很容易

% twistd -n web --path /Users/dsp/Sites

资源渲染

资源渲染发生在 Twisted Web 找到叶子资源对象来处理 Web 请求时。资源的 render 方法可以执行各种操作来生成将发送回浏览器的输出

  • 返回一个字符串

  • 调用 request.write(b"stuff") 多次,然后调用 request.finish() 并返回 server.NOT_DONE_YET(这具有欺骗性,因为您实际上已经完成了请求,但这是正确的方法)

  • 请求一个 Deferred,返回 server.NOT_DONE_YET,并在稍后在 Deferred 的回调中调用 request.write("stuff")request.finish()

Resource 类(通常是人们的资源类子类)具有 render 的便捷默认实现。它将调用名为 self.render_METHOD 的方法,其中“METHOD”是用于请求此资源的任何 HTTP 方法。示例:request_GET、request_POST、request_HEAD 等等。建议您让您的资源类子类化 Resource 并实现 render_METHOD 方法而不是 render 本身。请注意,对于某些资源,request_POST = request_GET 可能是可取的,以防人们希望处理传递给资源的参数,无论它们使用的是 GET (?foo=bar&baz=quux 等等) 还是 POST。

请求编码器

当使用 Resource 时,可以指定使用 EncodingResourceWrapper 并传递一个编码器工厂列表来包装它。当处理请求时,会调用编码器工厂,并可能返回一个编码器。默认情况下,Twisted 提供了 GzipEncoderFactory,它管理标准 gzip 压缩。您可以这样使用它

from twisted.web.server import Site, GzipEncoderFactory
from twisted.web.resource import Resource, EncodingResourceWrapper
from twisted.internet import reactor, endpoints

class Simple(Resource):
    isLeaf = True
    def render_GET(self, request):
        return b"<html>Hello, world!</html>"

resource = Simple()
wrapped = EncodingResourceWrapper(resource, [GzipEncoderFactory()])
site = Site(wrapped)
endpoint = endpoints.TCP4ServerEndpoint(reactor, 8080)
endpoint.listen(site)
reactor.run()

在 SSL 服务的资源上使用压缩,用户可以影响内容,这会导致信息泄露,因此请谨慎使用哪些资源使用请求编码器。

请注意,每个请求只能使用一个编码器:第一个返回对象的编码器工厂将被使用,因此它们的指定顺序很重要。

会话

HTTP 是一种无状态协议;每个请求-响应都被视为一个独立的单元,仅通过请求的 URL 与任何其他请求区分开来。随着 90 年代中期 Cookie 的出现,动态 Web 服务器获得了通过向浏览器发送 Cookie 来区分来自不同浏览器会话的请求的能力。然后,浏览器在向 Web 服务器发出请求时会发送此 Cookie,允许服务器跟踪哪些请求来自哪个浏览器会话。

Twisted Web 提供了这种浏览器跟踪行为的抽象,称为会话对象。调用 request.getSession() 检查是否已设置会话 Cookie;如果没有,它会创建一个唯一的会话 ID,创建一个会话对象,将其存储在站点中,并将其返回。如果会话对象已存在,则返回相同的会话对象。这样,您可以在会话对象中存储特定于会话的数据。

../../_images/web-session.png

代理和反向代理

代理是服务器的通用术语,它充当客户端和其他服务器之间的中介。

Twisted 支持两种主要的代理变体:ProxyReverseProxy

代理

代理将客户端发出的请求转发到目标服务器。代理通常位于客户端的内部网络或互联网上,并且具有许多用途,包括缓存、数据包过滤、审计和绕过对 Web 内容的本地访问限制。

以下是一个简单但完整的 Web 代理的示例

from twisted.web import proxy, http
from twisted.internet import reactor, endpoints

class ProxyFactory(http.HTTPFactory):
    def buildProtocol(self, addr):
        return proxy.Proxy()

endpoint = endpoints.TCP4ServerEndpoint(reactor, 8080)
endpoint.listen(ProxyFactory())
reactor.run()

运行此代理后,您可以将 Web 浏览器配置为使用 localhost:8080 作为代理。这样做之后,在浏览网页时,所有请求都将通过此代理。

Proxy 继承自 http.HTTPChannel 。代理的每个客户端请求都会生成一个 ProxyRequest,该请求代表客户端从代理发送到目标服务器。 ProxyRequest 使用 ProxyClientFactory 为连接创建 ProxyClient 协议的实例。 ProxyClient 继承自 http.HTTPClient 。子类 ProxyRequest 以自定义处理或记录请求的方式。

ReverseProxyResource

反向代理代表客户端从其他服务器检索资源。反向代理通常位于服务器的内部网络中,用于缓存、应用程序防火墙和负载均衡。

以下是一个基本反向代理的示例

from twisted.internet import reactor, endpoints
from twisted.web import proxy, server

site = server.Site(proxy.ReverseProxyResource('www.yahoo.com', 80, ''))
endpoint = endpoints.TCP4ServerEndpoint(reactor, 8080)
endpoint.listen(site)
reactor.run()

运行此反向代理后,您可以在 Web 浏览器中访问 http://localhost:8080,反向代理将代理您的连接到 www.yahoo.com

在此示例中,我们使用 server.Site 直接为 ReverseProxyResource 提供服务。在 twisted.web.proxy 中也有一组 ReverseProxy 类,它们与 Proxy 类相对应。

Proxy 一样,ReverseProxy 继承自 http.HTTPChannel 。反向代理的每个客户端请求都会生成一个 ReverseProxyRequest,该请求发送到目标服务器。与 ProxyRequest 一样,ReverseProxyRequest 使用 ProxyClientFactory 为连接创建 ProxyClient 协议的实例。

代理和反向代理的其他示例可以在 Twisted Web 示例 中找到。

高级配置

Twisted Web 的非平凡配置是通过 Python 配置文件实现的。这是一个 Python 代码段,它构建了一个名为 application 的变量。通常,twisted.application.strports.service 函数将用于构建一个服务实例,该实例将用于使应用程序监听 TCP 端口(80,如果需要直接 Web 服务),监听器将是 twisted.web.server.Site 。然后可以使用 twistd -y 运行生成的文件。或者,可以直接使用 reactor 对象来创建一个可运行的脚本。

Site 将包装一个 Resource 对象 - 根。

from twisted.application import internet, service, strports
from twisted.web import static, server

root = static.File("/var/www/htdocs")
application = service.Application('web')
site = server.Site(root)
sc = service.IServiceCollection(application)
i = strports.service("tcp:80", site)
i.setServiceParent(sc)

大多数高级配置将采用调整根资源对象的形式。

添加子项

通常,根的子项将基于文件系统的內容。可以通过显式 putChild 方法覆盖文件系统。

以下两个示例。第一个示例添加了一个 /doc 子项来为已安装软件包的文档提供服务,而第二个示例添加了一个 cgi-bin 目录用于 CGI 脚本。

from twisted.internet import reactor, endpoints
from twisted.web import static, server

root = static.File("/var/www/htdocs")
root.putChild(b"doc", static.File("/usr/share/doc"))
endpoint = endpoints.TCP4ServerEndpoint(reactor, 80)
endpoint.listen(server.Site(root))
reactor.run()
from twisted.internet import reactor, endpoints
from twisted.web import static, server, twcgi

root = static.File("/var/www/htdocs")
root.putChild(b"cgi-bin", twcgi.CGIDirectory("/var/www/cgi-bin"))
endpoint = endpoints.TCP4ServerEndpoint(reactor, 80)
endpoint.listen(server.Site(root))
reactor.run()

修改文件资源

File 资源(无论是根对象还是其子项)都有两个重要的属性,通常需要修改:indexNamesprocessorsindexNames 确定哪些文件被视为“索引文件” - 在渲染目录时被提供。 processors 确定如何处理某些文件扩展名。

以下是一个关于两者的示例,创建一个站点,其中所有 .rpy 扩展名都是资源脚本,并且通过搜索 index.rpy 文件来渲染目录。

from twisted.application import internet, service, strports
from twisted.web import static, server, script

root = static.File("/var/www/htdocs")
root.indexNames=['index.rpy']
root.processors = {'.rpy': script.ResourceScript}
application = service.Application('web')
sc = service.IServiceCollection(application)
site = server.Site(root)
i = strports.service("tcp:80", site)
i.setServiceParent(sc)

File 对象还有一个名为 ignoreExt 的方法。此方法可用于向用户提供无扩展名的 URL,以便隐藏实现。以下是一个示例

from twisted.application import internet, service, strports
from twisted.web import static, server, script

root = static.File("/var/www/htdocs")
root.ignoreExt(".rpy")
root.processors = {'.rpy': script.ResourceScript}
application = service.Application('web')
sc = service.IServiceCollection(application)
site = server.Site(root)
i = strports.service("tcp:80", site)
i.setServiceParent(sc)

现在,如果不存在名为 foo 的文件,则诸如 /foo 之类的 URL 可能会从名为 foo.rpy 的资源脚本中提供服务。

File 对象将尝试自动确定 Content-Type 和 Content-Encoding 标头。有一组小的已知 MIME 类型和编码,它们增强了 Python 标准库 mimetypes 提供的默认 MIME 类型。您始终可以通过操作实例变量来修改内容类型和编码映射。

例如,要识别 WOFF 文件格式 2.0 并设置正确的 Content-Type 标头,您可以修改实例的 contentTypes 成员

from twisted.application import internet, service, strports
from twisted.web import static, server, script

root = static.File("/srv/fonts")

root.contentTypes[".woff2"] = "application/font-woff2"

application = service.Application('web')
sc = service.IServiceCollection(application)
site = server.Site(root)
i = strports.service("tcp:80", site)
i.setServiceParent(sc)

虚拟主机

虚拟主机是通过一个特殊的资源完成的,该资源应该用作根资源 - NameVirtualHostNameVirtualHost 具有一个名为 default 的属性,它保存默认网站。如果需要为其他名称使用不同的根,则应调用 addHost 方法。

from twisted.application import internet, service, strports
from twisted.web import static, server, vhost, script

root = vhost.NameVirtualHost()

# Add a default -- htdocs
root.default=static.File("/var/www/htdocs")

# Add a simple virtual host -- foo.com
root.addHost("foo.com", static.File("/var/www/foo"))

# Add a simple virtual host -- bar.com
root.addHost("bar.com", static.File("/var/www/bar"))

# The "baz" people want to use Resource Scripts in their web site
baz = static.File("/var/www/baz")
baz.processors = {'.rpy': script.ResourceScript}
baz.ignoreExt('.rpy')
root.addHost('baz', baz)

application = service.Application('web')
sc = service.IServiceCollection(application)
site = server.Site(root)
i = strports.service("tcp:80", site)
i.setServiceParent(sc)

高级技巧

由于配置是一个 Python 代码段,因此可以使用 Python 的全部功能。以下是一些简单的示例

# No need for configuration of virtual hosts -- just make sure
# a directory /var/vhosts/<vhost name> exists:
from twisted.web import vhost, static, server
from twisted.application import internet, service, strports

root = vhost.NameVirtualHost()
root.default = static.File("/var/www/htdocs")
for dir in os.listdir("/var/vhosts"):
    root.addHost(dir, static.File(os.path.join("/var/vhosts", dir)))

application = service.Application('web')
sc = service.IServiceCollection(application)
site = server.Site(root)
i = strports.service("tcp:80", site)
i.setServiceParent(sc)
# Determine ports we listen on based on a file with numbers:
from twisted.web import vhost, static, server
from twisted.application import internet, service

root = static.File("/var/www/htdocs")

site = server.Site(root)
application = service.Application('web')
serviceCollection = service.IServiceCollection(application)

with open("/etc/web/ports") as f:
    for num in map(int, f.read().split()):
        serviceCollection.addCollection(
            strports.service("tcp:{}".format(num), site)
        )

运行 Twisted Web 服务器

在很多情况下,你最终会重复使用 Twisted.web 的常见用法模式。在这些情况下,你可能想要使用 Twisted 预先配置的 Web 服务器设置。

运行 Twisted Web 服务器最简单的方法是使用 Twisted Daemonizer。例如,以下命令将运行一个 Web 服务器,它从特定目录提供静态文件

% twistd web --path /path/to/web/content

如果你只想从自己的主目录提供内容,以下命令可以实现

% twistd web --path ~/public_html/

你可以随时通过返回到启动它的目录并运行以下命令来停止服务器

% kill `cat twistd.pid`

还有一些其他配置选项可用

  • --listen : 指定 Web 服务器监听的端口。默认值为 tcp:8080.

  • --logfile : 指定日志文件的路径。

  • --add-header: 指定每个响应中要提供的附加标头。这些标头的格式类似于 --add-header "HeaderName: HeaderValue"

可以使用以下命令查看所有可用的选项

% twistd web --help

提供静态 HTML

Twisted Web 提供静态 HTML 文件的方式与提供其他静态文件的方式相同。

资源脚本

资源脚本是一个以 .rpy 结尾的 Python 文件,它需要创建一个 (子类) twisted.web.resource.Resource 的实例。

资源脚本有 3 个特殊变量

  • __file__ : .rpy 文件的名称,包括完整路径。此变量在命名空间中自动定义并存在。

  • registry : 一个 static.Registry 类的对象。它可以用来访问和设置以类为键的持久数据。

  • resource : 脚本必须定义的变量,并将其设置为将用于渲染页面的资源实例。

一个非常简单的资源脚本可能如下所示

from twisted.web import resource
class MyGreatResource(resource.Resource):
    def render_GET(self, request):
        return b"<html>foo</html>"

resource = MyGreatResource()

一个稍微复杂一点的资源脚本,它访问一些持久数据,可能如下所示

from twisted.web import resource
from SillyWeb import Counter

counter = registry.getComponent(Counter)
if not counter:
   registry.setComponent(Counter, Counter())
counter = registry.getComponent(Counter)

class MyResource(resource.Resource):
    def render_GET(self, request):
        counter.increment()
        output = "you are visitor {}".format(counter.getValue())
        return output.encode("utf8")

resource = MyResource()

假设你拥有 SillyWeb.Counter 模块,并实现了类似以下内容

class Counter:

    def __init__(self):
        self.value = 0

    def increment(self):
        self.value += 1

    def getValue(self):
        return self.value

Web UI

Nevow 框架,作为 Quotient 项目的一部分,是一个为你的应用程序提供 Web UI 的高级系统。Nevow 使用 Twisted Web,但它本身不是 Twisted 的一部分。

可扩展的 Web 服务器

Twisted Web 最有趣的应用之一是分布式 Web 服务器;多个服务器都可以使用 twisted.spread 包进行“可扩展”计算,在同一个端口上响应请求。在两个不同的目录中,运行以下命令

% twistd web --user
% twistd web --personal [other options, if you desire]

运行这两个实例后,访问 http://localhost:8080/your_username.twistd/ - 你将看到使用 --personal 选项创建的服务器的前端页面。这里发生的事情是,你发送的请求通过 PB 连接从中央 (用户) 服务器转发到你自己的 (个人) 服务器。这种技术对于小型“社区”网站非常有用;使用使此演示正常工作的代码,你可以将一个 HTTP 端口连接到在同一台机器上、不同的本地机器上,甚至通过互联网连接到远程站点上运行的不同权限的多个资源。

默认情况下,个人服务器监听所有者主目录中的一个 UNIX 套接字。可以使用 --listen 选项使其监听不同的地址,例如 TCP 或 SSL 服务器,或监听不同位置的 UNIX 服务器。如果你使用此选项使个人服务器监听不同的地址,中央 (用户) 服务器将无法找到它,但使用与中央服务器相同 API 的自定义服务器可能会找到它。--listen 选项的另一个用途是使 UNIX 服务器能够抵御系统崩溃。如果服务器崩溃并且 UNIX 套接字保留在文件系统中,个人服务器将无法重新启动,直到它被删除。但是,如果在创建个人服务器时提供 --listen unix:/home/username/.twistd-web-pb:wantPID=1,那么将使用一个锁文件来跟踪服务器套接字是否正在使用,并在它不再使用时自动删除它。

提供 PHP/Perl/CGI

与 CGI 相关的所有内容都位于 twisted.web.twcgi 中,在这里你会找到你需要子类化的类,以便支持你 (或其他人) 的口味的语言。如果你使用的是非 Unix 操作系统 (例如 Windows),或者默认资源对解析器的路径名不正确,你还需要创建自己的资源类型。

以下代码段是一个提供 perl 文件的 .rpy。查看 twisted.web.twcgi 以获取更多关于 twisted.web 和 CGI 的示例。

from twisted.web import static, twcgi

class PerlScript(twcgi.FilteredScript):
    filter = '/usr/bin/perl' # Points to the perl parser

resource = static.File("/perlsite") # Points to the perl website
resource.processors = {".pl": PerlScript} # Files that end with .pl will be
                                          # processed by PerlScript
resource.indexNames = ['index.pl']

提供 WSGI 应用程序

WSGI 是 Web 服务器网关接口。它是一个规范,用于 Web 服务器和应用程序服务器与 Python Web 应用程序进行通信。所有现代 Python Web 框架都支持 WSGI 接口。

使用 WSGI 应用程序最简单的方法是使用 twistd 命令

% twistd -n web --wsgi=helloworld.application

假设你有一个名为 application 的 WSGI 应用程序,它位于你的 helloworld 模块/包中,它可能如下所示

def application(environ, start_response):
    """Basic WSGI Application"""
    start_response('200 OK', [('Content-type','text/plain')])
    return [b'Hello World!']

以上设置适用于许多应用程序,这些应用程序只需要在站点的根目录提供 WSGI 应用程序。但是,为了获得更大的控制,Twisted 提供了将 WSGI 应用程序用作资源 twisted.web.wsgi.WSGIResource 的支持。

以下是一个将 WSGI 应用程序用作站点根资源的示例,在以下 tac 文件中

from twisted.web import server
from twisted.web.wsgi import WSGIResource
from twisted.python.threadpool import ThreadPool
from twisted.internet import reactor
from twisted.application import service, strports

# Create and start a thread pool,
wsgiThreadPool = ThreadPool()
wsgiThreadPool.start()

# ensuring that it will be stopped when the reactor shuts down
reactor.addSystemEventTrigger('after', 'shutdown', wsgiThreadPool.stop)

def application(environ, start_response):
    """A basic WSGI application"""
    start_response('200 OK', [('Content-type','text/plain')])
    return [b'Hello World!']

# Create the WSGI resource
wsgiAppAsResource = WSGIResource(reactor, wsgiThreadPool, application)

# Hooks for twistd
application = service.Application('Twisted.web.wsgi Hello World Example')
server = strports.service('tcp:8080', server.Site(wsgiAppAsResource))
server.setServiceParent(application)

然后可以像其他任何 .tac 文件一样运行它

% twistd -ny myapp.tac

由于 WSGI 的同步性质,每个应用程序调用 (对于每个请求) 都在同一个线程中调用,并将结果写回 Web 服务器。为此,使用了一个 twisted.python.threadpool.ThreadPool 实例。

使用 VHostMonster

在具有多个名称的站点上使用一个服务器 (例如,Apache) 是很常见的,然后使用反向代理 (在 Apache 中,通过 mod_proxy) 到不同的内部 Web 服务器,可能在不同的机器上。但是,简单的配置会导致通信错误:内部服务器坚信它正在“internal-name:port” 上运行,并将生成指向该地址的 URL,这些 URL 在客户端接收时将完全错误。

虽然 Apache 有 ProxyPassReverse 指令,但它实际上是一个 hack,而且它远不够全面。相反,如果内部 Web 服务器是 Twisted Web,建议的做法是使用 VHostMonster。

从 Twisted 方面来看,使用 VHostMonster 很容易:只需放置一个名为 (例如) vhost.rpy 的文件,其中包含以下内容

from twisted.web import vhost
resource = vhost.VHostMonsterResource()

确保 Web 服务器配置了 rpy 扩展的正确处理器 (Web 服务器 twistd web --path 默认生成的配置就是这样)。

从 Apache 方面来看,不要使用以下 ProxyPass 指令

<VirtualHost ip-addr>
ProxyPass / http://localhost:8538/
ServerName example.com
</VirtualHost>

使用以下指令

<VirtualHost ip-addr>
ProxyPass / http://localhost:8538/vhost.rpy/http/example.com:80/
ServerName example.com
</VirtualHost>

以下是一个 Twisted Web 反向代理的示例

from twisted.application import internet, service, strports
from twisted.web import proxy, server, vhost
vhostName = b'example.com'
reverseProxy = proxy.ReverseProxyResource('internal', 8538,
                                          b'/vhost.rpy/http/'+vhostName+b'/')
root = vhost.NameVirtualHost()
root.addHost(vhostName, reverseProxy)
site = server.Site(root)
application = service.Application('web-proxy')
sc = service.IServiceCollection(application)
i = strports.service("tcp:80", site)
i.setServiceParent(sc)

重写 URL

有时在将 Request 对象传递之前修改其内容会很方便。由于这最常用于重写 URL,因此与 Apache 的 mod_rewrite 的相似性启发了 twisted.web.rewrite 模块。使用此模块是通过使用 twisted.web.rewrite.RewriterResource 包装资源来完成的,该资源具有重写规则。重写规则是接受请求对象的函数,并且可能修改它。在所有重写规则运行之后,子解析链将继续,就好像包装的资源 (而不是 RewriterResource) 是子资源一样。

以下是一个示例,使用 Twisted 自身提供的唯一规则

default_root = rewrite.RewriterResource(default, rewrite.tildeToUsers)

这会导致 URL /~foo/bar.html 被视为 /users/foo/bar.html 。如果在将默认的 users 子项设置为 distrib.UserDirectory 后执行此操作,它将提供类似于经典 Web 服务器配置的配置,这种配置自第一个 NCSA 服务器以来一直很常见。

知道何时不需要我们

有时,了解对方何时断开了连接非常有用。以下是一个执行此操作的示例

from twisted.web.resource import Resource
from twisted.web import server
from twisted.internet import reactor
from twisted.python.util import println


class ExampleResource(Resource):

    def render_GET(self, request):
        request.write(b"hello world")
        d = request.notifyFinish()
        d.addCallback(lambda _: println("finished normally"))
        d.addErrback(println, "error")
        reactor.callLater(10, request.finish)
        return server.NOT_DONE_YET

resource = ExampleResource()

这将允许我们对日志文件运行统计信息,以查看有多少用户在仅仅 10 秒后就感到沮丧。

按原样提供服务

有时,您希望能够直接发送标头和状态。虽然您可以使用 ResourceScript 来实现这一点,但更简单的方法是使用 ASISProcessor 。例如,通过将其添加为 .asis 扩展的处理器来使用它。以下是一个示例文件

HTTP/1.0 200 OK
Content-Type: text/html

Hello world