Flask的上下文源碼剖析

先寫一段Flask程序flask

from flask import Flask

app = Flask(__name__)


@app.route('/')
def hello_world():
    return 'Hello World!'


if __name__ == '__main__':
    app.__call__  # 不加括號就不會調用,看源碼方便
    app.run()  # 請求一旦到來,就執行app.__call__()方法

請求一旦到來,就會執行app.__call__()方法,咱們先看__call__的源碼。瀏覽器

    def __call__(self, environ, start_response):
        """The WSGI server calls the Flask application object as the
        WSGI application. This calls :meth:`wsgi_app` which can be
        wrapped to applying middleware."""
        return self.wsgi_app(environ, start_response)

這段代碼中的註釋翻譯過來是這樣的:WSGI服務器調用Flask應用程序對象做爲WSGI應用程序。這就叫: meth : ` wsgi _ app ',它能夠打包應用中間件。"服務器

這裏面有envirron和start_response參數,是WSGI傳過來的,傳過來就處理好。而後看下一步,點擊wsgi_app看源碼cookie

 
 
def wsgi_app(self, environ, start_response):
ctx = self.request_context(environ) # 這裏又處理一次environ
error = None
try:
try:
ctx.push() # 這裏就是打包起來
response = self.full_dispatch_request() # 打包以後,要經過路由去找視圖函數
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)

wsgi_app方法裏面有一個ctx變量,這裏面的request_context裏面封裝了不少方法,裏面一個是處理request,一個處理session的。而且又處理了一次environ。session

看request_context的源碼。多線程

    def request_context(self, environ):

        return RequestContext(self, environ)

它裏面返回了一個RequestContext(self, enveron)對象。app

看RequestContext源碼ide

class RequestContext(object):

    def __init__(self, app, environ, request=None):  # 這裏面的envieon是不太完全的請求
        self.app = app
        if request is None:  # 看着一句條件,看參數request=None,知足條件
            request = app.request_class(environ)  # 若是request=None就走這一行,把原生的請求給它,讓他繼續加工
        self.request = request  # 看到這就知道了前面的ctx.request就等於Request(environ)這個對象
        self.url_adapter = app.create_url_adapter(self.request)
        self.flashes = None
        self.session = None  # ctx.session = None
        self._implicit_app_ctx_stack = []
self.preserved = False
self._preserved_exc = None
self._after_request_functions = []
self.match_request()

        它裏面有environ參數和request參數。看裏面的if語句,若是參數request是空,就把原生的請求給request_class這個類繼續加工。裏面還有一個self.session,而且是空的。因此ctx.request就等於Request(environ)這個對象,ctx.session = None。而且ctx = self.request_context(environ)這一行代碼不只建立了一個對象,還實現了一個路由匹配,由於init裏面還有一行代碼self.match_request(),這個就是經過路由來找視圖函數,這裏就很少說了。綜上所述request.context也就是ctx封裝了處理request和session的方法。函數

 再看wsgi_app方法裏面有ctx.push(),這裏就是打包。點擊puth看源碼url

    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)  # 這就是往裏放

        if self.session is None:  # 打包以後,這時的session仍是空的,知足條件,就給session從新賦值
            session_interface = self.app.session_interface
            self.session = session_interface.open_session(
                self.app, self.request
            )

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

_request_ctx_stack.push(self)這一行代碼就是往裏放,放的時候若是遇到多線程確定有隔離處理,因此它裏面確定有相似threading.local()方法,local對象它自動就幫你進行數據隔離了。

點擊_request_ctx_stack看源碼。

_request_ctx_stack = LocalStack()

點擊LocalStack看源碼

class LocalStack(object):

def __init__(self):
self._local = Local()

def __release_local__(self):
self._local.__release_local__()

def _get__ident_func__(self):
return self._local.__ident_func__

def _set__ident_func__(self, value):...

def __call__(self):...

def push(self, obj):
"""Pushes a new item to the stack"""
rv = getattr(self._local, 'stack', None)
if rv is None:
self._local.stack = rv = [] # 若是沒有,就給stack建立一個空列表
rv.append(obj)
return rv

def pop(self):...

能夠看到它裏面又Local()對象,再看它的push方法,rv = getattr(self._local, 'stack', None)能夠看到要執行這個push方法的時候要去它的self._local對象找它的stack。這就至關於,當線程進來的時候,會先拿到它的惟一標識,而後找它的stack,若是有就使用,沒有就給它的stack建立一個空列表,把ctx放到列表裏面,ctx裏面有他本身的request和sesson,這就是打包放在這了。

# 結構就是這個形式
{
    線程一的惟一標識: {stack: [ctx(request, session), ]},
    線程二的惟一標識: {stack: [ctx(request, session), ]},
}

打包完以後,session仍是空的,接下來就是給session從新賦值了。

        _request_ctx_stack.push(self)  # 這就是往裏放

        if self.session is None:  # 打包以後,這時的session仍是空的,知足條件,就給session從新賦值
            session_interface = self.app.session_interface
            self.session = session_interface.open_session(
                self.app, self.request
            )

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

 打包以後,session也賦值了,接下來繼續看源碼。

def wsgi_app(self, environ, start_response):
    ctx = self.request_context(environ)  # 這裏又處理一次environ
    error = None
    try:
        try:
            ctx.push()  # 這裏就是打包起來
            response = self.full_dispatch_request()  # 打包以後,要經過路由去找視圖函數

在wsgi_app方法裏面就該經過路由去找視圖函數了,點擊full_dispatch_request看它裏面封裝了什麼方法。

def full_dispatch_request(self):

self.try_trigger_before_first_request_functions()
try:
request_started.send(self)
rv = self.preprocess_request() # 這個就是來執行before_request1的
if rv is None: # 若是執行以後,沒有結果,就來執行咱們的視圖函數
rv = self.dispatch_request()
except Exception as e:
rv = self.handle_user_exception(e)
return self.finalize_request(rv)

其餘的先不看,先看它的preprocess_request,看它裏面封裝了什麼。

    def preprocess_request(self):

        bp = _request_ctx_stack.top.request.blueprint

        funcs = self.url_value_preprocessors.get(None, ())
        if bp is not None and bp in self.url_value_preprocessors:
            funcs = chain(funcs, self.url_value_preprocessors[bp])
        for func in funcs:
            func(request.endpoint, request.view_args)

        funcs = self.before_request_funcs.get(None, ())  # 在Flask函數執行以前,會執行一些特殊的裝飾器before_request
        if bp is not None and bp in self.before_request_funcs:
            funcs = chain(funcs, self.before_request_funcs[bp])
        for func in funcs:
            rv = func()
            if rv is not None:
                return rv

因此preprocess_request就是來執行before_request的。

def full_dispatch_request(self):

    self.try_trigger_before_first_request_functions()
    try:
        request_started.send(self)
        rv = self.preprocess_request()  # 這個就是來執行before_request1的
        if rv is None:  # 若是執行以後,沒有結果,就來執行咱們的視圖函數
            rv = self.dispatch_request()  # 執行視圖函數
    except Exception as e:
        rv = self.handle_user_exception(e)
    return self.finalize_request(rv)  # 函數處理完,就該回去了,那麼接下來作什麼?看下面源碼流程

若是執行以後沒什麼結果,就去執行咱們的視圖函數。

無論函數執行結果怎樣,咱們都會拿到一個rv。函數執行以後,會有一個返回值,那咱們點擊self.finalize_request來看返回什麼。

    def finalize_request(self, rv, from_error_handler=False):

        response = self.make_response(rv)
        try:
            response = self.process_response(response)
            request_finished.send(self, response=response)
        except Exception:
            if not from_error_handler:
                raise
            self.logger.exception('Request finalizing failed with an '
                                  'error while handling an error')
        return response

裏面有一個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.session_interface.save_session(self, ctx.session, response)
        return response

裏面有after_request和save_session,原來咱們已經存了用戶的數據,這裏是拿到數據返回給用戶瀏覽器。

def full_dispatch_request(self):

    self.try_trigger_before_first_request_functions()
    try:
        request_started.send(self)
        rv = self.preprocess_request()  # 這個就是來執行before_request1的
        if rv is None:  # 若是執行以後,沒有結果,就來執行咱們的視圖函數
            rv = self.dispatch_request()  # 執行視圖函數
    except Exception as e:
        rv = self.handle_user_exception(e)
    return self.finalize_request(rv)  # 函數處理完,就該回去了,這裏面作的就是執行了after、_request和save_session,把手裏現有的值返回就好了

接下來咱們須要往回看。

def wsgi_app(self, environ, start_response):
ctx = self.request_context(environ) # 這裏又處理一次environ
error = None
try:
try:
ctx.push() # 這裏就是打包起來
response = self.full_dispatch_request() # 打包以後,要經過路由去找視圖函數
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) # 這裏面有一個ctx.auto_pop

給用戶返回數據後有個finally,裏面還有個ctx.auto_pop咱們進去看看它作了什麼

    def auto_pop(self, exc):
        if self.request.environ.get('flask._preserve_context') or \
           (exc is not None and self.app.preserve_context_on_exception):
            self.preserved = True
            self._preserved_exc = exc
        else:
            self.pop(exc)

裏面有個self.pop,繼續看裏面的源碼。

class RequestContext(object):
   
    def __init__(self, app, environ, request=None):......

    def copy(self):......

    def match_request(self):......

    def push(self):......  # 剛開始傳入數據就是經過push

    def pop(self, exc=_sentinel):......  # 這個pop就是刪掉用戶請求進來的信息

就是把線程一的信息刪掉,接下來執行線程二,這樣用戶請求進來的數據就不會混到一塊了。

# 結構就是這個形式
{
    線程二的惟一標識: {stack: [ctx(request, session), ]},
}

綜上,能夠總結爲三個階段:

第一階段:將ctx(request/session)放到Local對象裏面(Local()會給每個線程開闢一個空間來存數據)

第二階段:視圖函數導入,處理request和session

第三階段:請求處理完畢,獲取session保存到cookie裏面,而後刪掉ctx

相關文章
相關標籤/搜索