1. 程式人生 > >Wsgi研究

Wsgi研究

body com esp _for 規範 llb todo eas 基本

//轉載自http://blog.kenshinx.me/blog/wsgi-research/

wsgi是一個搞web開發的pythoner必須了解的內容,之前也零散的看過一些文章,但總感覺好多概念很模糊。這幾天抽空又把相關內容好好整理了一下,把筆記貼出來,一些只言片語也許對某些正在研究這個的人有所幫助。
wsgi 是一個 web 組件的接口規範.,wsgi將 web 組件分為三類: web服務器,web中間件,web應用程序,下圖來自ibm developerworks,很好的說明了三者之間的關系。

技術分享

從上圖可以看出來,wsgi基本處理模式為 : WSGI Server -> (WSGI Middleware)*-> WSGI Application 。

下面分別來看這三個組件

WSGI Server/gateway

wsgi server可以理解為一個符合wsgi規範的webserver,接收request請求,封裝一系列環境變量,按照wsgi規範調用註冊的wsgi app,最後將response返回給客戶端。
文字很難解釋清楚wsgi server到底是什麽東西,以及做些什麽事情,最直觀的方式還是看wsgiserver的實現代碼。以Python自帶的wsgiref為例,wsgiref是按照wsgi規範實現的一個簡單wsgiserver。它的代碼也不復雜,下圖是我讀wsgiref代碼後整理的。

技術分享

通過這個圖可以看出來wsgi server 基本工作流程

  1. 服務器創建socket,監聽端口,等待客戶端連接。

  2. 當有請求來時,服務器解析客戶端信息放到環境變量environ中,並調用綁定的handler來處理請求。

  3. handler解析這個http請求,將請求信息例如method,path等放到environ中。

  4. wsgi handler再將一些服務器端信息也放到environ中,最後服務器信息,客戶端信息,本次請求信息全部都保存到了環境變量environ中。

  5. wsgi handler 調用註冊的wsgi app,並將environ和回調函數傳給wsgi app

  6. wsgi app 將reponse header/status/body 回傳給wsgi handler

  7. 最終handler還是通過socket將response信息塞回給客戶端。

WSGI Application

wsgi application就是一個普通的callable對象,當有請求到來時,wsgi server會調用這個wsgi app。這個對象接收兩個參數,通常為environ,start_response。environ就像前面介紹的,可以理解為環境變量,跟一次請求相關的所有信息都保存在了這個環境變量中,包括服務器信息,客戶端信息,請求信息。start_response是一個callback函數,wsgi application通過調用start_response,將response headers/status 返回給wsgi server。此外這個wsgi app會return 一個iterator對象 ,這個iterator就是response body。這麽空講感覺很虛,對著下面這個簡單的例子看就明白很多了。

下面這個例子是一個最簡單的wsgi app,引自http://www.python.org/dev/peps/pep-3333/

def simple_app(environ, start_response):
    status = ‘200 OK‘
    response_headers = [(‘Content-type‘, ‘text/plain‘)]
    start_response(status, response_headers)
    return [u"This is hello wsgi app".encode(‘utf8‘)]

我們再用wsgiref 作為wsgi server ,然後調用這個wsgi app,就能直觀看到一次request,response的效果,簡單修改代碼如下:

from wsgiref.simple_server import make_server

def simple_app(environ, start_response):
    status = ‘200 OK‘
    response_headers = [(‘Content-type‘, ‘text/plain‘)]
    start_response(status, response_headers)
    return [u"This is hello wsgi app".encode(‘utf8‘)]

httpd = make_server(‘‘, 8000, simple_app)
print "Serving on port 8000..."
httpd.serve_forever()

訪問http://127.0.0.1:8000 就能看到效果了。

此外,上面講到了wsgi app只要是一個callable對象就可以了,因此不一定要是函數,一個實現了call方法的實例也可以,示例代碼如下:

from wsgiref.simple_server import make_server

class AppClass:

    def __call__(self,environ, start_response):
        status = ‘200 OK‘
        response_headers = [(‘Content-type‘, ‘text/plain‘)]
        start_response(status, response_headers)
    return ["hello world!"]

app = AppClass()
httpd = make_server(‘‘, 8000, app)
print "Serving on port 8000..."
httpd.serve_forever()

WSGI MiddleWare

上面的application看起來沒什麽意思,感覺沒有太大用,但加上一層層的middleware包裝之後就不一樣了。一堆文字解釋可能還沒有一個demo更容易說明白,我寫了一個簡單Dispatcher Middleware,用來實現URL 路由:

from wsgiref.simple_server import make_server

URL_PATTERNS= (
    (‘hi/‘,‘say_hi‘),
    (‘hello/‘,‘say_hello‘),
    )

class Dispatcher(object):

    def _match(self,path):
        path = path.split(‘/‘)[1]
        for url,app in URL_PATTERNS:
            if path in url:
                return app

    def __call__(self,environ, start_response):
        path = environ.get(‘PATH_INFO‘,‘/‘)
        app = self._match(path)
        if app :
            app = globals()[app]
            return app(environ, start_response)
        else:
            start_response("404 NOT FOUND",[(‘Content-type‘, ‘text/plain‘)])
            return ["Page dose not exists!"]

def say_hi(environ, start_response):
    start_response("200 OK",[(‘Content-type‘, ‘text/html‘)])
    return ["kenshin say hi to you!"]

def say_hello(environ, start_response):
    start_response("200 OK",[(‘Content-type‘, ‘text/html‘)])
    return ["kenshin say hello to you!"]

app = Dispatcher()

httpd = make_server(‘‘, 8000, app)
print "Serving on port 8000..."
httpd.serve_forever()

上面的例子可以看出來,middleware 包裝之後,一個簡單wsgi app就有了URL dispatch功能。然後我還可以在這個app外面再加上其它的middleware來包裝它,例如加一個權限認證的middleware:

class Auth(object):
    def __init__(self,app):
        self.app = app

    def __call__(self,environ, start_response):
        #TODO
        return self.app(environ, start_response)

app = Dispatcher()
auth_app = Auth(app)

httpd = make_server(‘‘, 8000, auth_app)
print "Serving on port 8000..."
httpd.serve_forever()

經過這些middleware的包裝,已經有點框架的感覺了。其實基於wsgi的框架,例如paste,pylons就是這樣通過一層層middleware組合起來的。只是一個成熟的框架,這樣的middleware會有很多,例如:

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

只要這些Middleware符合wsgi規範,甚至還可以在各個框架之間組合重用。例如pylons的認證Middleware可以直接被TurboGears拿去使用。

Wsgi研究