WSGI规格说明书

 PEP 333 这应该是WSGI最权威的文档了  http://www.python.org/dev/peps/pep-3333/  值翻译了最重要的前面部分,后面读者可以参考

 当然文档有些生硬,欢迎大家提出

PEP 333序言

这是PEP 333的更新版本,简单的修改为了提高在Python 3下的可用性,同时将几个长期存在的修正案合并到WSGI(它的代码样本也被移植到Python 3)

由于某些程序的原因,这是一份独特的PEP,以前在Python 2.x下兼容的服务器和运用程序如果不做修改将会失效。如果你的Python 2.x运用程序和服务器兼容PEP 333,那么它同样兼容这份PEP,即使在Python 3下,你的运用和服务器也应该遵守下面的章节标题描述的规则. A Note On String Types, and Unicode Issues.

如果你想详细了解,这份文档和PEP 333之间的区别,你需要去浏览 SVN 修正历史,从 修正84854开始浏览。

摘要 这份文档详细定义了Web服务器和Web运用以及Web框架之间的标准接口,提高了web运用在不同Web服务器之间的可移植性。

基本的原理和目标(来自 PEP 333)

Python现在有很多web框架,比如Zope,Quixote,Webware,SkunkWeb,PSO,Twisted Web。这么多选择对用来产生一个问题,因为一般来说,他们对框架的选择将限制他们可选的Web Server,反之亦然。

相比Java,Java也有很多可用的Web框架,Java 的 "servlet" API 使得任何Java运用程序和框架能够运行在支持Servlt的Web服务器上

如果Web服务器中包含这种适用于Python的可用性和广泛使用的API--不管这些Web服务器是用Python编写(比如Medusa),还是内嵌了Python解释器(如mod_python),或者通过网关协议调用Python(CGI,FastCGI)--使得框架的选择和服务器的选择分开,让用户选择自己合适的Frame和WebServer

因此这份PEP,在Web Server和Framework之间提出了一个简单并且通用的接口:Python Web 网关 接口(WSGI)

但WSGI规范的存在并没有解决现有的服务器和Python web应用程序的框架。为了达到以上效果,服务器和框架作者和维护者必须真正实现WSGI。

但是,现有的server或者framework没有一个支持WSGI,对于WSGI的实现者也没有什么回报。因此,WSGI必须非常容易实现,这样的话实现者就会花费很小的代价去实现这个接口

WSGI在server和framework两端实现的简单性对于能否有效的使用WSGI接口至关重要,这一点对设计者非常重要

尽管,WSGI对于Framework的作者的实现的简单性 和Web App 用户使用的的容易性并不是一件事,WSGI对于Framework的实现者提供了无任何 "包装" 的接口,因为对于响应对象,Cookie的处理每个框架都由自己的处理方式。重申,WSGI的目地是为了让现有的servers和app以及frameworks之间的交互变得更加简单,而不是创建一个新的web框架。

需要说明的是,这个目标排除了任何需要WSGI但是已经部署的Python版本,尽管,新的标准模块并不支持和需要这份说明,并且Python 2.2.2以上的也不需要WSGI(在标准库中支持WSGI,也许是个好主意,但是在将来的Python版本的标准库中包含支持WSGI接口)

除了让现有的以及将来的framwworks,servers实现的容易,也应该很容易创建request预处理器,response预处理器,其他基于 "WSGI"的"中间"组件对于包含它们的服务器来说看起来更像一个运用(也就说server中包含的中间组件就像一个app一样),同时对于他们包含的运用来说像一个服务器。

如果中间件简单而且健壮,并且WSGI得到广泛servers和frameworks的支持,它使得一种全新的Python Web application framework成为可能,一种包含高内聚,低耦合的WSGI中间组件的框架。事实上,现有的框架作者因该选择重构他们框架以得到这种目地,变得更像使用了WSGI的类库,而不是像一个完整的框架。这样就允许 app开发者 根据具体的作用选择 最合适的组件 而不是包含整个框架的优点和缺点

当然,再写这篇文章的时候,这一天无疑是很遥远的。当然,使得任何framework和server支持WSGI这是一个短期完全可以实现的目标。

