简介

wsgi 是一个 web 组件的接口规范. wsgi将 web 组件分为三类: web服务器,web中间件,web应用程序. 其目标是提高web组件的可重用性. wsgi 的设计目标是适合尽可能广泛的web应用, 较原始.

典型应用:

  •    1   app = A_WSGI_Application(...)
       2   app = Middleware1(app, ...)
       3   app = Middleware2(app, ...)
       4   ...
       5   server(app).serve_forever()
    

相关资料

http://wsgi.org/

理解WSGI
实用WSGI
WSGI框架

规范

WSGI应用程序

应用程序对象只是一个接受两个参数的 callable 对象而已. 别把术语 "对象" 误解为真的需要一个实例对象: 一个函数, 方法, 类, 或者实现了 __call__ 方法的实例对象都可以用来作为应用程序对象. 应用程序对象必须能够被重复调用, 因为实际上所有 服务器(除了CGI) 都会发出这样的重复的请求.

The application object is simply a callable object that accepts two arguments. The term "object" should not be misconstrued as requiring an actual object instance: a function, method, class, or instance with a call method are all acceptable for use as an application object. Application objects must be able to be invoked more than once, as virtually all servers/gateways (other than CGI) will make such repeated requests.

(注意: 虽然我们把它说成是一个 "应用程序" 对象, 但也别误解为应用程序开发者会把 WSGI 当一个 web编程API 来用! 我们假定应用程序开发者仍然使用现有的,高层的框架服务来开发他们的应用程序. WSGI 是一个给框架和服务器开发者用的工具, 并且不会提供对应用程序开发者的直接支持.)

(Note: although we refer to it as an "application" object, this should not be construed to mean that application developers will use WSGI as a web programming API! It is assumed that application developers will continue to use existing, high-level framework services to develop their applications. WSGI is a tool for framework and server developers, and is not intended to directly support application developers.)

示例

   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"

WSGI服务器

服务器 或 getway 每次收到 http客户端 发出的请求都会调用一次相应的 应用程序callable . 举例来说, 这里有一个简单的 CGI getway, 以一个接受一个应用程序对象作为参数的函数来实现的. 值得注意的是这个简单的例子还拥有有限的错误处理功能, 因为未捕捉的异常默认会写到 sys.error 里并被web服务器记录下来.

The server or gateway invokes the application callable once for each request it receives from an HTTP client, that is directed at the application. To illustrate, here is a simple CGI gateway, implemented as a function taking an application object. Note that this simple example has limited error handling, because by default an uncaught exception will be dumped to sys.stderr and logged by the web server.

示例

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

WSGI中间件

注意一个对象可以在一些应用程序面前是服务器, 而从一些服务器看来却是应用程序. 这样的 "中间件" 可以实现以下一些功能:

Note that a single object may play the role of a server with respect to some application(s), while also acting as an application with respect to some server(s). Such "middleware" components can perform such functions as:

  • 可以根据目的 URL 将一个请求分发 (routing) 给不同的应用程序对象, 并对 environ 做相应修改.

  • Routing a request to different application objects based on the
    • target URL, after rewriting the environ accordingly.

  • 允许多个应用程序或框架在同一个进程中一起运行.
  • Allowing multiple applications or frameworks to run side-by-side
    • in the same process
  • 通过在网络上转发请求和响应, 进行负载均衡和远程处理.
  • Load balancing and remote processing, by forwarding requests and
    • responses over a network
  • 对内容进行后加工, 比如应用 XSL 样式.
  • Perform content postprocessing, such as applying XSL stylesheets

通常中间件的存在对 服务器 和 应用程序 两边的接口都是透明的, 并且应该不需要提供什么特殊的支持. 用户如果想在应用程序中组合一个中间件, 只需简单地把中间件当个应用程序提供给服务器, 并配置该中间件, 让它以服务器的身份来调用应用程序. 当然中间件包装的这个应用程序实际上可能是一个包装着另一个应用程序的中间件, 如此反复, (以至无穷), 最终形成了传说中的 "中间件栈" .

The presence of middleware in general is transparent to both the "server/gateway" and the "application/framework" sides of the interface, and should require no special support. A user who desires to incorporate middleware into an application simply provides the middleware component to the server, as if it were an application, and configures the middleware component to invoke the application, as if the middleware component were a server. Of course, the "application" that the middleware wraps may in fact be another middleware component wrapping another application, and so on, creating what is referred to as a "middleware stack".

在多数情况下, 中间件必须同时遵守WSGI服务器和WSGI应用程序两边的限制和需要. 而且在某些情况下, 对中间件的要求比一个纯粹服务器或应用程序还要严格, 这几个方面在规范中还会涉及到.

For the most part, middleware must conform to the restrictions and requirements of both the server and application sides of WSGI. In some cases, however, requirements for middleware are more stringent than for a "pure" server or application, and these points will be noted in the specification.

