tips: 以下, 服务端代指服务端和网关, 框架端代指应用端和框架端
实施与应用笔记
服务端扩展 API
服务端的开发者可能想要提供一些更加高级的 API,让框架或应用程序作者用于一些特殊的目的.比如,基于 mode_python 的网关可能想暴露部分 Apache 的 API 作为 WSGI 的扩展.
在这种简单的情况下,这个需求无非是定义一个 environ 变量,比如 mod_python.some_api.但是,在大多数情况下,现存的一些中间件可能会使得的情况变得更为复杂.比如,一个 API 可以提供获取 environ 变量中相同 HTTP 首部的内容,如果 environ 被中间件修改,就可能返回不同的内容.
一般情况下,任何复制,取代或是绕过 WSGI 部分功能的 API 都有与中间件不兼容的风险.服务端开发者不应该假定没有任何人使用中间件,因为一些框架开发者在试图组织或重构他们的框架时,会完全从中间件的角度出发来提供功能.
因此,为了最大限度的提高兼容性,服务端提供的一些代替 WSGI 功能的扩展 API,必须由它们想要替代的那个 API 来调用.例如,访问 HTTP 首部的扩展 API 必须要求应用程序传入当前的环境,然后由服务端通过 API 去验证首部是否被中间件修改.如果扩展 API 不能保证它始终与传入环境变量中的 HTTP 首部保持一致,那它必须拒绝框架端的服务请求,并抛出异常,返回 None 来代替首部集合,或任何适用于 API 的内容.
类似的,如果一个扩展 API 提供写入响应数据或头部的另外一种方法,那么在应用程序在获得扩展服务之前,它应该要求框架端传入 start_response 函数.如果传入不是服务端提供的原始对象,那么它就不能保证会执行正确的操作,所以它应该拒绝提供扩展服务给框架端.
这些准则也适用于中间件,它们可能会添加诸如 cookies 解析,表单变量,会话等类似信息到 environ 变量.具体的来说,这样的中间件应该把对 envrion 的操作作为一个函数提供,而不是简单的把这些东西添加到 environ 变量中,这可以帮助确保在任何中间件在对任何 URL 进行重写或其他 envrion 修改后再调用该方法进行修改.
这些安全扩展的规则很重要,服务端和中间件开发者必须遵守,也为了避免将来中间件开发人员被迫从环境中删除任何和所有的扩展 API,来确保他们不被使用这些扩展的应用程序绕过!
应用程序配置
这篇文档并没有定义服务端如何选择或者获取要调用的应用程序.这些或其他的配置项与服务端密切相关.服务端开发者应该给出如何配置调用应用程序及有什么配置选项的详细文档(比如线程选项).
从另一方面来说,框架作者应该给出如何创建一个应用程序对象及打包框架的说明文档.选择了服务端和应用程序框架端的用户,应该把这两者连起来.不过,既然框架和服务端有一个共同的接口,这个仅仅是一个手工操作的问题,而不是对每一个新的服务端和框架的组合都要进行一系列的工程操作.
最后,一些应用,框架和中间件可能希望用 envrion 字典去接收一些简单的字符串作为配置信息.服务端应该支持应用部署人员把具体的键值对放入 envrion 变量中.最简单的情况,这个支持可以通过复制由 os.environ 提供的所有环境变量到 envrion 中,因为部署人员原则上可以把这些外部配置传递给服务器或像 CGI 中的情况一样通过服务器的配置文件进行设置.
应用程序应该最小化需要的变量,因为不是所有的服务器都支持简单的配置.当然,即使在最坏的情况下,部署人员可以通过创建一个脚本来提供必须必须的配置信息.
但是,大多数现在的应用程序和框架可能仅仅需要从环境变量获取一个配置信息,为了指明它们的应用程序和具体框架的配置文件的位置.(当然,应用程序应该缓存这些配置,避免再次调用的时候重新读取.)
from the_app import application
def new_app(environ, start_response):
environ['the_app.configval1'] = 'something'
return application(environ, start_response)
URL 重建
如果一个应用程序希望重建一个请求完整的 URL,它可能利用如下的算法,由 lan Bicking 贡献:
注意这样重新构建的 URL 可能与客户端请求时的 URL 并不完全一致.比如,服务端的重写规则,可能会修改客户端的原始请求并改为更为规范的形式.
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']
支持低于 2.2 版本的 Python
一些服务端,网关或应用程序可能希望支持低(<2.2)版本的 Python.如果 Jython 是一个目标平台,这一点尤为重要,因为在撰写本文时,Jython 2.2 的生产版本尚不可用.
对于服务端,这个相对来说会比较简单:对于低于 2.2 版本的 Python,简单的限制自己仅使用标准的 "for" 循环语句去迭代任何由应用程序返回的可迭代对象. 这也是唯一确保从源码层保持低版本迭代协议和 "今天的" 迭代协议兼容的方法.(参阅 PEP 234 ).
(注:这个方法仅仅适用于用 Python 写的服务端,网关或中间件,如何从其他语言中使用迭代协议不在这篇文档的讨论范围.)
在应用程序中,如何支持低版本的 Python 会稍微复杂一些:
不能返回一个文件对象并期待它可以像个可迭代对象一样工作.因为在 2.2 版本之前,文件不可迭代.(一般来说,你不应该这么做,因为在大多数情况下,它的性能会非常差!)使用 wsgi.file_wrapper 或类应用程序定义的 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 选项来暴露这个功能.一个应用程序用这个" 文件包装器 " 去把一个文件或类文件的对象转换为可迭代的对象并返回,比如:
如果服务端提供 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), '')
类文件是指,由应用程序提供的对象必须有一个 read() 方法,接收一个可选的大小参数.它可能有一个 close 方法,如果这样的话,由 wsgi.file_wrapper 返回的可迭代对象也必须拥有一个 close 方法,并调用类文件对象的原始 close 方法.如果类文件对象有其他的与 Python 内置文件对象中相同的方法和属性名(比如 fileno()),wsgi.file_wrapper 可能会假设这些方法或属性与系统内置的文件对象有相同的语义.
任何特定平台的文件处理的实现必须在应用程序返回后进行,服务器或网关将检查是否返回了包装对象.(需要强调的是,由于中间件,错误处理等的存在,不能保证所创建的任何包装将被实际使用.)
除了处理 close() 方法,从应用程序返回的文件包装器的语义应该与应用程序返回的
iter(filelike.read, '')
一样.换句话说,传输应该从文件当时开始传输时的位置开始,并继续传输直到结束.
当然,特定平台的文件传输 API 通常不能接收任意的类文件对象,因此,为了确定类文件对象是否适用于特定平台的 API,wsgi.file_wrapper 必须提供如 fileno()(类 Unix 操作系统)或
java.nio.FileChannel
(Jython 平台)方法.
注意,即使对象不适合平台的 API,wsgi.file_wrapper 仍然要返回一个拥有 read 和 close 方法的可迭代对象,使得应用程序可以在各个平台之间进行移植.这是一个简单的与平台无关的文件包装器类,适用于旧版本 2.2 和新版本的 Python:
这是一个服务端的代码片段,用来访问特定平台的 API.
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
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()
来源: http://www.jianshu.com/p/a2fc853cca18