Flask框架從入門到精通之上下文(二十三)

〆 小源。發表於2019-05-04

知識點 1、請求上下文 2、應用上下文

一、概況

Flask從客戶端收到請求時,要讓檢視函式能訪問一些物件,這樣才能處理請求。請求物件是一個很好的例子,它封裝了客戶端傳送的HTTP請求。

要想讓檢視函式能夠訪問請求物件,一個顯而易見的方式是將其作為引數傳入檢視函式,不過這會導致程式中的每個檢視函式都增加一個引數,除了訪問請求物件,如果檢視函式在處理請求時還要訪問其他物件,情況會變得更糟。為了避免大量可有可無的引數把檢視函式弄得一團糟,Flask使用上下文臨時把某些物件變為全域性可訪問。

  • request 和 session 都屬於請求上下文物件。
    • request:封裝了HTTP請求的內容,針對的是http請求。舉例:user = request.args.get('user'),獲取的是get請求的引數。
    • session:用來記錄請求會話中的資訊,針對的是使用者資訊。舉例:session['name'] = user.id,可以記錄使用者資訊。還可以通過session.get('name')獲取使用者資訊。

二、問題

request作為全域性物件就會出現一個問題,我們都知道後端會開啟很多個執行緒去同時處理使用者的請求,當多執行緒去訪問全域性物件的時候就會出現資源爭奪的情況。也會出現使用者A的請求引數被使用者B請求接受到,那怎麼解決每個執行緒只處理自己的request呢?

在這裡插入圖片描述
解決這個問題就是用到threading.local物件,稱之為執行緒區域性變數。用於為每個執行緒開闢一個空間來儲存它獨有的值。它的內部是如何實現的呢?解決這個問題可以考慮使用使用一個字典,key執行緒的id,val為對應執行緒的變數。下面是自己實現Local。

import threading
import time

try:
    from greenlet import getcurrent as get_ident  # 協程
except ImportError:
    try:
        from thread import get_ident
    except ImportError:
        from _thread import get_ident  # 執行緒

class Local(object):

    def __init__(self):
        object.__setattr__(self, '__storage__', {})
        object.__setattr__(self, '__ident_func__', get_ident)
        # 上面等價於self.__storage__ = {};self.__ident_func__ = get_ident;
        # 但是如果直接賦值的話,會觸發__setattr__造成無限遞迴

    def __getattr__(self, name):
        try:
            return self.__storage__[self.__ident_func__()][name]
        except KeyError:
            raise AttributeError(name)

    def __setattr__(self, name, value):
        ident = self.__ident_func__()
        storage = self.__storage__
        try:
            storage[ident][name] = value
        except KeyError:
            storage[ident] = {name: value}

    def __delattr__(self, name):
        try:
            del self.__storage__[self.__ident_func__()][name]
        except KeyError:
            raise AttributeError(name)

local_values = Local()

def func(num):
    local_values.name = num
    time.sleep(0.1)
    print(local_values.name,threading.current_thread().name)

for i in range(1,20):
    t = threading.Thread(target=func,args=(i,))
    t.start()

print(local_values.__storage__)
複製程式碼

三、請求上下文

一、請求到來時:

  • 將初始的初始request封裝RequestContext物件ctx
 def request_context(self, environ):
        return RequestContext(self, environ)
複製程式碼
  • 藉助LocalStack物件將ctx放到Local物件中
_request_ctx_stack = LocalStack()
_request_ctx_stack.push(self)
複製程式碼

二、執行檢視時:

  • 匯入from flask import request,其中request物件是LocalProxy類的例項化物件
request = LocalProxy(partial(_lookup_req_object, 'request'))
複製程式碼
  • 當我們取request的值時,request.mehod -->執行LocalProxy的__getattr__
 def __getattr__(self, name):
        if name == '__members__':
            return dir(self._get_current_object())
        return getattr(self._get_current_object(), name)
複製程式碼
  • 當我們設定request的值時request.header = 'xxx' -->執行LocalProxy的__setattr__
 __setattr__ = lambda x, n, v: setattr(x._get_current_object(), n, v)
