Following system colour scheme - Python 增强提案 Selected dark colour scheme - Python 增强提案 Selected light colour scheme - Python 增强提案

Python 增强提案

PEP 3333 – Python Web 服务器网关接口 v1.0.1

作者:
Phillip J. Eby <pje at telecommunity.com>
讨论对象:
Web-SIG 邮件列表
状态:
最终版
类型:
信息性
创建时间:
2010 年 9 月 26 日
发布历史:
2010 年 9 月 26 日,2010 年 10 月 4 日
替换:
333

目录

面向 PEP 333 阅读者的前言

这是 PEP 333 的更新版本,为了提高 Python 3 下的可使用性,并合并 WSGI 协议中一些长期存在的非正式修改,进行了微小的修改。(其代码示例也已移植到 Python 3。)

虽然出于程序上的原因 [6],这必须是一个独立的 PEP,但没有进行会使先前兼容的服务器或应用程序在 Python 2.x 下失效的更改。如果你的 2.x 应用程序或服务器与 PEP 333 兼容,那么它也与这个 PEP 兼容。

然而,在 Python 3 下,你的应用程序或服务器还必须遵循以下部分中概述的规则,即 关于字符串类型的说明Unicode 问题

有关本文档与 PEP 333 之间详细的逐行差异,你可以查看其 SVN 修订历史 [7],从修订版本 84854 开始。

摘要

本文档规定了 Web 服务器和 Python Web 应用程序或框架之间的一种提议的标准接口,旨在促进 Web 应用程序在各种 Web 服务器上的可移植性。

原始理由和目标(来自 PEP 333)

Python 目前拥有各种各样的 Web 应用程序框架,例如 Zope、Quixote、Webware、SkunkWeb、PSO 和 Twisted Web——仅举几例 [1]。这种广泛的选择对于新的 Python 用户来说可能是一个问题,因为一般来说,他们选择的 Web 框架会限制他们可用的 Web 服务器选择,反之亦然。

相比之下,尽管 Java 也拥有同样多的 Web 应用程序框架,但 Java 的“servlet”API 使得使用任何 Java Web 应用程序框架编写的应用程序能够在任何支持 servlet API 的 Web 服务器上运行。

在 Python 的 Web 服务器中,这样一个 API 的可用性和广泛使用——无论这些服务器是用 Python 编写的(例如 Medusa)、嵌入 Python 的(例如 mod_python),还是通过网关协议调用 Python 的(例如 CGI、FastCGI 等)——将使框架的选择与 Web 服务器的选择分离,使用户能够自由选择适合他们的组合,同时使框架和服务器开发人员能够专注于他们喜欢的专业领域。

因此,本 PEP 提出了一个简单且通用的 Web 服务器与 Web 应用程序或框架之间的接口:Python Web 服务器网关接口 (WSGI)。

但仅仅存在 WSGI 规范并不能解决 Python Web 应用程序的现有服务器和框架状态。服务器和框架的作者和维护人员必须实际实现 WSGI,才能产生任何影响。

然而,由于没有现有的服务器或框架支持 WSGI,因此对于实现 WSGI 支持的作者来说,几乎没有直接的回报。因此,WSGI **必须** 易于实现,这样作者对该接口的初始投资才能合理地低。

因此,在接口的服务器端和框架端实现的简单性对于 WSGI 接口的实用性至关重要,因此是任何设计决策的首要标准。

但是,请注意,对于框架作者而言,实现的简单性与对于 Web 应用程序作者而言的易用性不同。WSGI 向框架作者提供了一个绝对“无装饰”的接口,因为像响应对象和 Cookie 处理这样的铃铛和哨声只会妨碍现有框架处理这些问题。再次强调,WSGI 的目标是促进现有服务器和应用程序或框架之间的轻松互连,而不是创建一个新的 Web 框架。

还要注意,这个目标排除了 WSGI 要求任何部署版本的 Python 中尚未提供的东西。因此,本规范没有提出或要求任何新的标准库模块,并且 WSGI 中的任何内容都不需要高于 2.2.2 的 Python 版本。(但是,对于未来的 Python 版本,最好在标准库提供的 Web 服务器中包含对该接口的支持。)

除了现有和未来框架和服务器的易于实现之外,还应该易于创建请求预处理器、响应后处理器和其他基于 WSGI 的“中间件”组件,这些组件对它们所包含的服务器来说就像应用程序一样,而对它们所包含的应用程序来说就像服务器一样。

如果中间件既简单又健壮,并且 WSGI 在服务器和框架中广泛可用,那么它就能实现一种全新的 Python Web 应用程序框架:它由松散耦合的 WSGI 中间件组件组成。事实上,现有的框架作者甚至可以选择将他们框架中的现有服务重构为以这种方式提供,变得更像与 WSGI 一起使用的库,而不是像单块框架。然后,这将使应用程序开发人员能够选择“最佳”组件来实现特定功能,而不是必须承诺使用单个框架的所有优缺点。

当然,在撰写本文时,这一天无疑还很遥远。与此同时,WSGI 的一个足够短期目标是能够将任何框架与任何服务器一起使用。

最后,应该提到的是,当前版本的 WSGI 没有规定任何特定机制来“部署”应用程序以供 Web 服务器或服务器网关使用。目前,这是由服务器或网关通过实现进行定义的。当足够多的服务器和框架实现 WSGI 以提供关于不同部署要求的现场经验后,可能就有必要创建另一个 PEP,描述 WSGI 服务器和应用程序框架的部署标准。

规范概述

WSGI 接口有两端:“服务器”或“网关”端,“应用程序”或“框架”端。服务器端调用应用程序端提供的可调用对象。该对象是如何提供的,这取决于服务器或网关。假设某些服务器或网关将要求应用程序的部署人员编写一个简短的脚本,以创建服务器或网关的实例,并向其提供应用程序对象。其他服务器和网关可能会使用配置文件或其他机制来指定应从何处导入或以其他方式获取应用程序对象。

除了“纯”服务器/网关和应用程序/框架之外,还可以创建同时实现该规范两端的“中间件”组件。这些组件对它们所包含的服务器来说就像应用程序一样,而对它们所包含的应用程序来说就像服务器一样,可以用来提供扩展 API、内容转换、导航和其他有用的功能。

在整个规范中,我们将使用术语“可调用对象”来表示“函数、方法、类或具有 __call__ 方法的实例”。由实现可调用对象的服务器、网关或应用程序来选择适合其需求的实现技术。相反,调用可调用对象的服务器、网关或应用程序**不得** 依赖于提供给它的可调用对象的类型。可调用对象只能被调用,不能被内省。

关于字符串类型的说明

一般来说,HTTP 处理字节,这意味着本规范主要涉及字节的处理。

但是,这些字节的内容通常具有某种文本解释,在 Python 中,字符串是处理文本最方便的方式。

但在许多 Python 版本和实现中,字符串是 Unicode,而不是字节。这需要在可用的 API 和 HTTP 上下文中的字节与文本之间的正确转换之间取得谨慎的平衡……尤其是为了支持在具有不同 str 类型的 Python 实现之间移植代码。

因此,WSGI 定义了两种类型的“字符串”

  • “原生”字符串(始终使用名为 str 的类型实现),用于请求/响应头和元数据
  • “字节串”(在 Python 3 中使用 bytes 类型实现,在其他地方使用 str 类型实现),用于请求和响应的主体(例如 POST/PUT 输入数据和 HTML 页面输出)。

但是不要混淆:即使 Python 的 str 类型实际上是“在幕后”的 Unicode,原生字符串的内容仍然必须可以通过 Latin-1 编码转换为字节!(有关更多详细信息,请参阅本文档后面关于 Unicode 问题 的部分。)

简而言之:在本文档中,你看到“字符串”这个词时,它指的是“原生”字符串,即 str 类型的对象,无论它是内部实现为字节还是 Unicode。你看到对“字节串”的引用时,应该将其理解为“在 Python 3 中是 bytes 类型的对象,在 Python 2 中是 str 类型的对象”。

