欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

python wsgi PEP333 中文翻译

程序员文章站 2022-11-27 14:27:53
preface 前言 ------------------------------------------------------------------------------...
preface 前言

----------------------------------------------------------------------------------

这个版本更新的版本支持python 3.x以及一些其他的勘误说明等。请参阅pep 3333. 

 

 

 

abstract 简介

----------------------------------------------------------------------------------

 

本文档描述一份在web服务器与web应用/web框架之间的标准接口,此接口的目的是使得web应用在不同web服务器之间具有可移植性。

 

 

 

rationale and goals 基本原理与目标

----------------------------------------------------------------------------------

 

python目前拥有大量的web框架,比如 zope, quixote, webware, skunkweb, pso, 和twisted web。大量的选择使得新手无所适从,因为总得来说,框架的选择都会限制web服务器的选择。

 

对比之下,虽然java也拥有许多web框架,但是java的" servlet" api使得使用任何框架编写出来的应用程序可以在任何支持" servlet" api的web服务器上运行。服务器中这种针对python的api(不管服务器是用python写的,还是内嵌python,还是通过一种协议来启动python)的使用和普及,将分离人们对web框架和对web服务器的选择,用户可以*选择适合他们的组合,而web服务器和web框架的开发者也能够把精力集中到各自的领域。

 

因此,这份pep建议在web服务器和web应用/web框架之间建立一种简单的通用的接口规范,python web server gateway interface (wsgi).

 

但是光有这么一份规范对于改变web服务器和web应用/框架的现状是不够的,只有web服务器和web框架的作者们实现wsgi,他才能起应有的效果。

 

然而,既然还没有任何框架或服务器实现了wsgi,对实现wsgi也没有什么直接的奖励,那么wsgi必须容易实现,这样才能降低作者的初始投资。

 

服务器和框架两边接口的实现的简单性,对于wsgi的作用来说,绝对是非常重要的。所以这一点是任何设计决策的首要依据。

 

对于框架作者来说,实现的简单和使用的方便是不一样的。wsgi为框架作者展示一套绝对没有"frills"的接口,因为象response对象和对cookie的处理这些问题和框架现有的对这些问题的处理是矛盾的。再次重申一遍,wsgi的目的是使得web框架和web服务器之间轻松互连,而不是创建一套新的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服务器和应用框架的部署标准。

 

specification overview  概述

----------------------------------------------------------------------------------

wsgi接口有两种形式:一个是针对服务器或gateway的,另一个针对应用程序或框架。服务器接口请求一个由应用接口提供的可调用的对象,至于该对象是如何被请求的取决与服务器或gateway。我们假定一些服务器或gateway会需要应用程序的部署人员编写一个简短的脚本来启动一个服务器或gateway的实例,并把应用程序对象提供得服务器,而其他的服务器或gateway需要配置文件或其他机制来指定从哪里导入或者或得应用程序对象。

 

除了纯粹的服务器/gateway和应用程序/框架,还可以创建实现了这份规格说明书的中间件组件,对于包含他们的服务器他们是应用程序,而对于他们包含的应用程序来说他们是服务器,他们可以用来提供可扩展的api,内容转换,导航和其他有用的功能。

 

在整个规格说明书中,我们使用短语"一个可调用者"意思是"一个函数,方法,类,或者拥有 __call__ 方法的一个对象实例",这取决与服务器,gateway,应用程序根据需要而选择的合适实现方式。相反服务器,gateway和请求一个可调用者的应用程序不可以依赖具体的实现方式,not introspected upon.

the application/framework side 应用程序/框架 端

----------------------------------------------------------------------------------

一个应用程序对象是一个简单的接受两个参数的可调用对象,这里的对象并不是真的需要一个对象实例,一个函数、方法、类、或者带有 __call__ 方法的对象实例都可以用来做应用程序对象。应用程序对象必须可以多次被请求,实际上服务器/gateway(而非cgi)确实会产生这样的重复请求。

 

(注意:虽然我们把他叫做"应用程序"对象,但并不是说程序员要把wsgi当做api来调用,我们假定应用者仍然使用更高层面上的框架服务来开发应用程序,wsgi是提供给框架和服务器开发者使用的工具,并不打算直接对应用程序开发者提供支持)

 

这里有两个应用程序对象的示例,一个是函数,另一个是类:

复制代码

 1 def simple_app(environ, start_response):

 2     """simplest possible application object"""

 3     status = '200 ok'

 4     response_headers = [('content-type', 'text/plain')]

 5     start_response(status, response_headers)

 6     return ['hello world!\n']

 7 

 8 

 9 class appclass:

10     """produce the same output, but using a class

11 

12     (note: 'appclass' is the "application" here, so calling it

13     returns an instance of 'appclass', which is then the iterable

14     return value of the "application callable" as required by

15     the spec.

16 

17     if we wanted to use *instances* of 'appclass' as application

18     objects instead, we would have to implement a '__call__'

19     method, which would be invoked to execute the application,

20     and we would need to create an instance for use by the

21     server or gateway.

22     """

23 

24     def __init__(self, environ, start_response):

25         self.environ = environ

26         self.start = start_response

27 

28     def __iter__(self):

29         status = '200 ok'

30         response_headers = [('content-type', 'text/plain')]

31         self.start(status, response_headers)

32         yield "hello world!\n"

复制代码

the server/gateway side  服务器/网关 接口

----------------------------------------------------------------------------------

 

服务器/网关 为每一个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

54                 write(data)

55         if not headers_sent:

56             write('')   # send headers now if body was empty

57     finally:

58         if hasattr(result, 'close'):

59             result.close()

复制代码

middleware: components that play both sides  中间件 : 同时扮演两种角色的组件

注意到单个对象可以作为请求应用程序的服务器存在,也可以作为被服务器调用的应用程序存在。这样的中间件可以执行这样一些功能:

 

    重写前面提到的 environ 之后,可以根据目标url将请求传递到不同的应用程序对象

    允许多个应用程序和框架在同一个进程中运行

    通过在网络传递请求和响应,实现负载均衡和远程处理

    对内容进行后加工,比如附加xsl样式表

 

中间件的存在对于服务器接口和应用接口来说都应该是透明的,并且不需要特别的支持。希望在应用程序中加入中间件的用户只需简单得把中间件当作应用提供给服务器,并配置中间件足见以服务器的身份来请求应用程序。

 

当然,中间件组件包裹的可能是包裹应用程序的另一个中间件组件,这样循环下去就构成了我们称为"中间件堆栈"的东西了。for the most part,中间件要符合应用接口和服务器接口提出的一些限制和要求,有些时候这样的限制甚至比纯粹的服务器或应用程序还要严格,这些地方我们会特别指出。

 

这里有一个中间件组件的例子,他用joe strout的piglatin.py将text/plain的响应转换成pig latin(注意:真正的中间件应该使用更加安全的方式——应该检查内容的类型和内容的编码,同样这个简单的例子还忽略了一个单词might be split across a block boundary的可能性)。

复制代码

 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 

27 class 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 == '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))

复制代码

specification details 规格的详细说明

----------------------------------------------------------------------------------

应用程序对象必须接受两个参数,为了方便说明我们不妨分别命名为 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 参数是元组(header_name,header_value)的列表,描述http响应头。可选的 exc_info 参数会在下面的 `the start_response() callable`_ 和 error handling 两节中描述,他只有在应用程序产生了错误并希望在上显示错误的时候才有用。

 

start_response 可调用者必须返回一个 write(body_data) 可调用者,他接受一个可选参数:一个将要被做为http响应体的一部分输出的字符串(注意:提供可调用者 write() 只是为了支持现有框架的必要的输出api,新的应用程序或框架尽量避免使用,详细情况请看 buffering and streaming 一节。)

 

当被服务器请求的时候,应用程序对象必须返回一个0或多个可迭代的字符串,这可以通过多种方法完成,比如返回一个字符串的列表,或者应用程序本身是一个生产字符串的函数,或者应用程序是一个类而他的实例是可迭代的,不管怎么完成,应用程序对象必须总是返回0或多个可迭代的字符串。

 

服务器必须将产生的字符串以一种无缓冲的方式传送到客户端,每次传完一个字符串再去获取下一个。(换句话说,应用程序应该实现自己的缓冲,更多关于应用程序输出必须如何处理的细节请下面的 buffering and streaming 节。)

 

服务器或gateway应该把产生的字符串当字节流对待:特别地,他必须保证没修改行的结尾。应用程序负责确保字符串是以与客户端匹配的编码输出(服务器/gateway可能会附加http传送编码,或者为了实现一些http的特性而进行一些转换比如byte-range transmission,更多细节请看下面的 other http features )

 

如果调 len(iterable) 成功,服务器将认为结果是正确的。也就是说,应用程序返回的可迭代的字符串提供了一个有用 的__len__() 方法,么肯定返回了正确的结果(关于这个方法正常情况下如何被使用的请阅读 handling the content-length header )

 

