代码改变世界

WSGI的由来

2013-10-20 21:33  程序猴  阅读(184)  评论(0)    收藏  举报

原文:WSGI-Gateway or Glue?

http://zoomq.qiniudn.com/ZQScrapBook/ZqFLOSS/data/20070108123245/index.html

相关的另一篇文章:Python Web 服务器网关接口

http://pep-3333-wsgi.readthedocs.org/en/latest/

Introduction

WSGI是由pje在寻找web server和python web app或framework之间简单而通用的接口的结果。原先是叫python web container interface。PEP333之前的讨论稿时2003.10.8张贴到python web sig中,经过了python社区很多方面的有效讨论和修改,它逐渐演变成Web Server Gateway Interface,并最终提交成PEP 333。
那么WSGI试图要解决什么问题呢?一个python程序员面对许多web framework可供选择的时候,或者是framework中部分组件的时候,运行framework的web server是一个决定性的因素.对于一个framework作者,为所有的web server创建适配器是不可能完成的任务,如果framework作者面向WSGI API来编程,那只要web server有WSGI适配器的话,framework用户就能选择他们自己需要的web server了.

How WSGI works

PEP文档写的很好,非常容易理解,它是关于WSGI 知识最好的资料,而我们这里只是WSGI的基本知识。

WSGI规范了2个接口,一个接口是为web server与app的交互,另一个接口是为app与web server的交互。

对于web server与app交互,它是调用一个函数或一个callable对象,这些由app提供。这个函数或对象有2个位置参数,environ和start_response,environ参数必须是一个内建的python字典类型,包含有CGI形式的环境变量,比如REQUEST_METHOD,必要的WSGI变量,也包括server扩展变量。start_response则是一个python函数。

对于app与web server的交互,app准备它要发送的header信息,然后调用给定的start_response,并带有startus状态码和一个header信息列表,然后app准备响应信息的body,形式是字符串列表或是字符串迭代器,返回的响应信息被传给web server。

在接收到来自app的列表或迭代器中含有的响应信息,web server会把字符串以流形式发送给client。

来点代码吧,考虑一下下面简单的CGI脚本:

#!/usr/bin/env python
print 'Content-type: text/plain\n\n'
print 'Hello world!'


作为一个WSGI app,我们这样来编码:
(hello_world.py)

def application(environ, start_response):
    """Simplest possible application object"""
    status = '200 OK'
    response_headers = [('Content-type','text/plain')]
    start_response(status, response_headers)
    return ['Hello world!\n']


或者面向对象的方式则是:

class WSGIAppClass:
    """Produce the same output, but using a class
    """

    def __init__(self, environ, start_response):
        self.environ = environ
        self.start = start_response

    def __iter__(self):
        status = '200 OK'
        response_headers = [('Content-type','text/plain')]
        self.start(status, response_headers)
        yield "Hello world!\n"

现在要测试我们的代码,我们配置wsgiref模块中的SimpleHTTPServer。

from wsgiref import simple_server
import hello_world

httpd = simple_server.WSGIServer(('',8000),simple_server.WSGIRequestHandler)
httpd.set_app(hello_world.application)
httpd.serve_forever()
 

然后我把它部署到使用FastCGI的apache上:

from flup.server.fcgi import WSGIServer
import hello_world

WSGIServer(hello_world.application).run()
 

或者部署到使用ISAPI的IIS上:

import isapi_wsgi
import hello_world
# The entry points for the ISAPI extension.
def __ExtensionFactory__():
    return isapi_wsgi.ISAPISimpleHandler(hello = hello_world.application)

if __name__=='__main__':
    # If run from the command-line, install ourselves.
    from isapi.install import *
    params = ISAPIParameters()
    # Setup the virtual directories - this is a list of directories our
    # extension uses - in this case only 1.
    # Each extension has a "script map" - this is the mapping of ISAPI
    # extensions.
    sm = [
        ScriptMapParams(Extension="*", Flags=0)
    ]
    vd = VirtualDirParameters(Name="isapi-wsgi-hello",
                            Description = "ISAPI-WSGI Hello WOrld",
                            ScriptMaps = sm,
                            ScriptMapUpdate = "replace"
                            )
    params.VirtualDirs = [vd]
    HandleCommandLine(params)

ISAPI上的部署有些例外,在其他的WSGI webserver适配器则是相当简单的脚本。

为了更好地理解一个webserver适配器是如何实现的,pep 333文档上描述的CGI适配器是最好的起点。