因此,即使 HTTP 从某种意义上来说是“实际上只是字节”,使用 Python 的默认 str 类型的任何内容都将带来许多 API 方便之处。

应用程序/框架端

应用程序对象只是一个可调用对象,它接受两个参数。术语“对象”不应被误解为要求实际的对象实例:函数、方法、类或具有 __call__ 方法的实例都可以在应用程序对象中使用。应用程序对象必须能够被多次调用,因为几乎所有服务器/网关(除了 CGI)都会发出这种重复的请求。

(注意:虽然我们将其称为“应用程序”对象,但这不应被解释为意味着应用程序开发人员将使用 WSGI 作为 Web 编程 API!假设应用程序开发人员将继续使用现有的高级框架服务来开发他们的应用程序。WSGI 是框架和服务器开发人员的工具,并非旨在直接支持应用程序开发人员。)

以下是两个应用程序对象的示例;一个是函数,另一个是类

HELLO_WORLD = b"Hello world!\n"

def simple_app(environ, start_response):
    """Simplest possible application object"""
    status = '200 OK'
    response_headers = [('Content-type', 'text/plain')]
    start_response(status, response_headers)
    return [HELLO_WORLD]

class AppClass:
    """Produce the same output, but using a class

    (Note: 'AppClass' is the "application" here, so calling it
    returns an instance of 'AppClass', which is then the iterable
    return value of the "application callable" as required by
    the spec.

    If we wanted to use *instances* of 'AppClass' as application
    objects instead, we would have to implement a '__call__'
    method, which would be invoked to execute the application,
    and we would need to create an instance for use by the
    server or gateway.
    """

    def __init__(self, environ, start_response):
        self.environ = environ
        self.start = start_response

    def __iter__(self):
        status = '200 OK'
        response_headers = [('Content-type', 'text/plain')]
        self.start(status, response_headers)
        yield HELLO_WORLD

服务器/网关端

服务器或网关为应用程序接收到的每个来自 HTTP 客户端的请求调用一次应用程序可调用对象。为了说明,这是一个简单的 CGI 网关,它作为一个接受应用程序对象的函数来实现。请注意,这个简单的示例只有有限的错误处理,因为默认情况下未捕获的异常将被转储到 sys.stderr 并由 Web 服务器记录。

import os, sys

enc, esc = sys.getfilesystemencoding(), 'surrogateescape'

def unicode_to_wsgi(u):
    # Convert an environment variable to a WSGI "bytes-as-unicode" string
    return u.encode(enc, esc).decode('iso-8859-1')

def wsgi_to_bytes(s):
    return s.encode('iso-8859-1')

def run_with_cgi(application):
    environ = {k: unicode_to_wsgi(v) for k,v in os.environ.items()}
    environ['wsgi.input']        = sys.stdin.buffer
    environ['wsgi.errors']       = sys.stderr
    environ['wsgi.version']      = (1, 0)
    environ['wsgi.multithread']  = False
    environ['wsgi.multiprocess'] = True
    environ['wsgi.run_once']     = True

    if environ.get('HTTPS', 'off') in ('on', '1'):
        environ['wsgi.url_scheme'] = 'https'
    else:
        environ['wsgi.url_scheme'] = 'http'

    headers_set = []
    headers_sent = []

    def write(data):
        out = sys.stdout.buffer

        if not headers_set:
             raise AssertionError("write() before start_response()")

        elif not headers_sent:
             # Before the first output, send the stored headers
             status, response_headers = headers_sent[:] = headers_set
             out.write(wsgi_to_bytes('Status: %s\r\n' % status))
             for header in response_headers:
                 out.write(wsgi_to_bytes('%s: %s\r\n' % header))
             out.write(wsgi_to_bytes('\r\n'))

        out.write(data)
        out.flush()

    def start_response(status, response_headers, exc_info=None):
        if exc_info:
            try:
                if headers_sent:
                    # Re-raise original exception if headers sent
                    raise exc_info[1].with_traceback(exc_info[2])
            finally:
                exc_info = None     # avoid dangling circular ref
        elif headers_set:
            raise AssertionError("Headers already set!")

        headers_set[:] = [status, response_headers]

        # Note: error checking on the headers should happen here,
        # *after* the headers are set.  That way, if an error
        # occurs, start_response can only be re-called with
        # exc_info set.

        return write

    result = application(environ, start_response)
    try:
        for data in result:
            if data:    # don't send headers until body appears
                write(data)
        if not headers_sent:
            write(b'')   # send headers now if body was empty
    finally:
        if hasattr(result, 'close'):
            result.close()

中间件:同时扮演两端角色的组件

请注意,一个单独的对象可以相对于某些应用程序充当服务器的角色,同时也可以相对于某些服务器充当应用程序的角色。此类“中间件”组件可以执行以下功能:

  • 根据目标 URL 将请求路由到不同的应用程序对象,并在相应地重写 environ 之后。
  • 允许多个应用程序或框架并排在同一个进程中运行。
  • 通过网络转发请求和响应来实现负载均衡和远程处理。
  • 执行内容后处理,例如应用 XSL 样式表。

中间件的存在对于接口的“服务器/网关”和“应用程序/框架”两侧都是透明的,并且不需要任何特殊支持。希望将中间件纳入应用程序的用户只需将中间件组件提供给服务器,就好像它是应用程序一样,并配置中间件组件以调用应用程序,就好像中间件组件是服务器一样。当然,中间件包装的“应用程序”实际上可能是包装另一个应用程序的另一个中间件组件,依此类推,从而创建一个称为“中间件堆栈”的结构。

在大多数情况下,中间件必须符合 WSGI 服务器和应用程序两侧的限制和要求。但是,在某些情况下,中间件的要求比“纯”服务器或应用程序的要求更严格,这些要点将在规范中注明。

这是一个(戏谑的)中间件组件示例,它使用 Joe Strout 的 piglatin.pytext/plain 响应转换为 Pig Latin。(注意:“真正的”中间件组件可能会使用更强大的方法来检查内容类型,并且还应该检查内容编码。此外,这个简单的示例忽略了一个单词可能跨块边界分割的可能性。)

from piglatin import piglatin

class LatinIter:

    """Transform iterated output to piglatin, if it's okay to do so

    Note that the "okayness" can change until the application yields
    its first non-empty bytestring, so 'transform_ok' has to be a mutable
    truth value.
    """

    def __init__(self, result, transform_ok):
        if hasattr(result, 'close'):
            self.close = result.close
        self._next = iter(result).__next__
        self.transform_ok = transform_ok

    def __iter__(self):
        return self

    def __next__(self):
        data = self._next()
        if self.transform_ok:
            return piglatin(data)   # call must be byte-safe on Py3
        else:
            return data

class Latinator:

    # by default, don't transform output
    transform = False

    def __init__(self, application):
        self.application = application

    def __call__(self, environ, start_response):

        transform_ok = []

        def start_latin(status, response_headers, exc_info=None):

            # Reset ok flag, in case this is a repeat call
            del transform_ok[:]

            for name, value in response_headers:
                if name.lower() == 'content-type' and value == 'text/plain':
                    transform_ok.append(True)
                    # Strip content-length if present, else it'll be wrong
                    response_headers = [(name, value)
                        for name, value in response_headers
                            if name.lower() != 'content-length'
                    ]
                    break

            write = start_response(status, response_headers, exc_info)

            if transform_ok:
                def write_latin(data):
                    write(piglatin(data))   # call must be byte-safe on Py3
                return write_latin
            else:
                return write

        return LatinIter(self.application(environ, start_latin), transform_ok)


# Run foo_app under a Latinator's control, using the example CGI gateway
from foo_app import foo_app
run_with_cgi(Latinator(foo_app))

规范细节

应用程序对象必须接受两个位置参数。为了说明起见,我们将其命名为 environstart_response,但它们不需要这些名称。服务器或网关**必须**使用位置(而不是关键字)参数调用应用程序对象。(例如,通过调用 result = application(environ, start_response),如上所示。)

