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 标准的影响。制定任何允许基于 Python 的 Web 服务器和应用程序互操作的标准的总体理由已在 PEP 333 中概述。本文件基本上使用 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,使得当 'foo' 的值无法使用当前区域设置解码时,os.environ['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()
函数必须按长度定界。没有大小参数的调用不得读取超过 Content-Length 头部指定的内容。如果 Content-Length 头部缺失,流在读取时不得返回任何内容。它绝不能请求超过客户端指定的数据。- 中间件不再需要为了从应用程序获取更多信息以产生输出而生成空字符串(例如,没有“中间件对块边界的处理”)。
- 传递给“file_wrapper”的文件类对象必须有一个返回字节(而不是文本)的
__iter__
。 - 不支持
wsgi.file_wrapper
。 - 服务器必须将
QUERY_STRING
、SCRIPT_NAME
、PATH_INFO
的值放入 environ 中(如果 HTTP 请求中没有关联值,则每个都为空字节实例)。 - 如果可能,原始 Web3 服务器应将
web3.path_info
和web3.script_name
放入 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
字符将按照 CGI 的规定在PATH_INFO
中解码为字面斜杠字符。 PATH_INFO
- 请求 URL “路径”的其余部分,指定请求目标在应用程序中的虚拟“位置”。如果请求 URL 目标是应用程序根目录且没有尾部斜杠,这**可能**是一个字节实例。
PATH_INFO
将是一个字节实例,表示由斜杠字符 (/
) 分隔的 URL 编码段序列。根据 CGI 规定,%2F
字符将被解码为PATH_INFO
中的字面斜杠字符。 QUERY_STRING
- 请求 URL 中跟随
"?"
的部分(如果有),以字节表示,否则为空字节实例。 SERVER_NAME
、SERVER_PORT
- 当与
SCRIPT_NAME
和PATH_INFO
(或其原始等效项)结合使用时,这些变量可用于完成 URL。但请注意,如果存在HTTP_HOST
,则应优先使用它来重建请求 URL,而不是SERVER_NAME
。有关详细信息,请参阅下面的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" 作为行尾,并假定它将被服务器/网关转换为正确的行尾。应用程序不得将字节发送到此流的“写入”方法;它们只能发送文本。对于许多服务器来说, |
web3.multithread |
如果应用程序对象可能由同一进程中的另一个线程同时调用,则此值应评估为真;否则应评估为假。 |
web3.multiprocess |
如果等效的应用程序对象可能由另一个进程同时调用,则此值应评估为真;否则应评估为假。 |
web3.run_once |
如果服务器或网关期望(但不保证!)应用程序在其包含进程的生命周期中只被调用一次,则此值应评估为真。通常,这仅适用于基于 CGI(或类似)的网关。 |
web3.script_name |
未进行 URL 解码的 SCRIPT_NAME 值。由于 CGI 规范的历史不公平,SCRIPT_NAME 在环境中以已进行 URL 解码的字符串形式存在。这是从请求 URI 派生出的原始 URL 编码值。如果服务器无法提供此值,则必须将其从环境中省略。 |
web3.path_info |
未进行 URL 解码的 PATH_INFO 值。由于 CGI 规范的历史不公平,PATH_INFO 在环境中以已进行 URL 解码的字符串形式存在。这是从请求 URI 派生出的原始 URL 编码值。如果服务器无法提供此值,则必须将其从环境中省略。 |
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() |
错误 |
1 |
write(str) |
错误 |
2 |
writelines(seq) |
错误 |
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
值是 (header_name, header_value)
元组的字面 Python 列表。每个 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 3bytes
实例不支持这些方法。如果您使用这些方法,您的代码将在 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 编码。
在没有原生类字节类型(例如 IronPython 等)的 Python 平台上,通常使用类文本字符串来表示字节数据,“字节实例”的定义可以更改:它们的“字节实例”必须是仅包含 ISO-8859-1 编码(包括 \u0000
到 \u00FF
)中可表示的代码点的原生字符串。在此类平台上,应用程序提供包含任何其他 Unicode 字符或代码点的字符串是致命错误。同样,这些平台上的服务器和网关**不得**向应用程序提供包含任何其他 Unicode 字符的字符串。
HTTP 1.1 Expect/Continue
实现 HTTP 1.1 的服务器和网关**必须**为 HTTP 1.1 的“expect/continue”机制提供透明支持。这可以通过以下几种方式完成:
- 对于包含
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,供应用程序或框架作者用于特殊目的。例如,基于 mod_python
的网关可能希望将 Apache API 的一部分作为 Web3 扩展暴露。
在最简单的情况下,这只需要定义一个 environ
变量,例如 mod_python.some_api
。但是,在许多情况下,中间件的可能存在会使这变得困难。例如,一个提供对 environ
变量中找到的相同 HTTP 头部进行访问的 API,如果 environ
已被中间件修改,则可能会返回不同的数据。
一般来说,任何重复、取代或绕过 Web3 功能某些部分的扩展 API 都有可能与中间件组件不兼容。服务器/网关开发人员**不应**假定没有人会使用中间件,因为一些框架开发人员专门组织他们的框架,使其几乎完全充当各种中间件。
因此,为了提供最大的兼容性,提供替代 Web3 功能的扩展 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)之间提供兼容性。
目前不可能使用 WSGI 1.0 规范编写在 Python 2 和 Python 3 下都能可靠运行的组件,因为该规范隐式地假定 environ 中 CGI 和服务器变量的值以及通过 start_response
返回的值表示一个可以使用 Python 2 字符串 API 处理的字节序列。它之所以假定这样的事情,是因为那种数据类型是所有 Python 2 版本中表示字节的合理方式,而 WSGI 1.0 是在 Python 3 出现之前构思的。
Python 3 的 str
类型支持 Python 2 str
类型提供的完整 API,但 Python 3 的 str
类型不表示字节序列,它表示文本。因此,使用它来表示环境值也要求通过某种编码将环境字节序列解码为文本。我们无法将这些字节解码为文本(至少在任何解码除了隧道机制外没有其他含义的方式下),除非将 WSGI 的范围扩大到包括服务器和网关对解码策略和机制的了解。WSGI 1.0 从未关注编码和解码。它对允许的传输值做出了声明,并建议各种值最好以一种或另一种编码进行解码,但它从未要求服务器在之前执行任何解码
Python 3 没有可用于替代字节的类字符串类型:它有一个 bytes
类型。字节类型在 Python 3.1+ 中很像 Python 2 的 str
,但它缺乏与 str.__mod__
等效的行为,其迭代协议、包含、序列处理和等价比较都不同。
无论哪种情况,Python 3 中都没有与 Python 2 str
类型行为完全相同的类型,并且创建这种类型的方法不存在,因为没有“String ABC”允许构建合适的类型。由于这种设计不兼容性,现有的 WSGI 1.0 服务器、中间件和应用程序在 Python 3 下无法工作,即使它们经过 2to3
处理。
现有关于更新 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”类型(即“增强型字节”),特别是如果 String 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 边界之外造成问题时才暴露出来,那么他们将需要做更多的工作来修复应用程序所做的假设,而不是我们在一开始就以“这里有一些字节,你来解码它们”的方式呈现问题。这也是对“字节不便”假设的反驳:虽然将字节呈现给应用程序开发人员可能对不关心边缘情况的随意应用程序开发人员不便,但对于需要处理复杂、肮脏的意外情况的应用程序开发人员来说,它们非常方便,因为使用字节允许他们在明确的职责分离下获得适当的控制级别。
如果协议使用字节,则假定将创建库,以使在环境中和返回值中仅使用字节变得更愉快;例如,WSGI 1.0 库“WebOb”和“Werkzeug”的类似物。这些库将弥补便利性和控制之间的差距,允许规范保持简单和规则,同时仍然允许随意作者以方便的方式创建 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']
中存在内容,但内容长度未知。
web3.input
的 read()
应支持无大小调用约定
在[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头定义为(大部分是)*TEXT,并且“*TEXT的单词仅在根据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
最后修改时间:2025-02-01 08:59:27 GMT