这里有一个 (随手写的) 中间件的例子, 它将 text/plain 的响应转换成 pig latin, 用到 Joe Strout 的 piglatin.py. (注意: 一个 "真实" 的中间件组件很可能会使用一种更成熟的方式来检查 content type, 而且还应该检查一下 content encoding. 另外, 这个简单的例子还忽略了一个单词可能会在数据块的边界处被分割.)

Here is a (tongue-in-cheek) example of a middleware component that converts text/plain responses to pig latin, using Joe Strout's piglatin.py. (Note: a "real" middleware component would probably use a more robust way of checking the content type, and should also check for a content encoding. Also, this simple example ignores the possibility that a word 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     def __init__(self,result,transform_ok):
  12         if hasattr(result,'close'):
  13             self.close = result.close
  14         self._next = iter(result).next
  15         self.transform_ok = transform_ok
  16 
  17     def __iter__(self):
  18         return self
  19 
  20     def next(self):
  21         if self.transform_ok:
  22             return piglatin(self._next())
  23         else:
  24             return self._next()
  25 
  26 class Latinator:
  27 
  28     # by default, don't transform output
  29     transform = False
  30 
  31     def __init__(self, application):
  32         self.application = application
  33 
  34     def __call__(self, environ, start_response):
  35 
  36         transform_ok = []
  37 
  38         def start_latin(status,response_headers,exc_info=None):
  39 
  40             # Reset ok flag, in case this is a repeat call
  41             transform_ok[:]=[]
  42 
  43             for name,value in response_headers:
  44                 if name.lower()=='content-type' and value=='text/plain':
  45                     transform_ok.append(True)
  46                     # Strip content-length if present, else it'll be wrong
  47                     response_headers = [(name,value)
  48                         for name,value in response_headers
  49                             if name.lower()<>'content-length'
  50                     ]
  51                     break
  52 
  53             write = start_response(status,response_headers,exc_info)
  54 
  55             if transform_ok:
  56                 def write_latin(data):
  57                     write(piglatin(data))
  58                 return write_latin
  59             else:
  60                 return write
  61 
  62         return LatinIter(self.application(environ,start_latin),transform_ok)
  63 
  64 
  65 # Run foo_app under a Latinator's control, using the example CGI gateway
  66 from foo_app import foo_app
  67 run_with_cgi(Latinator(foo_app))

规范细节

应用程序对象必须接受两个固定参数,为了方便说明我们不妨把他们命名为 environstart_response ,但并非必须取这个名字。服务器或gateway必须使用这两个参数来调用应用程序对象 ( 就象上面展示的那样, 像这样调用 result = application(environ,start_response) )

The application object must accept two positional arguments. For the sake of illustration, we have named them environ and start_response, but they are not required to have these names. A server or gateway must invoke the application object using positional (not keyword) arguments. (E.g. by calling result = application(environ,start_response) as shown above.)

environ 参数是个字典,其中包含的是 CGI 风格的环境变量。这个对象必须是一个 python 内置的字典对象 (不能是其子类、UserDict或其他对字典对象的模仿) ,应用程序可以任意修改这个字典, environ 还应该包含一些 WSGI 需要的特定变量 (在后面的节里还会描述) ,还可以包含一些依据一定的命名规范的服务器特定的扩展变量, 命名规范在后面还会有描述。

The environ parameter is a dictionary object, containing CGI-style environment variables. This object must be a builtin Python dictionary (not a subclass, UserDict or other dictionary emulation), and the application is allowed to modify the dictionary in any way it desires. The dictionary must also include certain WSGI-required variables (described in a later section), and may also include server-specific extension variables, named according to a convention that will be described below.

参数 start_response 是一个接受两个必需的固定参数和一个可选参数的 callable 对象. 为便于说明, 我们把这三个参数分别命名为: status, response_headers, 和 exc_info, 当然你也可以给他们起其他名字. 应用程序必需使用固定参数调用 start_response (比如: start_response(status,response_headers))

The start_response parameter is a callable accepting two required positional arguments, and one optional argument. For the sake of illustration, we have named these arguments status, response_headers, and exc_info, but they are not required to have these names, and the application must invoke the start_response callable using positional arguments (e.g. start_response(status,response_headers)).

参数 status 是一个形如 "999 Message here" 的表示状态的字符串。而 response_headers 参数是一个描述 http 响应头的列表, 其中每一个元素都是像 (header_name,header_value) 这样的元组。可选的 exc_info 参数会在后面的 start_response() callable错误处理 两节中进行描述,该参数只有在应用程序产生了错误并希望在浏览器上显示错误信息的时候才用得上。

The status parameter is a status string of the form "999 Message here", and response_headers is a list of (header_name,header_value) tuples describing the HTTP response header. The optional exc_info parameter is described below in the sections on The start_response() Callable and Error Handling. It is used only when the application has trapped an error and is attempting to display an error message to the browser.