environ 参数是一个字典对象,包含 CGI 风格的环境变量。此对象**必须**是内置的 Python 字典(**不是**子类、UserDict 或其他字典模拟),并且应用程序可以以任何方式修改字典。字典还必须包含某些 WSGI 要求的变量(在后面的部分中描述),并且还可能包含服务器特定的扩展变量,这些变量的命名遵循下面将要描述的约定。

start_response 参数是一个可调用对象,它接受两个必需的位置参数和一个可选参数。为了说明起见,我们将这些参数命名为 statusresponse_headersexc_info,但它们不需要这些名称,并且应用程序**必须**使用位置参数调用 start_response 可调用对象(例如,start_response(status, response_headers))。

status 参数是形式为 "999 Message here" 的状态字符串,response_headers(header_name, header_value) 元组的列表,描述 HTTP 响应头。可选的 exc_info 参数将在下面关于 start_response() 可调用对象错误处理 的部分中描述。它仅在应用程序捕获错误并尝试向浏览器显示错误消息时使用。

start_response 可调用对象必须返回一个 write(body_data) 可调用对象,该对象接受一个位置参数:一个要作为 HTTP 响应正文的一部分写入的字节字符串。(注意:仅提供 write() 可调用对象以支持某些现有框架的命令式输出 API;如果可以避免,新应用程序或框架不应使用它。有关详细信息,请参阅 缓冲和流式传输 部分。)

当被服务器调用时,应用程序对象必须返回一个可迭代对象,该对象产生零个或多个字节字符串。这可以通过多种方式完成,例如返回一个字节字符串列表,或应用程序是一个产生字节字符串的生成器函数,或者应用程序是一个其实例是可迭代的类。无论通过何种方式完成,应用程序对象始终必须返回一个可迭代对象,该对象产生零个或多个字节字符串。

服务器或网关必须以非缓冲方式将产生的字节字符串传输到客户端,在请求另一个字节字符串之前完成每个字节字符串的传输。(换句话说,应用程序**应该**执行自己的缓冲。有关如何处理应用程序输出的更多信息,请参阅下面的 缓冲和流式传输 部分。)

服务器或网关应该将产生的字节字符串视为二进制字节序列:特别是,它应该确保不更改行尾。应用程序有责任确保要写入的字节字符串(s)格式适合客户端。(服务器或网关**可以**应用 HTTP 传输编码,或执行其他转换以实现 HTTP 功能,例如字节范围传输。有关详细信息,请参阅下面的 其他 HTTP 功能。)

如果对 len(iterable) 的调用成功,服务器必须能够依赖于结果的准确性。也就是说,如果应用程序返回的可迭代对象提供了一个可用的 __len__() 方法,它**必须**返回一个准确的结果。(有关如何通常使用此方法的信息,请参阅 处理 Content-Length 头 部分。)

如果应用程序返回的可迭代对象具有 close() 方法,服务器或网关**必须**在当前请求完成时调用该方法,无论请求是正常完成,还是由于迭代期间的应用程序错误或浏览器过早断开连接而过早终止。(close() 方法要求是为了支持应用程序释放资源。该协议旨在补充 PEP 342 的生成器支持,以及其他具有 close() 方法的常用可迭代对象。)

返回生成器或其他自定义迭代器的应用程序**不应**假设整个迭代器将被消耗,因为它**可能**会被服务器过早关闭。

(注意:应用程序**必须**在可迭代对象产生第一个正文字节字符串之前调用 start_response() 可调用对象,以便服务器可以在任何正文内容之前发送头。但是,此调用**可能**由可迭代对象的第一次迭代执行,因此服务器**不应**假设在它们开始迭代可迭代对象之前已调用 start_response()。)

最后,服务器和网关**不应**直接使用应用程序返回的可迭代对象的任何其他属性,除非它是该服务器或网关特定类型的实例,例如由 wsgi.file_wrapper 返回的“文件包装器”(请参阅 可选平台特定文件处理)。在一般情况下,仅在此处指定的属性,或通过例如 PEP 234 迭代 API 访问的属性是可以接受的。

environ 变量

environ 字典需要包含这些 CGI 环境变量,如公共网关接口规范 [2] 中定义。以下变量**必须**存在,除非它们的值为空字符串,在这种情况下,它们**可以**省略,除非下面另有说明。

REQUEST_METHOD
HTTP 请求方法,例如 "GET""POST"。这永远不会是空字符串,因此始终是必需的。
SCRIPT_NAME
请求 URL 的“路径”的初始部分,对应于应用程序对象,以便应用程序知道其虚拟“位置”。如果应用程序对应于服务器的“根”,则这**可能**是一个空字符串。
PATH_INFO
请求 URL 的“路径”的剩余部分,指定请求目标在应用程序中的虚拟“位置”。如果请求 URL 目标应用程序根目录并且没有尾部斜杠,则这**可能**是一个空字符串。
QUERY_STRING
请求 URL 的部分,位于 "?" 之后(如果有)。可以为空或缺失。
CONTENT_TYPE
HTTP 请求中任何 Content-Type 字段的内容。可以为空或缺失。
CONTENT_LENGTH
HTTP 请求中任何 Content-Length 字段的内容。可以为空或缺失。
SERVER_NAMESERVER_PORT
HTTP_HOST 未设置时,可以组合这些变量以确定默认值。有关更多详细信息,请参阅下面的 URL 重建 部分。 SERVER_NAMESERVER_PORT 是必需的字符串,并且绝不能为空。
SERVER_PROTOCOL
客户端用来发送请求的协议版本。通常这将是类似 "HTTP/1.0""HTTP/1.1" 的内容,并且可以被应用程序用来确定如何处理任何 HTTP 请求头。(此变量可能应该被称为 REQUEST_PROTOCOL,因为它表示请求中使用的协议,并不一定是在服务器响应中使用的协议。但是,为了与 CGI 保持兼容性,我们必须保留现有的名称。)
HTTP_ 变量
对应于客户端提供的 HTTP 请求头的变量(即,名称以 "HTTP_" 开头的变量)。这些变量的存在与否应该与请求中相应的 HTTP 头的存在与否相对应。

服务器或网关**应该**尝试提供尽可能多的其他 CGI 变量,只要适用。此外,如果使用 SSL,服务器或网关**应该**也提供尽可能多的 Apache SSL 环境变量[5],只要适用,例如HTTPS=onSSL_PROTOCOL。但是请注意,使用除上面列出的以外的任何 CGI 变量的应用程序必然无法移植到不支持相关扩展的 Web 服务器。(例如,不发布文件的 Web 服务器将无法提供有意义的DOCUMENT_ROOTPATH_TRANSLATED。)

符合 WSGI 规范的服务器或网关**应该**记录其提供的变量以及相应的定义。应用程序**应该**检查其所需的任何变量是否存在,并在该变量不存在的情况下制定备用方案。

注意:缺失的变量(例如在没有进行身份验证时REMOTE_USER)应该从environ字典中省略。还要注意,如果存在 CGI 定义的变量,则它们必须是本机字符串。如果任何CGI 变量的值不是str类型,则违反了本规范。

除了 CGI 定义的变量之外,environ字典**可能**还包含任意的操作系统“环境变量”,并且**必须**包含以下 WSGI 定义的变量

变量
wsgi.version 元组(1, 0),表示 WSGI 版本 1.0。
wsgi.url_scheme 表示调用应用程序的 URL 的“方案”部分的字符串。通常,此值将根据情况为"http""https"
wsgi.input 一个输入流(类文件对象),从中可以读取 HTTP 请求主体字节。(服务器或网关可以根据应用程序的要求按需执行读取操作,也可以预先读取客户端的请求主体并将其保存在内存或磁盘中,或者使用任何其他技术来提供这样的输入流,这取决于其偏好。)
wsgi.errors 一个输出流(类文件对象),可以向其中写入错误输出,用于将程序或其他错误记录在标准化且可能集中的位置。这应该是一个“文本模式”流;即,应用程序应该使用"\n"作为行尾,并假设它将被服务器/网关转换为正确的行尾。