最后要说明的是,当前WSGI版本没有详细规定 如何使用web server和server gateway部署一个 app。目前,server 或者 getway有必要实现 WSGI规范。如果有足够多的servers 和 framwworks实现了WSGI并且提供了不同领域,不同要求的部署经验,也就就有必要建立一个新的PEP,来描述 WSGI server和app framworks 的部署标准。

规格概述 

WSGI接口规定分为两端:"server"或者"gateway"一端,以及"application"或者"framework"一端。服务端调用运用端提供的对象。对象的定义细节 取决于 server和 gateway。假设有些server或者gateway需要一个application部署工具去写一个小脚本来创建server或者gateway实例,并且 将application 对象提供给他们。其他的server或者gateway需要配置文件(就跟servlet中需要web.xml一样)或者其他机制来说明application 对象应该从哪里导入或者通过其他方式获取

除了纯粹的 servers/gateways 和 applications/frameworks,还可以创建 同时 实现两端协议 的 "中间"组件。这样的组件对于包含它的 server来说它是一个application,同时对于它包含的application来说它是一个server,它能够用来扩展API,内容转换,导航,或者其他用途。

在整个规格说明书中,我们将会使用专业术语 "a callable"表示 "一个函数, 一个方法,一个类,或者拥有__call__方法的实例。它代表的 含义取决于server,gateway,或者application根据他们的需要选择合适的实现技术来实现callable(可调用)。相反,一个在正在调用 a callable 的server,gateway,or application不应该依赖于提供给它的任何类别的 callable。所有的 callable 只能被 called,不能进行内省。

字符串类型

通常,HTTP主要处理字节数组,这就意味着这份规格说明书大部分将的是如何处理字节数组。 尽管,字节数组的内容有不同种类的文本解释,而且在Python中,字符串是最常用的处理文本的方式。 但是在很多版本的python中,strings通常是Unicode,而不是字节数组。需要仔细对 可用的API 和 HTTP文本和字节数组正确的转换 取得平衡。 特别是支持在不同Python版本中对str类型的不同的实现 的移植。

目前WSGI定义了两种 string 类型 "Native strings":原生的strings(常被命名实现为str类型) 用于 request/response 头部 和元数据 "Bytestrings": 字节strings(Python 3中用作 bytes 实现,其他地方用str),用于 request 和 response 的主体(body)部分 (如 POST/PUT 输入数据和HTML 页面输出)

不要确认:尽管 Python的 str 确实是 Unicode "under the hood",原生strings的内容必须通过latin - 1编码集转化为字节数组。 (稍后请详细看 Unicode Issues 章节的内容)

简单来说:你这这篇文档中看到的单词 "string",其实是指 "native" string ,一个str类型对象,无论它在内部是以字节数组的方式实现还是 unicode方式实现。你在任何地方看到 的 "bytestring",则可理解为 "Python中的bytes类型的对象 或者Python 2中的str对象"

因此,尽管HTTP在某种意义上 "really just bytes"(只是字节数组),有很多API使用Python默认的str类型能够很方便的处理。

The Application/Framework Side 运用/框架 端

application object 是一个简单的 a callable(一个可调用) 对象,它接受两个参数。术语 "object"不要误解为需要一个真实的对象实例: 一个函数, 方法, 类, 或者拥有__call__方法的实例都可以用作 application object。 Application objects必须能够不止一次被调用, 事实上所有的servers/gateways(除了CGI)都会重复的产生这样的请求。 (Note:虽然我们称它为 "appkication" object,但并不是说运用开发者将会使用WSGI 作为web编程的API!假设运用程序开发者将会继续使用 已经存在的,高层的framework开发他们的运用。WSGI只是framework和server开发者的工具,并不打算直接支持运用开发者)

这里有两个application object的例子:一个是函数,另一个是类:

 1 HELLO_WORLD = b"Hello world!\n"
 2 
 3 def simple_app(environ, start_response):
 4     """Simplest possible application object"""
 5     status = '200 OK'
 6     response_headers = [('Content-type', 'text/plain')]
 7     start_response(status, response_headers)
 8     return [HELLO_WORLD]
 9 
