码迷,mamicode.com
首页 > 编程语言 > 详细

Python 中的“CGI” 接口——WSGI

时间:2015-06-26 14:49:04      阅读:219      评论:0      收藏:0      [点我收藏+]

标签:

今天在 git.oschina 的首页上看到他们推出演示平台,其中,Python 的演示平台支持 WSGI 接口的应用。虽然,这个演示平台连它自己提供的示例都跑不起来,但是,它还是成功的勾起了我对 WSGI 的强烈好奇心。一番了解,对该机制的认识,总结如下。如有不妥,还望斧正。

 

为什么是 WSGI?

写过网页应用的各位亲,应该对 CGI 有了解,我们知道,CGI 的全程是“Common Gateway Interface”,即通用 Gateway Interface。没错,这里的 WSGI,就是只针对 Python的网页应用接口“Python Web Server Gateway Interface”。通过这样的类比,想必大家对他的地位就有所了解了。

它只是一个接口定义:它不负责服务器的实现,也不负责网页应用的实现,它只是一个两边接口方式的约定。所以,它并不是另一个 WEB 应用框架,而通常意义上的 WEB 应用框架,也只相当于 WSGI 网页应用端的一种实现。

这样做的好处是?PEP 0333 中的解释是,为了实现一个类似于 Java Servelet 的 API,使得遵循该接口的应用拥有更广泛的适用性。是的,有了该接口,你就不用去考虑,服务器对 Python 的支持到底是如何实现——直接用 Python 实现的服务器?服务器嵌入 Python?或者是通过网关接口(CGI, Fastcgi...)——应用程序都有很好的适用性。就像是今天故事的开始,我们遇到了云平台,它提供了对 WSGI 接口的支持,那么,只要应用是基于该 WSGI 的,那么应用就可以直接跑起来。

此外,WSGI 的设计,也提供了另外一种可能性,那就是中间件(middleware)。或者说,我们可以写一些对 server 和 application 都兼容的模块,我们可以把他们部署在 Server 端,也可以部署在 Application 端,完成比如缓存、字符编码转换、根据 url 做应用 routing 等功能。这种设计模式,是 WSGI 降低了 server 和 application 耦合度之后的产物,同时,它从另一个角度大大提升了设计的灵活性。

 

WSGI 实施概略

上一小节,简要对 WSGI 做了介绍。这里从 application、server、middleware 三个角度对 WSGI 稍微进行深入,使我们对它有一个更具体的印象。

1)Application 端

WSGI 要求,应用端必须提供一个可被调用的实体(PEP 0333 使用的是 Object,文档还特别解释这有别于Object instance),该实体可以是:一个函数(function)、一个方法(method)、一个类(class)、或者是有__call__方法的对象(Object instance)。

这里有两个网页应用端的实现示例,一个 function,一个 class:

def simple_app(environ, start_response):
    status = 200 OK
    response_headers = [(Content-type, text/plain)]
    start_response(status, response_headers)
    return [Hello world!\n]

上面的 function 并没有处理来自 server 端的 environ,只是直接对请求直接做了 “200 ok” 回应;需要注意的是,server 对他进行调用(call)后,它返回一个 list(用“[]”包含在内)以保证结果的 iterable。下面的 class 功能类似。下面的 AppClass 作为应用实体的情况下,在具体实施时,server 对类进行调用(call),其实是对他进行了例化(可以参考后面 server 端的实现代码),正如我们看到,这次调用(call)的返回值也是可迭代的——虽然只迭代一次。

class AppClass:

    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"
        """ In fact, the interator ‘ends‘ here because of no more yield field"""

使用对象作为应用实体时,可参考上面使用 function 作为实体的方法:为类添加 __call__ 方法,同时,其返回值需为 iterable(比如 return [ something ])。

给 application 的两个参数,是两个位置相关的参数(不是命名参数),分别是:一个存放了 CGI 环境变量的 dictionary object,和一个可调用实体(需要给它三个位置相关的参数,两个必须,一个可选)。

其中,可调用实体(前例中的 start_response)必须调用一次,两个必须的参数分别为“ HTTP Response的状态(str 类型)“ 和 “HTTP Response Header(list of tuples)“;一个可选的参数 exc_info,必须是 Python sys.exc_info() tuple,只有在出错需要显示错误信息时使用。完整调用:start_response(status, response_headers,exc_info).

 

2)Server 端

下面是从 PEP 0333 拿来的一个简单的 WSGI 容器,适用于 Python 作为 CGI 的应用框架。

import os, sys

