参考:wsgi规范
原始理由和目标:
python当前拥有各种各样的web应用程序框架,例如django,flask,twisted,tornado等。这对于新入python web坑的用户而言,各种各样的选择可能是个问题,因为通常来说,他们对于web框架的选择将限制他们对于可用web服务器的选择,反之,对于web服务器开发者而言,也是一样。
相比之下,尽管Java也拥有着许多可用的web应用程序框架,但是Java的 Servlet API 使得使用任何Java web应用程序框架编写的应用程序都可以在支持Servlet API的任何web服务器中运行。
此类API在python网络服务器中的可用性和广泛性使得无论这些服务器是用python写的,还是通过网关协议(例如cgi,fastcig等),将框架的选择与web服务器分开,让开发者可以自由选择合适他们的配对,同时让框架和服务器开发者可以将精力集中在他们各自的专业领域中。因此PEP提出了web服务器与web应用程序或框架之间的简单且通用的接口:
the Python Web Server Gateway Interface (WSGI).
规格概述
WSGI接口具有两个方面:服务器端和应用程序端(也可以叫框架)。
服务器端调用应用程序端提供的可调用对象。该对象的提供方式具体取决于服务器。假定某些服务器将需要应用程序提供者写一个简短的脚本来创建服务器或者网关的实例,并为其提供应用程序对象,则其他服务器可以使用配置文件或其他机制来指定应该从何处导入或以其他方式获取应用程序对象。
除了“纯”服务器/网关和应用程序/框架外,还可以创建实现本规范两面的“中间件”组件。这些组件充当其所包含的服务器的应用程序,并且充当所包含的应用程序的服务器,并且可以用于提供扩展的API,内容转换,导航和其他有用的功能。
我们将使用术语“可调用”来表示“具有__call__方法的函数,方法,类或实例”。由实现可调用对象的服务器,网关或应用程序来选择适合其需要的合适的实现技术。相反,正在调用可调用对象的服务器,网关或应用程序必须不依赖于向其提供了哪种可调用对象。可调用对象仅被调用,而不是自省的。
关于字符串类型
通常,HTTP处理字节,这意味着此规范主要是关于处理字节的。
但是,这些字节的内容通常具有某种文本解释,在Python中,字符串是处理文本的最便捷方法。
但是在许多Python版本和实现中,字符串是Unicode而不是字节。这需要在可用的API和HTTP上下文中的字节和文本之间正确转换之间保持谨慎的平衡……尤其是要支持在具有不同str类型的Python实现之间移植代码。
因此,WSGI定义了两种“字符串”:
- 用于请求/响应标头和元数据的“本地”字符串(始终使用名为str的类型实现)
- “字节串”(使用Python 3中的字节类型实现,在其他地方使用str),用于请求和响应的主体(例如POST / PUT输入数据和HTML页面输出)。
但是请不要感到困惑:即使Python的str类型实际上是“幕后的” Unicode,本地字符串的_内容_仍必须可以通过Latin-1编码转换为字节!(有关更多详细信息,请参阅本文档后面的“Unicode问题”部分。)
简而言之:在本文档中看到“字符串”一词时,它指的是“本地”字符串,即str类型的对象,无论它是在内部实现为字节还是Unicode。在您看到对“ bytestring”的引用时,应将其理解为“在Python 3下为字节类型的对象,在Python 2下为str类型的对象”。
因此,即使HTTP在某种意义上“实际上只是字节”,使用任何Python的默认str类型都是有许多API便利。
应用程序/框架端
应用程序对象是可以接收两个参数的可调用对象。这里对象不应该误解为需要实际的对象实例,而应理解为 函数,方法,或具有__call__ 方法的类的实例都可以用作应用程序对象。应用程序对象必须能够多次调用,因为几乎所有的服务器都会发出此类重复请求。
以下是两个应用程序对象示例,一个是函数,一个是类:
HELLO_WORLD = b"Hello world!\n"
def simple_app(environ, start_response):
"""Simplest possible application object"""
status = '200 OK'
response_headers = [('Content-type', 'text/plain')]
start_response(status, response_headers)
return [HELLO_WORLD]
class AppClass:
"""Produce the same output, but using a class
(Note: 'AppClass' is the "application" here, so calling it
returns an instance of 'AppClass', which is then the iterable
return value of the "application callable" as required by
the spec.
If we wanted to use *instances* of 'AppClass' as application
objects instead, we would have to implement a '__call__'
method, which would be invoked to execute the application,
and we would need to create an instance for use by the
server or gateway.
"""
def __init__(self, environ, start_response):
self.environ = environ
self.start = start_response
def __iter__(self):
status = '200 OK'
response_headers = [('Content-type', 'text/plain')]
self.start(status, response_headers)
yield HELLO_WORLD
服务器/网关端
服务器或网关针对从HTTP客户端收到的针对该应用程序的每个请求,调用一次可调用的应用程序。为了说明,这是一个简单的CGI网关,实现为带有应用程序对象的功能。请注意,此简单示例的错误处理受到限制,因为默认情况下,未捕获的异常将转储到sys.stderr并由Web服务器记录。
import os
import sys
enc, esc = sys.getfilesystemencoding(), 'surrogateescape'
def unicode_to_wsgi(u):
# Convert an environment variable to a WSGI "bytes-as-unicode" string
return u.encode(enc, esc).decode('iso-8859-1')
def wsgi_to_bytes(s):
return s.encode('iso-8859-1')
def run_with_cgi(application):
environ = {k: unicode_to_wsgi(v) for k, v in os.environ.items()}
environ['wsgi.input'] = sys.stdin.buffer
environ['wsgi.errors'] = sys.stderr
environ['wsgi.version'] = (1, 0)
environ['wsgi.multithread'] = False
environ['wsgi.multiprocess'] = True
environ['wsgi.run_once'] = True
if environ.get('HTTPS', 'off') in ('on', '1'):
environ['wsgi.url_scheme'] = 'https'
else:
environ['wsgi.url_scheme'] = 'http'
headers_set = []
headers_sent = []
def write(data):
out = sys.stdout.buffer
if not headers_set:
raise AssertionError("write() before start_response()")
elif not headers_sent:
# Before the first output, send the stored headers
status, response_headers = headers_sent[:] = headers_set
out.write(wsgi_to_bytes('Status: %s\r\n' % status))
for header in response_headers:
out.write(wsgi_to_bytes('%s: %s\r\n' % header))
out.write(wsgi_to_bytes('\r\n'))
out.write(data)
out.flush()
def start_response(status, response_headers, exc_info=None):
if exc_info:
try:
if headers_sent:
# Re-raise original exception if headers sent
raise exc_info[1].with_traceback(exc_info[2])
finally:
exc_info = None # avoid dangling circular ref
elif headers_set:
raise AssertionError("Headers already set!")
headers_set[:] = [status, response_headers]
# Note: error checking on the headers should happen here,
# *after* the headers are set. That way, if an error
# occurs, start_response can only be re-called with
# exc_info set.
return write
result = application(environ, start_response)
try:
for data in result:
if data: # don't send headers until body appears
write(data)
if not headers_sent:
write('') # send headers now if body was empty
finally:
if hasattr(result, 'close'):
result.close()
中间件:兼顾双方的组件
注意,相对于某些应用程序,单个对象可以扮演服务器的角色,而对于某些服务器而言,它也可以充当应用程序。此类“中间件”组件可执行以下功能:
- 在相应地重写环境之后,基于目标URL将请求路由到其他应用程序对象。
- 允许多个应用程序或框架在同一过程中并行运行
- 通过在网络上转发请求和响应进行负载平衡和远程处理
- 执行内容后处理,例如应用XSL样式表
通常,中间件的存在对接口的“服务器/网关”和“应用程序/框架”而言都是透明的,并且不需要任何特殊支持。希望将中间件合并到应用程序中的用户只需将中间件组件提供给服务器(就好像它是一个应用程序一样),然后将中间件组件配置为调用该应用程序,就好像中间件组件是服务器一样。当然,中间件包装的“应用程序”实际上可能是包装另一个应用程序的另一个中间件组件,依此类推,从而创建了所谓的“中间件堆栈”。
在大多数情况下,中间件必须符合WSGI的服务器端和应用程序端的限制和要求。但是,在某些情况下,对中间件的要求比对“纯”服务器或应用程序的要求更为严格,这些要点将在规范中予以说明。
这是一个中间件组件的示例(中间例子),该组件使用Joe Strout的piglatin.py将文本/纯文本响应转换为PigLatin。(注意:“真正的”中间件组件可能会使用更健壮的方法来检查内容类型,并且还应该检查内容编码。此外,此简单示例忽略了单词可能跨块边界拆分的可能性。 )
from piglatin import piglatin
class LatinIter:
"""Transform iterated output to piglatin, if it's okay to do so
Note that the "okayness" can change until the application yields
its first non-empty bytestring, so 'transform_ok' has to be a mutable
truth value.
"""
def __init__(self, result, transform_ok):
if hasattr(result, 'close'):
self.close = result.close
self._next = iter(result).__next__
self.transform_ok = transform_ok
def __iter__(self):
return self
def __next__(self):
if self.transform_ok:
return piglatin(self._next()) # call must be byte-safe on Py3
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)) # call must be byte-safe on Py3
return write_latin
else:
return write
return LatinIter(self.application(environ, start_latin), transform_ok)
# Run foo_app under a Latinator's control, using the example CGI gateway
from foo_app import foo_app
run_with_cgi(Latinator(foo_app))
规格明细
应用程序必须能接收两个位置参数,为了说明起见,我们将这两个参数命名为environ和start_response,服务器必须使用位置参数调用应用程序对象。例如通过调用result = application(environ,start_response)
该ENVIRON参数是一个字典对象,包含CGI风格的环境变量。该对象必须是内置的Python字典(_而不是_子类,UserDict或其他字典仿真),并且允许应用程序以所需的任何方式修改字典。该字典还必须包含某些WSGI必需的变量(在后面的部分中进行描述),并且还可以包含服务器特定的扩展变量,这些扩展变量是根据以下将要描述的约定命名的。
所述start_response参数是一个可调用接受两个必需的位置参数,以及一个可选的参数。为了便于说明,我们已将这些参数命名为status,response_headers和exc__info,但是它们不需要具有这些名称,并且应用程序必须使用位置参数(例如start_response(status,response_headers))调用start_response可调用对象。
所述start_response参数是一个可调用接受两个必需的位置参数,以及一个可选的参数。为了便于说明,我们已将这些参数命名为status,response_headers和exc_info,但是它们不需要具有这些名称,并且应用程序必须使用位置参数(例如start_response(status,response_headers))调用start_response可调用对象。
该状态参数是以下形式的状态字符串“在这里999消息”,和response_headers是列表(信头,header_value)描述的HTTP响应报头元组。可选的exc_info参数在以下有关start_response()可调用和错误处理的部分中进行了描述。仅当应用程序捕获了错误并试图向浏览器显示错误消息时,才使用它。
所述start_response调用必须返回一个写(body_data)可调用,它有一个位置参数:一个字节串被写入作为HTTP响应身体的一部分。(注意:提供write()可调用性仅是为了支持某些现有框架的命令性输出API;如果可以避免,则不应由新应用程序或框架使用它。有关更多详细信息,请参见“缓冲和流传输”部分。)
当服务器调用该应用程序对象时,该应用程序对象必须返回一个可迭代的结果,产生零个或多个字节串。这可以通过多种方式来完成,例如,通过返回字节串列表,或者通过应用程序为产生字节串的生成器函数,或者通过应用程序为实例可迭代的类。无论如何完成,应用程序对象都必须始终返回可迭代的结果,产生零个或多个字节串。
服务器或网关必须以无缓冲的方式将产生的字节串传输到客户端,在请求另一个字节串之前完成每个字节串的传输。(换句话说,应用程序应该执行自己的缓冲。有关如何处理应用程序输出的更多信息,请参见下面的“缓冲和流传输”部分。)
服务器或网关应将产生的字节串视为二进制字节序列:特别是,应确保不更改行尾。应用程序负责确保要写入的字节串采用适合客户端的格式。(服务器或网关可以应用HTTP传输编码,或执行其他转换,以实现HTTP功能(例如字节范围传输)。有关更多详细信息,请参见下面的其他HTTP功能。)
如果对len(iterable)的调用成功,则服务器必须能够依靠准确的结果。也就是说,如果应用程序返回的iterable提供了有效的__len__()方法,则它必须返回准确的结果。(有关正常使用此方法的信息,请参见“处理内容长度标题”部分。)
如果应用程序返回的可迭代对象具有close()方法,则服务器或网关必须在当前请求完成后调用该方法,无论请求是否已正常完成,还是由于迭代期间的应用程序错误或提早断开连接而提前终止浏览器。(close()方法的要求是支持应用程序释放资源。该协议旨在通过close()方法来补充PEP 342的生成器支持以及其他常见的可迭代项。)
返回生成器或其他自定义迭代器的应用程序不应假定整个迭代器都将被消耗,因为服务器可能会提前关闭它。
(注意:应用程序必须在iterable产生其第一个主体字节串之前调用start_response(),以便服务器可以在任何主体内容之前发送标头。但是,此调用可以在iterable的第一次迭代中执行,因此服务器必须在开始迭代可迭代对象之前,不要假设已调用了start_response()。)
最后,服务器和网关不得直接使用应用程序返回的iterable的任何其他属性,除非它是特定于该服务器或网关的类型的实例,例如wsgi.file_wrapper返回的“文件包装器”(请参阅可选)特定于平台的文件处理)。在一般情况下,仅接受此处指定或通过PEP 234迭代API访问的属性。
环境变量
根据通用网关接口规范[[2]的](https://www.python.org/dev/pe...,需要环境词典包含这些CGI环境变量。除非以下值另有说明,否则必须存在以下变量,除非它们的值将为空字符串,在这种情况下,可以将它们省略。[](https://www.python.org/dev/pe...
REQUEST_METHOD
HTTP请求方法,例如“ GET”或“ POST”。这永远不能是空字符串,因此始终是必需的。
SCRIPT_NAME
与应用程序对象相对应的请求URL的“路径”的初始部分,以便应用程序知道其虚拟的“位置”。如果应用程序对应于服务器的“根”,则它可以是一个空字符串。
PATH_INFO
请求URL的其余“路径”,指定应用程序中请求目标的虚拟“位置”。这可能是一个空字符串,如果请求的URL目标应用程序的根,没有尾部斜杠。
QUERY_STRING
请求网址中“?”之后的部分(如果有)。可能为空或缺席。
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_Variables
与客户端提供的HTTP请求标头对应的变量(即,名称以“ HTTP_”开头的变量)。这些变量的存在与否应与请求中适当的HTTP标头的存在与否相对应。
服务器或网关应尝试提供尽可能多的其他CGI变量。另外,如果使用SSL,则服务器或网关还应提供尽可能多的Apache SSL环境变量[[5]](https://www.python.org/dev/pe...,例如HTTPS = on和SSL_PROTOCOL。但是请注意,使用除上面列出的变量之外的任何CGI变量的应用程序必然不可移植到不支持相关扩展的Web服务器上。(例如,不发布文件的Web服务器将无法提供有意义的DOCUMENT_ROOT或PATH_TRANSLATED。)