PEP 444 – Python Web3 接口
- 作者:
- Chris McDonough <chrism at plope.com>, Armin Ronacher <armin.ronacher at active-4.com>
- 讨论列表:
- Web-SIG 列表
- 状态:
- 已延期
- 类型:
- 信息性
- 创建日期:
- 2010年7月19日
摘要
本文档指定了 Web 服务器和 Python Web 应用程序或框架之间提出的第二代标准接口。
PEP 延期
由于缺乏当前热衷于推动 PEP 目标并收集和整合反馈的维护者,以及有效地做到这一点的充足时间,因此对本 PEP 中涵盖的概念的进一步探索已被推迟。
请注意,自从本 PEP 首次创建以来,PEP 3333 作为更渐进的更新被创建,允许在 Python 3.2+ 上使用 WSGI。但是,进一步实现 Python 3 目标(更清晰地分离二进制和文本数据)的替代规范可能仍然很有价值。
基本原理和目标
此协议和规范受到 PEP 333 中描述的 Web 服务网关接口 (WSGI) 1.0 标准的强烈影响。在 PEP 333 中概述了允许基于 Python 的 Web 服务器和应用程序互操作的任何标准的高级基本原理。本文档基本上使用 PEP 333 作为模板,并在各个地方更改其措辞,以形成不同的标准。
Python 目前拥有各种使用 WSGI 1.0 协议的 Web 应用程序框架。但是,由于语言的更改,WSGI 1.0 协议与 Python 3 不兼容。本规范描述了一个标准化的类似 WSGI 的协议,该协议允许 Python 2.6、2.7 和 3.1+ 应用程序与 Web 服务器通信。Web3 明显是 WSGI 的派生版本;它仅使用与“WSGI”不同的名称来表示它在任何方面都不向后兼容。
根据本规范编写的应用程序和服务器旨在在 Python 2.6.X、Python 2.7.X 和 Python 3.1+ 下正常工作。实现 Web3 规范的应用程序或服务器都不能轻松编写,以便在早于 2.6 的 Python 2 版本或早于 3.1 的 Python 3 版本下工作。
注意
无论哪个 Python 3 版本修复了 http://bugs.python.org/issue4006,以便 os.environ['foo']
在无法使用当前区域设置解码“foo”的值时返回代理(类似于 PEP 383),而不是引发 KeyError 异常,才是真正的最低 Python 3 版本。但是,特别是,Python 3.0 不受支持。
注意
Python 2.6 是第一个支持 bytes
的别名和 b"foo"
文字语法的 Python 版本。这就是为什么它是 Web3 支持的最低版本的原因。
可解释性和可记录性是标准内做出的决策的主要技术驱动因素。
与 WSGI 的区别
- 所有协议特定的环境名称都以
web3.
为前缀,而不是wsgi.
,例如web3.input
而不是wsgi.input
。 - 所有作为环境字典值存在的值都明确地是字节实例,而不是原生字符串。(但是,环境键始终是原生字符串,无论平台如何,始终为
str
)。 - 应用程序返回的所有值都必须是字节实例,包括状态代码、头名称和值以及主体。
- 在 WSGI 1.0 中引用
app_iter
的任何地方,本规范都引用body
。 - 没有
start_response()
回调(因此也没有write()
可调用对象或exc_info
数据)。 web3.input
的readline()
函数必须支持大小提示参数。web3.input
的read()
函数必须是长度限定的。没有大小参数的调用不得读取超过内容长度标头指定的长度。如果内容长度标头不存在,则流在读取时不得返回任何内容。它决不能请求超过客户端指定的更多数据。- 不需要中间件在需要从应用程序获取更多信息才能生成输出时产生空字符串(例如,没有“中间件处理块边界”)。
- 传递给“file_wrapper”的文件类对象必须具有一个
__iter__
,该对象返回字节(而不是文本)。 wsgi.file_wrapper
不受支持。QUERY_STRING
、SCRIPT_NAME
、PATH_INFO
值需要服务器放在 environ 中(如果在 HTTP 请求中未收到关联值,则每个值都为空字节实例)。web3.path_info
和web3.script_name
应该由原始 Web3 服务器尽可能地放入 Web3 环境中。如果可用,每个都是其 CGI 等效项的原始、简单的 7 位 ASCII、URL 编码变体,直接从请求 URI 派生(保留 %2F 段标记和其他元字符)。如果服务器无法提供其中一个(或两个)值,则必须从环境中省略它无法提供的值。- 此要求已删除:“中间件组件不得阻塞迭代以等待应用程序可迭代对象中的多个值。如果中间件需要在能够生成任何输出之前从应用程序累积更多数据,则必须产生空字符串。”
SERVER_PORT
必须是字节实例(而不是整数)。- 服务器不得通过从响应可迭代对象猜测长度来注入额外的
Content-Length
标头。这必须在所有情况下由应用程序本身设置。 - 如果原始服务器声明它具有
web3.async
功能,则服务器使用的 Web3 应用程序可调用对象可以返回一个不接受任何参数的可调用对象。当它这样做时,此可调用对象将由原始服务器定期调用,直到它返回一个非None
响应,该响应必须是正常的 Web3 响应元组。
规范概述
Web3 接口有两端:“服务器”或“网关”端,以及“应用程序”或“框架”端。服务器端调用由应用程序端提供的可调用对象。该对象提供方式的具体细节取决于服务器或网关。假设某些服务器或网关将要求应用程序的部署人员编写一个简短的脚本以创建服务器或网关的实例,并为其提供应用程序对象。其他服务器和网关可能会使用配置文件或其他机制来指定应从何处导入应用程序对象或以其他方式获取应用程序对象。
除了“纯”服务器/网关和应用程序/框架之外,还可以创建实现此规范两端的“中间件”组件。此类组件充当其包含服务器的应用程序,以及包含应用程序的服务器,并且可用于提供扩展的 API、内容转换、导航和其他有用功能。
在本规范中,我们将使用术语“应用程序可调用对象”来表示“函数、方法或具有 __call__
方法的实例”。实现应用程序可调用对象的服务器、网关或应用程序可以根据自己的需求选择合适的实现技术。相反,调用可调用对象的服务器、网关或应用程序不得对提供给它的可调用对象的类型有任何依赖性。应用程序可调用对象仅用于调用,而不是进行内省。
应用程序/框架端
应用程序对象只是一个接受一个参数的可调用对象。术语“对象”不应被误解为需要实际的对象实例:函数、方法或具有 __call__
方法的实例都可用于作为应用程序对象。应用程序对象必须能够被多次调用,因为几乎所有服务器/网关(CGI 除外)都会发出此类重复请求。如果实际应用程序的实现无法保证这一点,则必须将其包装在一个函数中,该函数在每次调用时创建一个新实例。
注意
尽管我们将其称为“应用程序”对象,但这不应被理解为应用程序开发人员将使用 Web3 作为 Web 编程 API。假设应用程序开发人员将继续使用现有的高级框架服务来开发他们的应用程序。Web3 是供框架和服务器开发人员使用的工具,并非旨在直接支持应用程序开发人员。)
一个应用的例子,它是一个函数(simple_app
)
def simple_app(environ):
"""Simplest possible application object"""
status = b'200 OK'
headers = [(b'Content-type', b'text/plain')]
body = [b'Hello world!\n']
return body, status, headers
一个应用的例子,它是一个实例(simple_app
)
class AppClass(object):
"""Produce the same output, but using an instance. An
instance of this class must be instantiated before it is
passed to the server. """
def __call__(self, environ):
status = b'200 OK'
headers = [(b'Content-type', b'text/plain')]
body = [b'Hello world!\n']
return body, status, headers
simple_app = AppClass()
或者,如果服务器支持异步执行,应用可调用对象可以返回一个可调用对象而不是元组。有关更多信息,请参阅关于 web3.async
的信息。
服务器/网关端
服务器或网关为 HTTP 客户端收到的每个针对该应用的请求调用一次应用可调用对象。为了说明,这是一个作为函数实现的简单 CGI 网关,该函数接受一个应用对象。请注意,此简单示例的错误处理有限,因为默认情况下,未捕获的异常将转储到 sys.stderr
并由 Web 服务器记录。
import locale
import os
import sys
encoding = locale.getpreferredencoding()
stdout = sys.stdout
if hasattr(sys.stdout, 'buffer'):
# Python 3 compatibility; we need to be able to push bytes out
stdout = sys.stdout.buffer
def get_environ():
d = {}
for k, v in os.environ.items():
# Python 3 compatibility
if not isinstance(v, bytes):
# We must explicitly encode the string to bytes under
# Python 3.1+
v = v.encode(encoding, 'surrogateescape')
d[k] = v
return d
def run_with_cgi(application):
environ = get_environ()
environ['web3.input'] = sys.stdin
environ['web3.errors'] = sys.stderr
environ['web3.version'] = (1, 0)
environ['web3.multithread'] = False
environ['web3.multiprocess'] = True
environ['web3.run_once'] = True
environ['web3.async'] = False
if environ.get('HTTPS', b'off') in (b'on', b'1'):
environ['web3.url_scheme'] = b'https'
else:
environ['web3.url_scheme'] = b'http'
rv = application(environ)
if hasattr(rv, '__call__'):
raise TypeError('This webserver does not support asynchronous '
'responses.')
body, status, headers = rv
CLRF = b'\r\n'
try:
stdout.write(b'Status: ' + status + CRLF)
for header_name, header_val in headers:
stdout.write(header_name + b': ' + header_val + CRLF)
stdout.write(CRLF)
for chunk in body:
stdout.write(chunk)
stdout.flush()
finally:
if hasattr(body, 'close'):
body.close()
中间件:同时扮演两端角色的组件
单个对象可以相对于某些应用充当服务器的角色,同时也可以相对于某些服务器充当应用的角色。此类“中间件”组件可以执行以下功能
- 根据目标 URL 将请求路由到不同的应用对象,并在相应地重写
environ
后。 - 允许多个应用或框架在同一进程中并行运行。
- 通过网络转发请求和响应,实现负载均衡和远程处理。
- 执行内容后处理,例如应用 XSL 样式表。
通常,中间件的存在对于接口的“服务器/网关”和“应用/框架”两侧都是透明的,并且不需要任何特殊支持。希望将中间件集成到应用的用户只需向服务器提供中间件组件(就像它是一个应用一样),并配置中间件组件以调用应用(就像中间件组件是一个服务器一样)。当然,“应用”的中间件封装实际上可能是另一个封装另一个应用的中间件组件,依此类推,从而创建所谓的“中间件栈”。
如果可能,中间件必须支持异步执行,否则回退到禁用自身。
这里有一个中间件,如果存在 X-Host
头,则更改 HTTP_HOST
键,并在所有 html 响应中添加注释
import time
def apply_filter(app, environ, filter_func):
"""Helper function that passes the return value from an
application to a filter function when the results are
ready.
"""
app_response = app(environ)
# synchronous response, filter now
if not hasattr(app_response, '__call__'):
return filter_func(*app_response)
# asynchronous response. filter when results are ready
def polling_function():
rv = app_response()
if rv is not None:
return filter_func(*rv)
return polling_function
def proxy_and_timing_support(app):
def new_application(environ):
def filter_func(body, status, headers):
now = time.time()
for key, value in headers:
if key.lower() == b'content-type' and \
value.split(b';')[0] == b'text/html':
# assumes ascii compatible encoding in body,
# but the middleware should actually parse the
# content type header and figure out the
# encoding when doing that.
body += ('<!-- Execution time: %.2fsec -->' %
(now - then)).encode('ascii')
break
return body, status, headers
then = time.time()
host = environ.get('HTTP_X_HOST')
if host is not None:
environ['HTTP_HOST'] = host
# use the apply_filter function that applies a given filter
# function for both async and sync responses.
return apply_filter(app, environ, filter_func)
return new_application
app = proxy_and_timing_support(app)
规范细节
应用可调用对象必须接受一个位置参数。为了说明,我们将其命名为 environ
,但它不需要这个名称。服务器或网关**必须**使用位置(而不是关键字)参数调用应用对象。(例如,通过调用 body, status, headers = application(environ)
,如上所示。)
environ
参数是一个字典对象,包含 CGI 样式的环境变量。此对象**必须**是内置的 Python 字典(不是子类、UserDict
或其他字典模拟),并且应用可以以任何它想要的方式修改该字典。该字典还必须包含某些 Web3 必需的变量(在后面的章节中描述),并且还可以包含服务器特定的扩展变量,这些变量的命名方式将在下面描述。
当被服务器调用时,应用对象必须返回一个包含三个元素的元组:status
、headers
和 body
,或者,如果异步服务器支持,则返回一个无参数的可调用对象,该对象要么返回 None
,要么返回这三个元素的元组。
status
元素是形式为 b'999 Message here'
的字节状态。
headers
是一个 Python 列表,其中包含 (header_name, header_value)
对,描述 HTTP 响应头。 headers
结构必须是文字 Python 列表;它必须产生二元组。 header_name
和 header_value
都必须是字节值。
body
是一个可迭代对象,产生零个或多个字节实例。这可以通过多种方式实现,例如通过返回包含字节实例的列表作为 body
,或者通过返回一个生成器函数作为 body
来产生字节实例,或者通过 body
是一个可迭代类的实例来实现。无论如何实现,应用对象都必须始终返回一个 body
可迭代对象,该对象产生零个或多个字节实例。
服务器或网关必须以非缓冲方式将产生的字节传输到客户端,在请求另一个字节之前完成每个字节集的传输。(换句话说,应用**应该**执行自己的缓冲。有关如何处理应用输出的更多信息,请参阅下面的 缓冲和流 部分。)
服务器或网关应将产生的字节视为二进制字节序列:特别是,它应确保不更改换行符。应用负责确保要写入的字符串(s) 格式适合客户端。(服务器或网关**可以**应用 HTTP 传输编码或执行其他转换以实现 HTTP 功能,例如字节范围传输。有关更多详细信息,请参阅下面的 其他 HTTP 功能。)
如果应用返回的 body
可迭代对象具有 close()
方法,则服务器或网关**必须**在当前请求完成后调用该方法,无论请求是否正常完成或由于错误而过早终止。这是为了支持应用释放资源,旨在补充 PEP 325 的生成器支持和其他具有 close()
方法的常用可迭代对象。
最后,服务器和网关**不得**直接使用应用返回的 body
可迭代对象的任何其他属性。
environ
变量
environ
字典需要包含各种 CGI 环境变量,如通用网关接口规范 [2] 中所定义。
以下 CGI 变量**必须**存在。每个键都是一个原生字符串。每个值都是一个字节实例。
注意
在 Python 3.1+ 中,“原生字符串”是使用 surrogateescape
错误处理程序解码的 str
类型,如 os.environ.__getitem__
所做的那样。在 Python 2.6 和 2.7 中,“原生字符串”是表示一组字节的 str
类型。
REQUEST_METHOD
- HTTP 请求方法,例如
"GET"
或"POST"
。 SCRIPT_NAME
- 请求 URL 的“路径”的初始部分,对应于应用对象,以便应用知道其虚拟“位置”。如果应用对应于服务器的“根”,则这可能是空字节实例。SCRIPT_NAME 将是一个字节实例,表示由斜杠字符 (
/
) 分隔的 URL 编码段的序列。假设%2F
字符将在PATH_INFO
中解码为文字斜杠字符,如 CGI 所述。 PATH_INFO
- 请求 URL 的“路径”的其余部分,指定请求目标在应用中的虚拟“位置”。如果请求 URL 针对应用根并且没有尾部斜杠,则这**可以**是字节实例。PATH_INFO 将是一个字节实例,表示由斜杠字符 (
/
) 分隔的 URL 编码段的序列。假设%2F
字符将在PATH_INFO
中解码为文字斜杠字符,如 CGI 所述。 QUERY_STRING
- 请求 URL(以字节为单位)中跟随
"?"
的部分(如果有),或者空字节实例。 SERVER_NAME
、SERVER_PORT
- 与
SCRIPT_NAME
和PATH_INFO
(或其原始等效项)结合使用时,这些变量可用于完成 URL。但是,请注意,如果存在,则应优先使用HTTP_HOST
而不是SERVER_NAME
来重建请求 URL。有关更多详细信息,请参阅下面的 URL 重建 部分。SERVER_PORT
应为字节实例,而不是整数。 SERVER_PROTOCOL
- 客户端用于发送请求的协议版本。通常,这将类似于
"HTTP/1.0"
或"HTTP/1.1"
,并且应用可以使用它来确定如何处理任何 HTTP 请求头。(此变量可能应该称为REQUEST_PROTOCOL
,因为它表示请求中使用的协议,并且不一定是在服务器响应中使用的协议。但是,为了与 CGI 保持兼容性,我们必须保留现有名称。)
以下 CGI 值**可能**存在于 Web3 环境中。每个键都是一个原生字符串。每个值都是一个字节实例。
CONTENT_TYPE
- HTTP 请求中任何
Content-Type
字段的内容。 CONTENT_LENGTH
- HTTP 请求中任何
Content-Length
字段的内容。 HTTP_
变量- 对应于客户端提供的 HTTP 请求头的变量(即,名称以
"HTTP_"
开头的变量)。这些变量的存在或不存在应与请求中相应的 HTTP 头的存在或不存在相对应。
服务器或网关**应该**尝试提供尽可能多的其他 CGI 变量,每个变量都使用字符串作为键,使用字节实例作为值。此外,如果使用 SSL,服务器或网关**也应该**提供尽可能多的 Apache SSL 环境变量 [4],例如 HTTPS=on
和 SSL_PROTOCOL
。但是请注意,使用上述列出变量之外的任何 CGI 变量的应用程序,必然无法移植到不支持相关扩展的 Web 服务器。(例如,不发布文件的 Web 服务器将无法提供有意义的 DOCUMENT_ROOT
或 PATH_TRANSLATED
。)
符合 Web3 规范的服务器或网关**应该**记录其提供的变量以及相应的定义。应用程序**应该**检查其所需的任何变量是否存在,并在变量不存在的情况下制定备用方案。
请注意,如果 CGI 变量值存在,则必须是字节实例。如果 CGI 变量的值不是 bytes
类型,则违反此规范。在 Python 2 中,这意味着它们将是 str
类型。在 Python 3 中,这意味着它们将是 bytes
类型。
但是,environ 中所有 CGI 和非 CGI 变量的键必须是“原生字符串”(在 Python 2 和 Python 3 中,它们都将是 str
类型)。
除了 CGI 定义的变量之外,environ
字典**可能**还包含任意操作系统“环境变量”,并且**必须**包含以下 Web3 定义的变量。
变量 | 值 |
---|---|
web3.version |
元组 (1, 0) ,表示 Web3 版本 1.0。 |
web3.url_scheme |
表示应用程序被调用的 URL 的“方案”部分的字节值。通常,这将具有 b"http" 或 b"https" 值,具体取决于情况。 |
web3.input |
输入流(类文件对象),从中可以读取构成 HTTP 请求正文的字节。(服务器或网关可以根据应用程序的请求按需执行读取操作,或者它可以预先读取客户端的请求正文并将其缓存在内存或磁盘中,或者使用任何其他技术来提供此类输入流,具体取决于其偏好。) |
web3.errors |
输出流(类文件对象),用于写入错误输出文本,以便在标准化且可能集中的位置记录程序或其他错误。这应该是一个“文本模式”流;即,应用程序应该使用 "\n" 作为行尾,并假设它将被服务器/网关转换为正确的行尾。应用程序不能将字节发送到此流的“write”方法;它们只能发送文本。对于许多服务器, |
web3.multithread |
如果应用程序对象可能被同一进程中的另一个线程同时调用,则此值应评估为真,否则应评估为假。 |
web3.multiprocess |
如果另一个进程可能同时调用等效的应用程序对象,则此值应评估为真,否则应评估为假。 |
web3.run_once |
如果服务器或网关期望(但不保证!)应用程序在其包含进程的生命周期内仅被调用一次,则此值应评估为真。通常,这仅适用于基于 CGI(或类似内容)的网关。 |
web3.script_name |
未进行 URL 解码的 SCRIPT_NAME 值。由于 CGI 规范的历史不平等,SCRIPT_NAME 作为已 URL 解码的字符串存在于环境中。这是从请求 URI 派生的原始 URL 编码值。如果服务器无法提供此值,则必须将其从 environ 中省略。 |
web3.path_info |
未进行 URL 解码的 PATH_INFO 值。由于 CGI 规范的历史不平等,PATH_INFO 作为已 URL 解码的字符串存在于环境中。这是从请求 URI 派生的原始 URL 编码值。如果服务器无法提供此值,则必须将其从 environ 中省略。 |
web3.async |
如果 Web 服务器支持异步调用,则此值为 True 。在这种情况下,应用程序允许返回一个可调用对象,而不是包含响应的元组。此规范未指定确切的语义。 |
最后,environ
字典还可以包含服务器定义的变量。这些变量的名称应该是原生字符串,仅由小写字母、数字、点和下划线组成,并且应该以定义服务器或网关的唯一名称作为前缀。例如,mod_web3
可以定义名称类似于 mod_web3.some_variable
的变量。
输入流
服务器提供的输入流(web3.input
)必须支持以下方法
方法 | 注释 |
---|---|
read(size) |
1,4 |
readline([size]) |
1,2,4 |
readlines([size]) |
1,3,4 |
__iter__() |
4 |
每个方法的语义与 Python 库参考中记录的相同,除了上表中列出的这些注释之外
- 服务器不需要读取超过客户端指定的
Content-Length
,并且如果应用程序尝试读取超过该点的数据,则允许模拟文件结束条件。应用程序**不应**尝试读取超过CONTENT_LENGTH
变量指定的数据量。 - 实现必须支持
readline()
的可选size
参数。 - 应用程序可以自由地不向
readlines()
提供size
参数,并且服务器或网关可以自由地忽略任何提供的size
参数的值。 read
、readline
和__iter__
方法必须返回字节实例。readlines
方法必须返回一个包含字节实例的序列。
上表中列出的方法**必须**由所有符合此规范的服务器支持。符合此规范的应用程序**不得**使用 input
对象的任何其他方法或属性。特别是,应用程序**不得**尝试关闭此流,即使它拥有 close()
方法。
输入流应该静默忽略尝试读取超过请求内容长度的操作。如果未指定内容长度,则流必须是不会返回任何内容的虚拟流。
错误流
服务器提供的错误流(web3.errors
)必须支持以下方法
方法 | 流 | 注释 |
---|---|---|
flush() |
errors |
1 |
write(str) |
errors |
2 |
writelines(seq) |
errors |
2 |
每个方法的语义与 Python 库参考中记录的相同,除了上表中列出的这些注释之外
- 由于
errors
流可能无法倒带,因此服务器和网关可以自由地立即转发写入操作,而无需缓冲。在这种情况下,flush()
方法可能是无操作的。但是,可移植应用程序不能假设输出是未缓冲的或flush()
是无操作的。如果需要确保输出已实际写入,则必须调用flush()
。(例如,为了最大程度地减少写入同一错误日志的多个进程的数据交织。) write()
方法必须接受字符串参数,但不一定必须接受字节参数。writelines()
方法必须接受完全由字符串组成的序列参数,但不一定必须接受任何字节实例作为序列的成员。
上表中列出的方法**必须**由所有符合此规范的服务器支持。符合此规范的应用程序**不得**使用 errors
对象的任何其他方法或属性。特别是,应用程序**不得**尝试关闭此流,即使它拥有 close()
方法。
Web3 应用程序返回的值
Web3 应用程序返回一个元组,其格式为(status
、headers
、body
)。如果服务器支持异步应用程序(web3.async
),则响应可能是一个可调用对象(不接受任何参数)。
网关或服务器假定 status
值是 HTTP“状态”字节实例,例如 b'200 OK'
或 b'404 Not Found'
。也就是说,它是一个由状态代码和原因短语组成的字符串,按此顺序并用单个空格分隔,没有周围的空格或其他字符。(有关更多信息,请参阅 RFC 2616,第 6.1.1 节。)该字符串**不得**包含控制字符,并且不得以回车符、换行符或两者的组合结尾。
网关或服务器假定headers
的值为一个字面意义上的 Python 列表,其中包含(header_name, header_value)
元组。每个header_name
必须是表示有效 HTTP 标头字段名称的字节实例(如RFC 2616第 4.2 节所定义),不带尾随冒号或其他标点符号。每个header_value
必须是字节实例,并且**不得**包含任何控制字符,包括回车符或换行符,无论是嵌入的还是结尾的。(这些要求是为了最大程度地降低服务器、网关和需要检查或修改响应标头的中间响应处理器必须执行的任何解析的复杂性。)
通常,服务器或网关负责确保将正确的标头发送到客户端:如果应用程序省略了 HTTP(或其他有效的相关规范)所需的标头,则服务器或网关**必须**添加它。例如,HTTP Date:
和Server:
标头通常由服务器或网关提供。但是,如果应用程序发出具有相同名称的值,则网关不得覆盖这些值。
(提醒服务器/网关作者:HTTP 标头名称不区分大小写,因此在检查应用程序提供的标头时请务必考虑这一点!)
应用程序和中间件禁止使用 HTTP/1.1 的“逐跳”功能或标头、HTTP/1.0 中的任何等效功能或会影响客户端与 Web 服务器连接持久性的任何标头。这些功能是实际 Web 服务器的专属领域,服务器或网关**应该**认为应用程序尝试发送它们是致命错误,并在应用程序在headers
结构中将其作为返回值提供时引发错误。(有关“逐跳”功能和标头的更多详细信息,请参见下面的其他 HTTP 功能部分。)
处理跨 Python 版本的兼容性
创建在 Python 2.6/2.7 和 Python 3.1+ 下运行的 Web3 代码需要开发人员格外小心。通常,Web3 规范假定 Python 2 的str
类型与 Python 3 的bytes
类型之间存在一定程度的等价性。例如,在 Python 2 下,Web3 environ
中存在的值将是str
类型的实例;在 Python 3 中,这些将是bytes
类型的实例。Python 3 的bytes
类型不具备 Python 2 的str
类型的所有方法,并且它所具有的某些方法的行为与 Python 2 的str
类型不同。有效地,为了确保 Web3 中间件和应用程序跨 Python 版本工作,开发人员必须执行以下操作
- 不要假设文本值和字节值之间存在比较等价性。如果您这样做,您的代码可能在 Python 2 下工作,但在 Python 3 下将无法正常工作。例如,不要编写
somebytes == 'abc'
。这在 Python 2 上有时为真,但在 Python 3 上永远不会为真,因为在 Python 3 下,字节序列永远不会与字符串比较相等。相反,始终将字节值与字节值进行比较,例如“somebytes == b’abc’”。执行此操作的代码与 Python 2.6、2.7 和 3.1 兼容并在其中具有相同的工作方式。b
位于'abc'
前面,向 Python 3 发出信号,表明该值为字面字节实例;在 Python 2 下,它是一个向前兼容的安慰剂。 - 不要使用旨在作为类字节对象的项目的
__contains__
方法(直接或间接),而无需确保其参数也是字节实例。如果您这样做,您的代码可能在 Python 2 下工作,但在 Python 3 下将无法正常工作。例如,'abc' in somebytes'
将在 Python 3 下引发TypeError
,但在 Python 2.6 和 2.7 下将返回True
。但是,b'abc' in somebytes
将在两个版本上都具有相同的工作方式。在 Python 3.2 中,此限制可能会部分解除,因为有传言称字节类型可能会获得__mod__
实现。 __getitem__
不应使用。- 不要尝试使用字节实例的
format
方法或__mod__
方法(直接或间接)。在 Python 2 中,我们将其等效对待为 Python 3 的bytes
的str
类型支持这些方法,但实际的 Python 3 的bytes
实例不支持这些方法。如果您使用这些方法,您的代码将在 Python 2 下工作,但在 Python 3 下不会工作。 - 不要尝试将字节值与字符串值连接起来。这可能在 Python 2 下工作,但在 Python 3 下不会工作。例如,执行
'abc' + somebytes
将在 Python 2 下工作,但在 Python 3 下将导致TypeError
。相反,始终确保您正在连接两个相同类型的项目,例如b'abc' + somebytes
。
Web3 在其他地方期望字节值,例如应用程序返回的所有值中。
简而言之,为了确保 Web3 应用程序代码在 Python 2 和 Python 3 之间的兼容性,在 Python 2 中,将环境中的 CGI 和服务器变量值视为具有 Python 3 的bytes
API,即使它们实际上具有更强大的 API 也是如此。Web3 应用程序返回的所有类似字符串的值也如此。
缓冲和流式传输
一般来说,应用程序可以通过缓冲其(适度大小的)输出并一次性发送它来获得最佳吞吐量。这是现有框架中的一种常见方法:输出缓存在 StringIO 或类似对象中,然后与响应标头一起一次性传输。
Web3 中相应的方法是应用程序只需返回一个包含响应主体作为单个字符串的单元素body
可迭代对象(例如列表)。对于绝大多数渲染 HTML 页面(其文本很容易放入内存)的应用程序函数,这是推荐的方法。
但是,对于大型文件或 HTTP 流的特殊用途(例如多部分“服务器推送”),应用程序可能需要分块提供输出(例如,为了避免将大型文件加载到内存中)。有时,响应的一部分可能需要花费很长时间才能生成,但提前发送其前面的响应部分将很有用。
在这些情况下,应用程序通常会返回一个body
迭代器(通常是生成器迭代器),该迭代器以分块的方式生成输出。这些块可以被分解以与多部分边界(用于“服务器推送”)一致,或者在耗时任务(例如读取磁盘上文件的另一块)之前。
Web3 服务器、网关和中间件**不得**延迟任何块的传输;它们**必须**将块完全传输到客户端,或保证即使在应用程序生成下一个块时也将继续传输。服务器/网关或中间件可以通过以下三种方式之一提供此保证
- 在将控制权返回到应用程序之前,将整个块发送到操作系统(并请求刷新任何操作系统缓冲区),或者
- 使用另一个线程来确保在应用程序生成下一个块时继续传输块。
- (仅限中间件)将整个块发送到其父网关/服务器。
通过提供此保证,Web3 允许应用程序确保传输不会在其输出数据的任意点停滞。这对于例如多部分“服务器推送”流的正常运行至关重要,其中多部分边界之间的数据应完整传输到客户端。
Unicode 问题
HTTP 不直接支持 Unicode,此接口也不支持。所有编码/解码都必须由**应用程序**处理;传递到服务器或来自服务器的所有值必须为 Python 3 类型bytes
或 Python 2 类型str
的实例,而不是 Python 2 unicode
或 Python 3 str
对象。
本规范中提到的所有“字节实例”**必须**
- 在 Python 2 上,类型为
str
。 - 在 Python 3 上,类型为
bytes
。
所有“字节实例”**不得**
- 在 Python 2 上,类型为
unicode
。 - 在 Python 3 上,类型为
str
。
在需要类字节对象的地方使用类文本对象的结果未定义。
Web3 应用程序作为状态或作为响应标头返回的值**必须**遵循RFC 2616关于编码的规定。也就是说,返回的字节必须包含 ISO-8859-1 字符的字符流,或者字符流应使用RFC 2047 MIME 编码。
在没有原生类字节类型的 Python 平台(例如 IronPython 等)上,但通常使用类文本字符串来表示字节数据,可以更改“字节实例”的定义:它们的“字节实例”必须是本机字符串,其中仅包含 ISO-8859-1 编码中可表示的代码点(包括\u0000
到\u00FF
)。对于此类平台上的应用程序提供包含任何其他 Unicode 字符或代码点的字符串,这是一个致命错误。类似地,这些平台上的服务器和网关**不得**向应用程序提供包含任何其他 Unicode 字符的字符串。
HTTP 1.1 Expect/Continue
实现 HTTP 1.1 的服务器和网关**必须**为 HTTP 1.1 的“期望/继续”机制提供透明支持。这可以通过多种方式完成
- 对于包含
Expect: 100-continue
请求的请求,应立即以“100 Continue”响应进行回复,并正常继续处理。 - 正常继续处理请求,但为应用程序提供一个
web3.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 节。)
但是,由于 Web3 服务器和应用程序不通过 HTTP 通信,因此 RFC 2616 所称的“逐跳”标头不适用于 Web3 内部通信。Web3 应用程序**不得**生成任何 “逐跳”标头,尝试使用需要它们生成此类标头的 HTTP 功能,或依赖于 environ
字典中任何传入的“逐跳”标头的内容。Web3 服务器**必须**自行处理任何支持的入站“逐跳”标头,例如通过解码任何入站 Transfer-Encoding
(包括适用的分块编码)。
将这些原则应用于各种 HTTP 功能,应该清楚的是,服务器**可以**通过 If-None-Match
和 If-Modified-Since
请求标头以及 Last-Modified
和 ETag
响应标头来处理缓存验证。但是,这并非强制要求,如果应用程序想要支持此功能,则**应该**执行自己的缓存验证,因为服务器/网关不需要执行此类验证。
类似地,服务器**可以**重新编码或传输编码应用程序的响应,但应用程序**应该**自行使用合适的內容编码,并且**不得**应用传输编码。如果客户端请求,并且应用程序本身不支持字节范围,则服务器**可以**传输应用程序响应的字节范围。但是,同样地,如果需要,应用程序**应该**自行执行此功能。
请注意,这些对应用程序的限制并不一定意味着每个应用程序都必须重新实现每个 HTTP 功能;许多 HTTP 功能可以通过中间件组件部分或完全实现,从而使服务器和应用程序作者都无需一遍又一遍地实现相同的功能。
线程支持
线程支持(或缺乏线程支持)也取决于服务器。可以并行运行多个请求的服务器,**应该**也提供以单线程方式运行应用程序的选项,以便可以使用该服务器运行那些不安全线程的应用程序或框架。
实现/应用程序说明
服务器扩展 API
某些服务器作者可能希望公开更高级的 API,应用程序或框架作者可以使用这些 API 来执行特殊用途。例如,基于 mod_python
的网关可能希望将 Apache API 的一部分作为 Web3 扩展公开。
在最简单的情况下,这只需要定义一个 environ
变量,例如 mod_python.some_api
。但是,在许多情况下,中间件的存在可能会使这变得困难。例如,提供对与 environ
变量中找到的 HTTP 标头相同的 HTTP 标头访问权限的 API,如果 environ
已被中间件修改,则可能会返回不同的数据。
一般来说,任何复制、取代或绕过 Web3 功能一部分的扩展 API 都有可能与中间件组件不兼容。服务器/网关开发人员**不应**假设没有人会使用中间件,因为某些框架开发人员专门组织他们的框架,使其几乎完全充当各种中间件。
因此,为了提供最大的兼容性,提供替换某些 Web3 功能的扩展 API 的服务器和网关,**必须**设计这些 API,以便使用它们所替换的 API 部分来调用这些 API。例如,访问 HTTP 请求标头的扩展 API 必须要求应用程序传入其当前 environ
,以便服务器/网关可以验证通过 API 访问的 HTTP 标头是否未被中间件更改。如果扩展 API 无法保证它始终与 environ
关于 HTTP 标头内容达成一致,则必须拒绝为应用程序提供服务,例如通过引发错误、返回 None
而不是标头集合,或任何适合 API 的操作。
这些指南也适用于将诸如已解析的 Cookie、表单变量、会话等信息添加到 environ
的中间件。具体来说,此类中间件应将这些功能作为对 environ
进行操作的函数来提供,而不是简单地将值填充到 environ
中。这有助于确保在任何中间件完成任何 URL 重写或其他 environ
修改后,从 environ
中计算信息。
服务器/网关和中间件开发人员都必须遵循这些“安全扩展”规则,这一点非常重要,以避免将来中间件开发人员被迫从 environ
中删除任何和所有扩展 API,以确保他们的中介不会被使用这些扩展的应用程序绕过!
应用程序配置
本规范未定义服务器如何选择或获取要调用的应用程序。这些和其他配置选项都是高度服务器特定的问题。预计服务器/网关作者将记录如何配置服务器以执行特定的应用程序对象,以及使用哪些选项(例如线程选项)。
另一方面,框架作者应该记录如何创建包装其框架功能的应用程序对象。用户(已选择服务器和应用程序框架)必须将两者连接在一起。但是,由于框架和服务器都有一个共同的接口,因此这应该只是一个机械问题,而不是每个新的服务器/框架对都需要进行大量的工程工作。
最后,某些应用程序、框架和中间件可能希望使用 environ
字典来接收简单的字符串配置选项。服务器和网关**应该**支持这一点,方法是允许应用程序的部署者指定要放置在 environ
中的名称-值对。在最简单的情况下,此支持可能仅包括将所有操作系统提供的环境变量从 os.environ
复制到 environ
字典中,因为原则上部署者可以在服务器外部配置这些变量,或者在 CGI 的情况下,他们可能能够通过服务器的配置文件设置这些变量。
应用程序**应该**尝试将此类必需变量保持在最少,因为并非所有服务器都支持轻松配置它们。当然,即使在最坏的情况下,部署应用程序的人员也可以创建一个脚本来提供必要的配置值。
from the_app import application
def new_app(environ):
environ['the_app.configval1'] = b'something'
return application(environ)
但是,大多数现有应用程序和框架可能只需要来自 environ
的一个配置值,以指示其应用程序或框架特定的配置文件的位置。(当然,应用程序应该缓存此类配置,以避免在每次调用时都重新读取它。)
URL 重建
如果应用程序希望重建请求的完整 URL(作为字节对象),则可以使用以下算法。
host = environ.get('HTTP_HOST')
scheme = environ['web3.url_scheme']
port = environ['SERVER_PORT']
query = environ['QUERY_STRING']
url = scheme + b'://'
if host:
url += host
else:
url += environ['SERVER_NAME']
if scheme == b'https':
if port != b'443':
url += b':' + port
else:
if port != b'80':
url += b':' + port
if 'web3.script_name' in url:
url += url_quote(environ['web3.script_name'])
else:
url += environ['SCRIPT_NAME']
if 'web3.path_info' in environ:
url += url_quote(environ['web3.path_info'])
else:
url += environ['PATH_INFO']
if query:
url += b'?' + query
请注意,此类重建的 URL 可能与客户端请求的 URI 不完全相同。例如,服务器重写规则可能已修改客户端最初请求的 URL 以将其置于规范形式。
未解决的问题
file_wrapper
替换。目前此处未指定任何内容,但很明显,如果旧的带内信号系统没有提供一种方法来确定过程中作为中间件的响应是否是文件包装器,则该系统已损坏。
争议点
下面概述了关于本规范的潜在争议点。
WSGI 1.0 兼容性
使用 WSGI 1.0 规范编写的组件将无法与使用本规范编写的组件透明地互操作。这是因为本提案的目标与 WSGI 1.0 的目标并不完全一致。
WSGI 1.0 有义务对 Python 2.2 到 2.7 之间版本的规范级向后兼容性提供支持。但是,本规范放弃了 Python 2.5 及更低版本的兼容性,以便在相对较新的 Python 2 版本(2.6 和 2.7)以及相对较新的 Python 3 版本(3.1)之间提供兼容性。
目前无法编写在 Python 2 和 Python 3 下都能可靠工作的组件,因为 WSGI 1.0 规范隐含地假设了 environ 中的 CGI 和服务器变量值以及通过 start_response
返回的值表示可以使用 Python 2 字符串 API 寻址的字节序列。它之所以这样假设,是因为这种数据类型是在所有 Python 2 版本中表示字节的明智方法,并且 WSGI 1.0 在 Python 3 存在之前就被构思出来了。
Python 3 的 str
类型支持 Python 2 str
类型提供的完整 API,但 Python 3 的 str
类型并不表示字节序列,而是表示文本。因此,使用它来表示 environ 值还需要通过某种编码将 environ 字节序列解码为文本。我们无法将这些字节解码为文本(至少在任何解码除了作为隧道机制之外没有任何意义的方式中),而无需将 WSGI 的范围扩大到包括服务器和网关对解码策略和机制的了解。WSGI 1.0 从未关注编码和解码。它对允许的传输值做出了声明,并建议各种值可能最好解码为一种或另一种编码,但它从未要求服务器在
Python 3 没有类似字符串的类型可以用来代替表示字节:它有一个 bytes
类型。bytes 类型的工作方式与 Python 3.1+ 中的 Python 2 str
非常相似,但它缺少等效于 str.__mod__
的行为以及它的迭代协议,并且包含、序列处理和等价比较是不同的。
无论哪种情况,Python 3 中都没有任何类型能够像 Python 2 的 str
类型那样工作,并且无法创建这样的类型,因为不存在“字符串 ABC”这样的东西,它可以用来构建合适的类型。由于这种设计上的不兼容性,即使现有的 WSGI 1.0 服务器、中间件和应用程序在经过 2to3
转换后,也无法在 Python 3 下工作。
关于更新 WSGI 规范以使其能够编写在 Python 2 和 Python 3 中都能运行的 WSGI 应用程序的现有 Web-SIG 讨论,往往围绕着创建 Python 2 str
类型(表示字节序列)和 Python 3 str
类型(表示文本)之间的规范级等价关系。鉴于这些类型的不同作用,这种等价关系在各个方面都变得很勉强。Python 3 bytes
类型 API 与 Python 2 str
类型 API 的一个子集之间存在一个可以说是更简单的等价关系。本规范利用了这种子集等价关系。
与此同时,除了任何 Python 2 与 Python 3 的兼容性问题之外,正如 Web-SIG 上的各种讨论所指出的那样,WSGI 1.0 规范过于通用,通过 .write
提供了对异步应用程序的支持,但代价是增加了实现的复杂性。本规范利用 WSGI 1.0 与 Python 3 之间的基本不兼容性作为自然的分歧点,通过更改对异步应用程序的专门支持来创建一个复杂性降低的规范。
为了向旧的 WSGI 1.0 应用程序提供向后兼容性,以便它们可以在 Web3 堆栈上运行,假设将创建 Web3 中间件,该中间件可以用于“位于”现有的 WSGI 1.0 应用程序“前面”,从而允许这些现有的 WSGI 1.0 应用程序在 Web3 堆栈下运行。此中间件在 Python 3 下运行时,需要在 Python 3 str
类型与 HTTP 请求表示的字节值之间建立等价关系,以及由此隐含的所有相关编码猜测(或配置)。
注意
这样的中间件可能在将来,而不是在 Python 3 str
和 HTTP 字节值之间建立等价关系,而是使用一个尚未创建的“ebytes”类型(又名“带福利的字节”),特别是如果字符串 ABC 提议被接受到 Python 核心并被实现。
相反,假设将创建 WSGI 1.0 中间件,它将允许 Web3 应用程序在 Python 2 平台上的 WSGI 1.0 堆栈后面运行。
Environ 和响应值作为字节
非专业的中间件和应用程序编写者可能会认为使用字节作为环境值和响应值不方便。特别是,他们将无法使用常见的字符串格式化函数,例如 ('%s' % bytes_val)
或 bytes_val.format('123')
,因为在 Python 3 等两个类型不同的平台上,字节与字符串的 API 不同。同样,在这些平台上,标准库 HTTP 相关的 API 对使用字节与文本互换的支持可能是不完整的。在字节不方便或与库 API 不兼容的地方,中间件和应用程序编写者将不得不显式地将这些字节解码为文本。这对中间件编写者来说尤其不方便:为了将环境值作为字符串处理,他们必须根据隐含的编码对其进行解码,如果他们需要修改环境值,则需要将该值编码为字节流,然后再将其放入环境中。虽然规范将字节用作环境值可能对非专业的开发人员来说不方便,但它也带来了一些好处。
使用字节类型来表示 HTTP 和服务器值到应用程序,最接近现实,因为 HTTP 从根本上来说是一个面向字节的协议。如果环境值必须是字符串,则每个服务器都需要使用启发式方法来猜测 HTTP 环境提供的各种值的编码。使用所有字符串可能会提高非专业中间件编写者的便利性,但当某个值无法解码为有意义的非代理字符串时,也会导致歧义和混淆。
使用字节作为环境值避免了规范需要强制参与服务器了解编码配置参数的任何潜在需求。如果环境值被视为字符串,因此必须从字节解码,则配置参数最终可能会成为应用程序部署者提供的策略线索。此策略将用于在各种情况下猜测合适的解码策略,有效地将执行特定应用程序编码策略的负担放在服务器上。如果服务器必须为多个应用程序提供服务,则此类配置将很快变得复杂。许多策略也不可能以声明的方式表达。
实际上,HTTP 是一种复杂且充斥着遗留问题的协议,需要一套复杂的启发式方法才能理解。如果我们能够允许此协议保护我们免受这种复杂性的影响,那就太好了,但我们无法在仍然为应用程序编写者提供与现实相称的控制水平的同时可靠地做到这一点。Python 应用程序通常必须处理嵌入在环境中的数据,这些数据不仅必须由遗留启发式方法进行解析,而且甚至不符合任何现有的 HTTP 规范。虽然这些最终结果令人不快,但它们会定期出现,这使得隐藏它们对应用程序开发人员来说是不可能且不可取的,因为应用程序开发人员是唯一能够在检测到 HTTP 规范违规时决定适当操作的人。
有些人主张混合使用字节和字符串值作为环境值。本提案避免了这种策略。仅使用字节作为环境值使得本规范能够完全容纳在人的脑海中;您无需猜测哪些值是字符串,哪些值是字节。
如果所有环境值都是字符串,此协议也适合开发人员的思维,但本规范没有使用这种策略。这可能是关于使用字节的最大争议点。为了维护字节:开发人员通常更喜欢具有前后一致契约的协议,即使契约本身不是最优的。如果我们将编码问题隐藏在开发人员面前,直到包含代理的值在已经超出其应用程序的 I/O 边界后才导致问题,那么他们需要花费更多精力来修复其应用程序所做的假设,而不是我们更早地以“这里有一些字节,您解码它们”的方式提出问题。这也是对“字节不方便”假设的反驳:虽然向应用程序开发人员提供字节对于不关心边缘情况的非专业应用程序开发人员来说可能不方便,但对于需要处理复杂、混乱的最终结果的应用程序开发人员来说却非常方便,因为使用字节允许他以明确的责任划分获得适当的控制水平。
如果协议使用字节,则假定将创建库以使仅在环境中和在返回值中使用字节更愉快;例如,类似于名为“WebOb”和“Werkzeug”的 WSGI 1.0 库的库。此类库将填补便利性和控制性之间的差距,允许规范保持简单和规则,同时仍然允许非专业作者以方便的方式创建 Web3 中间件和应用程序组件。这似乎是将编码策略烘焙到协议中的合理替代方案,因为许多此类库可以独立于协议创建,并且应用程序开发人员可以选择为特定工作提供适当控制级别和便利性的库。
以下是一些使用所有字节的替代方案
- 让服务器使用
latin-1
编码将表示 CGI 和服务器环境值的所有值解码为字符串,该编码是无损的。将任何无法解码的字节塞入生成的字符串中。 - 使用
utf-8
编码和surrogateescape
错误处理程序将所有 CGI 和服务器环境值编码为字符串。这在任何现有的 Python 2 中都不起作用。 - 根据其典型用法将某些值编码为字节,将其他值编码为字符串。
应用程序应该被允许读取 web3.input
超过 CONTENT_LENGTH
在 [5] 中,Graham Dumpleton 断言 wsgi.input
应该被要求返回空字符串作为数据耗尽的标志,并且应用程序应该被允许读取超过 CONTENT_LENGTH
中指定的字节数,仅依赖于空字符串作为 EOF 标记。WSGI 依赖于应用程序“行为良好,并且一旦读取了 CONTENT_LENGTH
指定的所有数据,它就会处理数据并返回任何响应。然后,相同的套接字连接可用于后续请求。”Graham 希望 WSGI 适配器被要求包装原始套接字连接:“此包装器对象需要计算已读取多少数据,并且当数据量达到 CONTENT_LENGTH
定义的数量时,任何后续读取都应该返回空字符串。”这可能有助于支持分块编码和输入过滤器。
web3.input
未知长度
没有记录的方法可以指示 environ['web3.input']
中是否有内容,但内容长度未知。
read()
函数的 web3.input
应该支持无大小调用约定
在 [5] 中,Graham Dumpleton 断言 wsgi.input
的 read()
方法应该可以在没有参数的情况下调用,并且结果应该是“所有可用的请求内容”。需要讨论。
评论 Armin:我更改了规范,要求实现这一点。过去我在这方面吃了太多苦头。不过,欢迎讨论。
输入过滤器应该将 environ CONTENT_LENGTH
设置为 -1
在 [5] 中,Graham Dumpleton 建议输入过滤器可能将 environ['CONTENT_LENGTH']
设置为 -1 以指示它修改了输入。
headers
作为二元组的文字列表
为什么我们要让应用程序返回一个 headers
结构,该结构是二元组的字面列表?我认为 headers
的可迭代性需要在它向上移动堆栈时保持不变,但我认为我们不需要能够始终在原地对其进行修改。我们可以放宽这个要求吗?
评论 Armin:强烈同意
删除中间件不阻塞的要求
此要求已删除:“中间件组件不得阻塞迭代以等待来自应用程序可迭代对象的多个值。如果中间件需要在能够生成任何输出之前从应用程序累积更多数据,则必须产生一个空字符串。”此要求是为了支持异步应用程序和服务器(参见 PEP 333 中的“中间件处理块边界”。异步应用程序现在由 web3.async
功能协议显式服务(Web3 应用程序可调用本身可以返回可调用对象)。
web3.script_name
和 web3.path_info
这些值需要由遵循此规范的源服务器放入环境中。与SCRIPT_NAME
和PATH_INFO
不同,这些值必须是从请求 URI 中派生的原始URL 编码变体。我们可能需要弄清楚这些值最初是如何计算的,以及如果服务器执行 URL 重写,它们的应该是什么。
长响应头
Bob Brewer 在 Web-SIG 上的注释 [6]
每个 header_value 都不应包含任何控制字符,包括回车符或换行符,无论是在嵌入式还是在结尾处。(这些要求是为了最大程度地降低任何必须由服务器、网关和需要检查或修改响应标头的中间响应处理器执行的解析的复杂性。)(PEP 333)
这可以理解,但是 HTTP 标头定义为(大部分)*文本,并且“*文本的词语仅当根据RFC 2047的规则进行编码时,才能包含来自 ISO-8859-1 以外字符集的字符。” [2] 并且RFC 2047指定“‘编码词’的长度不能超过 75 个字符……如果需要编码的文本超过 75 个字符的‘编码词’所能容纳的范围,则可以使用多个‘编码词’(用 CRLF SPACE 分隔)。“ [3] 这也满足了 HTTP 标头折叠规则:“标头字段可以通过在每行额外的行之前至少放置一个 SP 或 HT 来扩展到多行。” (PEP 333)
因此,根据我对 HTTP 的理解,某些地方的代码应该在较长的编码响应标头值中引入换行符。我看到三种选择
- 保持现状,如果响应标头值包含超过 75 个字符且不在 ISO-8859-1 字符集内的词语,则不允许使用。
- 允许 WSGI 响应标头中使用换行符。
- 要求/强烈建议 WSGI 服务器在通过 HTTP 发送值之前进行编码和折叠。
请求尾部和分块传输编码
当在请求内容上使用分块传输编码时,RFC 允许存在请求尾部。它们类似于请求标头,但位于最终的空数据块之后。这些尾部仅在分块数据流是有限长度且已全部读取时才可用。WSGI 和 Web3 目前都不支持它们。
参考文献
版权
本文档已进入公有领域。
来源:https://github.com/python/peps/blob/main/peps/pep-0444.rst
上次修改时间:2023-09-09 17:39:29 GMT