(在str类型为 unicode 的平台上,错误流**应该**接受并记录任意 unicode,而不会引发错误;但是,可以替换无法在流编码中呈现的字符。)

对于许多服务器,wsgi.errors将是服务器的主错误日志。或者,这可能是sys.stderr或某种类型的日志文件。服务器的文档应该包含如何配置此选项或在哪里找到已记录的输出的说明。如果需要,服务器或网关可以为不同的应用程序提供不同的错误流。

wsgi.multithread 如果应用程序对象可能在同一进程中被另一个线程同时调用,则此值应该计算为 true,否则应该计算为 false。
wsgi.multiprocess 如果另一个进程可能同时调用等效的应用程序对象,则此值应该计算为 true,否则应该计算为 false。
wsgi.run_once 如果服务器或网关期望(但不保证!)应用程序在其包含的进程的生命周期内只被调用一次,则此值应该计算为 true。通常,对于基于 CGI(或类似内容)的网关,这将仅为 true。

最后,environ字典还可能包含服务器定义的变量。这些变量的命名应该只使用小写字母、数字、点和下划线,并且应该以定义服务器或网关的唯一名称作为前缀。例如,mod_python可能定义具有mod_python.some_variable等名称的变量。

输入和错误流

服务器提供的输入和错误流必须支持以下方法

方法 说明
read(size) input 1
readline() input 1, 2
readlines(hint) input 1, 3
__iter__() input
flush() errors 4
write(str) errors
writelines(seq) errors

每个方法的语义如 Python 库参考中所述,除了上面表格中列出的说明之外。

  1. 服务器不需要读取超过客户端指定的Content-Length,并且**应该**模拟文件结束条件,如果应用程序尝试读取超过该点。应用程序**不应该**尝试读取超过CONTENT_LENGTH变量指定的数据量。

    服务器**应该**允许在没有参数的情况下调用read(),并返回客户端输入流的其余部分。

    服务器**应该**从任何尝试从空或已耗尽的输入流中读取的操作返回空字节串。

  2. 服务器**应该**支持readline()的可选“size”参数,但与 WSGI 1.0 中一样,它们可以省略对它的支持。

    (在 WSGI 1.0 中,size 参数不受支持,理由是它可能难以实现,并且在实践中并不常用……但后来cgi模块开始使用它,因此实际服务器不得不开始支持它!)

  3. 请注意,对于调用者和实现者来说,readlines()hint参数都是可选的。应用程序可以自由地不提供它,服务器或网关可以自由地忽略它。
  4. 由于errors流可能无法重新定位,因此服务器和网关可以自由地立即转发写入操作,而无需缓冲。在这种情况下,flush()方法可能是空操作。但是,可移植的应用程序不能假设输出是未缓冲的,也不能假设flush()是空操作。如果它们需要确保输出实际上已经写入,则必须调用flush()。(例如,为了最大程度地减少多个进程写入同一错误日志的数据交织。)

上面表格中列出的方法**必须**由所有符合本规范的服务器支持。符合本规范的应用程序**不得**使用inputerrors对象的任何其他方法或属性。特别是,应用程序**不得**尝试关闭这些流,即使它们拥有close()方法。

可调用对象 start_response()

传递给应用程序对象的第二个参数是一个具有start_response(status, response_headers, exc_info=None)形式的可调用对象。(与所有 WSGI 可调用对象一样,参数必须按位置提供,而不是按关键字提供。) start_response可调用对象用于开始 HTTP 响应,并且它必须返回一个write(body_data)可调用对象(参见下面的缓冲和流部分)。

status参数是一个 HTTP“状态”字符串,例如"200 OK""404 Not Found"。也就是说,它是一个字符串,由状态代码和原因短语组成,按此顺序排列并用一个空格分隔,没有周围的空格或其他字符。(有关更多信息,请参见RFC 2616第 6.1.1 节。) 该字符串**不得**包含控制字符,并且不得以回车符、换行符或它们的组合结尾。

response_headers参数是(header_name, header_value)元组的列表。它必须是 Python 列表;即type(response_headers) is ListType,并且服务器**可以**以任何它想要的方式更改其内容。每个header_name必须是一个有效的 HTTP 标头字段名称(如RFC 2616第 4.2 节中所定义),没有尾部的冒号或其他标点符号。

每个header_value**不得**包含任何控制字符,包括回车符或换行符,无论是嵌入的还是结尾的。(这些要求是为了最大程度地减少服务器、网关和需要检查或修改响应标头的中间响应处理器必须执行的任何解析的复杂性。)

通常,服务器或网关负责确保将正确的标头发送给客户端:如果应用程序省略了 HTTP(或其他生效的相关规范)所需的标头,则服务器或网关**必须**添加它。例如,HTTPDate:Server:标头通常由服务器或网关提供。

(提醒服务器/网关作者:HTTP 标头名称不区分大小写,因此在检查应用程序提供的标头时,请务必考虑这一点!)

应用程序和中间件禁止使用 HTTP/1.1 的“逐跳”功能或标头、HTTP/1.0 中的任何等效功能或会影响客户端与 Web 服务器连接持久性的任何标头。这些功能是实际 Web 服务器的专属领域,并且服务器或网关**应该**将应用程序尝试发送它们视为致命错误,并在将它们提供给start_response()时引发错误。(有关“逐跳”功能和标头的更多详细信息,请参见下面的其他 HTTP 功能部分。)

服务器**应该**在调用start_response时检查标头中的错误,以便在应用程序仍在运行时可以引发错误。

然而,start_response 可调用对象**不能**实际传输响应头。相反,它必须将响应头存储起来,以便服务器或网关**仅**在应用程序返回值的第一次迭代产生非空字节串后,或在应用程序第一次调用write() 可调用对象后进行传输。换句话说,响应头必须在有实际主体数据可用之前或应用程序返回的可迭代对象耗尽之前发送。(此规则的唯一可能例外情况是,如果响应头明确包含 Content-Length 为零。)

这种延迟响应头传输是为了确保缓冲和异步应用程序可以在最后时刻用错误输出替换其最初预期的输出。例如,如果在应用程序缓冲区中生成主体时发生错误,应用程序可能需要将响应状态从“200 OK”更改为“500 Internal Error”。

如果提供exc_info 参数,它必须是 Python sys.exc_info() 元组。此参数应仅在start_response 被错误处理程序调用时由应用程序提供。如果提供exc_info,并且尚未输出任何 HTTP 头,则start_response 应该用新提供的 HTTP 响应头替换当前存储的 HTTP 响应头,从而允许应用程序在发生错误时“改变主意”关于输出。

但是,如果提供exc_info,并且 HTTP 头已发送,则start_response**必须**引发错误,并且**应该**使用exc_info 元组重新引发错误。也就是说

raise exc_info[1].with_traceback(exc_info[2])

这将重新引发应用程序捕获的异常,原则上应该中止应用程序。(一旦 HTTP 头已发送,应用程序尝试向浏览器输出错误信息是不安全的。)应用程序**不能**捕获由start_response 引发的任何异常,如果它使用exc_info 调用start_response。相反,它应该允许这些异常传播回服务器或网关。有关更多详细信息,请参阅下面的 错误处理

应用程序**可以**多次调用start_response,当且仅当提供exc_info 参数。更准确地说,如果在应用程序的当前调用中已经调用了start_response,则在没有exc_info 参数的情况下调用start_response 是一个致命错误。这包括第一次调用start_response 引发错误的情况。(有关正确逻辑的说明,请参阅上面的示例 CGI 网关。)

注意:实现start_response 的服务器、网关或中间件**应该**确保在函数执行期间不会持有对exc_info 参数的引用,以避免通过涉及的跟踪和帧创建循环引用。最简单的方法类似于

