首页 > 代码库 > python web server gateway interface (wsgi ) notes
python web server gateway interface (wsgi ) notes
前言:
注:如果需要得到支持批Python3.x以及包含了勘误表,附录,和说明的更新版规范,请查看PEP 3333
摘要:
这篇文档详细说明了一套在web服务器与Python web应用程序(web框架)之间的已提出的标准接口,从而方便web应用在各种web服务器之间的移植。
理论和目标
Python世界目前拥有各种各样的web应用框架,仅举几例比如 Zope, Quixote, Webware, SkunkWeb, PSO, and Twisted Web 等[1],
对于新手来说面对如此多的选择会很纠结。因为一般来说,他们所选择的web框架会限制他们选择可用的Web服务器,反之亦然。
与此相反,尽管java也有很多web应用程序框架,但是Java‘s "servlet" API 能让用这些框架写的应用程序运行在任何支持servlet API的服务器上。
对于python, 在服务器中有这样一个类似的被广泛使用的有效API,会让人们对服务器和python web框架之间的选择搭配更加随意自由,同时让服务器
和框架的开发者更加专注于他们各自的领域--(之前的状况是要么服务器是否是用pyhon写的例如Medusa,要么服务器中有嵌入的Python解释器(mod_python),或者通过一个网管协议(cgi,
FastCgi等)调用)。
因此,这份PEP提出了一个在web服务器和web应用(框架)之间的简单而通用的接口:Python web服务器网关接口(wsgi).
但仅仅是提出一个WSGI规范并不能解决服务器与python web应用框架的问题,web服务器与框架的作者以及维护者必须实际实现wsgi接口才能起到作用。
然而由于没有现有的服务器和框架支持WSGI,而且对于实现WSGI的作者也几乎没有即时回报,因此,WSGI必须实现起来容易,
以至于作者对该接口花的精力能更少一点。
该接口在服务器和框架两方面实现起来简单,对于该接口的实用性很关键,所以这必须是设计决策时首先应该遵守的准则。
但是要注意的是,对于框架作者来说容易实现和对于web应用的作者来说容易使用不是一码事,对于框架作者WSGI绝不提供毫无必要的接口。因为像响应对象和
Cookie处理这类附加的东西只会妨碍已存在的框架对该类问题的解决。WSGI的目的是让已存在的服务器与web应用(框架)之间的联系或交互更加容易,而不是创造一个
新框架。
同时也要注意到,这个目标使得WSGI不能依赖任何在当前已部署版本的python没有提供的任何功能,因此,也不会依赖于任何新的标准模块,
并且 WSGI并不需要2.2.2以上版本的python(当然,在以后的python标准库中内建支持这份接口的web服务器也是个不错的主意)。--(转)
不光要让现有的或将要出现的框架和服务器容易实现,也应该容易创建请求预处理器、响应处理程序和其他基于WSGI的中间件组件,
对于服务器来说他们是应用程序,而对于他们包含的应用程序来说他们是服务器。--(转)
如果中间件既简单又健壮,而且WSGI被广泛地实现在服务器中和框架中,那么有可能出现一种全新的python web框架:整个框架由几个松散耦合的WSGI中间件
组成。实际上,已存在的框架作者们甚至都可以去重构他们框架中的服务从而以这种方式来提供,变得更像与WSGI一起的库使用,而不像以一个整体独立的框架来用。
这样web应用开发者就可以根据特定功能选择最适合的组件,而不是所有功能都由同一个框架提供。
当然,这一天无疑还要等很久,在这之间,一个合适的短期目标就是让任何框架在任何服务器上运行起来。
最后,需要指出的是当前版本的WSGI并没有规定一个应用具体以何种方式部署在web服务器或gateway上。目前,这个需要由服务器或 gateway的具体实现来定义。
如果足够多实现了WSGI的服务器或gateway通过领域实践产生了这个需求,也许可以产生另一份PEP来描述 WSGI服务器和应用框架的部署标准。--(转)
概述:
WSGI的定义涉及两方面:一方面针对服务器或网关的,一方面针对应用程序或框架的。服务器侧一个可调用的对象由应用程序提供的,
至于该对象是如何被请求的取决于服务器或gateway,我们假定一些服务器或gateway会需要应用程序的部署人员编写一个简短的脚本来启动一个服务器或 gateway的实例,
并把应用程序对象提供给服务器,而其他的服务器或gateway需要配置文件或其他机制来指定从哪里导入或者获得应用程序对象。
除了在两方面制定规范使其各自责任纯粹,也可以创建实现了该规范两方的中间组件。这些组件对于包含它们的服务器它们扮演者应用程序的角色,
对于一个应用程序来说它们就作为一个server,而且可以被用来提供可扩展的API,内容转换,导航和其他有用的功能。
在这个规范中我们将用术语“一个可调用者”去表示一个“函数,方法,或具有‘__call__’方法的类实例”。这取决与服务器,gateway,
应用程序根据需要而选择的合适实现方式。相反请求一个可调用者的服务器,gateway或应用程序不可以依赖可调用者的具体实现方式。调用者就是可调用,别无其他。
应用程序(框架)端的接口:
一个应用程序对象就是一个简单的可调用的对象,它接受两个参数。这里的对象并不是真的需要一个对象实例,一个函数、方法、类、或者
带有 __call__ 方法的对象实例都可以用来做应用程序对象。应用程序对象必须可以多次被请求,实际上服务器/gateway(而非CGI)确实会产生这样的重复请求。
(注意:虽然我们把他叫做"应用程序"对象,但并不是说程序员要把WSGI当做API来调用,我们假定应用程序开发者仍然使用更高层面上的框架服务来开发应用程序,
WSGI是提供给框架和服务器开发者使用的工具,并不打算直接对应用程序开发者提供支持)
这里有两个应用程序对象的示例,一个是函数,另一个是类:
1 def simple_app(environ, start_response): 2 """最简单的调用对象""" 3 status = ‘200 OK‘ 4 response_headers = [(‘Content-type‘, ‘text/plain‘)] 5 start_response(status, response_headers) 6 return [‘Hello world!\n‘] 7 8 class AppClass: 9 """ 产生同样的输出,不过是使用一个类来实现 10 11 (注意: \‘AppClass\‘ 在这里就是 "application" ,所以对它的调用返回\‘AppClass\‘的一个实例, 12 这个实例做为迭代器再返回"application callable"应该返回的那些值) 13 14 如果我们想使用 \‘AppClass\‘ 的实例直接作为应用程序对象, 我们就必须实现 ``__call__`` 方法, 15 外部通过调用这个方法来执行应用程序, 并且我们需要创建一个实例给服务器或gateway使用. 16 """ 17 18 def __init__(self, environ, start_response): 19 self.environ = environ 20 self.start = start_response 21 22 def __iter__(self): 23 status = ‘200 OK‘ 24 response_headers = [(‘Content-type‘, ‘text/plain‘)] 25 self.start(status, response_headers) 26 yield "Hello world!\n"
Django开发服务器使用的应用程序接口,django.core.handlers.WSGIHandler类.
class WSGIHandler(base.BaseHandler): initLock = Lock() request_class = WSGIRequest def __call__(self, environ, start_response): ############################## # 暂时略过 ############################## return response --(转)
服务器端接口:
服务器/gateway为每一个从http客户端发来的请求都会去请求应用程序可调用者一次。为了说明这里有一个简单的CGI gateway,以获得一个应用程序对象的一个函数来实现。
请注意,这个例子拥有有限的错误处理,因为默认情况下没有被捕获的异常都会被输出到 sys.stderr并被服务器记录下来。
1 import os, sys 2 3 def run_with_cgi(application): 4 5 environ = dict(os.environ.items()) 6 environ[‘wsgi.input‘] = sys.stdin 7 environ[‘wsgi.errors‘] = sys.stderr 8 environ[‘wsgi.version‘] = (1, 0) 9 environ[‘wsgi.multithread‘] = False 10 environ[‘wsgi.multiprocess‘] = True 11 environ[‘wsgi.run_once‘] = True 12 13 if environ.get(‘HTTPS‘, ‘off‘) in (‘on‘, ‘1‘): 14 environ[‘wsgi.url_scheme‘] = ‘https‘ 15 else: 16 environ[‘wsgi.url_scheme‘] = ‘http‘ 17 18 headers_set = [] 19 headers_sent = [] 20 21 def write(data): 22 if not headers_set: 23 raise AssertionError("write() before start_response()") 24 25 elif not headers_sent: 26 # Before the first output, send the stored headers 27 status, response_headers = headers_sent[:] = headers_set 28 sys.stdout.write(‘Status: %s\r\n‘ % status) 29 for header in response_headers: 30 sys.stdout.write(‘%s: %s\r\n‘ % header) 31 sys.stdout.write(‘\r\n‘) 32 33 sys.stdout.write(data) 34 sys.stdout.flush() 35 36 def start_response(status, response_headers, exc_info=None): 37 if exc_info: 38 try: 39 if headers_sent: 40 # Re-raise original exception if headers sent 41 raise exc_info[0], exc_info[1], exc_info[2] 42 finally: 43 exc_info = None # avoid dangling circular ref 44 elif headers_set: 45 raise AssertionError("Headers already set!") 46 47 headers_set[:] = [status, response_headers] 48 return write 49 50 result = application(environ, start_response) 51 try: 52 for data in result: 53 if data: # don‘t send headers until body appears body 出现以前不发送headers 54 write(data) 55 if not headers_sent: 56 write(‘‘) # send headers now if body was empty 如果这个时候body为空则发送header 57 finally: 58 if hasattr(result, ‘close‘): 59 result.close()
===================================================(转)
Django的服务器接口,django.core.servers.WSGIServer类.WSGIServer类继承于Python lib的HTTPServer类.
服务器启动开始于django.core.servers下的run()方法.它启动服务器接口,且传递一个应用程序接口WSGIHandler类的实例给服务器接口.
1 def run(addr, port, wsgi_handler, ipv6=False): 2 server_address = (addr, port) 3 httpd = WSGIServer(server_address, WSGIRequestHandler, ipv6=ipv6) 4 httpd.set_app(wsgi_handler) 5 httpd.serve_forever() 6 7 class WSGIServer(HTTPServer): 8 """BaseHTTPServer that implements the Python WSGI protocol""" 9 application = None 10 11 def __init__(self, *args, **kwargs): 12 if kwargs.pop(\‘ipv6\‘, False): 13 self.address_family = socket.AF_INET6 14 HTTPServer.__init__(self, *args, **kwargs) 15 16 def server_bind(self): 17 """Override server_bind to store the server name.""" 18 try: 19 HTTPServer.server_bind(self) 20 except Exception, e: 21 raise WSGIServerException(e) 22 self.setup_environ() 23 24 def setup_environ(self): 25 # Set up base environment 26 env = self.base_environ = {} 27 env[\‘SERVER_NAME\‘] = self.server_name 28 env[\‘GATEWAY_INTERFACE\‘] = \‘CGI/1.1\‘ 29 env[\‘SERVER_PORT\‘] = str(self.server_port) 30 env[\‘REMOTE_HOST\‘]=\‘\‘ 31 env[\‘CONTENT_LENGTH\‘]=\‘\‘ 32 env[\‘SCRIPT_NAME\‘] = \‘\‘ 33 34 def get_app(self): 35 return self.application 36 37 def set_app(self,application): 38 self.application = application
中间件:同时扮演服务器和应用程序的角色的组件
注意单个对象可以作为请求应用程序的服务器存在,也可以作为被服务器调用的应用程序存在。这样的中间件可以执行这样一些功能:
重写前面提到的 environ 之后,可以根据目标URL将请求传递到不同的应用程序对象
允许多个应用程序和框架在同一个进程中运行
通过网络转发请求和响应,实现负载均衡和远程处理
对内容进行后加工,比如附加xsl样式表。
中间件的存在对于服务器接口和应用接口来说都应该是透明的,并且不需要特别的支持.希望在应用程序中加入中间件的用户只需简单得把中间件当作应用提供给服务器,
并配置中间件去调用应用程序,就像服务器一样。当然,中间件包裹的“应用程序”可能是包裹另一个应用程序的另一个中间件组件,这样循环下去就构成了我们称为"中间件堆栈"的东西了。
在大多数情况下,中间件必须符合WSGI服务器接口和应用程序接口双方的限制和要求。有些时候这样的限制甚至比纯粹的服务器或应用程序还要严格,这些地方我们会特别指出。
这里有一个中间件组件的例子,他用Joe Strout的piglatin.py将text/plain的响应转换成pig latin(隐语),真正的中间件应该使用更加安全的方式——应该检查内容的类型和内容的编码,
同样这个简单的例子还忽略了一个单词也许在一行的边界出被隔开的可能性)
1 from piglatin import piglatin 2 3 class LatinIter: 4 5 """Transform iterated output to piglatin, if it‘s okay to do so 6 7 Note that the "okayness" can change until the application yields 8 its first non-empty string, so ‘transform_ok‘ has to be a mutable 9 truth value. 10 """ 11 12 def __init__(self, result, transform_ok): 13 if hasattr(result, ‘close‘): 14 self.close = result.close 15 self._next = iter(result).next 16 self.transform_ok = transform_ok 17 18 def __iter__(self): 19 return self 20 21 def next(self): 22 if self.transform_ok: 23 return piglatin(self._next()) 24 else: 25 return self._next() 26 27class Latinator: 28 29 # by default, don‘t transform output 30 transform = False 31 32 def __init__(self, application): 33 self.application = application 34 35 def __call__(self, environ, start_response): 36 37 transform_ok = [] 38 39 def start_latin(status, response_headers, exc_info=None): 40 41 # Reset ok flag, in case this is a repeat call 42 del transform_ok[:] 43 44 for name, value in response_headers: 45 if name.lower() == ‘content-type‘ and value =http://www.mamicode.com/= ‘text/plain‘: 46 transform_ok.append(True) 47 # Strip content-length if present, else it‘ll be wrong 48 response_headers = [(name, value) 49 for name, value in response_headers 50 if name.lower() != ‘content-length‘ 51 ] 52 break 53 54 write = start_response(status, response_headers, exc_info) 55 56 if transform_ok: 57 def write_latin(data): 58 write(piglatin(data)) 59 return write_latin 60 else: 61 return write 62 63 return LatinIter(self.application(environ, start_latin), transform_ok) 64 65 66 # Run foo_app under a Latinator‘s control, using the example CGI gateway 67 from foo_app import foo_app 68 run_with_cgi(Latinator(foo_app))
详细说明:
应用程序对象必须接受两个参数,为了方便说明我们分别把;两个参数命名为 environ 和 start_response,但并非必须取这样的名字。
服务器或gateway必须用这两个位置参数(非关键子参数)调用应用程序对象(比如象上面展示的,这样调用 result = application(environ,start_response) )
参数 environ 是个字典对象,包含CGI风格的环境变量。这个对象必须是一个python内建的字典对象(不能是子类、UserDict或其他对字典对象的模仿)。
应用程序可以以任何他愿意的方式修改这个字典。environ 还应该包含一些特定的WSGI需要的变量(在后面的节里会描述),也可以包含一些服务器特定的扩展变量,
通过下面将要描述的约定来命名。
start_response 参数是一个接受两个必须参数和一个可选参数的可调用者,方便说明,我们分别把他们命名为 status, response_headers ,和 exc_info ,
应用程序必须用这些参数来请求 start_response (比如象这样 start_response(status,response_headers) )
参数 status 是一个形如"999 Message here"的状态字符串。而 response_headers 参数是一个描述http响应头的元素为元组(header_name,header_value)的列表,
可选的 exc_info 参数会在下面的 `The start_response() Callable`_和 Error Handling 两节中描述,他只有在应用程序产生了错误并希望在浏览器上显示错误的时候才有用。
start_response 可调用者必须返回一个 write(body_data) 可调用者,他接受一个可选参数:一个将要被做为http响应体的一部分输出的字符串。(注意:提供可调用者 write()
只是为了支持现有框架的必要的输出API,新的应用程序或框架尽量避免使用,详细情况请看 Buffering and Streaming 一节。)
当被服务器请求的时候,应用程序对象必须返回一个可以产生0个或多个字符串的可迭代对象。这可以通过多种方法完成,比如返回一个字符串的列表,
或者应用程序本身是一个yield字符串的生成器函数,或者应用程序是一个类而他的实例是可迭代的,不管怎么完成,应用程序对象必须总是一个可以产生0个或多个字符串的可迭代对象.
服务器必须将产生的字符串以一种无缓冲的方式传送到客户端,每次传完一个字符串再去获取下一个。(换句话说,应用程序应该实现自己的缓冲,
更多关于应用程序输出必须如何处理的细节请阅读下面的 Buffering and Streaming 节。)
服务器或gateway应该把产生的字符串当字节流对待:特别地,他必须保证没修改行的结尾。应用程序负责确保字符串是以与客户端匹配的编码输出(服务器 /gateway可能会附加HTTP传送编码,
或者为了实现一些http的特性而进行一些转换比如byte-range transmission,更多细节请看下面的 Other HTTP Features )
如果调 len(iterable) 成功,服务器将认为结果是正确的。也就是说,应用程序返回的可迭代的对象提供了一个有用 的__len__() 方法,
就肯定返回了正确的结果(关于这方面正常情况下如何被使用的请阅读 Handling the Content-Length Header )。
如果应用程序返回的可迭代者有close()方法,则不管该请求是正常结束还是由于错误而终止,服务器/gateway都必须在结束该请求之前调用这个方法,这是用来支持应用程序对资源的释放。
此协议旨在补充PEP325的生成器支持。以及其他有close()方法的通用可迭代对象。
(注意:应用程序必须在可迭代者产生第一个响应体字符串之前调用 start_response(),这样服务器才能在发送任何主体内容之前发送响应头,
然而这一步也可以在可迭代者第一次迭代的时候执行,所以服务器不能以为开始迭代之前 start_response() 就已经被调用过了)。
最后,服务器或gateway不能使用应用程序返回的可迭代者的任何其他属性,除非是针对服务器或gateway特定类型的实例,比如wsgi.file_wrapper返回的“file wrapper”
(阅读 Optional Platform-Specific File Handling )。通常情况下,只有在这里指定的属性,或者通过PEP 234 iteration APIs访问才被允许。
environ 变量:(服务端定义)
environ 字典被用来包含这些在Common Gateway Interface specification [2]_中定义了的CGI环境变量。下面这些变量 必须被声明,
除非其值是空字符串,这种情况下如果下面没有特别指出的话他们 可能 会被忽略。
REQUEST_METHOD
HTTP请求的方式, 比如 "GET" 或者 "POST". 这个不可能是空字符串并且也是必须给出的。
SCRIPT_NAME
请求URL中路径的开始部分,对应应用程序对象,这样应用程序就知道它的虚拟位置。如果该应用程序对应服务器的 根 的话, 它 可能 是为空字符串。
PATH_INFO
请求URL中路径的剩余部分,指定请求的目标在应用程序内部的虚拟位置。如果请求的目标是应用程序根并且没有结尾斜线的话,可能为空字符串 。
QUERY_STRING
请求URL中跟在"?"后面的那部分,可能为空或不存在.
CONTENT_TYPE
HTTP请求中任何 Content-Type 域的内容。可能为空或不存在.
CONTENT_LENGTH
HTTP请求中任何 Content-Length 域的内容。可能为空或不存在.
SERVER_NAME, SERVER_PORT
这些变量可以和 SCRIPT_NAME、PATH_INFO 一起组成完整的URL。然而要注意的是,
重建请求URL的时候应该优先使用 HTTP_HOST 而非 SERVER_NAME 。详细内容请阅读下面的 URL Reconstruction(URL重构) 。
SERVER_NAME 和 SERVER_PORT 决不能是空字符串,所以它们总是必须被给出。
SERVER_PROTOCOL
客户端发送请求所使用协议的版本。通常是类似 "HTTP/1.0" 或 "HTTP/1.1" 的东西可以被应用程序用来判断如何处理请求headers。
(既然这个变量表示的是请求中使用的协议,而且和服务器响应时使用的协议无关,也许它应该被叫做 REQUEST_PROTOCOL 。
然后,为了保持和 CGI的兼容性,我们还是使用已有的名字。)
HTTP_ Variables
对应客户端提供的HTTP请求headers (也就是说名字以 "HTTP_" 开头的变量)。这些变量的存在与否应该和请求中的合适的HTTP header一致。
服务器或gateway 应该 尽可能提供其他可用的CGI变量。另外,如果用了SSL,服务器或 gateway也 应该 尽可能提供可用的Apache SSL环境变量 [5] ,
比如 HTTPS=on 和 SSL_PROTOCOL``。不过要注意,使用了任何上面没有列出的变量的应用程序对不支持相关扩展 的服务器来说是一定不能移植的,
(比如,不发布文件的web服务器就不能提供一 个有意义的 ``DOCUMENT_ROOT 或 PATH_TRANSLATED 。)
一个支持WSGI的服务器或gateway 应该 在描述它们自己的同时说明它们可以提供些什么变量,应用程序 应该 对所有他们需要的变量的存在性进行检查,
并且在某变量不存在的时候有备用的措施。ArithmeticError
注意: 不需要的变量 (比如在不需要验证的情况下的 REMOTE_USER ) 应该被移出 environ 字典。同样注意CGI定义的变量如果存在的话必须是字符串。
任何 str 类型以外的CGI变量的存在都是对本规范的违反
除了CGI定义的变量, environ 字典也可以包含任意操作系统的环境变量,并且 必须 包含下面这些WSGI定义的变量:
wsgi.version | 元组 (1,0), 表明WSGI 版本 1.0.
wsgi.url_scheme | 表示对应于被调用应用程序的Url地址的协议部分.一般来说,此字符串的值是”http”或”https”.
wsgi.input | 来自于可读取Request body的输入流(类文件对象).(根据其偏好,server/gateway可能会在应用程序要求时才读取此输入流,
| 或者预先读取客户端的request body然后缓存在内存或硬盘中,或者使用其他技术来提供此输入流.)
wsgi.errors | 可以写错误输出的输出流(类文件对象),从而可以在一个标准的和可能集中的位置记录程序或其他错误。这应该是一个“文本模式”的流,
| 即,应用程序应该使用“\n”作为行结束,并假设它会被服务器/网关转化为正确的行结束。
| 对于许多服务器,wsgi.errors将是该服务器的error log文件,另外也可能是sys.stderr,或者其他类型的日志文件。服务器文档应该有对如何配置或者
| 查看这个错误记录输出方法的说明。如果有必要,服务器或网关可能会为不同的应用程序提供不同的错误输出流。
wsgi.multithread | 如果同样的程序对象可以同时被另一个在同一个进程中的另一个线程调用,那么它的值为True,否则为False.
wsgi.run_once | 这个值应当为True,当服务器或网关期望(但不能保证)应用程序在包含它的进程的生命周期内只被调用一次。通常,这个值为真只对于那些基于CGI或内似的网关来说的。
这个environ字典也可以包含服务器定义的变量,这些变量的名字必须是小写字母、数字、点和下划线,并且应该用一个能唯一表示服务器或gateway的名字作为前缀。例如mod_python
也许会定义这样的变量mod_python.some_variable.
输入和错误流
服务器提供的错误和输入流必须支持下面的方法:
方法 | 流 | 注释
read(size) input 1
readline() input 1,2
readline(hint) inpit 1,3
__iter__() input
flush() errors 4
write(str) errors
writeline(seq) errors
每个方法的语义都与 Python Library Reference中描述的一致,除了对上面表中有特别注明的除外(如下):
1.服务端不是必须去读取超过客服端指定的Content-Length长度的内容,而且如果应用程序尝试读取超过这个长度的内容时,又是被允许去冒充一个end-of-file 的情况。那么应用程序不应该尝试去读取比这个CONTENT_LENGTH 变量指定的更多的数据
2.对于readline(),这个可选的‘size’参数是不被支持的,因为对于服务器的开发者去实现可能有点复杂,而且实践中也不常被使用。
3.注意那个readlines()函数的hint参数,对于调用者和实现者来说都是可选的。
4.因为errors流不可以被回溯,服务器和网关能任意的去立刻转发写操作,没有缓冲。在这种情况下,这个flush()方法也许是一个空操作。可移植的应用程序,然而却不能想当然的认为输出是不能被缓冲的,或者flush()方法是一个空操作。如果它们需要确保输出实际上已被写入,就一定要调用flush().(例如,由于多个进程写同一个错误日志,为了最大限度的减少数据的混合)
遵循这个规范,上表列出的方法必须要被所有的服务器支持,应用程序一定不要使用这个input或者errors对象的任何其他属性和方法。尤其是,应用程序一定不能尝试去关闭这些流,即使他们拥有close()方法
可调用的 start_response()方法
这第二个传递给应用程序对象的参数是一个 形如start_response(status, response_headers, exc_info=None)的可调用对象,(正如其他所有的WSGI可调用者一样,这个参数必须是位置参数,不是关键字参数),这个start_response常常被用着开始HTTP响应,而且它一定要返回一个write(body_data)可调用对象,(阅读如下 Buffering and Streaming 部分)
这个status 参数是一个HTTP “status” 字符串,诸如 “200 OK” , “Not Found”之类的,是由一个状态码和一个原因短语组成,按这个顺序并由一个空格隔开,两头没有其他空格和其他字符。 (更多信息请阅读RFC 2616, Section 6.1.1) 该字符串 禁止 包含控制字符,也不允许以回车、换行或他们的组合结束。
这个response_headers参数是一个元素为(header_name,header_value)元组的列表,它一定要是一个Python列表,也就是说 type(response_headers) is ListType。并且服务器 可以 以任何方式改变其内容每一个 header_name 必须是一个没有冒号或其他标点符号的合法的HTTP header字段名 (在RFC 2616, Section 4.2中有详细定义).
每一个 header_value 无论嵌入或结束都禁止 包含 任何 控制字符,包括回车或换行。 (这些要求是要使得那些必须检查或修改响应头的服务器、gateway、响应处理中间件所必须执行的解析工作的复杂性降到最低。)
通常,服务器和网关有责任确保正确的响应头被发送到客户端,如果应用程序忽略了HTTP要求的响应头字段或其他有效的相关规范,那么服务器或网关必须去添加它。例如,HTTP Date和Server:这些响应头,就会由服务器或网关给出。
( 对服务器/网关作者的提醒:HTTP头名字是不区分大小写的,因此当检查应用程序提供的头字段时,一定要考虑到这点)
应用程序和中间件应该被禁止使用HTTP/1.1 "hop-by-hop" 选项特征或头字段,任何在HTTP/1.0中的同等特征,或者任何影响客户端到web服务器的连接持久性的头字段。这些特征是web服务器专门提供。对于一个应用程序尝试发送这些字段时,服务器或网关都应该把这看作是一个致命的错误,而且如果由start_response()提供,应该抛出一个错误。(了解更多关于“hop-by-hop”特征和头字段的说明,请阅读下面 Other HTTP Feature 的部分)
这个可调用的start_response事实上不能发送这个响应头,它应该存储这些头,让服务器或网关仅仅在这个应用程序返回值产生一个非空字符串的第一次迭代后,去转发,或者是在基于应用程序的write()的第一次调用时。换句话说,响应头要直到实际的响应体数据可用时 被发送,或者直到应用程序的返回迭代完为止。(这个规则唯一可能的例外是 ,如果响应头明确地包含了值为0的Content-Length头字段)
响应头的传输延迟是为了确保缓冲和异步应用程序直到最后一刻可以用错误输出取代他们原来的打算的输出。例如,如果一个错误发在响应体内,而同时响应体是从一个应用程序的缓冲区内产生,此刻,该应用程序也许需要将响应状态从“200OK” 改变为 "500 Internal Erro"
这个exc_info参数,如果提供,必须是一个Python 中的sys.exc_info()元组。这个参数应当由应用程序只在start_response被一个错误处理程序调用时提供。如果
exe_info被提供,而且HTTP 头字段也没有被输出,那么start_response应该用新给出的响应头取代当前已经存储的HTTP响应头。从而允许应用程序在发生错误时改变这个输出。
但是,如果exc_info被提供时,而HTTP响应头已经被发送了,start_response必须抛出一个错误,而且抛出的应该是一个exc_info元组,即:
raise exc_info[0], exc_info[1], exc_info[2]
这将重新引发由应用程序捕获的异常,原则上应当中止该应用程序。(一旦HTTP响应头已经被发送时,应用程序尝试将错误输出到浏览器端,这样做是不安全的)。
如果start_response调用时有exc_info参数时,应用程序一定不能捕获由start_response抛出的任何异常。相反,应该允许这样的异常回传给服务器或网关。在下面Error Handling章节,有更详细的关于该情况的介绍。
当且仅当这个exc_info参数是被提供时,应用程序也许调用start_response而不止一次。更精确地说,这是一个致命的错误,如果start_response在应用程序的当前调用内部已经被调用,却没有exc_info参数。(参考上面CGI网关的例子关于正确逻辑的说明)
注意:服务器,网关,或中间件实现start_resonse时应确保这个exc_info参数在函数的执行期外没有被引用,以避免通过所涉及的回溯和框架创建一个循环引用。
做这件事的最简单方式,像下面:
1 2 3 4 5 6 | 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 "chunked encoding" [3],那么服务器可以用chunked encoding 发送每个each()调用或者由迭代器产生的字符串的块。从而产生每个块的一个Content-Length头。这允许服务器可以如它所愿去保持与客户端的连接。注意这个服务器一定要完全遵照RFC 2616规范去处理,要不然
就其他方法去处理这种情况下的Content-Length。
(注意:应用程序和中间件对于他们的输出一定不要应用任何一种Transfer-Encoding,例如chunking 或 gzipping;因为 "hop-by-hop"操作,这些编码是实际中web服务器/网关的领域。详细信息 可以参考下面 Other HTTP Features 章节。)
缓冲和流
一把来说,应用程序将通过缓冲他们的(适度规模)输出和一次性发送它以达到最佳的吞吐量。在现有的框架中例如Zope,这是一个常用的方法:输出缓存在一个StringIO或类似的一个对象中,然后与相应头同时一起传送。
对于WSGI中应用程序相应的方法是简单地返回一个单元素的迭代器(如list),包含一个相应体的单字符串。这是对于绝大多数应用功能所推荐的方法,这使得HTML页面的文本很容易保存在内存中。
对于大文件,或者专门用途的HTTP流(例如分段 server push),应用程序需要提供输出更小的块(例如 为了避免加载大文件到内存)。有时候也可能出现响应的部分输出耗费时间的意外,不提前发送响应的一部分是有用的。
在这些情况下,应用程序通常会返回一个迭代(通常是一个产生器迭代器),产生的输出是以 块跟块(block-by-block)的方式。这些块可能被拆开,为了与多段边界的一致(对于“Server push”),或只是为了之前耗时的任务(如读取磁盘上的文件的另一个块)。
WSGI服务器,网关和中间件,不得延迟任何块的传输;他们必须要么完整地将块传送到客户端,或者保证他们将继续传输,即使当应用程序正生成它的下一个数据块。
服务器/网关或中间件可以提供这种担保通过下列三种方式之一:
1.控制权返回给应用程序之前,将整个块给操作系统(以及要求的任何被刷新的O / S的缓冲区),或
2.使用不同的线程,以保证块继续被传送,当该应用程序产生下一个块。
3.(仅中间件),将整个块传送到它的上级网关/服务器
通过提供这种保证,WSGI允许应用程序以确保传输将不会停滞在他们的输出数据的任意点上。这是例如多段“Server push”流正常运行的关键,其中多段边界之间的数据应全部向客户端传输。
块边界的中间件处理
为了更好的支持异步应用和服务器,中间件组件一定不能阻塞迭代而等待来自应用程序可迭代的多个值。如果中间件需要从应用程序积累更多的数据后,才可以产生任何输出,它必须产生一个空字符串。
符合这种要求的另一种方式。中间件组件必须在每次其底层应用程序产生一个值时产生至少一个值。如果中间件不能产生任何其他的价值,它必须产生一个空字符串。
这个要求需要确保异步应用和服务器可以协作去减少需要同时运行给定数量的应用程序实例的线程数。
另请注意,这一要求意味着,中间件必须立即返回一个可迭代的值一旦其底层应用程序返回一个可迭代的值。它还禁止对中间件来使用write()来传输由底层应用产生的数据。
中间件只能使用它们的父服务器的write()调用来传输数据(底层应用使用中间件提供的write()发送的)
可调用的Write()
一些现有的应用程序和框架的API支持以较于WSGI更加不同的方式的无缓冲输出,特别地是,他们提供了一个“write”函数或某种写一个无缓冲数据块的方法,要不然他们提供了一个缓冲的“write”函数和一个刷新机制来刷新缓冲区。
不幸的是,这样的API在WSGI的“迭代”应用返回值方面是不能实现的,除非使用线程或其他特殊机制。
因此,为了让这些框架继续使用命令式的API,WSGI包括一个特殊的write(),由start_response调用返回。
新WSGI应用程序和框架如果有可能避免这样做,应该不使用write(),write()完全是一个支持必要的流API的hack,通常,应用程序应该通过他们返回的迭代器产生他们的 输出。因为这样使得web服务器同时运行其它任务再同一python线程内,作为一个整体,潜在地为服务器提供了更好的吞吐量。
write()是由start_response() 返回,它接受一个唯一的参数:要被写入HTTP响应体中作为其一部分的字符串,这正如与它被输出迭代器生成一样被对待,换句话说,在write()返回之前,当应用程序向前继续处理时,它必须保证传入的字符串是要么完全传入到客户端的,要么在传输时被缓冲的,
应用程序必须返回一个可迭代的对象,即使它使用write()来产生所有或部分的响应体。返回的迭代器可以为空(即不产非空字符串),但如果这样做会产生非空字符串,该输出通常必须由服务器或网关进行处理(即,它必须立即被发送或排队)。应用程序不能调用write()来自在它们内部返回的迭代器,因此任何由迭代器产生的字符串,要在所有的字符串通过write()发送到客户端后才被传输。
Unicode 问题
HTTP 并不直接支持Unicode,而且不会实现这个接口。所有编码/解码的工作必须得由应用程序来处理;传递到服务器或从服务器中接收的所有字符串必须是标准的Python字节串,不是Unicode 对象。在被要求一个字符串对象,使用Unicode对象,会导致不确定的结果。
还要注意传递到start_reponse()的作为一个状态或者响应头字段的字符串必须遵循RFC 2616中关于编码的规范。也就是说,它们必须是ISO-8859-1的字符,或者使用RFC2047 MIME编码。
在一些str或StringType实际上是基于Unicode编码的Python平台上(例如Jython, IronPython, Python 3000等),本规范中提到的所有“Strings”必须只包含在ISO-8859-1编码中能被表示(\ u0000到\ u00FF(包括在内))的代码点(code points UNICODE的字符被成为代码点).为应用程序提供包含任何其他的Unicode字符或代码点的字符串是一个致命的错误。同样,服务器和网关不能向应用程序提供一个包含任何其他Unicode字符的字符串。
同样,在本规范中提到的所有字符串必须是str或StringType类型,而不是unicode或UnicodeType类型。而且,对于在本说明中称为“串”的任何值,即使给定平台允许在STR/ StringType对象中每个字符超过8位,也只有低于8位的会被使用。
错误处理
通常应用程序应该尝试捕获它们自己的内部错误,并显示有用的错误信息到浏览器上(由应用程序来决定在上下文中什么是“有用的”)
然而,为了显示这样的消息时,应用程序必须还没有实际发送任何数据到浏览器,否则有破坏响应的风险。因此WSGI提供了一种机制,以允许应用程序发送它的错误信息,或自动中止:传给start_response的参数exc_info。下面是其使用的一个例子:
1 try: 2 # regular application code here 3 status = "200 Froody" 4 response_headers = [("content-type", "text/plain")] 5 start_response(status, response_headers) 6 return ["normal body goes here"] 7 except: 8 # XXX should trap runtime issues like MemoryError, KeyboardInterrupt 9 # in a separate handler before this bare ‘except:‘... 10 status = "500 Oops" 11 response_headers = [("content-type", "text/plain")] 12 start_response(status, response_headers, sys.exc_info()) 13 return ["error body goes here"]
如果发生错误时没有输出已被写入,调用start_response将正常返回,并且应用程序将返回一个错误的响应体然后被发送到浏览器。但是,如果任何输出已被发送到浏览器,start_response会再次抛出所提供的异常。这个异常不应该被应用程序捕获,因此应用程序将终止。服务器和网管可以捕获这个(致命)错误,并终止响应。
服务器应该捕获和记录任何异常信息,终止应用程序或它的返回值的迭代。如果当一个应用程序的错误发生时部分响应已被写入到浏览器端,服务器和网关可以尝试去添加错误消息到输出。如果已经发送的头字段表明了服务器知道如何干净地修改text/*的内容内型。
一些中间件不妨提供额外的异常处理服务,或拦截并替换应用程序的错误信息。在这种情况下,中间件可以选择不重新抛出提供给start_response的exc_info,而是提出一个中间件特定的异常,或者干脆存储提供的参数后返回无异常。允许中间件捕获和修改错误输出,这样就会导致应用程序返回其错误的可迭代的响应体(或者调用write()方法)。只要应用程序作者做到下面两点,这些技巧将会起作用:
1.当开始一个错误响应时,总是提供exe_info;
2。当exe_info正北提供时,绝不通过start_response 捕获抛出的错误
HTTP1.1 Expect/Cotinue
实现了HTTP1.1的服务器和网关必须提供对HTTP1.1的“Expect/Continue”机制的透明支持,这可以以任何一种方式来完成:
1.响应包含Expect的请求 :“100 Continue”的请求,立即以“100 Continue”的响应,并且继续正常处理后续请求
2.继续进行正常的请求,但要提供给应用程序一个wsgi.input流,如果当应用程序第一次尝试从这个输入流读取时,将发送“100 Continue”响应。
那么读请求必须保持阻塞,直到客户端响应。
3.等待直到客户端判定服务器不支持expect/continue,然后先发送它自己的请求体。(这不是最理想的方法,因此不推荐)
需要注意的是这些行为的限制并不适用于HTTP 1.0的请求,或者未定向到一个应用程序对象的请求。有关HTTP1.1 Expect/Continue的更多信息,
请参阅RFC2616,8.2.3和10.1.1节。
其他HTTP特点
通常,服务器和网关应该“Play dumb”,允许应用程序完全控制其输出。它们应该对不影响应用程序有效语义的响应进行更改。它应该总是使应用程序开发人员添加中间件组件提供额外的功能成为可能,因此服务器和网关开发人员应该在实现时采取保守的态度。从某种意义上说,一个服务器应该考虑自己要像一个HTTP“网关服务器”,对于应用程序则是一个HTTP“源服务器”。(这些术语的定义可查阅RFC 2616, 1.3节)
然而,因为WSGI服务器和应用程序不通过HTTP进行交流,RFC 2616中所谓的“hop-by-hop”头字段不适用于WSGI内部交流,WSGI应用程序不能生成任何“hop-by-hop”报头,尝试使用HTTP的功能,将要求他们产生这样的头字段,或依赖于environ字典中的任何传入的“hop-by-hop”头字段的内容。WSGI服务器必须处理任何支持入站的“hop-by-hop”的它们自己的头字段,如通过解码任何入站的Transfer-Encoding,合适的话 还包括chunked encoding
将这些原理应用于各种HTTP特性,服务器可以通过If-None-match和If-Modified-Since请求头和Last-Modified和ETag的响应报头处理缓存验证。然而做这些并不是必须的,如果应用程序想支持该功能,应该执行他们自己的缓存验证,因为服务器/网关是不被要求必须进行这样的验证。
同样地,一个服务器可以重新编码和传输编码一个应用程序的响应,但应用程序应该使用它自己合适的内容编码,而不能套用传输编码。如果客户端请求的服务器可以发送应用程序的响应的字节范围,并且应用程序本身不支持字节范围。此外,不管怎样,如果需要,应用程序应该执行他自己的功能。
请注意,在应用程序上的这些限制并不一定意味着每个应用程序必须重新实现每一个HTTP功能;许多HTTP特性可以通过中间件组件部分或完全实现,因此服务器和应用程序的作者不用一遍遍实现相同的功能。
线程支持
或缺乏对线程的支持,也依赖于服务器。服务器可以并行运行多个请求,也应该提供在一个单线程中运行一个应用程序的可选项。以便那些不是线程安全的应用程序和框架任然可以随服务器一起被使用。
实现/应用的注意事项
服务器扩展API
有些服务器作者可能希望公开更高级的API,应用程序或框架的作者可以使用专门的用途。例如,基于mod_python的网关可能希望暴露Apache的API的一部分,作为一个WSGI扩展。
在最简单的情况下,这个要求的只不过是定义一个环境变量,例如mod_python.some_api.但是在许多情况下,中间件的可能存在会造成困难。例如,一个API提供了访问同样的在环境变量里被发现的HTTP头字段,如果中间件修改了环境变量environ ,可能返回不同的数据。
一般情况下,任何扩展的API副本,替代品,或者绕过WSGI功能的某些部分,都会冒着与中间件不兼容的风险。服务器/网关开发人员不应该假设没有人会使用中间件,因为一些框架开发者会有意打算去组织和整理他们框架的功能成为几乎完全为各类中间件。
因此,为了提供最大的兼容性,服务器和网关提供替代WSGI功能的API,这些API必须被设计成具有他们所替代的API的用途。例如,一个访问HTTP请求头的扩展API必须要求应用程序进入其当前的environ,从而使服务器/网关可以通过没有被中间件修改过的API来验证可以访问的HTTP头。如果扩展的API不能保证始终与HTTP头字段内容的environ一致,它必须拒绝提供服务给应用程序,例如通过抛出一个错误,返回None,而不是一个header集合,或任何适当的API。
同样地,如果扩展API提供了写响应数据或头字段的另一种方法,它应该要求可调用的start_response 被传入,在应用程序可以获得扩展服务前。如果传入的对象与服务器/网关最初提供给应用程序的不是同一个,他不能保证正确的操作,它必须拒绝提供扩展服务给应用程序。
这些规则也适用于中间件增加信息,例如解析Cookies,表单变量,sessions,以及类似的环境变量environ 。具体来说,这种中间件应提供这些功能作为其对环境变量操作的函数,而不是简单地把值添加到环境变量environ 中。这有助于确保信息是从在任何中间件做了重定向或者其它修改之后的环境变量environ 来计算。
这是非常重要的,这些“安全扩展”规则,服务器/网关和中间件开发者 都要遵守,为了避免在未来,中间件开发者被迫从环境变量environ 中删除任何及所有扩展API,以确保使用那些扩展API的应用程序没有忽视他们之间的约定。
应用程序配置
本规范没有定义如何在服务器选择或获得一个应用程序来调用。这些和其他的配置选项是与服务器十分相关的问题。这要期望服务器/网关作者会给出文档:记录如何配置服务器去执行特定应用程序对象,以及使用哪些选项(例如线程选项)。
在另一方面,对于框架的作者,应当记录如何创建一个包装自己框架的功能的应用程序对象。选择了服务器和应用程序框架的用户,必须将两者结合起来。但是,对于每个新的服务器/框架搭配,因为框架和服务器现在都有一个共同的接口,这应该仅仅是一种机械的事情,而不是一个显著的工程工作。
最后,某些应用程序,框架和中间件不妨使用environ的字典来接收简单的字符串配置选项。服务器和网关应该支持,允许部署应用程序时,指定被放置在environ中的名称 - 值对。
在最简单的情况下,这样的支持可以只包括从os.environ所有操作系统提供的environ变量复制到environ字典。因为原则上部署可以从外部配置这些服务器,或在CGI的情况下,可能通过服务器的配置文件来设置。
应用程序应该尽量保持这种必要的变量到最低限度,因为不是所有的服务器都支持轻松配置这些变量。当然,即使是在最坏的情况下,部署应用程序的人可以创建一个脚本来提供必要的配置值:
1 from the_app import application 2 3 def new_app(environ, start_response): 4 environ[‘the_app.configval1‘] = ‘something‘ 5 return application(environ, start_response)
但是,大多数现有应用程序和框架可能只需要来自environ的单一的配置值,以表明他们的应用程序或特定框架的配置文件位置。(当然,应用程序应该缓存这样的配置,以避免在每次调用时重新读取它。)
URL重构
如果一个应用程序希望重建一个请求的完整URL,它可能会使用下面的算法,由Ian Bicking贡献:
1 from urllib import quote 2 url = environ[‘wsgi.url_scheme‘]+‘://‘ 3 4 if environ.get(‘HTTP_HOST‘): 5 url += environ[‘HTTP_HOST‘] 6 else: 7 url += environ[‘SERVER_NAME‘] 8 9 if environ[‘wsgi.url_scheme‘] == ‘https‘: 10 if environ[‘SERVER_PORT‘] != ‘443‘: 11 url += ‘:‘ + environ[‘SERVER_PORT‘] 12 else: 13 if environ[‘SERVER_PORT‘] != ‘80‘: 14 url += ‘:‘ + environ[‘SERVER_PORT‘] 15 16 url += quote(environ.get(‘SCRIPT_NAME‘, ‘‘)) 17 url += quote(environ.get(‘PATH_INFO‘, ‘‘)) 18 if environ.get(‘QUERY_STRING‘): 19 url += ‘?‘ + environ[‘QUERY_STRING‘]
注意,这样的一个重构URL可能与客户端请求的URL不完全相同,服务器的重写规则,例如,可能已经修改了客户端的最初请求的URL,用一个规范的形式代替它。
支持Python老的版本
一些服务器,网关或应用程序可能希望支持老(<2.2)的Python版本。这一点尤其重要,如果Jython是一个目标平台,因为写这篇文章时,Jython2.2的一个生产版本还尚不可用。
对于服务器和网关,这是相对简单的:服务器和网关针对早于2.2版本的Python必须简单地限制自己只使用一个标准的“for”循环来遍历可迭代的应用程序返回。这是唯一的途径去确保与2.2以前的版本的迭代器协议(下文进一步讨论)和“今天”的迭代器协议都保持代码级兼容。
注意此方法一定只适用于服务器,网关,或用python写的中间件。讨论如何从其他语言使用迭代器协议 超出了本文讨论的范围。
对于应用程序,支持2.2以前版本的Python稍微复杂一些:
你可能不会返回一个文件对象,并期望它如一个迭代器一样工作,因为在2.2以前,文件对象是不可迭代的。(一般情况下,无论如何你都不应该这样做,因为大部分时间它会执行得相当糟糕!)使用wsgi.file_wrapper或应用程序特定的文件的包装类。(请参阅 “可选的特定于平台的文件处理”小节,关于更多的wsgi.file_wrapper介绍,以及你可以用来包装一个文件作为一个迭代器的示例类。)
如果返回一个自定义的迭代器,它必须实现pre-2.2的迭代器协议。也就是说,提供了一个接受一个整数键的__getitem__方法,并当迭代完时抛出一个IndexError异常。
(需要注意的是内置序列类型也是可以接受的,因为他们也实现了这一协议。)
最后,中间件希望支持pre-2.2版本的Python,并且迭代应用程序的返回值或本身返回一个可迭代(或两者都有),必须按照上面的适当的建议进行。
(注:应该不用说,支持预2.2版本Python的任何服务器,网关,应用程序或中间件也只能使用在目标版本中可用的语言功能,例如使用1和0而不是True和False,等等。)
可选的特定于平台的文件处理
一些平台环境提供特殊的高性能文件传输工具,例如如在Unix的sendfile()。服务器和网关可以通过在environ中的可选wsgi.file_wrapper键来暴露此功能。应用程序可以使用这个“file wrapper”去转换一个文件或类文件对象成一个可迭代的,然后返回,例如:
1 if ‘wsgi.file_wrapper‘ in environ: 2 return environ[‘wsgi.file_wrapper‘](filelike, block_size) 3 else: 4 return iter(lambda: filelike.read(block_size), ‘‘)
如果服务器或网关提供了wsgi.file_wrapper,它必须接受一个被要求的位置参数,和一个可选的参数。第一个参数是被发送类文件对象,第二个参数是可选的block size(其中服务器/网关不需要使用),必须返回一个可迭代的对象。而不能执行任何数据传输直到除非服务器/网关实际收到从应用程序返回的可迭代值。(如果不这样做会妨碍中间件解析或重写响应数据。)
由应用程序提供的对象必须有一个接受一个可选的size参数的read()方法,被认为是“类文件”。它可能有一个close()方法,如果是这样,通过wsgi.file_wrappe返回的迭代器必须有个close()方法,该方法调用原始类文件的close()方法。 如果“类文件”对象有任何其他方法或名称符合这些的Python内置的文件对象(例如的fileno())的属性,该wsgi.file_wrapper可以假设这些方法或属性与那些内置的文件对象具有相同的语义。
任何特定于平台的文件处理必须在应用程序返回后才实际执行,并且服务器或网关会检查是否有包装对象被返回。(再次,因为中间件,错误处理程序,等的存在,不能保证创建的任何包装器将被实际使用。)
除了close()的处理,应用程序返回的文件包装器的语义应该与应用程序如果已经返回的iter(filelike.read, ‘‘)相同。换句话说,传输应开始在“file”内部的当前位置,在传输开始的时刻,并继续下去,直到结束为止。
当然,特定于平台的文件传输的API通常不接受任意的“类文件”对象。因此,wsgi.file_wrapper应该具备内省所提供的对象的东西,例如fileno()(类Unix操作系统)或 java.nio.FileChannel (依赖于Jython),以确定 是否类文件对象适合它支持的特定于平台的API使用。
注意,即使对象不适合该平台的API,该wsgi.file_wrapper仍然必须返回一个封装的read()和close()的可迭代对象,因此,使用文件封装的应用是可跨平台移植的。下面是一个简单的与平台无关的文件的包装类。
1 class FileWrapper: 2 3 def __init__(self, filelike, blksize=8192): 4 self.filelike = filelike 5 self.blksize = blksize 6 if hasattr(filelike, ‘close‘): 7 self.close = filelike.close 8 9 def __getitem__(self, key): 10 data =http://www.mamicode.com/ self.filelike.read(self.blksize) 11 if data: 12 return data 13 raise IndexError
这里是从服务器/网关使用它来提供访问特定于平台的API的一个片段:
1 environ[‘wsgi.file_wrapper‘] = FileWrapper 2 result = application(environ, start_response) 3 4 try: 5 if isinstance(result, FileWrapper): 6 # check if result.filelike is usable w/platform-specific 7 # API, and if so, use that API to transmit the result. 8 # If not, fall through to normal iterable handling 9 # loop below. 10 11 for data in result: 12 # etc. 13 14 finally: 15 if hasattr(result, ‘close‘): 16 result.close()
问答
1.为什么environ变量一定要是一个字典?用一个子类会出现什么错误?
对于要求一个字典的解释是最大化服务器之间的可移植性。另一种方法是定义一个字典的方法的一些子集作为标准的和可移植的接口。然而在实践中,大多数服务器可能会发现字典就能够满足他们的需求,因而框架作者期望具有字典所有的功能,因为他们多半都是这样的。但是,如果某些服务器选择不使用字典,那么将会出现互操作性的问题,尽管有服务器的“一致性”去规范。因此,强制性使用一个字典简化了规范,以及保证了互操作性。
请注意,这并不妨碍服务器或框架开发人员提供专业化服务作为environ 字典内部的自定义变量,这是对于提供任何此类增值服务的推荐方法。
2.为什么可以调用write()和生成字符串或者返回一个迭代值?难道我们不应该只选择一种方式吗?
如果我们只支持迭代的方法,那么具备“push”可用性的框架将会受到影响。但是,如果我们只支持通过write()推送,那么服务器的性能会受到影响的,例如传输大文件(如果一个工作线程直到所有的输出已发送才能开始新的工作在一个新的请求上)因此,这种妥协允许一个应用程序框架支持这两种方法,视情况而定,但对于服务器的实现者也只多了一点负担,相比于只要求一种放方法。
3.close()是干什么的?
当写操作在应用程序对象执行过程中完成时,应用程序使用try/finally代码块可以确保资源被释放。但是,如果应用程序返回一个可迭代的,使用的资源直到迭代器被垃圾回收时才会被释放。这个close()允许应用程序在请求结束时释放关键资源,而且是向前兼容的,随着在PEP325提出的生成器对try/finally的支持。
4.为什么这个接口这样低级,我想要更多功能(例如 cookies, sessions, persistence,。)?
这还不是另一种Python的Web框架。这只是一种方式框架和服务器之间通话的方式,反之亦然。如果你想要这些功能,您需要选择一个提供了你想要的功能的Web框架。如果该框架可以让你创建一个WSGI应用程序,你应该能够在大多数支持WSGI的服务器上运行它。此外,一些WSGI服务器可以通过在他们的environ 词典中提供的对象来提供额外的服务,详情请参见适用的服务器文档。当然,使用这种扩展的应用程序将无法移植到其它WSGI架构的服务器。
5.为什么用CGI变量,而不是直接用HTTP 头字段?为什么WSGI定义的变量要混合它们?
许多现有的Web框架很大程度上是建立在CGI的规范上的,而现有的Web服务器知道如何生成的CGI变量。相比之下,另外的表示HTTP信息的方式不常见而且缺乏市场份额,而使用CGI的“标准”似乎是利用现有实现的好方法。至于混合在它们里面的的WSGI变量,分开它们只需要分开传送两个字典参数,同时传递也不会有影响。
6.状态字符串是什么样的?为什么不能只用数字,例如传递‘200’,而不是“200 OK”
这样做将复杂化服务器或网关,通过要求他们有一个数字状态和相应信息的表。与此相反,一个应用程序或框架的作者很容易键入额外的文本到他们正在使用的特定的响应码中,而存在的框架已经包含了一个所需要信息的表。所以,总的来说,让应用程序/框架负责似乎更好,而不是服务器或网关。
7.为什么wsgi.run_once不能保证应用程序只有运行一次?
因为它只是给应用程序的一个建议,它应该很少运行。这是对那些有多种模式操作cache,session等等的应用框架的一个打算。在“multiple run”模式中,这种框架可以预加载缓存,并且在每个请求之后,可以不写诸如日志或会话类的数据到磁盘中。
然而,为了测试应用程序或框架,以验证在后一种模式下的正确操作。因此,应用程序不应该假设它肯定不会再次运行,只是因为当wsgi.run_once设置为true时它会被调用。
8.X功能(字典,可调用等)的使用在应用程序代码中是丑陋的;我们为什么不使用对象呢?
所有这些WSGI的实现的选择,是专门为了彼此分离功能;重新组合这些功能集成到封装的对象中,这样使得它有点难以编写服务器或网关,而写中间件只需要替换或修改全部功能的一小部分,这只是一个数量级的难度。
从本质上讲,中间件希望有一个“责任链”模式,即它可以充当一个“处理程序”的一些功能,同时允许其他人保持不变。如果接口是保持可扩展性,用普通的Python对象,这是很难做到。例如,一个必须重写__ getattr__或__ getattribute__,以确保扩展名(如由未来的WSGI版本中定义的属性)被通过。
这种类型的代码是非常困难的去达到100%正确,很少有人会想要写他们自己的对象。因此,他们会复制别人的实现,但当别人已经纠正了一处地方的情况下,会更新失败。
此外,this necessary boilerplate would be pure excise,中间件开发商会为应用框架开发者支持稍微更好的API。但是,应用程序框架开发人员通常只更新一个支持WSGI的框架,并当作其框架一个非常有限的一部分。这可能会是他们第一次(也许是唯一的)对WSGI实现,因此他们以本规范去着手实施。因此,用对象的属性之类的使API更漂亮 ,用户会觉得没必要。
我们鼓励那些想要一个更漂亮(或以其他方式改善)的WSGI接口的人,对于直接使用web应用程序(而不是Web框架开发),去开发包装了WSGI的API或框架,以方便应用程序开发者使用。用这种方式,WSGI对于服务器和中间件的作者可以方便地保持低级别,尽管对于应用程序开发者是丑陋的。
计划/正在讨论中的
These items are currently being discussed on the Web-SIG and elsewhere, or are on the PEP author‘s "to-do" list:
- Should wsgi.input be an iterator instead of a file? This would help for asynchronous applications and chunked-encoding input streams.
- Optional extensions are being discussed for pausing iteration of an application‘s output until input is available or until a callback occurs.
- Add a section about synchronous vs. asynchronous apps and servers, the relevant threading models, and issues/design goals in these areas.
致谢
。。。。
参考文献
[1] The Python Wiki "Web Programming" topic (http://www.python.org/cgi-bin/moinmoin/WebProgramming)
[2] The Common Gateway Interface Specification, v 1.1, 3rd Draft (http://ken.coar.org/cgi/draft-coar-cgi-v11-03.txt)
[3] "Chunked Transfer Coding" -- HTTP/1.1, section 3.6.1 (http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6.1)
[4] "End-to-end and Hop-by-hop Headers" -- HTTP/1.1, Section 13.5.1 (http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.5.1)
[5] mod_ssl Reference, "Environment Variables" (http://www.modssl.org/docs/2.8/ssl_reference.html#ToC25)