應用服務器與WSGI協議以及flask後端框架總結(後端接收請求返回響應的整個流程)

上次遺留了兩個問題,先說一下本身的見解
問題:
1.明明一個線程只能處理一個請求,那麼棧裏的元素永遠是在棧頂,那爲何須要用棧這個結構?用普通變量不行嗎.
2._request_ctx_stack和_app_ctx_stack都是線程隔離的,那麼爲何要分開?
我認爲在web runtime的狀況下是能夠不須要棧這個結構的,即便是單線程下也不須要,本來我覺得在單線程下,當前一個請求阻塞後,後一個請求還會被推入棧中,結果並非這樣,這也就說明了,棧的結構和是否是單線程不要緊,爲了驗證這點,我寫了個簡單的接口驗證這點:python

from flask import Flask,_request_ctx_stack

app = Flask(__name__)


@app.route('/')
def index():
    print(_request_ctx_stack._local.__storage__)
    time.sleep(1)
    return '<h1>hello</h1>'

app.run(port=3000)

def wsgi_app(self, environ, start_response):程序員

ctx = self.request_context(environ)
ctx.push()
print(_request_ctx_stack._local.__storage__)

我在Flask類中的wsgi_app()方法中加了這一句print(_request_ctx_stack._local.__storage__),wsgi_app()是後端接收應用服務器發來的包裝好的WSGI請求的函數,後面會講到,因爲一個線程只能處理一個請求,因此結果應該是棧中永遠只有一個請求對象,在路由接口中我延時了1秒,假設成阻塞,看一下結果:web

* Running on http://127.0.0.1:3000/ (Press CTRL+C to quit)
{139851542578944: {'stack': []}}
127.0.0.1 - - [14/Apr/2018 14:31:17] "GET / HTTP/1.1" 200 -
{139851542578944: {'stack': []}}
127.0.0.1 - - [14/Apr/2018 14:31:18] "GET / HTTP/1.1" 200 -
{139851542578944: {'stack': []}}
127.0.0.1 - - [14/Apr/2018 14:31:19] "GET / HTTP/1.1" 200 -

每次棧中只有一個請求對象,這也就說明了棧這個結構和web runtime下的單線程無關,那麼就剩下非web runtime的狀況了,最多見的是離線測試:flask

from flask import Flask,_request_ctx_stack,_app_ctx_stack


app = Flask(__name__)
app2 = Flask(__name__)


def offline_test():
    with app.app_context():
        print(_app_ctx_stack._local.__storage__)
        with app2.app_context():
            print(_app_ctx_stack._local.__storage__)

    with app.app_context():
        with app.test_request_context():
            print(_request_ctx_stack._local.__storage__)
            with app.test_request_context():
                print(_request_ctx_stack._local.__storage__)

離線測試是單線程的,經過這個例子也能獲得第二的問題的答案,爲何要將請求和應用分開,一個緣由是flask支持多個app共存,這須要用到中間件,另外一個緣由是離線測試時,有可能只須要用到應用上下文,因此須要將二者分開,在離線測試時若是進行了嵌套則棧結構的特色就發揮了出來,看一下運行的結果:後端

{140402410657536: {'stack': []}}
{140402410657536: {'stack': [, ]}}
{140402410657536: {'stack': []}}
{140402410657536: {'stack': [, ]}}

結果顯而易見
總結一下:棧結構和分離請求和應用是爲了離線測試更加靈活
web應用服務器 WSGI 後端之間的關係
web應用服務器的做用是監聽端口,當接收到客戶端的請求後將請求轉化成WSGI格式(environ)而後傳給後端框架
應用服務器<----WSGI協議---->後端框架
WSGI是應用服務器和後端框架之間的橋樑,使得服務器和後端框架分離,各司其職,程序員也能專一於本身的邏輯
在WSGI中規定了每一個python web應用都須要是一個可調用的對象,即實現了__call__這個特殊方法,Flask就是一個可調用對象
web應用服務器從哪裏將包裝好的請求發送給後端
在flask中使用了werkzeug這個工具包,在werkzeug.serving中有一個類,class WSGIRequestHandler(BaseHTTPRequestHandler, object)
這個類提供了environ字典對象,定義了start_response()和run_wsgi()方法,在run_wsgi()中有一個execute(),看一下源碼:瀏覽器

