1. 程式人生 > >flask0.1版本源碼淺析——請求上下文

flask0.1版本源碼淺析——請求上下文

函數 light host con contex 準備 patch remove quest

說先flask應用在請求時會調用 wsgi_app(self, environ, start_response) 這個方法

    def wsgi_app(self, environ, start_response):
        # ...
        with self.request_context(environ):
            rv = self.preprocess_request()
            if rv is None:
                rv = self.dispatch_request()
            response 
= self.make_response(rv) response = self.process_response(response) return response(environ, start_response)

這個函數的基本步驟是:

  1. 先是 打開request_context(environ), 然後我們就有了我們需要的“全局request”
  2. 然後preprocess_request()是處理數據前的準備
  3. 接下來調用dispatch_request()方法,這個方法會根據URL匹配的情況調用相關的視圖函數
  4. 之後調用response(environ, start_response)
    方法將響應發送回WSGI服務器
  5. 最後with語句結束,當前線程會被銷毀

request_context

可以看出,requests_context 方法會調用 _RequestContext(self, environ) 類, 所以我們看這個類就可以

    def request_context(self, environ):
        """Creates a request context from the given environment and binds
        it to the current context.  This must be used in combination with
        the `with` statement because the request is only bound to the
        current context for the duration of the `with` block.

        :params environ: a WSGI environment
        
""" return _RequestContext(self, environ)

_RequestContext(self, environ)

class _RequestContext(object):
    """The request context contains all request relevant information.  It is
    created at the beginning of the request and pushed to the
    `_request_ctx_stack` and removed at the end of it.  It will create the
    URL adapter and request object for the WSGI environment provided.
    """

    def __init__(self, app, environ):
        self.app = app
        self.url_adapter = app.url_map.bind_to_environ(environ)
        self.request = app.request_class(environ)
        self.session = app.open_session(self.request)
        self.g = _RequestGlobals()
        self.flashes = None

    def __enter__(self):
        _request_ctx_stack.push(self)

    def __exit__(self, exc_type, exc_value, tb):
        # do not pop the request stack if we are in debug mode and an
        # exception happened.  This will allow the debugger to still
        # access the request object in the interactive shell.
        if tb is None or not self.app.debug:
            _request_ctx_stack.pop()

其中對該類的屬性:

  • app —— 屬於當前Flask應用
  • url_adapter —— 創建一個 Map 實例,為了註冊路由使用
  • request —— 接收 environ 後創建了 Request 的實例,儲存請求信息
  • session —— 接收 request 信息後,創建Session的相關對象,儲存會話信息
  • g —— 存儲全局變量
  • flash —— 消息閃現

_request_ctx_stack

_request_ctx_stack = LocalStack()

_request_ctx_stack 是由 LocalStack 類實現的類似棧的結構。

我們通過 push(self) 可以看出,_request_ctx_stackrequest_context 存儲到裏面。存儲結構為

{14272: {stack: [<RequestContext http://localhost/ [GET] of test5>]}, 16696: {stack: [<RequestContext http://localhost/ [GET] of test5>]}, 16856: {stack: [<RequestContext http://localhost/ [GET] of test5>]}}

測試的代碼為

技術分享
from flask import Flask, _request_ctx_stack
import threading

app = Flask(__name__)

print _request_ctx_stack._local.__storage__


def foo():
    request_context = app.test_request_context()
    _request_ctx_stack.push(request_context)


q = []

for i in range(3):
    t = threading.Thread(target=foo)
    t.start()
    q.append(t)

for j in q:
    j.join()
View Code

這是一個字典形式的結構,鍵代表當前線程的數值,值代表當前線程存儲的變量,這樣很容易實現線程分離,從而使每個線程都可訪問自己的內的“全局變量”

current_app = LocalProxy(lambda: _request_ctx_stack.top.app)
request = LocalProxy(lambda: _request_ctx_stack.top.request)
session = LocalProxy(lambda: _request_ctx_stack.top.session)
g = LocalProxy(lambda: _request_ctx_stack.top.g)

LocalProxy 是一個 Local 對象的代理,負責把所有對自己的操作轉發給內部的 Local 對象。

我們可以看出,current_apprequestsessiong 都會是自己線程內的“全局變量”

with

這裏的 __enter____exit__ 方法是為了 wsgi_app 內 with 語句調用

flask0.1版本源碼淺析——請求上下文