10 class AppClass:
11     """Produce the same output, but using a class
12 
13     (Note: 'AppClass' is the "application" here, so calling it
14     returns an instance of 'AppClass', which is then the iterable
15     return value of the "application callable" as required by
16     the spec.
17 
18     If we wanted to use *instances* of 'AppClass' as application
19     objects instead, we would have to implement a '__call__'
20     method, which would be invoked to execute the application,
21     and we would need to create an instance for use by the
22     server or gateway.
23     """
24     """产生相同的输出,但是使用了一个类
25     Note:AppClass 在这里是一个 application,调用它直接返回一个AppClass实例,
26     然后这个可迭代对象按照这篇规格说明书的要求返回"application callable"的值
27     
28     如果我们想使用 AppClass 作为 application objects,我们必须实现 __call__方法(Python类中的一个魔法方法,实现该方法之后类的实例也像方法那样直接调用
29     ,它将会被调用去执行application,并且我们创建一个实例为server或者gateway使用)
30     """
31     def __init__(self, environ, start_response):
32         self.environ = environ
33         self.start = start_response
34 
35     def __iter__(self):
36         status = '200 OK'
37         response_headers = [('Content-type', 'text/plain')]
38         self.start(status, response_headers)
39         yield HELLO_WORLD

 

 通过上面的例子,我们可以知道 application object 有两个参数 environ, start_response,并且调用application object后产生的是一个application callable(可迭代对象)      The Server/Gateway Side Server/Gateway 端  Sever或者Gateway接受来自HTTP Client的每次请求调用一次 application管理的 application callable.举例,这是一个简单的CGI gateway  作为一个函数实现application object。这个简单的例子对于错误处理能力有限,因为通常来说,不能捕获的异常将会被送往 sys.stderr     并且被web server所纪录。

 1 import os, sys
 2 
 3 enc, esc = sys.getfilesystemencoding(), 'surrogateescape'
 4 
 5 def unicode_to_wsgi(u):
 6     # Convert an environment variable to a WSGI "bytes-as-unicode" string
 7     return u.encode(enc, esc).decode('iso-8859-1')
 8 
 9 def wsgi_to_bytes(s):
10     return s.encode('iso-8859-1')
11 
12 def run_with_cgi(application):
13     environ = {k: unicode_to_wsgi(v) for k,v in os.environ.items()}
14     environ['wsgi.input']        = sys.stdin.buffer
15     environ['wsgi.errors']       = sys.stderr
16     environ['wsgi.version']      = (1, 0)
17     environ['wsgi.multithread']  = False
18     environ['wsgi.multiprocess'] = True
19     environ['wsgi.run_once']     = True
20 
21     if environ.get('HTTPS', 'off') in ('on', '1'):
22         environ['wsgi.url_scheme'] = 'https'
23     else:
24         environ['wsgi.url_scheme'] = 'http'
25 
26     headers_set = []
27     headers_sent = []
28 
29     def write(data):
30         out = sys.stdout.buffer
31 
32         if not headers_set:
33              raise AssertionError("write() before start_response()")
34 
35         elif not headers_sent:
36              # Before the first output, send the stored headers
37              status, response_headers = headers_sent[:] = headers_set
38              out.write(wsgi_to_bytes('Status: %s\r\n' % status))
39              for header in response_headers:
40                  out.write(wsgi_to_bytes('%s: %s\r\n' % header))
41              out.write(wsgi_to_bytes('\r\n'))
42 
43         out.write(data)
44         out.flush()
45 
46     def start_response(status, response_headers, exc_info=None):
47         if exc_info:
48             try:
49                 if headers_sent:
50                     # Re-raise original exception if headers sent
51                     raise exc_info[1].with_traceback(exc_info[2])
52             finally:
53                 exc_info = None     # avoid dangling circular ref
54         elif headers_set:
55             raise AssertionError("Headers already set!")
56 
57         headers_set[:] = [status, response_headers]
58 
59         # Note: error checking on the headers should happen here,
60         # *after* the headers are set.  That way, if an error
61         # occurs, start_response can only be re-called with
62         # exc_info set.
63 
64         return write
65 
66     result = application(environ, start_response)
67     try:
68         for data in result:
69             if data:    # don't send headers until body appears
70                 write(data)
71         if not headers_sent:
72             write('')   # send headers now if body was empty
73     finally:
74         if hasattr(result, 'close'):
75             result.close()

 

  Middleware: Components that Play Both Sides(中间件: 分别饰演两端的角色) 单个对象相对于application(s)担任server的角色,同时相对于server(s)表现application的功能。 这样的中间组件做以下事情: *路由器角色,通过重写environ,根据URL将请求路由到不同的application object *允许多个application或者frameworks在同一个进程中,并发进行 *负载均衡和运程处理,通过网络转发请求和响应 *对内容进行加工,比如运用XSL的样式

