PEP 333 – Python Web 服务器网关接口 v1.0
- 作者:
- Phillip J. Eby <pje at telecommunity.com>
- 讨论至:
- Web-SIG 列表
- 状态:
- 最终版
- 类型:
- 信息性
- 创建日期:
- 2003年12月7日
- 发布历史:
- 2003年12月7日,2004年8月8日,2004年8月20日,2004年8月27日,2010年9月27日
- 取代者:
- 3333
前言
注意:有关支持 Python 3.x 并包含社区勘误、增补和澄清的更新版本规范,请参阅 PEP 3333。
摘要
本文档规定了 Web 服务器与 Python Web 应用程序或框架之间拟议的标准接口,以促进 Web 应用程序在各种 Web 服务器上的可移植性。
基本原理和目标
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 服务器(无论是用 Python 编写的(例如 Medusa)、嵌入 Python 的(例如 mod_python),还是通过网关协议(例如 CGI、FastCGI 等)调用 Python)能够提供并广泛使用这种 API,将把框架选择与 Web 服务器选择分开,使用户可以自由选择适合他们的搭配,同时让框架和服务器开发人员能够专注于他们擅长的专业领域。
因此,本 PEP 提出了 Web 服务器与 Web 应用程序或框架之间一个简单通用的接口:Python Web 服务器网关接口(WSGI)。
然而,仅仅存在 WSGI 规范并不能解决 Python Web 应用程序现有服务器和框架的现状。服务器和框架的作者和维护者必须实际实现 WSGI 才能产生任何效果。
但是,由于目前没有现有服务器或框架支持 WSGI,因此实现 WSGI 支持的作者并不能立即获得回报。因此,WSGI **必须**易于实现,以便作者在接口上的初始投入可以合理地低。
因此,在接口的服务器和框架**两端**实现简单性对于 WSGI 接口的实用性至关重要,也是所有设计决策的主要标准。
然而,请注意,对于框架作者而言,实现的简单性与 Web 应用程序作者易于使用并非一回事。WSGI 向框架作者提供了一个绝对“无装饰”的接口,因为像响应对象和 cookie 处理之类的额外功能只会妨碍现有框架处理这些问题。重申,WSGI 的目标是促进现有服务器与应用程序或框架之间的轻松互连,而不是创建一个新的 Web 框架。
另请注意,此目标排除了 WSGI 要求任何在已部署的 Python 版本中尚不可用的东西。因此,本规范未提议或要求新的标准库模块,并且 WSGI 中没有任何内容要求 Python 版本高于 2.2.2。(但是,未来的 Python 版本中包含标准库提供的 Web 服务器对此接口的支持是一个好主意。)
除了对现有和未来框架和服务器的易于实现外,还应该容易创建请求预处理器、响应后处理器以及其他基于 WSGI 的“中间件”组件,这些组件对其包含的服务器来说就像一个应用程序,同时对其包含的应用程序来说又像一个服务器。
如果中间件既简单又健壮,并且 WSGI 在服务器和框架中广泛可用,它就有可能催生一种全新的 Python Web 应用程序框架:一种由松散耦合的 WSGI 中间件组件组成的框架。事实上,现有框架作者甚至可能选择重构其框架的现有服务,以这种方式提供,从而更像与 WSGI 一起使用的库,而不再是单片框架。这将使应用程序开发人员能够为特定功能选择“最佳组件”,而不是必须接受单个框架的所有优点和缺点。
当然,截至本文撰写之时,那一天无疑还很遥远。在此期间,WSGI 的短期目标是使任何框架都能与任何服务器一起使用。
最后,需要提及的是,当前版本的 WSGI 并未规定将应用程序“部署”与 Web 服务器或服务器网关结合使用的任何特定机制。目前,这必然由服务器或网关根据实现来定义。在足够多的服务器和框架实现了 WSGI,以提供各种部署要求的实际经验后,可能会有意义地创建另一个 PEP,描述 WSGI 服务器和应用程序框架的部署标准。
规范概述
WSGI 接口有两端:“服务器”或“网关”端,以及“应用程序”或“框架”端。服务器端调用由应用程序端提供的可调用对象。如何提供该对象的具体细节取决于服务器或网关。假定某些服务器或网关会要求应用程序的部署者编写一个简短的脚本来创建服务器或网关的实例,并向其提供应用程序对象。其他服务器和网关可能会使用配置文件或其他机制来指定应用程序对象应从何处导入,或以其他方式获取。
除了“纯”服务器/网关和应用程序/框架之外,还可以创建实现本规范两端的“中间件”组件。此类组件对其包含的服务器而言充当应用程序,对其包含的应用程序而言充当服务器,可用于提供扩展 API、内容转换、导航和其他有用的功能。
在本规范中,我们将“可调用对象”一词定义为“函数、方法、类,或带有 __call__ 方法的实例”。由实现该可调用对象的服务器、网关或应用程序根据其需求选择适当的实现技术。反之,调用可调用对象的服务器、网关或应用程序**不得**依赖于所提供的可调用对象的类型。可调用对象只能被调用,不能被内省。
应用程序/框架端
应用程序对象只是一个接受两个参数的可调用对象。“对象”一词不应被误解为需要实际的对象实例:函数、方法、类或带有 __call__ 方法的实例都可以用作应用程序对象。应用程序对象必须能够被多次调用,因为几乎所有服务器/网关(CGI 除外)都会发出此类重复请求。
(注意:尽管我们将其称为“应用程序”对象,但这不应被误解为应用程序开发人员将使用 WSGI 作为 Web 编程 API!假定应用程序开发人员将继续使用现有的高级框架服务来开发其应用程序。WSGI 是为框架和服务器开发人员提供的工具,并非旨在直接支持应用程序开发人员。)
这里有两个应用程序对象示例;一个是函数,另一个是类
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!\n']
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!\n"
服务器/网关端
服务器或网关对从 HTTP 客户端收到的每个指向应用程序的请求调用应用程序可调用对象一次。为了说明,这里有一个简单的 CGI 网关,实现为一个接受应用程序对象的函数。请注意,这个简单示例的错误处理有限,因为默认情况下,未捕获的异常将转储到 sys.stderr 并由 Web 服务器记录。
import os, sys
def run_with_cgi(application):
environ = dict(os.environ.items())
environ['wsgi.input'] = sys.stdin
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):
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
sys.stdout.write('Status: %s\r\n' % status)
for header in response_headers:
sys.stdout.write('%s: %s\r\n' % header)
sys.stdout.write('\r\n')
sys.stdout.write(data)
sys.stdout.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[0], exc_info[1], exc_info[2]
finally:
exc_info = None # avoid dangling circular ref
elif headers_set:
raise AssertionError("Headers already set!")
headers_set[:] = [status, response_headers]
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('') # send headers now if body was empty
finally:
if hasattr(result, 'close'):
result.close()
中间件:两端兼顾的组件
请注意,单个对象可能相对于某些应用程序扮演服务器角色,同时相对于某些服务器扮演应用程序角色。此类“中间件”组件可以执行以下功能:
- 根据目标 URL 将请求路由到不同的应用程序对象,并相应地重写
environ。 - 允许多个应用程序或框架在同一进程中并行运行
- 通过网络转发请求和响应以实现负载均衡和远程处理
- 执行内容后处理,例如应用 XSL 样式表
中间件的存在通常对接口的“服务器/网关”和“应用程序/框架”两端都是透明的,并且不应需要任何特殊支持。希望将中间件集成到应用程序中的用户只需将中间件组件提供给服务器,就像它是一个应用程序一样,并配置中间件组件来调用应用程序,就像中间件组件是一个服务器一样。当然,中间件所包装的“应用程序”实际上可能是另一个中间件组件包装的另一个应用程序,依此类推,从而形成所谓的“中间件堆栈”。
在大多数情况下,中间件必须符合 WSGI 的服务器和应用程序两端的所有限制和要求。但是,在某些情况下,对中间件的要求比对“纯”服务器或应用程序的要求更严格,这些点将在规范中注明。
以下是一个(开玩笑的)中间件组件示例,它使用 Joe Strout 的 piglatin.py 将 text/plain 响应转换为猪拉丁语。(注意:“真实”的中间件组件可能会使用更健壮的方式检查内容类型,并且还应检查内容编码。此外,这个简单示例忽略了一个单词可能跨越块边界的可能性。)
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 string, 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):
if self.transform_ok:
return piglatin(self._next())
else:
return self._next()
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))
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))
规范详情
应用程序对象必须接受两个位置参数。为了便于说明,我们将其命名为 environ 和 start_response,但它们不要求具有这些名称。服务器或网关**必须**使用位置参数(而不是关键字参数)调用应用程序对象。(例如,如上所示调用 result = application(environ, start_response)。)
environ 参数是一个字典对象,包含 CGI 风格的环境变量。此对象**必须**是内置的 Python 字典(*不是*子类、UserDict 或其他字典仿真),并且应用程序可以随意修改该字典。该字典还必须包含某些 WSGI 要求的变量(在后面的章节中描述),并且还可能包含服务器特定的扩展变量,其命名遵循下面将描述的约定。
start_response 参数是一个可调用对象,接受两个必需的位置参数和一个可选参数。为了说明,我们已将这些参数命名为 status、response_headers 和 exc_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;如果可以避免,新的应用程序或框架不应使用它。有关如何处理应用程序输出的更多详细信息,请参阅 缓冲与流式传输 部分。)
当服务器调用时,应用程序对象必须返回一个可迭代对象,该对象生成零个或多个字符串。这可以通过多种方式实现,例如返回一个字符串列表,或者应用程序是一个生成字符串的生成器函数,或者应用程序是一个其实例可迭代的类。无论如何实现,应用程序对象必须始终返回一个生成零个或多个字符串的可迭代对象。
服务器或网关必须以无缓冲方式将生成的字符串传输给客户端,在请求下一个字符串之前完成每个字符串的传输。(换句话说,应用程序**应该**执行自己的缓冲。有关如何处理应用程序输出的更多信息,请参见下面的缓冲和流式传输部分。)
服务器或网关应将生成的字符串视为二进制字节序列:特别是,它应确保行结束符不会被更改。应用程序负责确保要写入的字符串适合客户端的格式。(服务器或网关**可以**应用 HTTP 传输编码,或执行其他转换以实现 HTTP 特性,例如字节范围传输。有关详细信息,请参见下面的其他 HTTP 特性。)
如果调用 len(iterable) 成功,服务器必须能够依赖结果的准确性。也就是说,如果应用程序返回的可迭代对象提供了可用的 __len__() 方法,它**必须**返回准确的结果。(有关通常如何使用此功能的信息,请参见处理 Content-Length 头部部分。)
如果应用程序返回的可迭代对象具有 close() 方法,服务器或网关**必须**在当前请求完成时调用该方法,无论请求是正常完成还是由于错误提前终止(这是为了支持应用程序释放资源)。该协议旨在补充 PEP 325 的生成器支持以及其他具有 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_NAME、SERVER_PORT- 结合
SCRIPT_NAME和PATH_INFO,这些变量可以用于完成 URL。但请注意,如果存在HTTP_HOST,应优先于SERVER_NAME用于重建请求 URL。有关详细信息,请参见下面的 URL 重建 部分。SERVER_NAME和SERVER_PORT永远不能是空字符串,因此始终是必需的。 SERVER_PROTOCOL- 客户端用于发送请求的协议版本。通常,这将是类似
"HTTP/1.0"或"HTTP/1.1"的内容,应用程序可以使用它来确定如何处理任何 HTTP 请求头。(这个变量可能应该叫做REQUEST_PROTOCOL,因为它表示请求中使用的协议,而不一定是服务器响应中使用的协议。然而,为了与 CGI 兼容,我们必须保留现有名称。) HTTP_变量- 与客户端提供的 HTTP 请求头对应的变量(即名称以
"HTTP_"开头的变量)。这些变量的存在与否应与请求中相应 HTTP 头的存在与否相对应。
服务器或网关**应该**尝试提供尽可能多的适用 CGI 变量。此外,如果使用 SSL,服务器或网关**应该**提供尽可能多的 Apache SSL 环境变量 [3],例如 HTTPS=on 和 SSL_PROTOCOL。但请注意,使用除了上面列出的变量以外的任何 CGI 变量的应用程序必然无法移植到不支持相关扩展的 Web 服务器。(例如,不发布文件的 Web 服务器将无法提供有意义的 DOCUMENT_ROOT 或 PATH_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" 作为行结束符,并假定它将由服务器/网关转换为正确的行结束符。对于许多服务器, |
wsgi.multithread |
如果应用程序对象可能由同一进程中的另一个线程同时调用,则此值应评估为真;否则应评估为假。 |
wsgi.multiprocess |
如果等效的应用程序对象可能由另一个进程同时调用,则此值应评估为真;否则应评估为假。 |
wsgi.run_once |
如果服务器或网关期望(但不保证!)应用程序在其包含进程的生命周期中只被调用一次,则此值应评估为真。通常,这仅适用于基于 CGI(或类似)的网关。 |
最后,environ 字典还可以包含服务器定义的变量。这些变量应仅使用小写字母、数字、点和下划线命名,并应以定义服务器或网关唯一的名称作为前缀。例如,mod_python 可能会定义诸如 mod_python.some_variable 的变量。
输入和错误流
服务器提供的输入和错误流必须支持以下方法:
| 方法 | 流 | 备注 |
|---|---|---|
read(size) |
输入 |
1 |
readline() |
输入 |
1, 2 |
readlines(hint) |
输入 |
1, 3 |
__iter__() |
输入 |
|
flush() |
错误 |
4 |
write(str) |
错误 |
|
writelines(seq) |
错误 |
除了上表列出的这些注意事项外,每个方法的语义都按照 Python 库参考中的说明:
- 服务器无需读取超出客户端指定的
Content-Length,并且如果应用程序尝试读取超出该点,则允许模拟文件结束条件。应用程序**不应**尝试读取超出CONTENT_LENGTH变量指定的数据。 readline()的可选“size”参数不受支持,因为服务器作者实现起来可能很复杂,并且在实践中不常使用。- 请注意,
readlines()的hint参数对于调用者和实现者都是可选的。应用程序可以自由选择不提供它,服务器或网关也可以自由选择忽略它。 - 由于
errors流不能倒回,服务器和网关可以自由地立即转发写入操作,而无需缓冲。在这种情况下,flush()方法可能是空操作。但是,可移植应用程序不能假设输出是无缓冲的,或者flush()是空操作。如果它们需要确保输出确实已被写入,则必须调用flush()。(例如,为了最大限度地减少多个进程写入同一错误日志时数据的混杂。)
上表中列出的方法**必须**得到所有符合本规范的服务器的支持。符合本规范的应用程序**不得**使用 input 或 errors 对象的任何其他方法或属性。特别是,应用程序**不得**尝试关闭这些流,即使它们拥有 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(或其他相关规范)要求的头部,服务器或网关**必须**添加它。例如,HTTP Date: 和 Server: 头部通常由服务器或网关提供。
(提醒服务器/网关作者:HTTP 头部名称不区分大小写,所以在检查应用程序提供的头部时请务必考虑到这一点!)
应用程序和中间件禁止使用 HTTP/1.1“逐跳”特性或头部,HTTP/1.0 中的任何等效特性,或任何会影响客户端与 Web 服务器连接持久性的头部。这些特性是实际 Web 服务器的专属领域,服务器或网关**应**将其视为致命错误,如果应用程序尝试发送它们,并在提供给 start_response() 时引发错误。(有关“逐跳”特性和头部的更多具体信息,请参见下面的其他 HTTP 特性部分。)
start_response 可调用对象**不得**实际传输响应头。相反,它必须存储它们,由服务器或网关**仅在**应用程序返回值第一次迭代生成非空字符串时,或在应用程序第一次调用 write() 可调用对象时传输。换句话说,响应头必须等到实际有主体数据可用,或者应用程序返回的可迭代对象耗尽后才能发送。(此规则唯一可能的例外是响应头明确包含零 Content-Length。)
延迟响应头传输是为了确保缓冲和异步应用程序可以在最后一刻用错误输出替换其最初预期的输出。例如,如果应用程序缓冲区中正在生成主体时发生错误,应用程序可能需要将响应状态从“200 OK”更改为“500 Internal Error”。
如果提供了 exc_info 参数,它必须是一个 Python sys.exc_info() 元组。此参数应仅在错误处理程序调用 start_response 时由应用程序提供。如果提供了 exc_info,并且尚未输出任何 HTTP 头部,start_response 应将当前存储的 HTTP 响应头部替换为新提供的头部,从而允许应用程序在发生错误时“改变主意”输出。
但是,如果提供了 exc_info,并且 HTTP 头部已经发送,start_response **必须**引发错误,并且**应该**引发 exc_info 元组。也就是说
raise exc_info[0], exc_info[1], exc_info[2]
这将重新引发应用程序捕获的异常,原则上应中止应用程序。(一旦 HTTP 头部已发送,应用程序尝试向浏览器输出错误是不安全的。)如果应用程序使用 exc_info 调用 start_response,则**不得**捕获 start_response 引发的任何异常。相反,它应该允许此类异常传播回服务器或网关。有关更多详细信息,请参见下面的错误处理。
应用程序**可以**多次调用 start_response,当且仅当提供了 exc_info 参数时。更准确地说,如果在当前应用程序调用中已经调用过 start_response,则不带 exc_info 参数调用 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 头部,服务器或网关可以选择几种处理方法。其中最简单的是在响应完成后关闭客户端连接。
然而,在某些情况下,服务器或网关可能能够生成 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 服务器、网关和中间件**不得**延迟任何块的传输;它们**必须**要么将整个块完全传输到客户端,要么保证即使在应用程序生成下一个块时它们也将继续传输。服务器/网关或中间件可以通过以下三种方式之一提供此保证:
- 在将控制权返回给应用程序之前,将整个块发送到操作系统(并请求刷新任何操作系统缓冲区),或者
- 使用不同的线程来确保在应用程序生成下一个块时继续传输该块。
- (仅限于中间件)将整个块发送到其父网关/服务器
通过提供此保证,WSGI 允许应用程序确保传输不会在其输出数据中的任意点停滞。这对于例如多部分“服务器推送”流的正常运行至关重要,其中多部分边界之间的数据应完整传输到客户端。
中间件对块边界的处理
为了更好地支持异步应用程序和服务器,中间件组件**不得**阻塞迭代,等待应用程序可迭代对象中的多个值。如果中间件在生成任何输出之前需要从应用程序累积更多数据,它**必须**生成一个空字符串。
换句话说,中间件组件**必须在每次其底层应用程序生成一个值时,至少生成一个值**。如果中间件无法生成任何其他值,它必须生成一个空字符串。
此要求确保异步应用程序和服务器可以协作减少同时运行给定数量应用程序实例所需的线程数。
另请注意,此要求意味着中间件**必须**在其底层应用程序返回可迭代对象后立即返回可迭代对象。中间件也禁止使用 write() 可调用对象传输由底层应用程序生成的数据。中间件只能使用其父服务器的 write() 可调用对象来传输底层应用程序使用中间件提供的 write() 可调用对象发送的数据。
write() 可调用对象
一些现有应用程序框架 API 以与 WSGI 不同的方式支持无缓冲输出。具体来说,它们提供某种“写入”函数或方法来写入无缓冲数据块,或者它们提供一个缓冲“写入”函数和一个“刷新”机制来刷新缓冲区。
不幸的是,除非使用线程或其他特殊机制,否则这些 API 无法根据 WSGI 的“可迭代”应用程序返回值来实现。
因此,为了允许这些框架继续使用命令式 API,WSGI 包含一个特殊的 write() 可调用对象,由 start_response 可调用对象返回。
如果可以避免,新的 WSGI 应用程序和框架**不应**使用 write() 可调用对象。write() 可调用对象严格来说是为了支持命令式流 API 而进行的变通。通常,应用程序应通过其返回的可迭代对象生成输出,因为这使得 Web 服务器能够在同一个 Python 线程中交错其他任务,从而可能为整个服务器提供更好的吞吐量。
write() 可调用对象由 start_response() 可调用对象返回,它接受一个参数:一个要作为 HTTP 响应主体一部分写入的字符串,其处理方式与通过输出可迭代对象生成完全相同。换句话说,在 write() 返回之前,它必须保证传入的字符串要么已完全发送给客户端,要么已缓冲等待传输,而应用程序继续进行。
应用程序**必须**返回一个可迭代对象,即使它使用 write() 来生成其响应主体或其一部分。返回的可迭代对象**可以**为空(即不生成任何非空字符串),但如果它确实生成非空字符串,则服务器或网关必须正常处理该输出(即,必须立即发送或排队)。应用程序**不得**从其返回的可迭代对象内部调用 write(),因此,可迭代对象生成的任何字符串将在所有传递给 write() 的字符串发送到客户端后传输。
Unicode 问题
HTTP 不直接支持 Unicode,此接口也不支持。所有编码/解码必须由应用程序处理;传递给服务器或从服务器传递的所有字符串必须是标准的 Python 字节字符串,而不是 Unicode 对象。在需要字符串对象的地方使用 Unicode 对象的后果是未定义的。
另请注意,传递给 start_response() 作为状态或响应头的字符串**必须**遵循 RFC 2616 中的编码规定。也就是说,它们必须是 ISO-8859-1 字符,或者使用 RFC 2047 MIME 编码。
在 str 或 StringType 类型实际上基于 Unicode 的 Python 平台(例如 Jython、IronPython、Python 3000 等)上,本规范中提及的所有“字符串”必须只包含 ISO-8859-1 编码中可表示的代码点(包括 \u0000 到 \u00FF)。应用程序提供包含任何其他 Unicode 字符或代码点的字符串是致命错误。同样,服务器和网关**不得**向应用程序提供包含任何其他 Unicode 字符的字符串。
再次强调,本规范中提及的所有字符串**必须**是 str 或 StringType 类型,并且**不得**是 unicode 或 UnicodeType 类型。而且,即使给定平台允许 str/StringType 对象中每个字符超过 8 位,本规范中作为“字符串”提及的任何值也只能使用低 8 位。
错误处理
通常,应用程序**应该**尝试捕获自己的内部错误,并在浏览器中显示一条有用的消息。(在此上下文中,“有用”意味着什么由应用程序决定。)
但是,要显示这样的消息,应用程序必须尚未实际向浏览器发送任何数据,否则它可能会破坏响应。因此,WSGI 提供了一种机制,允许应用程序发送其错误消息,或自动中止:start_response 的 exc_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_response 的 exc_info,而是引发中间件特定的异常,或者在存储提供的参数后简单地不带异常返回。这将导致应用程序返回其错误主体可迭代对象(或调用 write()),从而允许中间件捕获和修改错误输出。这些技术只要应用程序作者满足以下条件即可奏效:
- 在开始错误响应时始终提供
exc_info - 在提供
exc_info时,绝不捕获start_response引发的错误
HTTP 1.1 Expect/Continue
实现 HTTP 1.1 的服务器和网关**必须**为 HTTP 1.1 的“expect/continue”机制提供透明支持。这可以通过以下几种方式完成:
- 对于包含
Expect: 100-continue请求的请求,立即以“100 Continue”响应,并正常进行。 - 正常处理请求,但向应用程序提供一个
wsgi.input流,该流将在应用程序首次尝试从输入流读取时发送“100 Continue”响应。然后,读取请求必须保持阻塞,直到客户端响应。 - 等待客户端决定服务器不支持 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-Match 和 If-Modified-Since 请求头部以及 Last-Modified 和 ETag 响应头部来处理缓存验证。然而,它不强制这样做,应用程序**应该**执行自己的缓存验证,如果它希望支持该功能,因为服务器/网关不强制进行此类验证。
同样,服务器**可以**对应用程序的响应进行重新编码或传输编码,但应用程序**应该**自行使用合适的内容编码,并且**不得**应用传输编码。如果客户端请求,并且应用程序本身不支持字节范围,服务器**可以**传输应用程序响应的字节范围。然而,如果需要,应用程序**应该**自行执行此功能。
请注意,对应用程序的这些限制并不一定意味着每个应用程序都必须重新实现所有 HTTP 特性;许多 HTTP 特性可以由中间件组件部分或完全实现,从而使服务器和应用程序作者都无需重复实现相同的特性。
线程支持
线程支持,或缺乏线程支持,也取决于服务器。能够并行运行多个请求的服务器**应该**提供以单线程方式运行应用程序的选项,以便不线程安全的应用程序或框架仍可与该服务器一起使用。
实现/应用说明
服务器扩展 API
一些服务器作者可能希望公开更高级的 API,供应用程序或框架作者用于特殊目的。例如,基于 mod_python 的网关可能希望将 Apache API 的一部分作为 WSGI 扩展公开。
在最简单的情况下,这只需要定义一个 environ 变量,例如 mod_python.some_api。但是,在许多情况下,中间件的可能存在会使这变得困难。例如,一个提供对 environ 变量中找到的相同 HTTP 头部进行访问的 API,如果 environ 已被中间件修改,则可能会返回不同的数据。
通常,任何重复、取代或绕过 WSGI 功能一部分的扩展 API 都存在与中间件组件不兼容的风险。服务器/网关开发人员**不应**假设没有人会使用中间件,因为一些框架开发人员明确打算组织或重组其框架,使其几乎完全充当各种中间件。
因此,为了提供最大的兼容性,提供替代某些 WSGI 功能的扩展 API 的服务器和网关,**必须**设计这些 API,使其使用它们所替代的 API 部分进行调用。例如,用于访问 HTTP 请求头的扩展 API 必须要求应用程序传入其当前的 environ,以便服务器/网关可以验证通过 API 访问的 HTTP 头是否未被中间件更改。如果扩展 API 无法保证它将始终与 environ 在 HTTP 头内容方面保持一致,则它必须拒绝向应用程序提供服务,例如通过引发错误,返回 None 而不是头部集合,或任何适合该 API 的方式。
同样,如果扩展 API 提供了写入响应数据或头部的替代方式,它应该要求在应用程序获取扩展服务之前传入 start_response 可调用对象。如果传入的对象与服务器/网关最初提供给应用程序的对象不同,则无法保证正确操作,并且必须拒绝向应用程序提供扩展服务。
这些指导方针也适用于将解析的 cookie、表单变量、会话等信息添加到 environ 的中间件。具体来说,此类中间件应将这些功能作为操作 environ 的函数提供,而不是简单地将值塞入 environ。这有助于确保信息在任何中间件执行 URL 重写或其他 environ 修改后**从** 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 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。(请注意,内置序列类型也是可以接受的,因为它们也实现了此协议。)
最后,希望支持 Python 2.2 版本之前且迭代应用程序返回值或自身返回可迭代对象(或两者兼有)的中间件,必须遵循上述相应的建议。
(注:不言而喻,为了支持 Python 2.2 版本之前的版本,任何服务器、网关、应用程序或中间件也必须只使用目标版本中可用的语言特性,使用 1 和 0 代替 True 和 False 等。)
可选的平台特定文件处理
某些操作系统环境提供特殊的高性能文件传输功能,例如 Unix 的 sendfile() 调用。服务器和网关可以通过 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,它必须是一个可调用对象,接受一个必需的位置参数和一个可选的位置参数。第一个参数是要发送的类文件对象,第二个参数是可选的块大小“建议”(服务器/网关不必使用)。该可调用对象必须返回一个可迭代对象,并且不得在服务器/网关实际接收到该可迭代对象作为应用程序返回值之前执行任何数据传输。(否则将阻止中间件解释或覆盖响应数据。)
为了被视为“类文件”,应用程序提供的对象必须有一个接受可选 size 参数的 read() 方法。它可以有一个 close() 方法,如果是这样,由 wsgi.file_wrapper 返回的可迭代对象必须有一个 close() 方法,该方法调用原始类文件对象的 close() 方法。如果“类文件”对象具有任何其他方法或属性,其名称与 Python 内置文件对象的名称匹配(例如 fileno()),则 wsgi.file_wrapper 可以假定这些方法或属性与内置文件对象的语义相同。
任何平台特定文件处理的实际实现必须在应用程序返回之后发生,并且服务器或网关检查是否返回了包装器对象。(同样,由于中间件、错误处理程序等的存在,不保证创建的任何包装器都会被实际使用。)
除了对 close() 的处理之外,从应用程序返回文件包装器的语义应与应用程序返回 iter(filelike.read, '') 的语义相同。换句话说,传输应从传输开始时“文件”内的当前位置开始,并一直持续到结束。
当然,平台特定的文件传输 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()
问答
- 为什么
environ必须是字典?使用子类有什么问题?要求使用字典的理由是为了最大限度地提高服务器之间的可移植性。另一种选择是定义字典方法的一个子集作为标准和可移植的接口。然而,在实践中,大多数服务器可能会发现字典足以满足其需求,因此框架作者会期望可以使用完整的字典功能,因为它们通常都会存在。但是,如果某些服务器选择不使用字典,那么尽管该服务器“符合”规范,仍会出现互操作性问题。因此,强制使用字典简化了规范并保证了互操作性。
请注意,这并不妨碍服务器或框架开发人员在
environ字典中提供专门服务作为自定义变量。这是提供任何此类增值服务的推荐方法。 - 为什么您可以调用
write()并且返回字符串/可迭代对象?我们不应该只选择一种方式吗?如果只支持迭代方式,那么假定存在“推”功能的当前框架就会受到影响。但是,如果只支持通过
write()推送,那么服务器在传输例如大文件时的性能就会受到影响(如果一个工作线程在所有输出发送完毕之前不能开始处理新的请求)。因此,这种折衷允许应用程序框架根据需要支持两种方法,但服务器实现者的负担只比纯推送方法多一点。 - 那么
close()的作用是什么?当在应用程序对象执行期间进行写入时,应用程序可以使用 try/finally 块确保资源被释放。但是,如果应用程序返回一个可迭代对象,则在垃圾回收该可迭代对象之前,所使用的任何资源都不会被释放。
close()习语允许应用程序在请求结束时释放关键资源,并且它与 PEP 325 提出的生成器中 try/finally 的支持前向兼容。 - 为什么这个接口如此底层?我想要功能 X!(例如 cookies、会话、持久化等)
这并不是另一个 Python Web 框架。它只是一个框架与 Web 服务器通信的方式,反之亦然。如果您想要这些功能,您需要选择一个提供您所需功能的 Web 框架。如果该框架允许您创建 WSGI 应用程序,您应该能够在大多数支持 WSGI 的服务器中运行它。此外,一些 WSGI 服务器可能会通过其
environ字典中提供的对象提供附加服务;有关详细信息,请参阅适用的服务器文档。(当然,使用此类扩展的应用程序将无法移植到其他基于 WSGI 的服务器。) - 为什么使用 CGI 变量而不是传统的 HTTP 头?为什么将它们与 WSGI 定义的变量混在一起?
许多现有的 Web 框架都严重依赖 CGI 规范,并且现有 Web 服务器知道如何生成 CGI 变量。相比之下,表示入站 HTTP 信息的替代方法是分散的且市场份额不足。因此,使用 CGI“标准”似乎是利用现有实现的好方法。至于将它们与 WSGI 变量混合,分离它们只会需要传递两个字典参数,而没有带来实际好处。
- 状态字符串呢?我们不能只使用数字,传入
200而不是"200 OK"吗?这样做会使服务器或网关复杂化,因为需要它们维护一个数字状态及其对应消息的表格。相比之下,应用程序或框架作者很容易为他们正在使用的特定响应代码键入额外的文本,并且现有框架通常已经有一个包含所需消息的表格。因此,综合来看,最好将责任交给应用程序/框架,而不是服务器或网关。
- 为什么不保证
wsgi.run_once只运行应用程序一次?因为它只是向应用程序建议“为不频繁运行做准备”。这适用于具有多种操作模式的应用程序框架,例如缓存、会话等等。在“多运行”模式下,此类框架可能会预加载缓存,并且可能不会在每次请求后将例如日志或会话数据写入磁盘。在“单运行”模式下,此类框架避免预加载并在每次请求后刷新所有必要的写入。
然而,为了测试应用程序或框架以验证在后一种模式下是否正确运行,可能需要(或至少方便)多次调用它。因此,应用程序不应仅仅因为以
wsgi.run_once设置为True调用它而假定它肯定不会再次运行。 - 功能 X(字典、可调用对象等)在应用程序代码中使用起来很丑陋;为什么我们不改用对象呢?
WSGI 的所有这些实现选择都明确旨在将功能彼此解耦;将这些功能重新组合到封装的对象中,会使编写服务器或网关变得有些困难,并且使编写只替换或修改整体功能一小部分的中间件变得困难一个数量级。
本质上,中间件希望拥有一个“责任链”模式,它可以充当某些函数的“处理器”,同时允许其他函数保持不变。如果接口要保持可扩展性,这对于普通 Python 对象来说很难做到。例如,必须使用
__getattr__或__getattribute__覆盖,以确保扩展(例如未来 WSGI 版本定义的属性)能够通过。这种类型的代码在实现上 notoriously 难以做到 100% 正确,很少有人愿意自己编写。因此,他们会复制其他人的实现,但当他们复制的人纠正了另一个边缘情况时,却没有更新。这导致了持续的错误和不兼容。
此外,这种必要的样板代码将是纯粹的额外负担,是中间件开发人员为了支持一个稍微漂亮一点的应用程序框架开发人员 API 而付出的代价。但是,应用程序框架开发人员通常只会更新一个框架来支持 WSGI,并且只在其框架的非常有限的部分中进行。这很可能是他们第一次(也可能是唯一一次)WSGI 实现,因此他们很可能会随时准备好此规范进行实现。因此,为这个受众通过对象属性等使 API“更漂亮”的努力很可能会被浪费。
我们鼓励那些希望在直接 Web 应用程序编程(而不是 Web 框架开发)中使用更漂亮(或以其他方式改进)WSGI 接口的人,开发包装 WSGI 的 API 或框架,以方便应用程序开发人员使用。通过这种方式,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功能。 - Mark Nottingham,他广泛审查了规范的 HTTP RFC 合规性问题,特别是关于 HTTP/1.1 功能,直到他指出我才知道这些功能的存在。
参考资料
版权
本文档已置于公共领域。
来源: https://github.com/python/peps/blob/main/peps/pep-0333.rst
最后修改: 2025-02-01 08:59:27 GMT