Flask 源碼之WSGI

WSGI

WSGI是python容器服務(wù)器和python web app通信協(xié)議標準

server負責去進行http層的處理,外部看來,server接受client端請求

返回http response

而中間處理過程則是調(diào)用web app

WSGI就是調(diào)用標準,規(guī)定了app暴露的接口標準和返回標準以及服務(wù)器傳參標準

這樣一來不同的app和server之間能夠相互統(tǒng)一交互

其中對app的要求是

  1. callable
  2. 結(jié)果iterable
  3. 接受server的 兩個參數(shù) : environ環(huán)境參數(shù), start_response 生成標準http包頭的函數(shù)

callable好理解,實際上的app可以理解為server處理http request 的邏輯處理函數(shù),所以要求一定是可以被服務(wù)器調(diào)用的,需要暴露調(diào)用接口

iterable 要和start_response結(jié)合起來理解, iterable 的結(jié)果 我們將其用data簡寫 data[], 實際上這個data[]是http body, server 不停迭代,data[] 然后傳輸內(nèi)容

和start_response相結(jié)合,start_response也是一個callable筛谚,接受兩個必須的參數(shù),status(HTTP狀態(tài))和response_headers(響應(yīng)消息的頭).實際上返回的就是http header

在wsgi app 內(nèi)部, return data[]之前要先調(diào)用,但是不會立刻返回包頭給 server, 而一定要等到迭代 data[]到第一個非空對象以后才會傳

換句話說,如果這時候迭代data[]出錯了,你先傳過去包頭狀態(tài)里寫的是200那不就完犢子了
所以一定要先等到迭代到第一個非空對象以后,這個頭才會被傳過去

這里表述有問題,是一定在傳data[]給server之前,先傳start_response,也就是先傳頭,但是這時候server沒有直接把這個header發(fā)給client,而是等body內(nèi)容
body一定不能為空,不能只給一個頭

這里start_response還有一個可選參數(shù)也就是exc_info,當處理請求的過程遇到錯誤時停忿,這個參數(shù)會被設(shè)置驾讲,同時調(diào)用 start_response.如果這時候headers 還在 start_response內(nèi)沒出來呢,可以用這個參數(shù)直接去設(shè)置header 頭, 也就是把status code改為其他錯誤代碼
如果已經(jīng)輸出到server了,則需要raise 一個 error 讓 服務(wù)器 去處理,跳出應(yīng)用.

為了避免循環(huán)引用,start_response實現(xiàn)時需要保證 exc_info在函數(shù)調(diào)用后不再包含引用席赂。 也就是說start_response用完 exc_info后吮铭,需要保證執(zhí)行一句

exc_info = None

釋放掉引用.

下面來一個示例簡陋的WSGI程序

