首頁(yè)技術(shù)文章正文

Python Web 應(yīng)用:WSGI基礎(chǔ)

更新時(shí)間:2018-08-16 來(lái)源:黑馬程序員人工智能+Python培訓(xùn)學(xué)院 瀏覽量:

 

在Django,F(xiàn)lask,Bottle和其他一切Python web 框架底層的是Web Server Gateway Interface,簡(jiǎn)稱WSGI。WSGI對(duì)Python來(lái)說(shuō)就像 Servlets對(duì)Java一樣——一種用于web服務(wù)器并允許不同web服務(wù)器和應(yīng)用框架基于通用API交互的通用規(guī)范。然而,對(duì)于大多數(shù)事情,Python版本實(shí)現(xiàn)相當(dāng)簡(jiǎn)單。

WSGI被定義在PEP 3333協(xié)議里面,如果在讀完本文之后你想學(xué)到更多東西的話,作者建議讀者先閱讀一下簡(jiǎn)介。

本文將從一個(gè)應(yīng)用開(kāi)發(fā)者的角度來(lái)向你介紹WSGI說(shuō)明,并且向你展示怎樣直接通過(guò)WSGI來(lái)開(kāi)發(fā)應(yīng)用程序(如果你迫不及待的話)。

你的第一個(gè)WSGI應(yīng)用

下面是最基本的Python web應(yīng)用:

def app(environ, start_fn):    start_fn('200 OK', [('Content-Type', 'text/plain')])    return ["Hello World!\n"]

就是這樣!整個(gè)文件。先命名為app.py然后在任何WSGI可編譯服務(wù)器上面運(yùn)行,然后你就可以得到一個(gè)Hello World并伴隨一個(gè)200的響應(yīng)狀態(tài)碼。你可以使用gunicorn來(lái)完成,通過(guò)pip(pip install gunicorn)來(lái)安裝并執(zhí)行g(shù)unicorn app:app。這條命令告訴gunicorn從應(yīng)用模塊里的應(yīng)用變量去獲取可調(diào)用的WSGI。

剛才,十分興奮吧。僅僅三行代碼就可以運(yùn)行一個(gè)應(yīng)用?那一定是某種意義上的記錄(不包括PHP,因?yàn)閙od_php在起作用)。我敢打賭你現(xiàn)在一定想更加深入了解下去了。

所以一個(gè)WSGI應(yīng)用最重要的部分是什么呢?

一個(gè)WSGI應(yīng)用是Python可調(diào)用的,就像一個(gè)函數(shù),一個(gè)類,或者一個(gè)有__call__方法的類實(shí)例

可調(diào)用的應(yīng)用程序必須接受兩個(gè)參數(shù):environ,一個(gè)包含必要數(shù)據(jù)的Python字典,start_fn,它自己是可調(diào)用的。

應(yīng)用程序必須能調(diào)用start_fn和兩個(gè)參數(shù):狀態(tài)碼(字符串),和一個(gè)頭部以兩個(gè)元組表述的列表。

應(yīng)用程序返回一個(gè)在返回體里包含bytes的方便的可迭代對(duì)象,流式的部分——例如,一個(gè)個(gè)只包含“Hello,World!”字符串的列表。(如果app是一個(gè)類的話,可以在__iter__方法里完成)

例如下面兩個(gè)例子和第一個(gè)是等價(jià)的:

 

class app(object):    def __init__(self, environ, start_fn):        self.environ = environ        self.start_fn = start_fn    def __iter__(self):        self.start_fn('200 OK', [('Content-Type', 'text/plain')])        yield "Hello World!\n"class Application(object):    def __call__(self, environ, start_fn):        start_fn('200 OK', [('Content-Type', 'text/plain')])        yield "Hello World!\n"app = Application()

你可能已經(jīng)開(kāi)始思考能用這些東西來(lái)做什么了,但是最可能相關(guān)的一項(xiàng)是用來(lái)寫(xiě)中間件。

讓它活躍起來(lái)

中間件是一種方便擴(kuò)展WSGI應(yīng)用功能性的方法。因?yàn)槟阒恍杼峁┮粋€(gè)可調(diào)用的對(duì)象,你可以任意把它包裹在其他函數(shù)里。

例如,假設(shè)我們想檢測(cè)一下environ里面的內(nèi)容。我們可以輕易地創(chuàng)建一個(gè)中間件來(lái)完成,如下所示:

import pprintdef handler(environ, start_fn):    start_fn('200 OK', [('Content-Type', 'text/plain')])    return ["Hello World!\n"]def log_environ(handler):    def _inner(environ, start_fn):        pprint.pprint(environ)        return handler(environ, start_fn)    return _innerapp = log_environ(handler)

這里,log_environ是一個(gè)返回函數(shù)的函數(shù),它在environ參數(shù)延遲原始回調(diào)之前裝飾該參數(shù)。