对于"server/gateway" 和"application/framework"来说中间件的存在是透明的,也不需要特殊的支持. 用户想要把中间件合并到application简单的提供中间组件给server,就像一个application, 配置中间组件来调用application,就像中间组件是一个server.当然中间组件包装的 "application" 也许是另一个 中间组件同时包装了另一个application,等等。创建一个中间件栈

大部分场景,中间件必须同时满足WSGI对于server和application的要求.在一些情况下,对于中间件的要求比单纯的 server和application更加严格.这些点都会在这份说明书中指出。

下面是一个中间组件的例子将text/plain的响应转换为pig latin,用到了Joe Strout的piglatin.py (说明:一个真实的中间组件也许使用了更加健壮的检查内容类型的方法,而且应该检查内容的编码。当然 这个简单的例子忽略掉这些可能)

 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 bytestring, 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())   # call must be byte-safe on Py3
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))   # call must be byte-safe on Py3
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 详细定义 application必须接受两个位置参数。为了说明参数的作用,我们将这两个参数分别命名为 environ 和 start_response, 但是他们不一定要这么命名.一个server或者gateway必须使用位置参数的方式调用application(而不是关键字参数) 比如 result = appkication(environ, start_response)

environ是一个字典对象,包含CGI样式的环境变量.它必须是Python内建的字段类型(不是子类,UserDIct或者其他类字典对象), 而且application可以以字典所描述方法去修改这个字典。这个字典必须包含WSGI规定的一些变量(稍后说明),也可以包含 server说明的扩展变量,命名约定将在下面说明。

start_response 参数是一个接受两个位置参数和一个可选参数的的可调用对象,为了说明参数的含义,我们分别把这些参数命名 为 status, response_headers, exc_info,不一定非要这么命名,但是application 调用 start_response 必须使用位置参数 的方式调用(比如 start_response(status, response_headers))

status 参数表示状态信息, response_headers 是一个元组列表(比如[(header_name1, header_value1),(header_name2, header_value2)]) 用于描述HTTP 响应头。可选参数 exc_info 将会在 The start_response() Callable 和  Error Handling。 它只是用来当application出现错误并且尝试向浏览器显示错误。

调用start_response后必须返回一个 a warite(body_data)可调用对象,一个字节串将会被写入到HTTP response body中. (Node:提供write()只是用来支持现有的某些框架的必要输出API,如果可以避免使用它,尽量不要在新的application和framework中使用, See the Buffering and Streaming section for more details.)

当被服务器调用的时候 application对象必须返回一个可迭代对象.可以通过多个方式产生,比如字节串列表,或者 使用yield 字节串使application变成一个生成器函数,或者使application成为一个类,并且它的实例是可迭代的。 不管怎么实现,application必须返回一个可迭代的对象。

serve和gateway必须将application产生的字节串以无缓冲的形式传输到客户端,在另一个请求到达之前传输完每个、 字节串。(In other words, applications should perform their own buffering. See the Buffering and Streaming section below for more on how application output must be handled.)

server和gateway必须以二进制字节序列的方式传输这些迭代生成的字节串,必须确保每行的结尾不会发生变化. appkication必须确保 字节串 以一种适合客户端的格式 写出

 

 

 

 

 

 

 

 

 

 

 

 

posted on 2013-12-18 00:00  Arts&Crafts  阅读(922)  评论(0编辑  收藏  举报

导航