複製程式碼

說道底,這些方法的內部都是呼叫_lookup_req_object函式:去local中將ctx獲取到,再去獲取其中的method或header 三、請求結束:

  • ctx.auto_pop,鏈式呼叫LocalStack的pop,將ctx從Local中pop
 def pop(self, exc=_sentinel):
        """Pops the request context and unbinds it by doing that.  This will
        also trigger the execution of functions registered by the
        :meth:`~flask.Flask.teardown_request` decorator.

        .. versionchanged:: 0.9
           Added the `exc` argument.
        """
        app_ctx = self._implicit_app_ctx_stack.pop()

        try:
            clear_request = False
            if not self._implicit_app_ctx_stack:
                self.preserved = False
                self._preserved_exc = None
                if exc is _sentinel:
                    exc = sys.exc_info()[1]
                self.app.do_teardown_request(exc)

                # If this interpreter supports clearing the exception information
                # we do that now.  This will only go into effect on Python 2.x,
                # on 3.x it disappears automatically at the end of the exception
                # stack.
                if hasattr(sys, 'exc_clear'):
                    sys.exc_clear()

                request_close = getattr(self.request, 'close', None)
                if request_close is not None:
                    request_close()
                clear_request = True
        finally:
            rv = _request_ctx_stack.pop()

            # get rid of circular dependencies at the end of the request
            # so that we don't require the GC to be active.
            if clear_request:
                rv.request.environ['werkzeug.request'] = None

            # Get rid of the app as well if necessary.
            if app_ctx is not None:
                app_ctx.pop(exc)

            assert rv is self, 'Popped wrong request context.  ' \
                '(%r instead of %r)' % (rv, self)
複製程式碼

四、 應用上下文

from flask import g:在一次請求週期裡,用於儲存的變數,便於程式設計師傳遞變數的時候使用。

四個全域性變數原理都是一樣的

# globals.py
_request_ctx_stack = LocalStack()
_app_ctx_stack = LocalStack()
current_app = LocalProxy(_find_app)
request = LocalProxy(partial(_lookup_req_object, 'request'))
session = LocalProxy(partial(_lookup_req_object, 'session'))
g = LocalProxy(partial(_lookup_app_object, 'g'))
複製程式碼

當我們request.xxx和session.xxx的時候,會從_request_ctx_stack._local取對應的值

當我們current_app.xxx和g.xxx的時候,會從_app_ctx_stack._local取對應的值

  • current_app就是flask的應用例項物件
  • g變數是解決我們在一次請求過程傳遞引數的問題,我們可以往g變數塞很多屬性,在同一次請求中,可以從另一個函式中取出。當然也可以用函式引數這種方法解決,g變數適用於引數多的情況。
from flask import Flask, request, g

app = Flask(__name__)


@app.route('/')
def index():
    name = request.args.get('name')
    g.name = name
    g.age = 12
    get_g()
    return name


# 在一起請求中,可以用g變數傳遞引數
def get_g():
    print(g.name)
    print(g.age)


if __name__ == '__main__':
    # 0.0.0.0代表任何能代表這臺機器的地址都可以訪問
    app.run(host='0.0.0.0', port=5000)  # 執行程式

複製程式碼

五、多執行緒下的請求

當程式開始執行,並且請求沒到來的時候,就已經生成了兩個空的Local,即:

_request_ctx_stack = LocalStack()  ->LocalStack類中的__init__定義了Local物件
_app_ctx_stack = LocalStack()
複製程式碼

當同時有執行緒處理請求的時候,兩個上下文對應的Local物件變成如下:

_request_ctx_stack._local = {
    執行緒id1:{‘stack’;[ctx1]},  # 只放一個為什麼用list,其實是模擬棧
    執行緒id1:{‘stack’;[ctx2]},
   ... 
}

_app_ctx_stack._local = {
    執行緒id1:{‘stack’;[app_ctx1]},
    執行緒id1:{‘stack’;[app_ctx2]},
   ... 
}
複製程式碼

歡迎關注我的公眾號:

image

相關文章