這樣寫(xiě)中間件的優(yōu)點(diǎn)是中間件和處理器不需要知道或者關(guān)心對(duì)方。你可以輕易地綁定log_environ到一個(gè)Flask應(yīng)用上面,例如,因?yàn)镕lask應(yīng)用是WSGI應(yīng)用。

其他一些有用的中間件設(shè)計(jì):

import pprintdef handle_error(handler):    def _inner(environ, start_fn):        try:            return handler(environ, start_fn)        except Exception as e:            print e  # Log error            start_fn('500 Server Error', [('Content-Type', 'text/plain')])            return ['500 Server Error']    return _innerdef wrap_query_params(handler):    def _inner(environ, start_fn):        qs = environ.get('QUERY_STRING')        environ['QUERY_PARAMS'] = urlparse.parse_qs(qs)        return handler(environ, start_fn)    return _inner

如果你不想讓你的文件有一個(gè)很大的金字塔底的話,你可以使用reduce一次應(yīng)用到多個(gè)中間件上。

# Applied from bottom to top on the way in, then top to bottom on the way outMIDDLEWARES = [wrap_query_params,               log_environ,               handle_error]app = reduce(lambda h, m: m(h), MIDDLEWARES, handler)

利用start_fn參數(shù)的優(yōu)點(diǎn)你也可以寫(xiě)裝飾響應(yīng)體的中間件。下面是一個(gè)內(nèi)容類型頭部是text/plain反轉(zhuǎn)輸出結(jié)果的中間件。

def reverser(handler):    # A reverse function    rev = lambda it: it[::-1]    def _inner(environ, start_fn):        do_reverse = []  # Must be a reference type such as a list        # Override start_fn to check the content type and set a flag        def start_reverser(status, headers):            for name, value in headers:                if (name.lower() == 'content-type'                        and value.lower() == 'text/plain'):                    do_reverse.append(True)                    break            # Remember to call `start_fn`            start_fn(status, headers)        response = handler(environ, start_reverser)        try:            if do_reverse:                return list(rev(map(rev, response)))            return response        finally:            if hasattr(response, 'close'):                response.close()    return _inner

由于start_fn和響應(yīng)體的分離有一點(diǎn)混亂,但是還是完美運(yùn)行的。

同時(shí)請(qǐng)注意,為了嚴(yán)格遵循WSGI規(guī)范,如果存在的話,我們必須在響應(yīng)體里檢查close方法并且調(diào)用它。WSGI應(yīng)用程序有可能返回一個(gè)write函數(shù)而不是一個(gè)調(diào)用處理器的可迭代對(duì)象,如果你想要你的中間件支持更早的應(yīng)用的話,你可能需要處理這個(gè)問(wèn)題。

一旦你開(kāi)始一點(diǎn)點(diǎn)把玩原生WSGI,你就開(kāi)始理解為什么Python有一堆web框架。WSGI讓從底層造一些東西變得十分簡(jiǎn)單。例如,你也許正在考慮下面的路由問(wèn)題:

routes = {    '/': home_handler,    '/about': about_handler,}class Application(object):    def __init__(self, routes):        self.routes = routes    def not_found(self, environ, start_fn):        start_fn('404 Not Found', [('Content-Type', 'text/plain')])        return ['404 Not Found']    def __call__(self, environ, start_fn):        handler = self.routes.get(environ.get('PATH_INFO')) or self.not_found        return handler(environ, start_fn)

如果你喜歡下面資源集合的靈活性的話,直接使用WSGI造輪子還是十分方便的。

模板庫(kù):投入任何你喜歡的模板(例如,Jinja2,Pystashe)然后從你的處理器返回渲染后的模板!

用一個(gè)庫(kù)幫助你的路由,如Routes或者Werkzeug’s routing。實(shí)際上,如果你想輕松使用WSGI的話,看一看Werkzeug吧。

使用任何Flask或者相似的數(shù)據(jù)庫(kù)遷移庫(kù)。

當(dāng)然,對(duì)于非專業(yè)的應(yīng)用,你也可能想使用一下框架,那樣的話,一些特殊的諸如此類的例子也可以得到合理解決。

關(guān)于服務(wù)器呢?

有許許多多服務(wù)WSGI應(yīng)用的方法。我們已經(jīng)討論過(guò)Gunicorn了,一個(gè)相當(dāng)好的選擇。uWSGI是另外一個(gè)不錯(cuò)的選擇。但是請(qǐng)確保在服務(wù)這些靜態(tài)東西之前設(shè)置好nginx一類的東西,并且你應(yīng)該有一個(gè)固定的開(kāi)始節(jié)點(diǎn)。

本文版權(quán)歸黑馬程序員人工智能+Python培訓(xùn)學(xué)院所有,歡迎轉(zhuǎn)載,轉(zhuǎn)載請(qǐng)注明作者出處。謝謝!

作者:黑馬程序員人工智能+Python培訓(xùn)學(xué)院

首發(fā):http://python.itheima.com/

分享到:
在線咨詢 我要報(bào)名
和我們?cè)诰€交談!