def run_with_cgi(application):

    environ = dict(os.environ.items())
    environ[wsgi.input]        = sys.stdin
    environ[wsgi.errors]       = sys.stderr
    environ[wsgi.version]      = (1, 0)
    environ[wsgi.multithread]  = False
    environ[wsgi.multiprocess] = True
    environ[wsgi.run_once]     = True

    if environ.get(HTTPS, off) in (on, 1):
        environ[wsgi.url_scheme] = https
    else:
        environ[wsgi.url_scheme] = http

    headers_set = []
    headers_sent = []

    def write(data):
        if not headers_set:
             raise AssertionError("write() before start_response()")

        elif not headers_sent:
             # Before the first output, send the stored headers
             status, response_headers = headers_sent[:] = headers_set
             sys.stdout.write(Status: %s\r\n % status)
             for header in response_headers:
                 sys.stdout.write(%s: %s\r\n % header)
             sys.stdout.write(\r\n)

        sys.stdout.write(data)
        sys.stdout.flush()

    def start_response(status, response_headers, exc_info=None):
        if exc_info:
            try:
                if headers_sent:
                    # Re-raise original exception if headers sent
                    raise exc_info[0], exc_info[1], exc_info[2]
            finally:
                exc_info = None     # avoid dangling circular ref
        elif headers_set:
            raise AssertionError("Headers already set!")

        headers_set[:] = [status, response_headers]
        return write

    result = application(environ, start_response)
try: for data in result: if data: # don‘t send headers until body appears write(data) if not headers_sent: write(‘‘) # send headers now if body was empty finally: if hasattr(result, close): result.close()

上面的容器,大概实现了:a)将 CGI 环境变量放入 dictionary object (environ)中,供 Application 实体使用;b)定义了 start_response 方法,供 Application 实体调用;c)调用 application 实体,对 web 请求进行处理;d)将 application 的返回结果,以及通过 start_response 设置的 HTTP Response HEADER,写到 stdout ——像其他 CGI 一样,实际上是被发往网页。

 

3) 作为 middleware

因为 WSGI 的宽松耦合的特性,我们可以轻松的在 Application 和 Server 之前插入任何的中间插件,在不需要改动 Server 和 Application 的前提下,实现一些特殊功能。但是,这种放在 Server 和 Application “中间”的模块,并不是这里要讲的 middleware ;或者,这只能算是一种特殊的 middleware,因为它仅仅是实现了 PEP 0333 中 middleware 定义的 Application 侧的功能。这种仅实施在一侧的 middleware,需要在发布时,特别的声明。

PEP 0333 中约定,中间件是一些即可以在 Server 端实施,又可以在 Application 端实施的模块。所以,在设计的时候,对两边的特性都要做适当考虑。幸好,WSGI 接口设计的足够简单。

class Router():
    
    def __init__(self):
        self.path_info = {}

    def route(self, environ, start_response):
        application = self.path_info[environ[PATH_INFO]]
        return application(environ, start_response)

    def __call__(self, path):
        def wrapper(application):
            self.path_info[path] = application
        return wrapper
""" The above is the middleware"""
router
= Router() @router(/world) def world(environ, start_response): status = 200 OK output = World!start_response(status, response_headers) return [output] @router(/hello) def hello(environ, start_response): status = 200 OK output = Hello response_headers = [(Content-type, text/plain), (Content-Length, str(len(output)))] start_response(status, response_headers) return [output]

简单解释一下:

- 作为 Application 时,我们用 Router 实例化一个对象。然后对 “ PATH-APP “ 进行注册,根据不同的 PATH,我们要进一步选择哪个 App。接着,就是把 router.route() 喂给 Server ,作为 Application 侧的可调用实体。有请求到来时,根据已经注册的 “PATH-APP” 对选择应用并执行。

- Server 端类似,我们要先实例化并完成注册。然后,比如,拿我们上一小节实现的 WSGI 容器为例,我们需要修改 result = router.route(environ, start_response),同样完成了router的功能。

下面是另外一个,实现了 postprocessor 的一个例子,在 Application 返回的 HTTP Header 里面再加一个 Header。

def myapp(environ, start_response):
    response_headers = [(content-type, text/plain)]
    start_response(200 OK, response_headers)
    return [Check the headers!]

class Middleware:
    def __init__(self, app):
        self.wrapped_app = app

    def __call__(self, environ, start_response):
        def custom_start_response(status, headers, exc_info=None):
            headers.append((X-A-SIMPLE-TOKEN, "1234567890"))
            return start_response(status, headers, exc_info)
        return self.wrapped_app(environ, custom_start_response)

app = Middleware(myapp)

这里通过改写传递给 Application 的实体,实现了 postprocess 的目的。

 

其他资源:

 - WSGI 的一些详细资料,包括应用列表什么的:http://wsgi.readthedocs.org/en/latest/

 - 支持 WSGI 的多线程 WEB 服务器,基于SimpleHttpServer:http://www.owlfish.com/software/wsgiutils/

 - Paste 为构建以 WSGI 为基础的 WEB 应用程序或框架提供一个良好的基础

- 官方的 WSGI 实现参考:https://pypi.python.org/pypi/wsgiref

- 啄木鸟社区的 WSGI 中文 wiki:http://wiki.woodpecker.org.cn/moin/WSGI

- 和 Paste 一样有名的基本架构:https://pypi.python.org/pypi/Pylons/1.0

- 目前 Python 比较流行的三大 WEB 框架:TurboGears,Django,web2py。+1,代码在 K 级别的服务小框架:webpy。

Python 中的“CGI” 接口——WSGI

标签:

原文地址:http://www.cnblogs.com/pied/p/4597740.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!