def execute(app):
    application_iter = app(environ, start_response)  #從這裏發送到後端
    try:
        for data in application_iter:
            write(data)
        if not headers_sent:
            write(b'')
    finally:
        if hasattr(application_iter, 'close'):
            application_iter.close()
        application_iter = None

第一句application_iter = app(environ, start_response)就調用了Flask.__call__(),並將environ, start_response傳入,而Flask.__call__()就return了self.wsgi_app(),
這個wsgi_app(environ, start_response)是一個標準的請求處理函數,因此它就是整個後端處理請求的入口函數,environ是一個包含全部HTTP請求信息的字典對象,start_response是一個發送HTTP響應的函數,environ是從應用服務器傳過來的,start_response是定義好的,這些都不須要後端開發人員關心
總結一下:
1.WSGI規定了後端處理函數的格式,即須要接受environ,start_response這兩個參數,這兩個參數從應用服務器傳給後端框架
2.python web應用對象須要是可調用的,即實現了__call__方法,返回WSGI規定格式的後端處理函數來處理請求及返回響應
3.應用服務器會使用werkzeug.serving中的WSGIRequestHandler類中的相應方法,將http請求轉化成WSGI格式,因此說werkzeug是一個遵循WSGI協議的工具包,提供給應用服務器使用
後端處理請求返回響應整個流程
以前說到,後端處理請求的入口函數是wsgi_app(self,environ,start_response),先看下源碼:服務器

def wsgi_app(self, environ, start_response):
        ctx = self.request_context(environ)  #1
        ctx.push()  #2
        error = None
        try:
            try:
                response = self.full_dispatch_request()  #3
            except Exception as e:
                error = e
                response = self.handle_exception(e)
            except:
                error = sys.exc_info()[1]
                raise
            return response(environ, start_response)
        finally:
            if self.should_ignore_error(error):
                error = None
            ctx.auto_pop(error)

其中有三句比較關鍵,我寫了序號
第一句:self.request_context(environ),看下request_context這個方法:cookie

def request_context(self, environ):
        return RequestContext(self, environ)

簡而言之,傳入environ,初始化一個請求上下文對象並返回
第二句:ctx.push(),看下源碼:session

def push(self):
        top = _request_ctx_stack.top
        if top is not None and top.preserved:
            top.pop(top._preserved_exc)

        app_ctx = _app_ctx_stack.top
        if app_ctx is None or app_ctx.app != self.app:
            app_ctx = self.app.app_context()
            app_ctx.push()
            self._implicit_app_ctx_stack.append(app_ctx)
        else:
            self._implicit_app_ctx_stack.append(None)

        if hasattr(sys, 'exc_clear'):
            sys.exc_clear()

        _request_ctx_stack.push(self)

        self.session = self.app.open_session(self.request)
        if self.session is None:
            self.session = self.app.make_null_session()

簡而言之,推入應用上下文和請求上下文,若是設置了secret_key則開啓一個session,關於flask的session放到後面說
第三句:self.full_dispatch_request(),是處理請求的關鍵函數,看下源碼:app

def full_dispatch_request(self):
        """Dispatches the request and on top of that performs request
        pre and postprocessing as well as HTTP exception catching and
        error handling.

        .. versionadded:: 0.7
        """
        self.try_trigger_before_first_request_functions()
        try:
            request_started.send(self)
            rv = self.preprocess_request()  #function1
            if rv is None:
                rv = self.dispatch_request()  #function2
        except Exception as e:
            rv = self.handle_user_exception(e)
        return self.finalize_request(rv)  #function3