WSGI Web Server Implementations



WSGI Enabled Frameworks



WSGI Middleware

在过去的6个月里,WSGI的讨论和开发已经开始聚焦于WSGI Middleware上了,也就是使用python组件,这些组件是支持WSGI app和Server API的,把这些组件用pipeline管道线连接起来创建一个web app。下面是一个简单的WSGI Authentication组件,我们用它来包装一个已经存在的WSGI app外:

class AuthenticationMiddleware:
    def __init__(self, app, allowed_usernames):
        self.app = app
        self.allowed_usernames = allowed_usernames

    def __call__(self, environ, start_response):
        if environ.get('REMOTE_USER','anonymous') in self.allowed_usernames:
            return self.app(environ, start_response)
        start_response(
            '403 Forbidden', [('Content-type', 'text/html')])
        return ['You are forbidden to view this resource']
 

现在部署一下这个代码,就用PEP 333上的WSGI CGI适配器来运行它,

#!/usr/local/bin/python2.4
import hello_world
import middleware

allowed_users = ['guido','monty']

if __name__ == '__main__':
    from cgi_wsgi import run_with_cgi
    run_with_cgi(middleware.AuthenticationMiddleware(
        hello_world.application,allowed_users))

(注:我还是用wsgiref的Simple HTTP Server)
WSGIUtils,Python Paste,flup,Python Web Modules这些包都提供了middleware组件,有session management,error handling,authentication,compression和URL parsing。( 还有wsgi_xlst,pyfileserver,省略)

Configuration

在创建一个大规模的python web app中使用一些框架中立的组件时,会有一些麻烦和问题。组合所有的middleware组件的代码会显得很乱,看看这个假设的代码:

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

if __name__ == '__main__':
    app = Application()
    app_pipeline = configure(app)
    server = Server(app_pipeline)
    server.serve()
 

一旦把需要传入的参数加入,代码的维护和调试将会变成噩梦。开发一个标准的方式来配置WSGI app已经成为Python Web SIG讨论的话题。Ian Bicking已经开发了Paste Deployment,这是一个用来查找和配置WSGI app和server的系统。要让Paste Deployment来配置加载WSGI app,需要用一个app factory来包装WSGI app。

(hello_world.py)

def application(environ, start_response):
    """Simplest possible application object"""
    status = '200 OK'
    response_headers = [('Content-type','text/plain')]
    start_response(status, response_headers)
    return ['Hello world!\n']

def app_factory(global_config, **local_config):
    return application

配置文件(config.ini)

[app:main]
paste.app_factory = hello_world:app_factory

相应的代码是:

from paste.deploy import loadapp
wsgi_app = loadapp('config:/path/to/config.ini')

(注:这是uri,一定要用绝对路径,如我的机子上是=>d:\work\wsgi\config.ini)

运行环境的部署的WSGI代码,我们用FastCGI:

from flup.server.fcgi import WSGIServer
WSGIServer(wsgi_app).run()

(注:我还是用wsgiref的simple_server)

Paste Deploy配置文件有一些标签来定义多个application,global,local变量,filter和定义的pipeline。这个pipeline的定义是用来简化带有WSGI middleware的app的创建。把我们前面写的Authentication Middleware加入一个filter,这个是由paste deploy使用,我们需要创建一个filter factory。

(filter_auth.py)

def auth_filter_factory(global_conf, **local_conf):
    if local_conf.has_key('allowed_users'):
        allowed_users = local_conf['allowed_users']
    def filter(app):
        return AuthenticationMiddleware(app, allowed_users)
    return filter

然后我们用一个配置文件pipeline把这个AuthenticationMiddleware和其他的filter串起来:

[pipeline:main]
pipeline = errorhandler session ident auth hello

[app:hello]
paste.app_factory = hello_wsgi:app_factory

[filter:auth]
paste.filter_factory = filter_auth:auth_filter_factory
allowed_users = ['guido', 'monty']

[filter:ident]
....

(注:这里的配置要根据自己的机子实际情况来调整)

如果我们向变换另一个authentication方法,配置文件只要更改为另一个authentication filter就行了。

更多的配置信息,可以看Paste Deployment文档。

Conclusions

WSGI是什么,一个gateway还是glue?我想这篇文章已经说明WSGI的两方面的特点。它既有webserver和python web app之间交互良好的API,也能把框架中立的组件glue粘合在一起。......