def application(environ, start_response): 
    status = '200 OK' 
    output = 'World!' 
    response_headers = [('Content-type', 'text/plain'), 
                        ('Content-Length', str(12)] 
    write = start_response(status, response_headers) 
    write('Hello ') 
    return [output]

關(guān)于這個write,我看了一下start_response的返回對象也就是理論上的header,實際也是一個callable,
形式為write(body_data)
不過理論上,下面這么寫應(yīng)該更合理

def application(environ, start_response): 
    status = '200 OK' 
    output = 'Hello,World!' 
    response_headers = [('Content-type', 'text/plain'), 
                        ('Content-Length', str(12)] 
    start_response(status, response_headers) 
    return [output]

output就是http body
startresponse就是header,這樣子更易接受
以上參考:
wsgiref 源代碼分析 --start_response()

進一步理解WSGI對app端的定義

  1. callable
  2. return iterable
  3. 接受environ,start_response

callable一定是function么? 類或是對象實現(xiàn)了callable不也可以?
iterable 一定要是[],{}這種基本數(shù)據(jù)結(jié)構(gòu)么?實現(xiàn)iter不也可以?

至于接收參數(shù)就更簡單了

所以從wsgi規(guī)則上,我們可以定義出的不止是app可以是對象,或是直接是類如下

# 1. 可調(diào)用對象是一個函數(shù)
def application(environ, start_response):

   response_body = 'The request method was %s' % environ['REQUEST_METHOD']

   # HTTP response code and message
   status = '200 OK'

   # 應(yīng)答的頭部是一個列表,每對鍵值都必須是一個 tuple颅停。
   response_headers = [('Content-Type', 'text/plain'),
                       ('Content-Length', str(len(response_body)))]

   # 調(diào)用服務(wù)器程序提供的 start_response谓晌,填入兩個參數(shù)
   start_response(status, response_headers)

   # 返回必須是 iterable
   return [response_body]    

# 2. 可調(diào)用對象是一個類
class AppClass:
    """這里的可調(diào)用對象就是 AppClass 這個類,調(diào)用它就能生成可以迭代的結(jié)果癞揉。
        使用方法類似于: 
        for result in AppClass(env, start_response):
             do_somthing(result)
    """

    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"

# 3. 可調(diào)用對象是一個實例 
class AppClass:
    """這里的可調(diào)用對象就是 AppClass 的實例纸肉,使用方法類似于: 
        app = AppClass()
        for result in app(environ, start_response):
             do_somthing(result)
    """

    def __init__(self):
        pass

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

嚴格意義講,這個標準是允許嵌套的,可以更進一步

也就是server調(diào)用一個app,但是app繼續(xù)往下調(diào)用app.
從結(jié)果來看,中間這個app是一個中間件,對服務(wù)器來說是app,對app來說是服務(wù)器.

PEP333給這個嵌套就定義為中間件,并給出了假設(shè)的場景

  • 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
  • Perform content postprocessing, such as applying XSL stylesheets

直接翻譯一下

  • 根據(jù) url 把請求給到不同的客戶端程序(url routing),在把environ 改寫以后
  • 允許多個客戶端程序/web 框架同時運行,就是把接到的同一個請求傳遞給多個程序喊熟。
  • 負載均衡和遠程處理:把請求在網(wǎng)絡(luò)上傳輸
  • 應(yīng)答的過濾處理

PEP333直接給了一個中間件使用例子,但是不是那么多直觀
大概意思是先實現(xiàn)了一個迭代器類LatinIter,然后實現(xiàn)了一個類Latinator,在這個類中實現(xiàn)了callable,設(shè)置了對environ和start_response的處理
并且指定了接下來要調(diào)用的可能的self.app

最后實驗調(diào)用這個Lationtor作為中間件,處理foo_app
服務(wù)器環(huán)境是cgi

PEP333
這里面同時演示說明了,處理response頭的過程中如果出意外了該怎么辦.

from piglatin import piglatin

class LatinIter:

    """Transform iterated output to piglatin, if it's okay to do so

    Note that the "okayness" can change until the application yields
    its first non-empty string, so 'transform_ok' has to be a mutable
    truth value.
    """

    def __init__(self, result, transform_ok):
        if hasattr(result, 'close'):
            self.close = result.close
        self._next = iter(result).next
        self.transform_ok = transform_ok

    def __iter__(self):
        return self

    def next(self):
        if self.transform_ok:
            return piglatin(self._next())
        else:
            return self._next()

class Latinator:

    # by default, don't transform output
    transform = False

    def __init__(self, application):
        self.application = application

    def __call__(self, environ, start_response):

        transform_ok = []

        def start_latin(status, response_headers, exc_info=None):

            # Reset ok flag, in case this is a repeat call
            del transform_ok[:]

            for name, value in response_headers:
                if name.lower() == 'content-type' and value == 'text/plain':
                    transform_ok.append(True)
                    # Strip content-length if present, else it'll be wrong
                    response_headers = [(name, value)
                        for name, value in response_headers
                            if name.lower() != 'content-length'
                    ]
                    break

            write = start_response(status, response_headers, exc_info)

            if transform_ok:
                def write_latin(data):
                    write(piglatin(data))
                return write_latin
            else:
                return write

        return LatinIter(self.application(environ, start_latin), transform_ok)


# Run foo_app under a Latinator's control, using the example CGI gateway
from foo_app import foo_app
run_with_cgi(Latinator(foo_app))

我覺得上面這個例子不夠直觀,同時驗證了好幾條,我們看下面這個例子就是將路由設(shè)置為中間件,更容易理解

class Router(object):
    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

router = Router()

## 上面是中間件router,實際是一個wsgi app

#here is the application
@router('/hello')    #調(diào)用 route 實例柏肪,把函數(shù)注冊到 paht_info 字典
def hello(environ, start_response):
    status = '200 OK'
    output = 'Hello'
    response_headers = [('Content-type', 'text/plain'),
                        ('Content-Length', str(len(output)))]
    write = start_response(status, response_headers)
    return [output]

@router('/world')
def world(environ, start_response):
    status = '200 OK'
    output = 'World!'
    response_headers = [('Content-type', 'text/plain'),
                        ('Content-Length', str(len(output)))]
    write = start_response(status, response_headers)
    return [output]

#here run the application
result = router.route(environ, start_response)
for value in result: 
    write(value)

以上來自博客python wsgi簡介
我覺得更容易理解一些
這里還有一點,我看到這個路由實現(xiàn)的時候驚呆了,懷疑是否flask里的路由也是這么實現(xiàn)的,hh但不是,
那么為什么不這么做呢?
這就是一個問題了,我需要問問老師,
效率問題么?還是這么一來解耦太厲害了?

但確實前后端分離中,后端只負責開發(fā)RESTfulAPI的話,連路由都不用寫的,只需要處理數(shù)據(jù)庫和暴露API給前段就好,node和vue自然會處理好路由.

我們下一步開始分析流程

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市芥牌,隨后出現(xiàn)的幾起案子烦味,更是在濱河造成了極大的恐慌,老刑警劉巖壁拉,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件谬俄,死亡現(xiàn)場離奇詭異柏靶,居然都是意外死亡琢融,警方通過查閱死者的電腦和手機号醉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進店門象颖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來冯遂,“玉大人批糟,你說我怎么就攤上這事甲抖∏陡伲” “怎么了在抛?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵笔诵,是天一觀的道長返吻。 經(jīng)常有香客問我,道長乎婿,這世上最難降的妖魔是什么测僵? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮谢翎,結(jié)果婚禮上捍靠,老公的妹妹穿的比我還像新娘。我一直安慰自己森逮,他們只是感情好榨婆,可當我...
    茶點故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著褒侧,像睡著了一般良风。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上闷供,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天烟央,我揣著相機與錄音,去河邊找鬼歪脏。 笑死疑俭,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的唾糯。 我是一名探鬼主播怠硼,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼移怯!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起这难,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤舟误,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后姻乓,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體嵌溢,經(jīng)...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡眯牧,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了赖草。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片学少。...
    茶點故事閱讀 39,919評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖秧骑,靈堂內(nèi)的尸體忽然破棺而出版确,到底是詐尸還是另有隱情,我是刑警寧澤乎折,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布绒疗,位于F島的核電站,受9級特大地震影響骂澄,放射性物質(zhì)發(fā)生泄漏吓蘑。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一坟冲、第九天 我趴在偏房一處隱蔽的房頂上張望磨镶。 院中可真熱鬧,春花似錦健提、人聲如沸琳猫。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽沸移。三九已至,卻和暖如春侄榴,著一層夾襖步出監(jiān)牢的瞬間雹锣,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工癞蚕, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蕊爵,地道東北人。 一個月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓桦山,卻偏偏與公主長得像攒射,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子恒水,可洞房花燭夜當晚...
    茶點故事閱讀 44,864評論 2 354

推薦閱讀更多精彩內(nèi)容