這個函數中嵌套了另外三個函數,預處理函數preprocess_request(),主處理函數dispatch_request()和最終處理函數finalize_request(rv)
1.preprocess_request()是處理被before_request裝飾器裝飾的函數
2.dispatch_request()匹配請求的URL,並返回視圖函數的返回值rv
3.finalize_request(rv)接受視圖函數的返回值,並生成響應,這裏有make_response和process_response這兩個函數,make_response生成響應對象,process_response對響應作一些處理,好比後面要講到的session
響應生成後,在wsgi_app中return response,最後調用ctx.auto_pop()將請求和應用上下文推出棧,return的response會經過start_response發送到應用服務器,並由其發送到客戶端,這樣一次請求就結束了.
最後說說session
flask中的session是client side session,說白了就是session會封裝在cookie中在最終響應時會發送給客戶端,而在服務器本地不會存儲,因此叫做client side session,要使用session須要設置secret_key這個配置,經過app.secret_key來設置,用來驗證簽名,等到下次客戶端發來帶有cookie的請求時,後端就能從生成對應的session中解析出帶有的信息,寫個簡單的應用來看下session怎麼用:

from flask import Flask,session


app = flask.Flask(__name__)
app.secret_key = 'gjx'


@app.route('/')
def index():
    if 'name' in session:
        print(session['name'])
    else:
        print('stranger')
    return '<h1>/</h1>'


@app.route('/')
def test(name):
    session['name'] = name
    print('session set successful')
    return '<h1>test</h1>'


app.run(port=3000)

跑起來後,在瀏覽器輸入127.0.0.1:3000/,會打印出stranger,
而後訪問127.0.0.1:3000/jx後,後端打印出session set successful,而且瀏覽器會收到服務器發來的cookie,
Set-Cookie:session=eyJuYW1lIjoiangifQ.DbNHuQ.MPZLWzoLdga2SPMg0plMYmKlJMc; HttpOnly; Path=/ 這是我測試時收到的,有三個字段,第一個是session的內容,第二個是時間戳,第三個是驗證信息
這時已經設置好了session,而且獲得了cookie,再次訪問127.0.0.1:3000/,後端打印出了jx,就是以前設置的值
若是對session內的值更改,則返回的cookie也會更改,那麼在那保存,在那建立session呢?
以前在分析後端請求流程是提到了,在RequestContext的push方法最後:

self.session = self.app.open_session(self.request)
        if self.session is None:
            self.session = self.app.make_null_session()

若是設置了secret_key則會執行open_session開啓一個session,那若是更改了在哪裏保存呢?
在finalize_request執行的self.process_response中:

def process_response(self, response):
        ctx = _request_ctx_stack.top
        bp = ctx.request.blueprint
        funcs = ctx._after_request_functions
        if bp is not None and bp in self.after_request_funcs:
            funcs = chain(funcs, reversed(self.after_request_funcs[bp]))
        if None in self.after_request_funcs:
            funcs = chain(funcs, reversed(self.after_request_funcs[None]))
        for handler in funcs:
            response = handler(response)
        if not self.session_interface.is_null_session(ctx.session):
            self.save_session(ctx.session, response)
        return response

在最後判斷若是session不是null session的話會執行self.save_session來保存更新session,在self.save_session中會調用response.set_cookie,flask中的session大概就是這樣總結一下:1.分析了應用服務器封裝好的environ從哪發送給後端2.分析了應用服務器 WSGI 後端之間的關係以及WSGI協議對接口的標準定義,使得後端人員只須要關心本身的邏輯3.分析了後端接收到應用服務器發來的WSGI請求以後的一系列處理流程,主要函數是wsgi_app(environ,start_response)4.最後簡單分析了flask中的session機制,它是client side session的.

相關文章
相關標籤/搜索