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 服务器中提供并广泛使用此类 API——无论这些服务器是用 Python 编写的(例如 Medusa),嵌入 Python(例如 mod_python),还是通过网关协议(例如 CGI、FastCGI 等)调用 Python——都会将框架的选择与 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 响应主体的一部分写入的字符串。(注意:仅为了支持某些现有框架的命令式输出 API 而提供 write()
可调用对象;如果可以避免,则新应用程序或框架不应使用它。有关更多详细信息,请参阅缓冲和流 部分。)
当服务器调用时,应用程序对象必须返回一个可迭代对象,该对象产生零个或多个字符串。这可以通过多种方式实现,例如返回字符串列表,或者应用程序是一个产生字符串的生成器函数,或者应用程序是一个其实例是可迭代的类。无论如何实现,应用程序对象都必须始终返回一个产生零个或多个字符串的可迭代对象。
服务器或网关必须以非缓冲方式将产生的字符串传输到客户端,在请求另一个字符串之前完成每个字符串的传输。(换句话说,应用程序**应该**执行自己的缓冲。有关如何处理应用程序输出的更多信息,请参阅下面的缓冲和流 部分。)
服务器或网关应将产生的字符串视为二进制字节序列:特别是,它应确保不更改换行符。应用程序负责确保要写入的字符串(s)采用适合客户端的格式。(服务器或网关**可以**应用 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) |
input |
1 |
readline() |
input |
1, 2 |
readlines(hint) |
input |
1, 3 |
__iter__() |
input |
|
flush() |
errors |
4 |
write(str) |
errors |
|
writelines(seq) |
errors |
每个方法的语义如 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(或其他有效的规范)所需的标头,则服务器或网关**必须**添加它。例如,HTTPDate:
和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
参数必须是一个 Pythonsys.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 的方式支持无缓冲输出。具体来说,它们提供某种“写入”函数或方法来写入无缓冲的数据块,或者它们提供一个缓冲的“写入”函数和一个“刷新”机制来刷新缓冲区。
不幸的是,除非使用线程或其他特殊机制,否则无法根据 WSGI 的“可迭代”应用程序返回值来实现此类 API。
因此,为了允许这些框架继续使用命令式 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”机制提供透明支持。这可以通过多种方式完成
- 使用立即的“100 Continue”响应回复包含
Expect: 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
。(请注意,内置序列类型也可以接受,因为它们也实现了此协议。)
最后,希望支持 2.2 之前版本的 Python 的中间件,并且迭代应用程序返回值或自身返回可迭代对象(或两者兼而有之),必须遵循上述相应的建议。
(注意:应该不言而喻,要支持 2.2 之前版本的 Python,任何服务器、网关、应用程序或中间件也必须仅使用目标版本中可用的语言特性,使用 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
,则它必须是一个可调用对象,该对象接受一个必需的位置参数和一个可选的位置参数。第一个参数是要发送的文件类对象,第二个参数是可选的块大小“建议”(服务器/网关不必使用)。可调用对象**必须**返回一个可迭代对象,并且**不得**在服务器/网关实际收到应用程序返回的可迭代对象之前执行任何数据传输。(否则将阻止中间件解释或覆盖响应数据。)
要被视为“类文件”,应用程序提供的对象必须具有一个 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!(例如 cookie、会话、持久性等)
这不是另一个 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 版本定义的属性)传递下去。这种类型的代码以其难以做到 100% 正确而臭名昭著,很少有人愿意自己编写它。因此,他们会复制其他人的实现,但在他们复制的人员纠正了另一个极端情况时却未能更新它。
此外,这种必要的样板文件将是纯粹的练习,是中件件开发人员为支持应用程序框架开发人员略微更漂亮的 API 而支付的开发人员税。但是,应用程序框架开发人员通常只会更新一个框架以支持 WSGI,并且在其整个框架中仅限于非常有限的一部分。这可能是他们第一次(也可能是唯一一次)WSGI 实现,因此他们可能会使用此规范随时准备实施。因此,使用对象属性等使 API“更漂亮”的努力可能会浪费在这个受众身上。
我们鼓励那些希望为直接 Web 应用程序编程(而不是 Web 框架开发)使用更漂亮(或其他改进)的 WSGI 接口的人开发 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,他在 WSGI-on-Jython 上进行了勇敢的尝试(在规范最终确定之前),这有助于塑造“支持旧版本的 Python”部分,以及可选的
wsgi.file_wrapper
功能。 - Mark Nottingham,他广泛审查了规范中与 HTTP RFC 兼容性相关的问题,尤其是在我甚至不知道存在哪些 HTTP/1.1 功能之前,直到他指出它们。
参考文献
版权
本文档已置于公共领域。
来源:https://github.com/python/peps/blob/main/peps/pep-0333.rst
上次修改时间:2023-09-09 17:39:29 GMT