如果应用程序返回的可迭代者有close()方法,则不管该请求是正常结束还是由于错误而终止,服务器/gateway都**必须**在结束该请求之前调用这个方法,(这是用来支持应用程序对资源的释放,this protocol is intended to complement pep 325's generator support, and other common iterables with close() methods.)

 

(注意:应用程序必须在可迭代者产生第一个字符串之间请求 start_response() 可调用者,这样服务器才能在发送任何主体内容之前发送响应头,然而这一步也可以在可迭代者第一次迭代的时候执行,所以服务器不能假定开始迭代之前 start_response() 已经被调用过了)

 

最后,服务器或gateway不能应用程序返回的可迭代者的任何其他属性,除非是针对服务器或gateway特定类型的实例,比如wsgi.file_wrapper返回的“file wrapper”(阅读 optional platform-specific file handling )。通常情况下,只有在这里指定的属性,或者通过pep 234 iteration apis才是可以访问的。

 

environ variables environ变量

environ 字典被用来包含这些在common gateway interface specification [2]_中定义了的cgi环境变量。下面这些变量 必须 呈现出来, 除非其值是空字符串,这种情况下如果下面没有特别指出的话他们 可能 会被忽略

 

request_method

    http请求的方式, 比如 "get" 或者 "post". 这个不可能是空字符串并且也是必须给出的。

script_name

    请求url中路径的开始部分,对应应用程序对象,这样应用程序就知道它的虚拟位置。如果该应用程序对应服务器的 根 的话, 它 可能 是为空字符串。

path_info

    请求url中路径的剩余部分,指定请求的目标在应用程序内部的虚拟位置。如果请求的目标是应用程序跟并且没有trailing slash的话,可能为空字符串 。

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 。 server_name 和 server_port 永远是空字符串,也总是必须存在的。

server_protocol

    客户端发送请求所使用协议的版本。通常是类似 "http/1.0" 或 "http/1.1" 的东西可以被用来判断如何处理请求headers。(既然这个变量表示的是请求中使用的协议,而且和服务器响应时使用的协议无关,也许它应该被叫做 request_protocol 。然后,为了保持和cgi的兼容性,我们还是使用已有的名字。)

http_ 变量

    对应客户端提供的http请求headers (也就是说名字以 "http_" 开头的变量)。这些变量的存在与否应该和请求中的合适的http header一致。

 

服务器或gateway 应该 尽可能提供其他可用的cgi变量。另外,如果用了ssl,服务器或gateway也 应该 尽可能提供可用的apache ssl环境变量 [5] ,比如 https=on 和ssl_protocol``。不过要注意,使用了任何上面没有列出的变量的应用程序对不支持相关扩展的服务器来说就有点necessarily non-portable。(比如,不发布文件的web服务器就不能提供一个有意义的 ``document_root 或 path_translated 。)

 

一个支持wsgi的服务器或gateway 应该 在描述它们自己的同时说明它们可以提供些什么变量应用程序 应该 对所有他们需要的变量的存在性进行检查,并且在某变量不存在的时候有备用的措施

 

注意: 不需要的变量 (比如在不需要验证的情况下的 remote_user ) 应该被移出 environ字典。同样注意cgi定义的变量如果存在的话必须是字符串。任何 str 类型以外的cgi变量的存在都是对本规范的违反

 

除了cgi定义的变量, environ 字典也可以包含任意操作的环境变量,并且 必须包含下面这些wsgi定义的变量:

 

 

 

 

variable value

wsgi.version the tuple (1, 0), representing wsgi version 1.0.

wsgi.url_scheme a string representing the "scheme" portion of the url at which the application is being invoked. normally, this will have the value "http" or "https", as appropriate.

wsgi.input an input stream (file-like object) from which the http request body can be read. (the server or gateway may perform reads on-demand as requested by the application, or it may pre- read the client's request body and buffer it in-memory or on disk, or use any other technique for providing such an input stream, according to its preference.)

wsgi.errors

an output stream (file-like object) to which error output can be written, for the purpose of recording program or other errors in a standardized and possibly centralized location. this should be a "text mode" stream; i.e., applications should use "\n" as a line ending, and assume that it will be converted to the correct line ending by the server/gateway.

 

for many servers, wsgi.errors will be the server's main error log. alternatively, this may be sys.stderr, or a log file of some sort. the server's documentation should include an explanation of how to configure this or where to find the recorded output. a server or gateway may supply different error streams to different applications, if this is desired.

 

wsgi.multithread this value should evaluate true if the application object may be simultaneously invoked by another thread in the same process, and should evaluate false otherwise.

wsgi.multiprocess this value should evaluate true if an equivalent application object may be simultaneously invoked by another process, and should evaluate false otherwise.

wsgi.run_once this value should evaluate true if the server or gateway expects (but does not guarantee!) that the application will only be invoked this one time during the life of its containing process. normally, this will only be true for a gateway based on cgi (or something similar).

最后 environ 字典也可以包含服务器定义的变量。这些变量的名字必须是小写字母、数字、点和下划线,并且应该带一个能唯一代表服务器或gateway的前缀。比如, mod_python 可能会定义象这样的一些变量:mod_python.some_variable.

 

input and error streams 输入和错误流

服务器提供的输入输出流必须提供以下的方法

 

method stream notes

read(size) input 1

readline() input 1, 2

readlines(hint) input 1, 3

__iter__() input

flush() errors 4

write(str) errors

writelines(seq) errors

 

 

 

 

 

 

 

 

 

 

 

 

以上方法语义同python library reference相同,处理下面列表指出的注意点之外。

 

1.服务器不一定需要读取客户端指定的content-length, 它可以模拟一个流结束(end-of-file)的条件,如果应用程序

试图去读取它的话。另外应用程序不应该去尝试读取比指定content-length长度更多的数据。

2.可选参数size是不支持用于readline()方法中的。因为它有可能是服务器程序作者复杂的实现,而且它不怎么用的上。

3.注意这个用户readlines(hint)中的参数hint,对于调用方和实现方都是可选的。

应用程序可以*选择是否支持,服务器也可以*选择是否忽略它。

4.由于错误流可能不能回转(rewound),服务器和网关可以立即*的不需要缓存地向前写(forward write)。在这种情况下

flush()方法可能是个空操作(no-op)。然后可移植的程序不能假定这个output是无缓冲的或者flush是空操作。

在需要确保数据流被真正写入的情况下,他们大多都会调用flush()。

(例如:从多个进程中写入同一个日志文件的时候,可以做到最小化的数据交织)

 

上表列出的方法,每个服务器都必须符合此规范。应用程序方也需符合此规范,不要用其他的其他不同方法或属性的input 和 errors对象。

尤其,应用程序端不要试图去关闭这些流,尽管他们有close()方法。

the start_response() callable 

传递给应用程序的第二个参数是一个可调用的形式,start_response(status, reponse_headers, exc_info=none).(同所有的wsgi调用,参数位置必须是

通过位置对应,而不是用key来对应)。

start_response调用被用来开始http相应,并且必须返回一个 write(body_data) callable(参考下面的buffering and streaming段)

 

此status参数是http的'status'字符,如"200 ok", "404 not found".也就是说,它是一个字符串组成的一个状态码和一个原因短语,按这样的顺序并用个空格分隔。

两头不包含其他的字符或空格。(见rfc2616, 6.1.1段获取更多信息),字符串不能包含控制字符,不能有终止符或换行符等其他组合结束的符号。

 

response_headers参数是个tuples(header_name, header_value)的列表list,必须是个python的list,即type(response_headers) is listtype.

并且服务器可以随意更改其中的内容如果它需要的话,所有的header_name必须是合法的http header field-name(参见rfc2616 4.2段),没有冒号或其他标点。

所有的header_value不能包含任何控制字符,包括回车和换行。(这样的要求是为了方便那些必须检查相应头的服务器,网关,中间件,使他们所必须的解析工作复杂度降到最低)

 

一般来说,服务器或网关负责确保正确的头信息发送到客户端,如果应用程序(application)遗漏了必要的头信息(或其他相关的规范信息),服务器或网关须补上。

比如:http date:和server:头信息通常是由服务器或网关提供。

(一个要提醒给服务器/网关作者的事情: http 头名称是区分大小写的,所以在检查application提供的头信息时一定要考虑大小写的问题)

 

应用程序和中间件禁止使用http/1.1的'hop-by-hop'特性或头信息,任何在http/1.0中等价或类似的特性或头信息,都会影响到客户端和服务器的持久的连接。

这特性专属的目前的服务器,服务器/网关须要考虑考虑这一个致命错误,一个应用程序尝试发送它们,并抛出一个错误,如果他们是提供给start_response()。

(为了解更多'hop-by-hop'的细节和特性,请参阅下面的other http features段)。

 

start_response回调必须不实际地传输response头,代替的,它用来存贮头信息供服务器/网关传输,只有在应用程序第一次返回后第一次迭代的时候返回一个非空字符串,

或者在应用程序的一个调用write()回调的时候。换句话说,response头不能被发送在没有实际的body数据前,或者是应用程序返回的迭代器终止前,(唯一的例外就是response头信息里明确包含了content-length为0)

这样延迟的头信息传输是为了确保有缓存或异步的应用程序能用出错信息替换掉原来可能要发送的数据,直到最后一刻。例如应用程序可能会替换到头状态'200 ok'为'500 internal error', 如果当body数据是有应用程序缓存构成的但发送了错误。

 

exc_info参数是可选的,但如果被提供,必须是python sys.exc_info()tuple.此参数只有在start_response请求错误handler才要求被提供。如果exc_info提供了,并且还没有任何http header被输出,start_response应该替换the currently-stored http response headers with the newly-supplied ones,应此允许应用程序在有错误发生的情况下能够改变output。

然而,如果exc_info被提供,并且http头已经被发送,start_response必须抛出错误,并且抛出exc_info tuple,也就是下面的

raise exc_info[0], exc_info[1], exc_info[2]

抛出的异常会被应用程序重新捕获到,原则上应到终止应用程序。(应用程序会尝试发送error output到浏览器,一旦http headers被发送,这样是不安全的)应用程序必须不捕获任何来自于start_response调用的异常,如果调用start_response用来exc_info参数,替代的做法应该允许这样的异常传回给服务器/网关。更多信息见下面的error handing。

 

应用程序可能多次调用start_response当且仅当exc_info参数提供的时候。更确切的说,如果start_response已经被当且应用程序调用过后,再次调用没有exc_info参数的start_response是个很致命的错误。(参考上面cgi网关示例,其中说明了正确的逻辑)

注意:服务器/网关/中间件实现start_response应当确保exc_info没有持续指向任何引用,当start_response方法调用完成之后。为了避免通过traceback和frames involved创建了回环的引用,最简单的例子如下。

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网关例子提供了这种技术的另一个说明。

 

 

handling the content-length header 处理content-length头信息

如果应用程序没有提供content-length头,服务器/网关可以选择几种方式之一来处理它,最简单的方式便是当response完成的时候关闭客户端连接。

然而在某些情况下,服务器/网关之一可能会生成content-length头,或者至少避免了要关闭客户端连接,如果应用程序没有调用write(),并返回一个len()是1的iterable,

那么服务器便可以自动的确定content-length长度,通过第一个iterable出来的字符串。

如果服务器和客户段都支持http/1.1 "chunked encoding",那么服务器可能在调用write()或迭代字符串的时候使用chunked encoding来发送块数据,因此会为每个chunk块数据生成content-length。这允许服务器保持客户端长连接如果需要的话,注意如果真要这么做的话,服务器必须完成符号rfc2616规范,否则便回到了另外的策略上来处理content-length的缺失。

 

(注意:应用程序必须不能提供适合任何类型的transfer-encoding输出,像chunking or gzipping,as "hop-by-hop"操作,这些编码都是实际的服务器/网关的职权。详细信息参见下面的other http features段)

buffering and streaming  缓冲和流

一般而言,应用程序都会通过缓存output并且一次性发送来提高吞吐量。现有的zope框架就有常见的处理方法:它通过缓存output到stringio或类似的对象里面,并且顺着response相应头一次性发送.

在wsgi中相应的处理方法是应用程序简单地返回一个单元素迭代(single-element iterable)如列表list,其中包含单一的response body字符串。这是对于绝大多数应用程序都推荐的方案,渲染html页面的文本很容易停留在内存中。

然后对于大文件或专门用途的http流媒体(如“服务器推送”),应用程序或许需要提供小块状的输出(比如为了避免加载一个大的文件到内存中),有些时候部分的response可能需要花费多的时间来生成,但是再它之前发送一部分的数据还是很有用的。

这种情况下,应用程序通常会返回一个可迭代的(通常是迭代生成器),使生成的输出是一块一块的。这些块大小可能会超过最大的边界(for 'server push'),或者在某个耗时的任务之前(比如在磁盘中读取其他块)。

wsgi服务器/网关和中间件不能延迟传输任何块。他们要么完全将所有的块都传输给客户端,或者保证他们会继续传输即使应用程序正在生成下一个块。服务器/网关或中间件可能会提供以下三种方案中的一种。

1.发送整个块到操作系统(要求任何的o/s缓存被刷新)在返回控制之前给应用程序

2.使用不同的线程确保块被继续传输,而应用程序生成下一个块。

3.(仅中间件)发送整个块到它的父服务器/网关

 

通过提供这样的保证措施,wsgi就可以允许应用程序确保在他们输出数据的任意点上不会变得停滞。这对正常允许是至关重要的,例如多媒体服务器推送流,多个边界间的数据应该被完整的传输到客户端。

middleware handling of block boundaries 中间件处理块边界

 

为了更好地支持异步应用程序和服务器,中间件必须不是块迭代从应用程序迭代中等待多个值。如果中间件需要在自己能生成输出流之前积攒从应用程序中的值,那么它必须yield一个空字符串。

或者达到这种需求的其他方式,一个中间件必须每次在它底层应用程序生成一个值的时候至少生成一个值,如果中间件不能成才其他值那么它必须生成一个空字符串。

这样的要求确保了异步的服务器和应用程序能共同协作,在需要同时提供多个应用程序实例的时候减少线程的数量。

注意,这样的要求意味着中间件必须返回一个iterable一旦其底层应用程序返回一个iterable。它也不允许中间件调用write()方法来发送数据。中间件仅可能用它父服务器的write()方法来传输数据,它底层的应用程序利用中间件提供的write()发送数据。

the write() callable 

一些现有框架apis与wsgi的一个不同处理方式是他们支持无缓存的输出,特别指出的是,他们提供一个write函数或方法来写一个无缓冲的块或数据,或者他们提供一个缓冲的write函数和一个flush机制来flush缓冲。

不幸的是,这样的apis无法实现像wsgi这样应用程序可迭代返回值,除非使用多线程或其他的机制。

因此为了允许这些框架继续使用这些必要的api,wsgi包含一个特殊的write()调用,由start_response调用返回。

新的wsgi应用程序和框架不应该使用write()调用如果可以避免这样做。这个write()调用是完全hack用来支持必要的流能力的apis。一般来说,应用程序应该通过返回的iterable来生成输出流,因为这样可以让web服务器交错在同一个python的其他线程上,总的来说可以为服务器提供更大的吞吐量。

这个write()调用是由start_response调用返回的,并且它接受一个单一的参数:一个将用来写入到http 一部分response体中的字符串,它被看作是已经被迭代生成后的结果。换句话说,在writer()返回前,它必须保证传入的字符串要么是完全发送给客户机,或者已经缓冲了应用程序要向前的传输。(or that it is buffered for transmission while the application proceeds onward)

一个应用程序必须返回一个可迭代的对象,即使它使用write()来生成全部或部分response body。返回的可迭代可以是空的(例如一个空字符串),但是如果它不生成空字符串,那么output必须被处理,通常是由服务器/网关来做(例如它必须立即发送或加入队列中)。应用程序必须不在他们返回的iterable内调用write()。因此所有由iterable生成的字符串会在传递给write()之后发送到客户端。

 

unicode issues  unicode问题

 

 

http不直接支持unicode,并且也没有这样的接口。所有的编码解码必须由应用程序来处理,所有传递给服务器的都必须是python标准的字节字符串而不是unicode对象。其结果使用unicode对象,一个字符串对象是必需的,是未定义的。

 

 

注意 所有的作为response头的字符传递到start_response必须遵循rfc2616的编码,也就是它们必须是iso-8859-1字符,或者使用rfc 2047 mime编码。

 

 

python平台的str或stringtype实际上是基于unicode(如jython,ironpython, python 3000等),所有的字符串都只包含iso-8859-1编码规范中可表示的代码点(从\u0000-\u00ff)。如果应用程序提供的字符串包含任何unicode字符或编码点,这将是个致命的错误。同样地,服务器/网关也必须不提供任何unicode字符到应用程序。

 

 

再次声明,本规定所有的字符串都必须是str或stringtype,并且不能是unicode或unicodetype。尽管一个开发的平台允许超过8位字节的字符对象,也仅仅是低8位的字节被用到,对于本规范所指定的字符串来说。

error handling  错误处理

一般来说,应用程序应到捕获自己的内部错误,并显示有用的信息到浏览器上。(由浏览器来决定哪些信息是有用的)

然后要显示这些信息,应用程序不能实际地发送任何数据到浏览器上,否则会使response恶化。因此wsgi提供了一种机制,要么允许应用程序发送它自己的错误信息,要不自动终止:这个exc_info参数到start_response。这里有个如何使用的例子。

复制代码

 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将正常返回,并且应用程序返回的包含错误信息的body将被发送到浏览器。然而如果有任何的output被发送到浏览器,start_response会重新抛出提供的异常。此异常不应该被应用程序捕获,并且应用程序会终止。服务器/网关会捕获异常这个致命的异常并终止响应。

服务器应该捕获任何异常并记录日志当终止应用程序或反复迭代他们的返回值。如果当应用程序出错的时候已经有一部分response被发送到浏览器了,服务器或网关可以试图添加一个错误消息到output,如果已经发送了指明text/*的头信息,那么服务器就知道如何修改清理。

一些中间件或许希望提供额外的异常处理服务,或拦截并替换应用程序的错误信息。在此情况下,中间件可能选择不再重新抛出提供的exc_info到start_response,但换作抛出中间件特制的错误,或者在存贮了提供的参数后简单地返回且不包含错误。这将导致应用程序返回错误的body iterable(或调用write()).允许中间件来拦截并修改错误流,这些技术只会工作只要应用程序作者做到:

1.总是提供exc_info当开始一个错误的响应的时候。

2.当exc_info已经提供的情况下不要捕获来自start_response的异常。

http 1.1 expect/continue  http1.1的长连接

实现了http1.1的服务器/网关必须提供对http1.1中"expect/continue"(长连接)的透明支持。可以用下面的几种方式完成。

1.用一个立即执行的"100 continue"response相应一个包含expect: 100-continue的请求,并且正常处理

2.正常处理请求,但提供给应用程序一个wsgi.input流,当应用程序首先尝试从输入流中读取的时候发送一个"100 continue"响应。这个读取必须保持阻塞至到客户端的响应。

3.等待客户端判断服务器不支持expect/continue以及客户端自身发来的request body。(不好的方式,不推荐)

注意这些行为的限制不适用于htttp 1.o,或请求不是针对特定的应用程序对象。更多http 1.1 except/continue的信息,请参阅rfc 2616的8.2.3段和10.1.1段。

 

other http features 其他的http特性

通常来说,应用程序对其的output有完全的控制权,服务器/网关对此因不理不问,他们只做一些不影响应用程序响应语义的修改。往往很可能的是为应用程序开发者提供中间件来支持一些特性,所以服务器/网关开发者在他们实现的时候应当偏保守。在某种意义上,一个服务器应当使自己更像是一个http网关服务器,配合一个应用程序来作为一个http "origin server"(这些定义参加rfc 2616 1.3段)

 

然后由于wsgi服务器和应用程序不通过http通信,rfc 2616提到的"hop-by-hop"在wsgi内部通信中不适用。wsgi应用程序必须不生成任何"hop-by-hop"头信息,试图使用http的特性需要他们生成这样的头,或者依赖任何传入的"hop-by-hop"头内容在environ字典中。wsgi服务器必须自己处理掉任何被支持入站的"hop-by-hop"头信息,像such as by decoding any inbound transfer-encoding, including chunked encoding if applicable。

 

将这些原则应用到各种各样的http特性中去,应当清楚一个服务器可能需要处理缓存验证这些if-none-match和if-modified-sine的请求头,和一些last-modified和etag响应头。然而它并不是必须要这样做,并且一个应用程序应到处理自己的缓存验证等,如果它自身相提供这样的特性,然后服务器/网关就不需要做这样的验证。

 

同样地,服务器可能会重新编码或传输编码一个应用程序的响应,但是应用程序应当对自己发送的内容做适当的编码,并且不提供transport encoding。如果客户端需要服务器可能以字节码的方式传输应用程序的响应,并且应用程序本身是不支持字节码方式。再次申明,如果需要,应用程序应当自己执行相应的方法。

 

注意,这些在应用程序上的限制不是要求应用程序为每个http特性重新实现一次,许多http特性可以完全或部分地由中间件来实现,这样便可以让服务器和应用程序作者在一遍又一遍的实现这些特性中解放出来。

thread support  线程支持

线程的支持或缺乏,也是服务器依赖。服务器可以同时运行多个请求,也应当提供让应用程序运行在单一线程上的选项,因此让一些不是线程安全的应用程序或框架仍旧可以在这些服务器上使用。

 

implementation/application notes  实现/应用 事项

server extension apis  服务扩展api

有些服务器作者可能希望揭示更多的高级api,应用程序和框架作者便可以专门的使用。例如一个以mod_python为基础的网关可能就希望揭示一些apache的api作为wsgi的扩展。

在最简单的情况下,它只需要定义一个environ变量,类似mod_python.some_api。但是,更多情况下,可能存在的中间件会使此变得更困难。例如,一个api提供的在environ变量中访问的http 头,可能会被中间件修改掉而返回不一样的值。

 

通常,任何扩展的api,重复,取代或者绕过部分wsgi的功能与中间件会有不兼容的风险。服务器/网关开发者不应当假设没人会使用中间件,因为一些框架作者专门的打算重新改写或组织他们的框架代码,使之几乎就像是各种各样的中间件。

 

所以,为了提供最大的兼容性,服务器/网关提供一些扩展的api,取代一些wsgi的功能,必须设计这些api以便他们使用部分替换过的api调用。例如:一个允许访问http请求头的扩展api需要求应用程序通过当前的environ,所以服务器/网关可能会验证那些可被访问的http头,保证没有被中间件修改过。如果这扩展api不能保证,它总是evniron和http头信息里面的内容,它必须拒绝向应用程序提供服务。例如,通过抛出一个错误,返回none代替头信息的收集等等,是适合api的。

 

同样地,如果扩展api提供交替的方法来写response数据或是头信息,它应当要求start_response调用在应用程序能获得的扩展的服务之前已被传入。如果传入的对象和最开始服务器/网关提供给应用程序的不一样,它不能保证正确的操作并且必须拒绝给应用程序提供扩展的服务。

 

这些指导方针同样适用于中间件,添加类似解析过的cookies信息,表单变量,sessions,或者像evniron。特别地,这样的中间件应当使提供这些的特性类似一个在environ里操作的函数,而不是简单的想evniron里面添加些东西。这些有助于保证来自evniron里面的信息计算是在任何中间件对url重写过后或者是其他evniron变量的修改之后。

 

或许以后一些中间件作者为了保证应用程序在使用他们的中间件过程中不被跳过,他们不得不从environ中删除全部或部分的扩展api. 为了避免这些,这些安全的规则对所有的服务器/网关和中间件作者来说都是非常重要的。

application configuration  应用程序结构配置

这些规格说明没有定义服务器如何选择如何得到一个应用程序来调用。这些和其他一些配置选项都是高度地服务于服务器的。这些期望服务器/网关作者能讲如何配置选项(如线程的选项),如何配置执行一个特定的应用程序对象等文档化。

 

另一方面,框架作者应当将如何创建一个被框架功能包装过的应用程序对象文档化。用户可以选择服务器和应用程序框架,但必须将两者连接在一起。然而,因为现在有了两者共同的接口,这应该只是一个机械式的问题,而不是为了将新的应用程序或服务器配对组合的重大工程了。

 

最后,一些应用程序,框架,中间件可能希望使用evniron字典来接受一些简单的字符串配置选项。服务器/网关应当通过允许应用程序部署者向evniron里面指定特殊的名值对来支持这些。在最简单的情况下,这些支持可以仅仅由拷贝操作系统提供的环境变量os.environ到environ字典中,那么部署者原则上可以配置这些外部的信息到服务器上,或者在cgi的情况下他们可能是通过服务器的配置文件来设置。

 

应用程序应该保持这些变量的依赖到最小程度,并不是所有的服务器都支持简单地配置它们。当然,即使在最槽糕的情况下,部署一个应用程序可以创建一个脚本来提供一些必要的选项值:

from the_app import application

 

def new_app(environ, start_response):

    environ['the_app.configval1'] = 'something'

    return application(environ, start_response)

但是,大多存在的应用程序和框架可能只需要从environ里面一个单一的配置值,用来指示它们的应用程序或框架专门的配置文件位置。(当然,应用程序应当缓存这些配置,以避免每次调用都重复读取)

 

url reconstruction  url的构建

如果应用程序希望改造请求的完整url,可以使用如下的算法,由lan 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']

复制代码

supporting older (<2.2) versions of python 支持老版本的python(< 2.2)

略(没翻译,也没看)

 

optional platform-specific file handling 可选的特别的平台文件处理

一些操作环境提供高性能的文件传输设施,像unix的sendfile()方法。服务器和网关可能会通过environ中的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), '')

如果服务器或网关提供wsgi.file_wrapper,它必须是个可调用的,并且接受一个必要的位置参数,和一个可选的位置参数。第一个参数是将被发送的类文件对象,第二个参数是可选的,标示分快大小的建议(这个服务器/网关不需要)。这个调用必须返回一个可迭代的对象,并且不能执行任何的数据传输直到服务器/网关真正接受到了作为应用程序返回值的迭代对象(否则会阻止中间件解析或覆盖响应体)。

 

由应用程序提供的被认为是类文件的对象必须有可选大小参数的read()方法。它或许有close()方法,如果有,那么wsgi.file_wrapper必须有一个会调用类文件对象原始close()方法的close()方法。如果类文件对象有任何的方法或属性与python内置的文件对象的属性或方法名相同(例如fileno()),那么wsgi.file_warpper可能会假定这些方法和属性与python内置的语义是相同的。

 

实际在任何特定平台上实现的文件处理必须发生在应用程序返回之后,并且服务器/网关检查是否是一个包装对象被返回。(再次声明,因为存在的中间件,错误处理等等类似的东西,它不保证任何生成的包装会被实际使用)

 

除了处理close(),从语义上讲,应用程序返回一个包装的文件应该和返回iter(filelike.read, '')一样。换句话说,当传输开始的时候,应当从文件的当前位置开始传输,并且继续直到最后完成。

 

当然,特定平台的文件传输api通常不接受随意的类文件对象,所以,一个wsgi.file_wrapper为了判断类文件对象是否适应于他们特定的平台,不得不对提供的对象做一些像fileno()(unix-like oses)或者是java.nio.filechannel(在jython下)的自省检查。

 

注意:即使对象不适用与特定的平台api,wsgi.file_wrapper必须仍旧返回一个包装了read()和close()的迭代,因此应用程序使用这文件包装器便可以再不同平台间移植。这里有个简单的平台无关的文件包装类,适应于老(2.2之前)的和新的python,如下:

复制代码

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

复制代码

这里是一个用它来访问特定平台api的服务器/网关代码片段。

 

复制代码

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()

复制代码

questions and answersqa问答

1.为什么evniron必须是字典?用它的子类会有什么问题呢?

用字典的原理是为了最大化满足在服务器间的移植性。另一种选择就是定义一些字典的子集,用字典的方法作为标准的便捷的接口。然而事实上,大多服务器可能只需要找到一个字典就足够了,并且框架的作者会预计完整的字典特性可用,因为多半情况是这样的。但是如果一些服务器选择不使用字典,那么将可能会有互用性的问题出现尽管服务器给出了一