def start_response(status, response_headers, exc_info=None):
    if exc_info:
         try:
             # do stuff w/exc_info here
         finally:
             exc_info = None    # Avoid circular ref.

示例 CGI 网关提供了另一种使用这种技术的方法。

处理 Content-Length 头部

如果应用程序提供Content-Length 头,则服务器**不应**向客户端传输超过头允许的字节数,并且**应该**在发送了足够的数据后停止遍历响应,或者如果应用程序尝试在该点之后write(),则引发错误。(当然,如果应用程序没有提供足够的数据来满足其声明的Content-Length,则服务器**应该**关闭连接并记录或以其他方式报告错误。)

如果应用程序没有提供Content-Length 头,则服务器或网关可以选择几种方法来处理它。其中最简单的方法是在响应完成时关闭客户端连接。

但是,在某些情况下,服务器或网关可能能够生成Content-Length 头,或者至少避免关闭客户端连接的必要性。如果应用程序调用write() 可调用对象,并且返回一个len() 为 1 的可迭代对象,则服务器可以通过获取可迭代对象产生的第一个字节串的长度来自动确定Content-Length

而且,如果服务器和客户端都支持 HTTP/1.1 “分块编码”,则服务器**可以**使用分块编码为每次write() 调用或可迭代对象产生的字节串发送一个块,从而为每个块生成一个Content-Length 头。这允许服务器保持客户端连接活动,如果它希望这样做。请注意,服务器**必须**完全遵守RFC 2616,否则回退到处理Content-Length 缺失的其他策略之一。

(注意:应用程序和中间件**不能**对它们的输出应用任何类型的Transfer-Encoding,例如分块或 gzip;作为“逐跳”操作,这些编码属于实际的 Web 服务器/网关。有关更多详细信息,请参阅下面的 其他 HTTP 功能。)

缓冲和流式传输

一般来说,应用程序通过缓冲其(适度大小的)输出并将其一次性全部发送来实现最佳吞吐量。这是现有框架(如 Zope)中的一种常见方法:输出被缓冲在一个 StringIO 或类似的对象中,然后与响应头一起一次性传输。

WSGI 中的对应方法是应用程序只返回一个包含响应主体作为单个字节串的单元素可迭代对象(例如列表)。对于绝大多数渲染 HTML 页面(其文本很容易放入内存)的应用程序函数来说,这是推荐的方法。

但是,对于大型文件,或者对于 HTTP 流的特殊用途(例如多部分“服务器推送”),应用程序可能需要以更小的块提供输出(例如,为了避免将大型文件加载到内存中)。有时,响应的一部分可能需要很长时间才能生成,但提前发送其前面的部分将很有用。

在这些情况下,应用程序通常会返回一个迭代器(通常是生成器迭代器),它以块的方式生成输出。这些块可以被分解,以与多部分边界(对于“服务器推送”)相一致,或者在耗时的任务(例如读取磁盘上的另一个块)之前。

WSGI 服务器、网关和中间件**不能**延迟任何块的传输;它们**必须**将块完全传输到客户端,或者保证它们将在应用程序生成下一个块时继续传输。服务器/网关或中间件可以通过以下三种方式之一提供这种保证

  1. 在返回控制权给应用程序之前,将整个块发送到操作系统(并请求刷新任何 O/S 缓冲区),或者
  2. 使用不同的线程来确保在应用程序生成下一个块时继续传输块。
  3. (仅限中间件)将整个块发送到其父网关/服务器

通过提供这种保证,WSGI 允许应用程序确保传输不会在输出数据的任意点停滞。这对于例如多部分“服务器推送”流的正常运行至关重要,其中多部分边界之间的数据应完全传输到客户端。

中间件处理块边界

为了更好地支持异步应用程序和服务器,中间件组件**不能**阻塞迭代以等待来自应用程序可迭代对象的多个值。如果中间件需要在生成任何输出之前从应用程序累积更多数据,它**必须**产生一个空字节串。

换句话说,中间件组件**必须在**其底层应用程序产生一个值时**至少产生一个值**。如果中间件不能产生任何其他值,它必须产生一个空字节串。

此要求确保异步应用程序和服务器可以协同合作以减少同时运行给定数量的应用程序实例所需的线程数。

还要注意,此要求意味着中间件**必须**在其底层应用程序返回可迭代对象时立即返回可迭代对象。中间件使用write() 可调用对象来传输底层应用程序产生的数据也是被禁止的。中间件只能使用其父服务器的write() 可调用对象来传输底层应用程序使用中间件提供的write() 可调用对象发送的数据。

可调用对象 write()

一些现有的应用程序框架 API 支持非缓冲输出的方式与 WSGI 不同。具体来说,它们提供某种“写入”函数或方法来写入非缓冲数据块,或者它们提供一个缓冲的“写入”函数和一个“刷新”机制来刷新缓冲区。

不幸的是,除非使用线程或其他特殊机制,否则这些 API 无法用 WSGI 的“可迭代”应用程序返回值来实现。

因此,为了允许这些框架继续使用命令式 API,WSGI 包含一个特殊的write() 可调用对象,它由start_response 可调用对象返回。

新的 WSGI 应用程序和框架**不应该**使用write() 可调用对象,如果可能的话。write() 可调用对象严格来说是为了支持命令式流 API 的黑客行为。一般来说,应用程序应该通过其返回的可迭代对象生成其输出,因为这使得 Web 服务器能够在同一个 Python 线程中插入其他任务,从而潜在地为整个服务器提供更好的吞吐量。

start_response() 可调用对象返回的 write() 可调用对象接受一个参数:一个字节字符串,作为 HTTP 响应主体的一部分写入,该字节字符串的处理方式与输出可迭代对象产生的字节字符串完全相同。换句话说,在 write() 返回之前,它必须保证传入的字节字符串要么完全发送到客户端,要么在应用程序继续执行时被缓冲以进行传输。

应用程序 **必须** 返回一个可迭代对象,即使它使用 write() 来生成其响应主体的全部或部分内容。返回的可迭代对象 **可以** 为空(即不产生非空字节字符串),但如果它确实产生了非空字节字符串,则该输出必须由服务器或网关正常处理(即,必须立即发送或排队)。应用程序 **不能** 从其返回的可迭代对象中调用 write(),因此可迭代对象产生的任何字节字符串都在所有传递到 write() 的字节字符串发送到客户端之后传输。

Unicode 问题

HTTP 不直接支持 Unicode,本接口也不支持。所有编码/解码都必须由应用程序处理;传递到服务器或从服务器传递的所有字符串必须是 strbytes 类型,而不是 unicode 类型。在需要字符串对象的地方使用 unicode 对象的结果是未定义的。

还要注意,传递到 start_response() 作为状态或响应头的字符串 **必须** 遵循 RFC 2616 关于编码的要求。也就是说,它们必须是 ISO-8859-1 字符,或者使用 RFC 2047 MIME 编码。

strStringType 类型实际上是基于 Unicode 的 Python 平台(例如 Jython、IronPython、Python 3 等),本规范中提到的所有“字符串”都必须只包含可表示为 ISO-8859-1 编码的代码点(\u0000\u00FF,含)。对于应用程序来说,提供包含任何其他 Unicode 字符或代码点的字符串是致命错误。同样,服务器和网关 **不能** 向应用程序提供包含任何其他 Unicode 字符的字符串。

再次强调,本规范中提到的所有“字符串”对象 **必须** 是 strStringType 类型,**不能** 是 unicodeUnicodeType 类型。而且,即使给定平台允许 str/StringType 对象中每个字符超过 8 位,但对于本规范中提到的任何“字符串”值,都只能使用最低的 8 位。

对于本规范中提到的“字节字符串”值(即从 wsgi.input 读取的值、传递到 write() 或由应用程序产生的值),该值 **必须** 是 Python 3 中的 bytes 类型,在早期版本的 Python 中是 str 类型。

错误处理

