原理
Request
Flask
把前端傳過來的數(shù)據(jù)environ
封裝成了 flask.wrappers.Request
類
這個類的實例又是RequestContext
的request
屬性值
class RequestContext(object):
request=flask.wrappers.Request(environ)
當然實際的代碼是這樣
class RequestContext(object):
request=app.request_class(environ)
這個app.request_class
= flask.wrappers.Request
之所以這樣寫印叁,是為了擴展性情屹,你可以修改Flask
的request_class
屬性來自定義你的Request
類
from flask import g, request
def get_tenant_from_request():
auth = validate_auth(request.headers.get('Authorization'))
return Tenant.query.get(auth.tenant_id)
def get_current_tenant():
rv = getattr(g, 'current_tenant', None)
if rv is = None:
rv = get_tenant_from_request()
g.current_tenant = rv
return rv
例如
import flask
class MyFlask(flask.Flask):
request_class=MyRequest
class MyRequest(flask.wrappers.Request):
pass
繼續(xù)來看RequestContext
,這個類在源碼中實例化了
ctx=RequestContext(self,envirion)
所以诗良,也就是說以后我們只要拿到這個實例ctx
,然后訪問ctx.request
就相當于訪問flask.wrappers.Request
了舌狗,也就相當于可以訪問envirion
,俱两,而RequestContext
就是請求上下文饱狂。
那ctx
存儲在哪里呢?怎么訪問呢锋华?
RequestContext
存儲
實際上嗡官,ctx
存在棧結構中,也就是后進先出毯焕,這是為了處理一個客戶端請求需要多個ctx
的情況
用偽代碼表示就是
stack=[RequestContext(),RequestContext()]
而且衍腥,我們知道有的wsgi server
對于每個請求都開一個線程磺樱,因此為了處理多線程隔離的情況,這個棧結構又存在了local
中婆咸,這個local
數(shù)據(jù)結構類似ThreadLocal
竹捉,他們共同組成了LocalStack
用偽代碼表示就是
localstack={0:{"stack":stack}} # 0是線程id或者協(xié)程id
訪問
訪問ctx.request
也不是直接訪問的,是通過一個代理類尚骄,叫LocalProxy
块差,他是代理模式在flask
中的應用
具體來說你要訪問 ctx.request
的某個屬性,先訪問LocalProxy
的對應屬性倔丈,LocalProxy
幫你訪問
LocalProxy
代理了對 ctx.request
的所有操作
偽代碼就是
# 例如flask.wrapper.Request()有一個get_json()方法
# localproxy也實現(xiàn)這個方法憨闰,幫我們訪問
class LocalProxy(object):
def get_json(self):
# 從localstack中獲取請求上下文
ctx:RequestContext=local["這次請求的線程id"]["stack"].top
json_data=ctx.request.get_json()
return json_data
當然這個例子是不真實的,如果對于每個 flask.wrapper.Request
的方法我們都在 LocalProxy
實現(xiàn)一遍需五,那太麻煩了
request
我們經(jīng)常會引用這個request
對象鹉动,它實際上就是LocalProxy
的實例,位置在flask.globals.py
from flask import request
request = LocalProxy(partial(_lookup_req_object, "request"))
他代理了對 RequestContext.request
也就是flask.wrappers.Request
實例的操作
而current_app
和g
則分別代理了對AppContext.app
(也就是flask
實例)和 AppContext.g
的操作
實現(xiàn)
Stack
一個棧結構宏邮,一般要實現(xiàn) push,top,pop
這幾個方法
class Stack(object):
def __init__(self):
self.items = []
def push(self, item):
self.items.append(item)
def is_empty(self):
return self.items == []
def pop(self):
if self.is_empty():
return None
# 后進先出
return self.items[-1]
![image-20210123001932225](https://upload-images.jianshu.io/upload_images/9003674-0a0db7f8b46d9d24.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
Local
local
源碼泽示,作用就是線程或者協(xié)程隔離
class Local(object):
__slots__ = ("__storage__", "__ident_func__")
def __init__(self):
# 實例化的時候給self綁定__storage__屬性,初始值是{}
object.__setattr__(self, "__storage__", {})
# 實例化的時候給self綁__ident_func__屬性蜜氨,初始值是get_ident函數(shù)械筛,這個方法用于獲取當前線程或協(xié)程id
object.__setattr__(self, "__ident_func__", get_ident)
def __setattr__(self, name, value):
# 這個方法會在屬性被設置時調用
# 因此如果我們這樣操作
# s=Stack()
# s.a=1
# 那么self.__storage__屬性就變成了 {0:{"a",1}},0表示當前線程id
ident = self.__ident_func__()
storage = self.__storage__
try:
storage[ident][name] = value
except KeyError:
storage[ident] = {name: value}
def __getattr__(self, name):
# 獲取屬性時,該方法會被調用
# 容易看出飒炎,通過把線程或協(xié)程id設置為key埋哟,可以實現(xiàn)線程或寫成隔離
try:
return self.__storage__[self.__ident_func__()][name]
except KeyError:
raise AttributeError(name)
def __iter__(self):
# 迭代的時候會被調用
return iter(self.__storage__.items())
def __release_local__(self):
# 清空當前線程的堆棧數(shù)據(jù)
self.__storage__.pop(self.__ident_func__(), None)
def __delattr__(self, name):
# 刪除屬性時候會被調用
try:
del self.__storage__[self.__ident_func__()][name]
except KeyError:
raise AttributeError(name)
LocalStack
LocalStack
大概相當于
{0:{"stack":[ctx,]}}
class LocalStack(object):
def __init__(self):
# _local屬性是Local對象,相當于字典{}
# push方法就是給_local實例添加一個stack屬性厌丑,初始值是[]定欧,然后append
# {0:'stack':[]}
self._local = Local()
def __release_local__(self):
self._local.__release_local__()
@property
def __ident_func__(self):
return self._local.__ident_func__
@__ident_func__.setter
def __ident_func__(self, value):
object.__setattr__(self._local, "__ident_func__", value)
def push(self, obj):
rv = getattr(self._local, "stack", None)
if rv is None:
self._local.stack = rv = []
rv.append(obj)
return rv
def pop(self):
stack = getattr(self._local, "stack", None)
if stack is None:
return None
elif len(stack) == 1:
release_local(self._local)
return stack[-1]
else:
return stack.pop()
@property
def top(self):
try:
return self._local.stack[-1]
except (AttributeError, IndexError):
return None
def __call__(self):
def _lookup():
rv = self.top
if rv is None:
raise RuntimeError("object unbound")
return rv
return LocalProxy(_lookup)
值得注意的是,當我們執(zhí)行如下代碼
ls=LocalStack()
ls()
會調用 __call__
方法怒竿,返回的是目前棧頂對象的代理對象,棧頂對象例如RequestContext
LocalProxy
LocalProxy
就是代理對象了扩氢,它可以代理對RequestContext
的操作
class LocalProxy(object):
__slots__ = ("__local", "__dict__", "__name__", "__wrapped__")
def __init__(self, local, name=None):
object.__setattr__(self, "_LocalProxy__local", local)
def _get_current_object(self):
if not hasattr(self.__local, "__release_local__"):
return self.__local()
def __getattr__(self, name):
return getattr(self._get_current_object(), name)
原理很簡單耕驰,我們訪問 LocalProxy
的某個屬性,會調用 __getattr__
方法录豺,__getattr__
方法又會調用 _get_current_object
去獲取棧頂對象或者棧頂對象的屬性朦肘,例如獲取RequestContext
對象或者RequestContext.request
以request
對象為例
他是一個LocalProxy
的實例
request = LocalProxy(partial(_lookup_req_object, "request"))
LocalProxy
實例化傳入了一個偏函數(shù) _lookup_req_object
(偏函數(shù)作用就是固定函數(shù)的參數(shù)),這個函數(shù)的作用就是獲取棧頂?shù)?code>RequestContext對象的request
屬性双饥,也就是 flask.wrappers.Request()
def _lookup_req_object(name):
top = _request_ctx_stack.top
if top is None:
raise RuntimeError(_request_ctx_err_msg)
return getattr(top, name)
也就是說 LocalProxy
的__local
就是 partial(_lookup_req_object, "request")
那么 _get_current_object
實際上就是執(zhí)行 partial(_lookup_req_object, "request")()
來獲取棧頂對象
入棧
我們繼續(xù)來看wsgi_app
這個方法媒抠,我去掉了一些無關代碼,那些部分會在其他博文中介紹
def wsgi_app(self, environ, start_response):
# 調用了self.request_context這個方法
# 此方法把environ封裝成了RequestContext對象
ctx = self.request_context(environ)
def request_context(self, environ):
# 注意request_context是Flask的類方法咏花,那么self就是Flask的實例或者說對象
# 也就是說下面的 RequestContext 的實例化需要Flask實例和environ參數(shù)
return RequestContext(self, environ)
簡單看一下RequestContext
的定義趴生,位置在源碼的ctx.py
中
class RequestContext(object):
def __init__(self, app, environ, request=None, session=None):
self.app = app
if request is None:
request = app.request_class(environ)
self.request = request
回到wsgi_app
這個方法阀趴,我們看到接下來ctx.push
這一句調用了RequestContext
的push
方法,這個方法就是把自身也就是 RequestContext
實例壓入到 LocalStack
這個數(shù)據(jù)結構中
def wsgi_app(self, environ, start_response):
# ctx是 RequestContext 實例
ctx = self.request_context(environ)
error = None
try:
try:
# 只看這一句
ctx.push()
response = self.full_dispatch_request()
except Exception as e:
error = e
response = self.handle_exception(e)
except: # noqa: B001
error = sys.exc_info()[1]
raise
return response(environ, start_response)
finally:
if self.should_ignore_error(error):
error = None
ctx.auto_pop(error)
push
方法
def push(self):
top = _request_ctx_stack.top
_request_ctx_stack
的定義在globals.py
中苍匆,就是創(chuàng)建了一個空的本地棧
_request_ctx_stack = LocalStack()
它現(xiàn)在的狀態(tài)是這樣刘急,棧是空的
{0:'stack':[]}
如果棧是空的話,我們把self
也就是RequestContext
壓入棧(最后一句)浸踩,注意push
是RequestContext
的方法叔汁,所以self
就是RequestContext
的實例
def push(self):
top = _request_ctx_stack.top
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)
# 壓入棧
_request_ctx_stack.push(self)
我們還注意到這里還有一個_app_ctx_stack
,這也是LocalStack
检碗,位置在globals.py
据块,他現(xiàn)在也是空的
_app_ctx_stack = LocalStack()
只不過這個棧里面存儲的是應用上下文,類似下面的字典
{0:'stack':[AppContext]}
然后我們也執(zhí)行了app_ctx.push()
方法折剃,也就是把應用上下文壓入棧
到這里我們就知道了另假,執(zhí)行RequestContext
對象的push
方法會把RequestContext
的實例壓入_request_ctx_stack
中,還會把AppContext
的實例壓入_app_ctx_stack
中
為什么要用LocalProxy
我們常常需要在一個視圖函數(shù)中獲取客戶端請求中的參數(shù)微驶,例如url
浪谴,remote_address
我們當然可以每次手動獲取_request_ctx_stack
棧頂?shù)?code>RequestContext對象,然后調用RequestContext
的request
屬性因苹,但每次操作棧結構還是有點繁瑣苟耻,像下面這樣
from flask import Flask, request, Request
from flask.globals import _request_ctx_stack
flask_app = Flask(__name__)
@flask_app.route('/')
def hello_world():
req: Request = _request_ctx_stack.top.request
remote_addr=req.remote_addr
return "{}".format(remote_addr)
flask
的做法是使用LocalProxy
from flask import Flask, request
我們執(zhí)行request.remote_addr
就相當于執(zhí)行 _request_ctx_stack.top.request.remote_addr
那為什么用LocalProxy
而不是直接request=_request_ctx_stack.top.request
呢?
原因是這樣寫扶檐,項目run
的時候凶杖,這句 request=_request_ctx_stack.top.request
就已經(jīng)執(zhí)行了(因為被引用了),但是項目啟動的時候_request_ctx_stack.top
還是None
款筑,因為還沒有請求進來智蝠,push
方法還沒執(zhí)行。這就導致了request
固定成了None
奈梳,這顯然不行
而LocalProxy
重寫了__getattr__
方法杈湾,讓每次執(zhí)行 request.remote_addr
會先去 LocalStack
中拿到 RequestContext
,然后執(zhí)行 RequestContext.request.remote_addr
,獲取其他屬性也是一樣
也就是說攘须,代理模式延遲了 被代理對象的獲取漆撞,代理對象Localproxy
創(chuàng)建的時候不會獲取,獲取被代理對象屬性的時候才會獲取被代理對象
總結
到這里于宙,我們就可以看出flask
中的請求上下文是如何存儲和獲取的
請求上下文存儲在LocalStack
結構中浮驳,Local
是為了線程安全,LocalStack
是為了多請求上下文的場景
而獲取是通過LocalProxy
捞魁,使用代理模式是為了動態(tài)地獲取請求上下文至会,在訪問request屬性的時候,才會從棧中獲取真實的請求上下文谱俭,然后代理 屬性的獲取
flask
中還有應用上下文奉件,current_app
宵蛀,我們有時候會通過current_app.config
來獲取配置信息,原理和request
類似瓶蚂,只不過代理的是AppContext
對象
說了這么多糖埋,wsgi_app
的第一步,也就是請求的第一步窃这,就是先把請求上下文和應用上下文入棧