start_response callable 必须返回一个 write(body_data) callable,该 callable 接受一个可选参数:一个将会被作为 http body 一部分输出的字符串. (注意:提供 write() callable 只是为了支持几个现有框架急需的输出API,新的应用程序或框架应尽量避免使用,详细情况请看 Buffering and Streaming 一节。)

The start_response callable must return a write(body_data) callable that takes one positional parameter: a string to be written as part of the HTTP response body. (Note: the write() callable is provided only to support certain existing frameworks' imperative output APIs; it should not be used by new applications or frameworks if it can be avoided. See the Buffering and Streaming section for more details.)

当被服务器调用的时候, 应用程序对象必须返回一个能产生零或多个字符串的 iterable , 这可以有好几种实现方法, 比如返回一个字符串的列表,或者应用程序本身就是一个产生字符串的生成器, 或者应用程序本身是一个类 而他的实例是 iterable . 不管它是怎么实现,应用程序对象总是必须返回一个能产生零或多个字符串的 iterable 。

When called by the server, the application object must return an iterable yielding zero or more strings. This can be accomplished in a variety of ways, such as by returning a list of strings, or by the application being a generator function that yields strings, or by the application being a class whose instances are iterable. Regardless of how it is accomplished, the application object must always return an iterable yielding zero or more strings.

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

The server or gateway must transmit the yielded strings to the client in an unbuffered fashion, completing the transmission of each string before requesting another one. (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.)

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

The server or gateway should treat the yielded strings as binary byte sequences: in particular, it should ensure that line endings are not altered. The application is responsible for ensuring that the string(s) to be written are in a format suitable for the client. (The server or gateway may apply HTTP transfer encodings, or perform other transformations for the purpose of implementing HTTP features such as byte-range transmission. See Other HTTP Features, below, for more details.)

如果调用 len(iterable) 成功的话, 服务器便会假定返回的结果是正确的。也就是说, 只要应用程序返回的 iterable 提供了有效的 __len__() 方法,就可以肯定它返回的结果是正确的. (关于通常情况下这个方法应该如何使用的问题请阅读 Handling the Content-Length Header 一节 )

If a call to len(iterable) succeeds, the server must be able to rely on the result being accurate. That is, if the iterable returned by the application provides a working len() method, it must return an accurate result. (See the Handling the Content-Length Header section for information on how this would normally be used.)

如果应用程序返回的 iterable 拥有 close() 方法, 则不管该请求是正常结束的还是由于出错而终止的, 服务器/gateway 都 *必须* 在结束该请求之前调用这个方法,(这是用来支持应用程序对资源的释放的, 这个协议是特别用来提供对 PEP 325 的生成器 和 其他带有 close() 方法的 iterable 的支持 )

If the iterable returned by the application has a close() method, the server or gateway must call that method upon completion of the current request, whether the request was completed normally, or terminated early due to an error. (This is to support resource release by the application. This protocol is intended to complement PEP 325's generator support, and other common iterables with close() methods.

(注意:应用程序必须在 iterable 产生第一个 body 字符串之间调用 start_response() callable, 这样服务器才能在发送任何 body 内容之前发送 response header, 不过这个步骤也可以在 iterable 执行第一次迭代的时候执行,所以服务器不能假定在开始迭代之前 start_response() 一定被调用过了)

(Note: the application must invoke the start_response() callable before the iterable yields its first body string, so that the server can send the headers before any body content. However, this invocation may be performed by the iterable's first iteration, so servers must not assume that start_response() has been called before they begin iterating over the iterable.)

最后, 服务器和gateway 绝不能直接使用应用程序返回的 iterable 的任何其他的属性, 除非是针对该 服务器或gateway 特定的情形, 比如 wsgi.file_wrapper 返回的 file wrapper (阅读 Optional Platform-Specific File Handling)。通常, 只能访问这里指定的属性, 或者也可以通过 PEP 234 iteration APIs 进行访问。

Finally, servers and gateways must not directly use any other attributes of the iterable returned by the application, unless it is an instance of a type specific to that server or gateway, such as a "file wrapper" returned by wsgi.file_wrapper (see Optional Platform-Specific File Handling). In the general case, only attributes specified here, or accessed via e.g. the PEP 234 iteration APIs are acceptable.

environ 变量

start_response() callable

Buffering and Streaming

讨论

深思WSGI

Limodou 问题

limodou <[email protected]>
reply-to        [email protected]
to      "Python.cn@google" <[email protected]>
date    Thu, Dec 25, 2008 at 10:59
subject [CPyUG:74762] WSGI的问题集

希望对wsgi了解的介绍下:

  1. WSGI的app之间如何传递消息?在environ中添加吗?
  2. 多个app之间通过wsgi方式来工作,一般是怎么被调用的?它们的调用如何实现配置化?
  3. 对于request, response这样的东西如何在wsgi中处理,如何传递?是每次通过environ直接生成吗?如:

request = Request(environ)

这样做,会不会有效率问题。另外如何处理request中动态添加的属性,丢失吗?

讨论集锦

WSGI的app之间如何传递消息

Limodou
def configure(app):
   return ErrorHandlerMiddleware(
           SessionMiddleware(
            IdentificationMiddleware(
             AuthenticationMiddleware(
              UrlParserMiddleware(app))))))

因此出现了paster,在每个app程序中添加:

def app_factory(global_config, **local_config):
   return application
  • 然后通过paster来管理这些app的执行顺序。这样每个app还可以有自已的配置参数。这样就可以理解为什么tg和pylons要使用paster了。不过对我来说的确是理解复杂。甚至象pkg_resources的使用也是最近才一点点理解并开始使用的。
  • Qiangning Hong
    • WSGI的处理模式为 WSGI Server -> (WSGI Middleware)* -> WSGI Application

    • 一次请求只有一个app。middleware和app之间通过environ交换消息。
    YoungKing(Zopen.cn)
    • 每个wsgi app是一个callable对象,传递两个参数,一个是environment ,一个是start_response 函数,
    • start_response这个函数用来response,
    • 你在这里设置status和header,最后返回一个iterator(通常是字符串list)
    潘俊勇 <[email protected]
    • filter/app之间使用environment的消息机制了交换数据的,这种基于消息,而非API的通讯方式,更加底层,可以减少依赖。

多个app之间的调用

Qiangning Hong
  • 同上,一个request最终落到一个app上。
  • 不过每一个middleware对于调用者而言,接口和app是一样的。
  • 一般在前端有一个appdispatcher通过URL选择执行哪一个app,可以是直接在WSGI Server里的代码指定,也可以这个dispatcher本身就是一个middleware。
  • Pylons使用的Routes这两种方式都支持。WSGI有一个route标准的草案。
潘俊勇 <[email protected]
  • paste支持ini文件方式的部署配置,如:

[app:main]
use = egg:wo
filter-with = fs

[filter:fs]
paste.filter_factory = zopen.frs4wsgi.filter:filter_factory
directory = var/uploadtemp

[server:main]
use = egg:Paste#http
host = 0.0.0.0
port = 8092
  • 可以看到,这里不仅支持wsgi组件关系的部署,还支持各个组件的参数配置

request/response的WSGI 处置

Qiangning Hong
  • 是的,webob其实就是怎么干的。
  • webob好像是用的lazy方式,初始化一个对象很轻量。如果要考虑效率问题,可以把生成的request对象也放在environ里面,之后的middleware和app就都可以直接使用了。Pylons是这样做的。
  • Limodou: 对于第三个问题,可能就与具体的框架相关了。目前不管是django也好,还是uliweb也好,其本上只有一个主wsgi应用在运行。不过因为uliweb的主要处理就是一个wsgi的app,所以是可以放在wsgi中进行使用的。

    • Pylons是每一个Controller就是一个WSGI app。通过Routes来dispatch到某一个特定的controller运行
YoungKing(Zopen.cn)
  • 在wsgi中,environment对应request,start_response对应response.envrionment中可以传任意python对象,这个比request方便多了.你可以修改envionment,把属性放到这里来
潘俊勇 <[email protected]

WSGI中间件是如何复用的?

潘俊勇 <[email protected]
  • 关于profile的middleware,repoze社区有了repoze.profile,这里有个可用的中间件清单:
  • http://repoze.org/repoze_components.html#middleware

  • 其中repoze.what作为认证的中间件,功能很强。
  • 好像paste社区也有一个wsgi middleware清单的,刚刚没找到。
  • 目前paste社区和repoze社区关系非常紧密的,一些中间件开发力量和项目也合并起来的
Qiangning Hong
  • Pylons推进Routes成为一个WSGI middleware是一个创举,这样使得框架中的dispatcher也变成可替换的了。
    • 以前Pylons和TurboGears的最大的区别就在于一个是基于Routes一个是基于CherryPy,不可调和。

    • 现在有了WSGI route标准,连dispatcher也可以替换了。
    • WSGI middleware的好处是可重用性好,所有的WSGI-aware框架都可以使用。
    • PASTE本身是一些WSGI组件的集合和工具,用不着的话当然完全可以不使用它。

  • Pylons是从RoR抄过来的,所以有Controller/View的概念。喜欢用函数的话,完全也可以写成一个函数就是一个WSGI

application的。比如写成这样:

def say_hello(environ, start_response):
   start_response("200 OK", [('Content-type', 'text/plain')])
   return ["Hello %s!" %environ['selector.vars']['name']]

from selector import Selector

s = Selector()
s.add('/myapp/hello/{name}', GET=say_hello)

from flup.server.scgi import WSGIServer
WSGIServer(s).run()

selector是一个WSGI URL Dispatcher Middleware,根据URL选择一个WSGI app运行,

用Routes也可以实现类似效果。

顺便说一句,paste.debug.profile.ProfileMiddleware就是一个用来做 profile 的 WSGI middleware

meilanweb

paste分成三个部分

  • 一个就叫Paste,是一个组件库,包含几个WSGI server和很多WSGI middleware。
  • Paste Script提供一个命令行界面,便于创建项目、启动服务等等。
  • Paste Deploy提供一个配置方法,便于系统配置和部署。你如果用不到这些东西,自然不需要。

比如我自己为了在GAE上做东西玩,自己做了一个framework玩,由于只是自己用,不需要考虑配置、互换性等问题,就完全没有使用pastescript/deploy,只用了他一个middleware,代码很简单,一共142行,但基本的功能(URL Dispatch、Template、Authentication)已经可以用了。

  • 但uliweb如果要做成一个通用的框架满足更多人的需要的话,可能还是看一下paste比较好。

   1 #meilanweb.py
   2 import os
   3 import sys
   4 import wsgiref.simple_server
   5 import wsgiref.handlers
   6 from types import TypeType, ClassType
   7 import threading
   8 
   9 import webob
  10 import webob.exc
  11 from routes import Mapper as _Mapper
  12 from routes.middleware import RoutesMiddleware
  13 from paste.exceptions.errormiddleware import ErrorMiddleware
  14 import webhelpers as h
  15 from mako.lookup import TemplateLookup
  16 from google.appengine.api import users
  17 
  18 c = threading.local()
  19 
  20 def Mapper(*a, **kw):
  21    prefix = kw.pop('prefix', None)
  22    m = _Mapper(*a, **kw)
  23    if prefix:
  24        m.prefix = prefix
  25    return m
  26 
  27 def make_app(mapper, controllers, template_dir=None, debug=False):
  28    app = Application(controllers, template_dir=template_dir)
  29    app = AuthenticateMiddleware(app)
  30    app = RoutesMiddleware(app, mapper)
  31    app = ErrorDocumentMiddleware(app)
  32    app = ErrorMiddleware(app, debug=debug)
  33    return app
  34 
  35 def run(app, host='', port=8080):
  36    server = wsgiref.simple_server.make_server(host, port, app)
  37    server.serve_forever()
  38 
  39 def run_cgi(app):
  40    wsgiref.handlers.CGIHandler().run(app)
  41 
  42 class Application(object):
  43    def __init__(self, controllers, template_dir=None):
  44        self.controllers = controllers
  45        if template_dir:
  46            self.lookup = TemplateLookup(template_dir, input_encoding='utf8',
  47                                         output_encoding='utf8')
  48 
  49    def __call__(self, environ, start_response):
  50        controller = self.resolve(environ)
  51        environ['meilanweb.render'] = self.render
  52        s = controller(environ, start_response)
  53        return s
  54 
  55    def resolve(self, environ):
  56        """Get the controller app from the environ."""
  57        match = environ['wsgiorg.routing_args'][1]
  58        try:
  59            controller_name = match['controller']
  60        except KeyError:
  61            raise webob.exc.HTTPNotFound()
  62        controller_class_name = \
  63                class_name_from_controller_name(controller_name)
  64        controller = self.controllers[controller_class_name]
  65        if isinstance(controller, (TypeType, ClassType)):
  66            controller = controller()
  67        return controller
  68 
  69    def render(self, template, **kwargs):
  70        return self.lookup.get_template(template).render(**kwargs)
  71 
  72 
  73 def class_name_from_controller_name(controller_name):
  74    # the code is stolen from Pylons
  75    """
  76    Excample::
  77 
  78        >>> class_name_from_controller_name('with-dashes')
  79        'WithDashes'
  80        >>> class_name_from_controller_name('with_underscores')
  81        'WithUnderscores'
  82        >>> class_name_from_controller_name('oneword')
  83        'Oneword'
  84    """
  85    words = controller_name.replace('-', '_').split('_')
  86    return ''.join(w.title() for w in words)
  87 
  88 
  89 class Controller(object):
  90    def __call__(self, environ, start_response):
  91        c.__dict__.clear()
  92        self.environ = environ
  93        match = environ['wsgiorg.routing_args'][1]
  94        action_name = match['action'].replace('-', '_')
  95        action = getattr(self, action_name)
  96        kwargs = match.copy()
  97        del kwargs['controller']
  98        del kwargs['action']
  99        self.request = webob.Request(environ)
 100        self.response = webob.Response(request=self.request)
 101        retval = action(**kwargs)
 102        if retval:
 103            self.response.write(retval)
 104        return self.response(environ, start_response)
 105 
 106    def render(self, template, **kwargs):
 107        return self.environ['meilanweb.render'](template, c=c, h=h, **kwargs)
 108 
 109 
 110 def default_template_dir(filepath):
 111    here = os.path.dirname(os.path.abspath(filepath))
 112    return os.path.join(here, 'templates')
 113 
 114 
 115 class ErrorDocumentMiddleware(object):
 116    def __init__(self, app):
 117        self.app = app
 118 
 119    def __call__(self, environ, start_response):
 120        try:
 121            return self.app(environ, start_response)
 122        except webob.exc.WSGIHTTPException, e:
 123            e.environ = environ
 124            return e(environ, start_response)
 125 
 126 
 127 class AuthenticateMiddleware(object):
 128    def __init__(self, app):
 129        self.app = app
 130 
 131    def __call__(self, environ, start_response):
 132        user = users.get_current_user()
 133        if user:
 134            environ['REMOTE_USER'] = user
 135        try:
 136            return self.app(environ, start_response)
 137        except webob.exc.HTTPUnauthorized, e:
 138            req = webob.Request(environ)
 139            url = users.create_login_url(req.url)
 140            raise webob.exc.HTTPTemporaryRedirect(location=url)
 141 
 142 h.create_logout_url = users.create_logout_url

整体

Gu Yingbo <[email protected]
  • 我认为wsgi的中间件倾向于做一些简单独立事情,每个中间件app 的依赖性比较低,
  • 像werkzeug的DebuggedApplication这种风格就很好,一条语句就可以为一个wsgi应用添加一个调试界面。

  • 我自己也就用中间件做一个请求的计时,sql语句调试开关这样简单的事情。
  • 我觉得wsgi的最大好处是:

    • 分离了服务器和应用的实现,
    • 写一个wsgi应用就有一大把的部署方式,自己可以根据需要选择合适的。
黄毅 <[email protected]>
再啰嗦几句,web框架要统一就要标准化组件之间的接口,但这些已经不是wsgi关注的领域了。
  • environ字典对 app 来说只代表最原始的 web请求,它不应该是框架内部组件之间交换数据的场所。
  • 框架应该在入口处统一解析一次 environ,并构造一个 request 对象,其后的事情都应该建立在这个request对象的基础之上,而不用再去直接处理environ了,
  • url dispatcher解析URL的结果也应该存放在这个 request 对象中,而不是 environ 中。
    • 像 url dispatcher、auth 这些都应该是框架内的组件,比如说可以有一个框架内的middleware协议,就像 django 的middleware那样,不需要wsgi那么复杂,它们之间就只需要传递 request和response 对象即可,environ、start_response对它们来说都是太底层的东西。
  • 而 wsgi middleware 一定要对所有wsgi application透明,这是我理解的 wsgi。
  • 标准化web框架应该是在 web框架内部做的事情,而不应该一味地去扩展wsgi,试图把 wsgi 变成一个框架。
  • 标准化web框架也不准确,顶多是标准化一个MVC的web框架。
  • uliweb没怎么看过,不过我认为limudou完全不必把controller变成wsgi application,应该把框架暴露成一个wsgi app,并且制定一些框架内部组件之间的标准化接口,比如框架内的middleware之间的接口,存取配置的接口,调用模板的接口,使用ORM的接口等。

>>> Limodou:好的建议。一个框架中有许多的处理,有些是与wsgi相关的,有些不是。所以只有wsgi统一化也只解决了一部分问题。从看到的一些wsgimiddleware的互访来说,它们其实也约定了许多标准的组件,比如sqlalchemy,webob等。而这些只是组件的选择上的一致性,还没有达到接口的规范统一。不过这是很难的,而且也不知道是否真有意义。

  • 现在uliweb基本上就是你说的样子。middleware的标准基本上和django是一样的,不过我增加了一个__init__的定义,这样是可以进行初始化的,做一些特殊的事情。

WSGI不是框架

黄毅 <[email protected]>
reply-to        [email protected]
to      [email protected]
date    Thu, Dec 25, 2008 at 14:29
subject [CPyUG:74822] Re: WSGI的问题集
  • Limodou:到是可以考虑把uliweb下的expose改造一下,一方面进行url的注册,另一方面将一个view函数封装成一个wsgi的application的东西。

    • 这样的话,哪些东西做成wsgi方式的会有意义呢?因为一个功能,依赖越少可能越好。而象view这样的东西,多数与你的后台,逻辑相关,这些东西比较困难。直接把框架的部分功能做成wsgi方式的有难度,因为框架最具特色的可能就是组件的搭配,配置管理,组件管理和开发方式,思想,而这些东西好象也很难wsgi化。而且有些根本与wsgi的处理无关的,象组件的管理,配置管理之类的。现在想不出来uliweb有哪些可能是独持的可以做成wsgi的东西,因为uliweb能实现的功能,别人基本上都有。而且涉及到wsgi的别人的比uliweb的好象还要更多。
  • 黄毅:我想框架对wsgi友好的含义,一是把自己暴露为一个符合标准wsgi app,二是尽可能抽象出并重用对框架app透明的wsgi middleware,就够了。框架肯定会有很多自己的东西,wsgi并不是一个框架。

  • Qiangning Hong
    • 可重用的部分做成wsgi middleware的方式就是有意义的。把框架内部的各组件都做成WSGI化的,有助于从中提炼出来可重用的部分。
    • 对于Pylons而言,这个框架的终极思想就是没有框架,只作为一个WSGI组件的粘合剂存在,每个人用的Pylons都是不一样的。所以他尽可能的把各个组件都WSGI化,用户根据自己的偏好去搭建。 TurboGears 2.0挑选了自己喜欢的一些组件(比如Genshi之类),作为默认组件,就可以在Pylons上建立起一个有特点的框架。

      • <<< Limodou ~ 其实没有框架就意味着按照自已的方式来组织框架,就是我说的定制化。只不过只有wsgi还无法完成web的处理,我们还需要象template,orm之类的通用模块,而这些与wsgi没什么关系也是一个问题。所以全部wsgi首先是不可能,完全利用wsgi搭建的也只是处理这一块,还有许多的工作要处理。因此我想定制化不一定要在wsgi之上,可以是在一个初步定制化的基础之上的二次定制。

        • >>> Qiangning Hong~ template的WSGI化: http://pypi.python.org/pypi/wsgiview/

          • <<< Limodou ~ 这个是把模板的处理进行了包装,可以返回一个wsgi的application。虽然是一种办法,但是我并不认为到处都是wsgi就可以简化web开发,还不如自动对应模板来得简单。应该把这种处理封装到框架内部,如果让一般的web用户来使用,太麻烦,我感觉不好。

            • >>> Qiangning Hong~ 我也觉得这个没什么大用,因为基本上到模板的时候,app把应该干的事情都已经干完了,渲染模板仅仅是一个函数调用,所以看不出来把渲染这件事情做成wsgi app有什么实质的好处。贴出来只是说一下有人在做这样的事情。

          • ORM的WSGI化: http://pypi.python.org/pypi/Alchemyware2.0

            • <<< Limodou ~ 好象会共享出一个session对象,起到了一个全局化的作用。这也算ORM的wsgi?好象与使用全局模块不是一样吗?

              • Qiangning Hong~ 这个的作用很大,可以打破不同的middleware对SQLAlchemy全局变量调用可能存在的冲突。如果一个middleware对SQLAlchemy的使用有特殊性,可以放在自己的session里,不和其他的发生冲突。

            • <<< 黄毅 <[email protected]> ~我觉得上面这两个路子不对,还是认为不是什么东西都可以通过扩展wsgi来做,template已经turbogears已经有一个叫做 buffet 的标准化接口了吧,我觉得那个已经够了

    • Uliweb如果也要走"不可知"的道路,那最后也是这样的模型。所以我提议uliweb多看看pylons,如果能在pylons之上构架框架,会少走很多弯路,产生出来的结果也会更有意义(正如TurboGears项目贡献出来很多WSGI组件一样)。

      • <<<Limodou ~ uliweb的不可知只是核心部分,它可以被其他人用来构建定制化的东西。

        • 所以一般用户使用的应该是可知的。并且我希望uliweb在工作时,会有一个缺省选择,用户可以更换,但不配置时就是缺省的。
        • 不过要完全做到不可知很难,因为你的调用接口总要的统一的,但这些可能只是一个局部的标准,别人不一定能遵守或做到,所以要么采用和别人一样的接口,但是仍然可能有不满足的情况。要么自已来定义接口,由用户来针对不同的组件进行适配。
          • >>>Qiangning Hong~ 这个就是TurboGears的路线。TurboGears原来就是完全自己做,选择自己喜欢的第三方组件搭建框架,和RoR一样。等发展到一定程度了,用的人多了,就开始有替换组件的需求。弄来弄去,最后发现直接在Pylons上搭建,选择一套默认组件,由Pylons去负责组件的可替换性,TurboGears只需要专注在用户的易用性和功能增强上面就行了,这样的方案对整个社区都有利。所以才有了TurboGears

      • 比如我很看好的uliweb的可重用app模型,如果能够独立成一个wsgi app,就可以直接在Pylons里重用了。这样uliweb就成为一个可重用的wsgi的app的集合,而非一个巨型的大wsgi app。
        • <<<Limodou ~ 这个想法到是很有意思。不过uliweb的app其实就是一个python的包,它包含了静态信息,配置信息,翻译信息等。uliweb在启动时可以根据配置或动态发现它们,然后合并它们的配置信息,提取url等,而这些处理都是由Dispatcher来完成的。所以这些app并不是一个wsgi的应用。不太明白你希望的重用是什么样子,是Dispatcher还是那些个app?

          • >>> Qiangning Hong ~ 如果uliweb能变成一个wsgi middleware,把它往其他的wsgi app上一套,那些app就能像在uliweb里一样变得可重用了,这个世界就很美妙了 :) 不过我对uliweb理解不深,不知道能不能实现。

            • <<<Limodou ~ UliWeb的app是一个包,不是一个真正的对象啊,怎么套啊。不是一样的东西

              • >>> Qiangning Hong ~ 给这个包加上 call(start_response, environ) 试试?

  • Controller本身是wsgi app有一个好处:

    • 即在框架内部也可以直接使用wsgi middlewares来进行定制。
    • 比如不同的controller使用不同的authentication机制,我就可以用不同的auth middleware去decorate相应的controller。
    • 如果只在整个框架外部才暴露一个wsgi app接口,那内部的这些东西都只能用私有的模式实现,重用度就大大降低了
    • <<< 黄毅 <[email protected]> ~ 这样就有一个问题,authentication 组件做成 wsgi middleware 的话需要自己去解析请求,但作为一个 app 内部的组件的话,可以由app对请求解析一次,authentication 直接取解析后的数据即可。 除非说要把 request、response对象也标准化,那按照这条路走下去的话,就是要搞一个标准统一所有框架,变成一个大一统的框架而已。

      • <<<Limodou~ 的确是这样,对于django的auth middleware我很理解,并且它在处理后会将用户对象赋组request上。而authkit是一个wsgi middleware,它如何关心后台的用户机制是数据库,还是ldap,还是什么的,又如何与request相关联的呢?因为用户认证的情况在后面的应用处理也要使用的。不知道它怎么做的?

        • >>> Qiangning Hong~ 如果不希望在authentication组件里面自己解析request的话,可以在authentication middleware前面套一个解析request的middleware,比如webob。

          • 这样这个authentication middleware只依赖于这个request parser middleware,而不是依赖于整个框架。
          • 所有其他使用那个request parser middleware,也就是webob的wsgi框架都可以使用这些authentication middleware,可重用性显然要好很多。
          • BTW,我是希望看到request对象标准化的,webob很有这方面的潜质。

  • 所以我说Pylons社区推进routing标准化是一个创举。

    • 可以想象,如果request对象也标准化了,比如都使用和webob相同的接口,那各个框架之间的鸿沟又可以再大大缩小一步,wsgi组件在不同框架间可重用的程度也可以大大增加。

for UliWeb

Limodou
  • 谢谢,这下就清楚许多了。
  • 按上面的回答,uliweb中的是Dispatcher(类名都是这么起的),所以它本身是wsgi的。
  • 在uliweb的前段时间,还可以通过config.py来添加在执行Dispatcher前的wsgi序列,不过现在去了,因为修改了许多地方,加回来也很容易。
  • 因此我想uliweb会同时支持wsgi middleware和django-like middleware。
  • 因为django-like middleware更多是在针对request, response的处理,目标比较统一。
  • 对于wsgi的互用,我提第4个问题:
    • 不同的wsgi middleware在不同的项目中是如何互用的?

      • 是不是目前都是需要基于paster才可以?有共它的方式,还是根本不存在问题。
      • 因为我看到paster是可以在创建wsgi middleware时引入一些local_conf的,这个倒是可以继续用,自已写一个简单的装入器。其它的有没有类似要依赖于paster的东西呢?

复用vs易用

UliWeb 对策
  • 现在想一想wsgi就是提供了一个标准的接口,可以在不同的组件之间进行互相调用。不过把view函数写成app还是感觉麻烦,一个hello在uliweb中只要:

   1 @expose('/myapp/hello/<name>')
   2 def hello(name):
   3    return 'hello, %s!' % name
  • 就可以了。按你的做法,say_hello其实是需要知道前面使用了selector的。这种我认为是一种假定的方式,可能会有问题。主要还是显得麻烦,对于开发者来说需要了解的东西太多。做为框架完全可以再简化。
  • 有时追求复用与代码简化是冲突的,而我是希望用户用起来简单,在一定程度上的复用。而且与业务相关的处理复用本来难度就很大,倒不如考虑将与业务无关的东西复杂,并且减少对外部环境的假定或依赖更好一些。当然,具体还要看到底是什么东西是可以复用,应该如何被复用,也不是件容易的事。
Qiangning Hong
  • 那个仅仅是selector的一个演示而已,一般情况下view里面是不需要使用selector.vars的。
  • 框架要干的事情就是把问题简化,比如用selector的作者写的另一个工具yaro的话,上面的例子就变成这样:

from yaro import Yaro

@Yaro
def hello_world(req):
   return "Hello World!"

from selector import Selector
s = Selector()
s.add('/myapp/hello/{name}', GET=say_hello)
from flup.server.scgi import WSGIServer
WSGIServer(s).run()
  • 完全还可以更简单。比如我前面贴的meilanweb.py里Controller.__call__里面那一堆就是为了让写应用的时候变简单的,写代码时只要操作webob的对象就可以了,不用涉及WSGI的细节。

WSGI (last edited 2009-12-25 07:17:11 by localhost)