通常,应用程序 **应该** 尝试捕获自身内部错误,并在浏览器中显示一条有用的消息。(由应用程序决定在这种情况下“有用”的含义。)

但是,要显示这样的消息,应用程序必须尚未实际发送任何数据到浏览器,否则它有可能破坏响应。因此,WSGI 提供了一种机制,可以让应用程序要么发送其错误消息,要么自动中止:start_responseexc_info 参数。以下是一个使用示例

try:
    # regular application code here
    status = "200 Froody"
    response_headers = [("content-type", "text/plain")]
    start_response(status, response_headers)
    return ["normal body goes here"]
except:
    # XXX should trap runtime issues like MemoryError, KeyboardInterrupt
    #     in a separate handler before this bare 'except:'...
    status = "500 Oops"
    response_headers = [("content-type", "text/plain")]
    start_response(status, response_headers, sys.exc_info())
    return ["error body goes here"]

如果在异常发生时没有写入任何输出,对 start_response 的调用将正常返回,应用程序将返回一个错误主体发送到浏览器。但是,如果已经将任何输出发送到浏览器,start_response 将重新引发提供的异常。应用程序 **不应该** 捕获此异常,因此应用程序将中止。然后,服务器或网关可以捕获此(致命)异常并中止响应。

服务器 **应该** 捕获并记录任何中止应用程序或其返回值迭代的异常。如果在发生应用程序错误时已经将部分响应写入浏览器,服务器或网关 **可以** 尝试在输出中添加错误消息,前提是已发送的标头指示 text/* 内容类型,并且服务器知道如何干净地修改该类型的内容。

一些中间件可能希望提供额外的异常处理服务,或者拦截和替换应用程序错误消息。在这种情况下,中间件可以选择 **不** 重新引发提供给 start_responseexc_info,而是引发特定于中间件的异常,或者在存储提供参数后,不引发异常直接返回。这将导致应用程序返回其错误主体可迭代对象(或调用 write()),允许中间件捕获和修改错误输出。只要应用程序作者

  1. 始终在开始错误响应时提供 exc_info
  2. 在提供 exc_info 时,永远不要捕获由 start_response 引发的错误

HTTP 1.1 Expect/Continue

实现 HTTP 1.1 的服务器和网关 **必须** 为 HTTP 1.1 的“expect/continue”机制提供透明支持。这可以通过以下几种方式实现

  1. 立即使用“100 Continue”响应来响应包含 Expect: 100-continue 请求的请求,并继续正常执行。
  2. 继续正常处理请求,但为应用程序提供一个 wsgi.input 流,该流将在应用程序第一次尝试从输入流读取时发送“100 Continue”响应。然后,读取请求必须保持阻塞,直到客户端响应。
  3. 等待客户端决定服务器不支持 expect/continue,并自行发送请求主体。(这不是最佳选择,不建议这样做。)

请注意,这些行为限制不适用于 HTTP 1.0 请求,也不适用于未定向到应用程序对象的请求。有关 HTTP 1.1 Expect/Continue 的更多信息,请参阅 RFC 2616 的第 8.2.3 节和第 10.1.1 节。

其他 HTTP 功能

通常,服务器和网关应该“装傻”,让应用程序完全控制其输出。它们只应该进行不会改变应用程序响应的有效语义的更改。应用程序开发人员始终可以添加中间件组件来提供额外的功能,因此服务器/网关开发人员应该在实现时保持保守。从某种意义上说,服务器应该将自己视为 HTTP “网关服务器”,而应用程序则视为 HTTP “源服务器”。(有关这些术语的定义,请参阅 RFC 2616 的第 1.3 节。)

但是,由于 WSGI 服务器和应用程序不通过 HTTP 通信,因此 RFC 2616 中称为“逐跳”的标头不适用于 WSGI 内部通信。WSGI 应用程序 **不能** 生成任何 “逐跳”标头,尝试使用需要它们生成此类标头的 HTTP 功能,或依赖于 environ 字典中任何传入的“逐跳”标头的内容。WSGI 服务器 **必须** 自行处理任何支持的传入“逐跳”标头,例如解码任何传入的 Transfer-Encoding,包括如果有的话,对分块编码进行解码。

将这些原则应用于各种 HTTP 功能,应该很清楚,服务器 **可以** 通过 If-None-MatchIf-Modified-Since 请求标头以及 Last-ModifiedETag 响应标头来处理缓存验证。但是,它不需要这样做,应用程序 **应该** 执行自己的缓存验证,如果它希望支持该功能,因为服务器/网关不需要执行此类验证。

类似地,服务器 **可以** 重新编码或传输编码应用程序的响应,但应用程序 **应该** 自行使用合适的编码内容,并且 **不能** 应用传输编码。如果客户端请求,并且应用程序本身不支持字节范围,则服务器 **可以** 传输应用程序响应的字节范围。但是,再次强调,如果需要,应用程序 **应该** 自行执行此功能。

请注意,这些对应用程序的限制并不一定意味着每个应用程序都必须重新实现每个 HTTP 功能;许多 HTTP 功能可以通过中间件组件部分或完全实现,从而使服务器和应用程序作者不必一遍又一遍地实现相同的功能。

线程支持

线程支持(或缺乏线程支持)也取决于服务器。可以并行运行多个请求的服务器 **应该** 还提供以单线程方式运行应用程序的选项,以便那些不是线程安全的应用程序或框架仍然可以与该服务器一起使用。

实现/应用程序说明

服务器扩展 API

一些服务器作者可能希望公开更高级的 API,应用程序或框架作者可以将这些 API 用于特殊目的。例如,基于 mod_python 的网关可能希望公开 Apache API 的一部分作为 WSGI 扩展。

在最简单的情况下,这只需要定义一个 environ 变量,例如 mod_python.some_api。但是,在许多情况下,中间件可能存在,这可能会使此操作变得困难。例如,提供对 environ 变量中找到的相同 HTTP 标头的访问权限的 API,如果 environ 被中间件修改,可能会返回不同的数据。

通常,任何复制、替代或绕过 WSGI 功能一部分的扩展 API 都存在与中间件组件不兼容的风险。服务器/网关开发人员 *不应* 假设没有人会使用中间件,因为一些框架开发人员专门打算组织或重组他们的框架,以便它们几乎完全充当各种类型的中间件。

因此,为了提供最大的兼容性,提供扩展 API 来替换某些 WSGI 功能的服务器和网关**必须**设计这些 API,以便使用它们替换的 API 部分来调用它们。例如,访问 HTTP 请求头的扩展 API 必须要求应用程序传入其当前的environ,以便服务器/网关可以验证通过 API 访问的 HTTP 头是否已被中间件更改。如果扩展 API 不能保证它始终与environ关于 HTTP 头内容保持一致,它必须拒绝为应用程序提供服务,例如通过引发错误、返回None而不是头集合,或者对 API 来说合适的操作。

类似地,如果扩展 API 提供了一种写入响应数据或头的替代方法,它应该要求在应用程序可以获得扩展服务之前传入start_response可调用对象。如果传入的对象与服务器/网关最初提供给应用程序的对象不同,则它无法保证正确操作,并且必须拒绝为应用程序提供扩展服务。

这些准则也适用于向environ添加信息(如解析后的 cookie、表单变量、会话等)的中间件。具体来说,此类中间件应该将这些功能提供为在environ上运行的函数,而不是简单地将值塞入environ。这有助于确保信息是从environ任何中间件完成任何 URL 重写或其他environ修改之后计算出来的。

为了避免将来中间件开发人员被迫从environ中删除任何和所有扩展 API 以确保他们的调解没有被使用这些扩展的应用程序绕过,遵循这些“安全扩展”规则对于服务器/网关和中间件开发人员来说都非常重要!

应用程序配置

此规范没有定义服务器如何选择或获取要调用的应用程序。这些和其他配置选项是高度服务器特定的问题。预计服务器/网关作者将记录如何配置服务器以执行特定应用程序对象,以及使用哪些选项(如线程选项)。

另一方面,框架作者应该记录如何创建一个包装其框架功能的应用程序对象。用户已经选择服务器和应用程序框架,必须将两者连接在一起。但是,由于框架和服务器现在都有一个共同的接口,因此这应该只是一个机械问题,而不是每个新服务器/框架对的重大工程工作。

最后,一些应用程序、框架和中间件可能希望使用environ字典来接收简单的字符串配置选项。服务器和网关**应该**通过允许应用程序的部署者指定要放置在environ中的键值对来支持这一点。在最简单的情况下,这种支持可以仅包括将所有操作系统提供的环境变量从os.environ复制到environ字典中,因为原则上部署者可以从外部配置这些变量到服务器,或者在 CGI 情况下,他们可能能够通过服务器的配置文件进行设置。

应用程序**应该**尽量减少这些必需变量,因为并非所有服务器都支持轻松配置它们。当然,即使在最坏的情况下,部署应用程序的人也可以创建一个脚本来提供必要的配置值。

from the_app import application

def new_app(environ, start_response):
    environ['the_app.configval1'] = 'something'
    return application(environ, start_response)

但是,大多数现有的应用程序和框架可能只需要从environ中获取一个配置值,以指示其应用程序或框架特定配置文件的位置。(当然,应用程序应该缓存这种配置,以避免在每次调用时都重新读取它。)

URL 重构

如果应用程序希望重建请求的完整 URL,它可以使用以下算法,由 Ian Bicking 提供。

from urllib.parse import quote
url = environ['wsgi.url_scheme']+'://'

if environ.get('HTTP_HOST'):
    url += environ['HTTP_HOST']
else:
    url += environ['SERVER_NAME']

    if environ['wsgi.url_scheme'] == 'https':
        if environ['SERVER_PORT'] != '443':
           url += ':' + environ['SERVER_PORT']
    else:
        if environ['SERVER_PORT'] != '80':
           url += ':' + environ['SERVER_PORT']

url += quote(environ.get('SCRIPT_NAME', ''))
url += quote(environ.get('PATH_INFO', ''))
if environ.get('QUERY_STRING'):
    url += '?' + environ['QUERY_STRING']

请注意,这种重建的 URL 可能与客户端请求的 URI 不完全相同。例如,服务器重写规则可能修改了客户端最初请求的 URL 以将其置于规范形式。

支持较旧(<2.2)版本的 Python

某些服务器、网关或应用程序可能希望支持旧版(<2.2)版本的 Python。如果 Jython 是目标平台,这一点尤其重要,因为截至撰写本文时,尚未提供生产就绪版本的 Jython 2.2。

对于服务器和网关来说,这相对简单:针对 2.2 之前版本的 Python 的服务器和网关必须简单地限制自己只使用标准的“for”循环来迭代应用程序返回的任何可迭代对象。这是确保与 2.2 之前迭代器协议(在下面进一步讨论)和“今天的”迭代器协议(参见PEP 234)的源代码级兼容性的唯一方法。

(请注意,此技术必然只适用于用 Python 编写的服务器、网关或中间件。从其他语言正确使用迭代器协议的讨论超出了本 PEP 的范围。)

对于应用程序来说,支持 2.2 之前版本的 Python 稍微复杂一些。

  • 您不能返回文件对象并期望它作为可迭代对象工作,因为在 Python 2.2 之前,文件不可迭代。(一般来说,您不应该这样做,因为在大多数情况下它会执行得很差!)使用wsgi.file_wrapper或应用程序特定的文件包装类。(有关wsgi.file_wrapper的更多信息,以及一个可用于将文件包装为可迭代对象的示例类,请参见可选的平台特定文件处理。)
  • 如果您返回自定义可迭代对象,它**必须**实现 2.2 之前的迭代器协议。也就是说,提供一个接受整数键的__getitem__方法,并在耗尽时引发IndexError。(请注意,内置序列类型也是可以接受的,因为它们也实现了此协议。)

最后,希望支持 2.2 之前版本的 Python 的中间件,并且迭代应用程序返回值或本身返回可迭代对象(或两者),必须遵循上述适当的建议。

(注意:应该不言而喻的是,要支持 2.2 之前版本的 Python,任何服务器、网关、应用程序或中间件也必须只使用目标版本中可用的语言特性,使用 1 和 0 而不是TrueFalse,等等。)

可选平台特定文件处理

某些操作系统环境提供特殊的、高性能的文件传输工具,例如 Unixsendfile()调用。服务器和网关**可以**通过environ中的可选wsgi.file_wrapper键来公开此功能。应用程序**可以**使用此“文件包装器”将文件或类文件对象转换为可迭代对象,然后返回它,例如。

if 'wsgi.file_wrapper' in environ:
    return environ['wsgi.file_wrapper'](filelike, block_size)
else:
    return iter(lambda: filelike.read(block_size), '')

如果服务器或网关提供wsgi.file_wrapper,它必须是一个可调用对象,它接受一个必需的位置参数,和一个可选的位置参数。第一个参数是要发送的类文件对象,第二个参数是一个可选的块大小“建议”(服务器/网关不需要使用它)。该可调用对象**必须**返回一个可迭代对象,并且**绝不能**在服务器/网关实际收到来自应用程序的可迭代对象作为返回值之前执行任何数据传输。(否则,将阻止中间件能够解释或覆盖响应数据。)

要被认为是“类文件”,应用程序提供的对象必须有一个read()方法,该方法接受一个可选的大小参数。它**可以**有一个close()方法,如果有,则由wsgi.file_wrapper返回的可迭代对象**必须**有一个close()方法来调用原始类文件对象的close()方法。如果“类文件”对象有任何其他方法或属性的名称与 Python 内置文件对象的名称匹配(例如fileno()),则wsgi.file_wrapper**可以**假设这些方法或属性具有与内置文件对象相同的语义。

任何平台特定文件处理的实际实现必须在应用程序返回之后发生,并且服务器或网关检查是否返回了包装器对象。(同样,由于存在中间件、错误处理程序等,因此无法保证创建的任何包装器都会被实际使用。)

除了处理close()之外,从应用程序返回文件包装器的语义应该与应用程序返回iter(filelike.read, '')时相同。换句话说,传输应该从“文件”中传输开始时的当前位置开始,并一直持续到文件结束,或者直到写入Content-Length字节为止。(如果应用程序没有提供Content-Length,服务器**可以**使用它对底层文件实现的了解从文件中生成一个。)

当然,平台特定文件传输 API 通常不接受任意“类文件”对象。因此,wsgi.file_wrapper必须内省提供的对象以查找诸如fileno()(类 Unix 操作系统)或java.nio.FileChannel(在 Jython 下)之类的东西,以确定类文件对象是否适合与它支持的平台特定 API 一起使用。

请注意,即使该对象适合平台 API,wsgi.file_wrapper**仍然**必须返回一个包装read()close()的可迭代对象,以便使用文件包装器的应用程序可以在平台之间移植。以下是一个简单的平台无关文件包装器类,适用于旧版(2.2 之前)和新的 Python。

class FileWrapper:

    def __init__(self, filelike, blksize=8192):
        self.filelike = filelike
        self.blksize = blksize
        if hasattr(filelike, 'close'):
            self.close = filelike.close

    def __getitem__(self, key):
        data = self.filelike.read(self.blksize)
        if data:
            return data
        raise IndexError

这是一个使用它来提供对平台特定 API 访问的服务器/网关的代码片段。

environ['wsgi.file_wrapper'] = FileWrapper
result = application(environ, start_response)

try:
    if isinstance(result, FileWrapper):
        # check if result.filelike is usable w/platform-specific
        # API, and if so, use that API to transmit the result.
        # If not, fall through to normal iterable handling
        # loop below.

    for data in result:
        # etc.

finally:
    if hasattr(result, 'close'):
        result.close()

问答

  1. 为什么environ必须是字典?使用子类有什么问题?

    要求字典的理由是为了最大限度地提高服务器之间的可移植性。另一种方法是定义字典方法的某个子集作为标准和可移植接口。然而,在实践中,大多数服务器可能会发现字典足以满足其需求,因此框架作者会期望所有字典功能都可用,因为它们在大多数情况下都会存在。但是,如果某些服务器选择使用字典,那么尽管该服务器“符合”规范,但会存在互操作性问题。因此,使字典成为强制性的简化了规范并保证了互操作性。

    请注意,这不会阻止服务器或框架开发人员在 environ 字典内部提供定制变量作为专门服务。这是提供任何此类增值服务的推荐方法。

  2. 为什么你可以调用 write() 生成字节串/返回可迭代对象?难道我们不应该只选择一种方法吗?

    如果我们只支持迭代方法,那么当前假设“推送”可用性的框架将受到影响。但是,如果我们只支持通过 write() 推送,那么服务器性能将因例如大文件传输而受到影响(如果工作线程在所有输出发送完毕之前无法开始处理新请求)。因此,这种折衷方案允许应用程序框架根据需要支持这两种方法,但对服务器实现者来说,其负担仅比仅推送方法略高。

  3. close() 是做什么用的?

    当在应用程序对象执行期间进行写入时,应用程序可以使用 try/finally 块确保释放资源。但是,如果应用程序返回可迭代对象,则在垃圾回收可迭代对象之前不会释放任何使用的资源。 close() 习惯用法允许应用程序在请求结束时释放关键资源,并且它与 PEP 325 提出的在生成器中支持 try/finally 向前兼容。

  4. 为什么这个接口如此底层?我想要 X 功能!(例如,cookie、会话、持久性等)

    这不是另一个 Python Web 框架。它只是一种框架与 Web 服务器相互通信的方式。如果你想要这些功能,你需要选择一个提供你想要的功能的 Web 框架。如果该框架允许你创建 WSGI 应用程序,你应该能够在大多数支持 WSGI 的服务器上运行它。此外,一些 WSGI 服务器可能会通过其 environ 字典中提供的对象提供额外的服务;有关详细信息,请参阅相关服务器文档。(当然,使用此类扩展的应用程序不可移植到其他基于 WSGI 的服务器。)

  5. 为什么使用 CGI 变量而不是传统的 HTTP 标头?为什么将它们与 WSGI 定义的变量混合在一起?

    许多现有的 Web 框架都是基于 CGI 规范构建的,而现有的 Web 服务器知道如何生成 CGI 变量。相比之下,表示传入 HTTP 信息的替代方法支离破碎且缺乏市场份额。因此,使用 CGI “标准” 似乎是利用现有实现的一个好方法。至于将它们与 WSGI 变量混合在一起,将它们分开只需要传递两个字典参数,而没有实际的好处。

  6. 状态字符串怎么样?难道我们不能只使用数字,传递 200 而不是 "200 OK" 吗?

    这样做会使服务器或网关变得复杂,因为它们需要有一个数字状态表和相应的消息。相比之下,应用程序或框架作者很容易为他们正在使用的特定响应代码输入额外的文本,并且现有的框架通常已经有一个包含所需消息的表。因此,总的来说,让应用程序/框架负责,而不是服务器或网关,似乎更好。

  7. 为什么不能保证 wsgi.run_once 只运行应用程序一次?

    因为它仅仅是对应用程序的建议,表明它应该“针对不频繁的运行进行调整”。这是针对具有用于缓存、会话等的多种操作模式的应用程序框架而设计的。在“多次运行”模式下,此类框架可能会预加载缓存,并且可能不会在每次请求后将例如日志或会话数据写入磁盘。在“单次运行”模式下,此类框架会避免预加载并在每次请求后刷新所有必要的写入。

    但是,为了测试应用程序或框架以验证在后一种模式下的正确操作,可能需要(或至少是方便)多次调用它。因此,应用程序不应该假设它绝对不会再次运行,仅仅因为它被调用时 wsgi.run_once 设置为 True

  8. X 功能(字典、可调用对象等)在应用程序代码中使用起来很丑陋;为什么我们不使用对象呢?

    WSGI 的所有这些实现选择都是专门为了分离功能而设计的;将这些功能重新组合到封装的对象中使得编写服务器或网关变得有点困难,而编写仅替换或修改整体功能中一小部分的中间件则要困难一个数量级。

    本质上,中间件希望具有“责任链”模式,它可以充当某些功能的“处理程序”,同时允许其他功能保持不变。如果接口要保持可扩展性,那么用普通的 Python 对象来做到这一点很困难。例如,必须使用 __getattr____getattribute__ 覆盖,以确保扩展(例如未来 WSGI 版本定义的属性)被传递。

    这种类型的代码很难做到 100% 正确,而且很少有人愿意自己编写它。因此,他们会复制其他人的实现,但在复制的人纠正另一个边缘情况时却未能更新它。

    此外,这种必要的样板代码纯粹是赘余,是中间件开发人员为了支持应用程序框架开发人员略微更漂亮的 API 而支付的开发者税。但是,应用程序框架开发人员通常只会更新一个框架以支持 WSGI,并且只在他们整个框架中非常有限的一部分。它很可能是他们第一次(也许也是唯一一次)实现 WSGI,因此他们可能会使用这个规范来实现。因此,用对象属性等使 API 更“漂亮”的努力对于这个受众来说很可能是浪费。

    我们鼓励那些想要一个更漂亮(或其他改进)的 WSGI 接口用于直接 Web 应用程序编程(而不是 Web 框架开发)的人,开发 API 或框架,这些 API 或框架包装 WSGI 以便应用程序开发人员方便使用。这样,WSGI 可以为服务器和中间件作者保持方便的底层,而不会对应用程序开发人员来说很“丑陋”。

提议/讨论中

这些项目目前正在 Web-SIG 和其他地方讨论,或者在 PEP 作者的“待办事项”列表中。

  • wsgi.input 应该是一个迭代器而不是文件吗?这将有助于异步应用程序和分块编码输入流。
  • 正在讨论可选扩展,以暂停应用程序输出的迭代,直到输入可用或回调发生。
  • 添加关于同步与异步应用程序和服务器、相关线程模型以及这些领域中的问题/设计目标的部分。

鸣谢

感谢 Web-SIG 邮件列表中的许多人,他们富有洞察力的反馈使这个修订稿成为可能。尤其是

  • Gregory “Grisha” Trubetskoy, mod_python 的作者,他批评了第一个草案,因为它没有提供比“普通 CGI” 任何优势,从而鼓励我寻找更好的方法。
  • Ian Bicking,他帮助我督促我正确地指定多线程和多进程选项,并敦促我提供一种机制,使服务器能够向应用程序提供定制的扩展数据。
  • Tony Lownds,他提出了一个 start_response 函数的概念,该函数接受状态和标头,返回一个 write 函数。他的输入还指导了异常处理机制的设计,尤其是在允许中间件覆盖应用程序错误消息的方面。
  • Alan Kennedy,他在规范最终确定之前就勇敢地尝试在 Jython 上实现 WSGI,这帮助形成了“支持旧版本的 Python”部分,以及可选的 wsgi.file_wrapper 功能,以及一些早期的字节/Unicode 决策。
  • Mark Nottingham,他广泛地审查了规范,以发现与 HTTP RFC 兼容性方面的问题,尤其是在我甚至不知道存在直到他指出它们之前 HTTP/1.1 功能方面。
  • Graham Dumpleton,他不知疲倦地工作(即使面对我的懒惰和愚蠢),以获得 WSGI 的某种 Python 3 版本,他提出了“原生字符串”与“字节字符串”的概念,并 thoughtfully wrestled through a great many HTTP, wsgi.input 以及其他修改。这个新的 PEP 的大部分,如果不是全部,功劳都归功于他。

参考资料


来源: https://github.com/python/peps/blob/main/peps/pep-3333.rst

上次修改时间: 2023-10-23 19:28:26 GMT