Flask中本地代理的使用

本地代理

當請求到來時應用上下文和程序上下文被推入本地棧中,全局變量current_app,request,g,session都可以使用了。以current_app爲例,current_app代表的是app這個程序實例,但和app並不是同一個類型。current_app和app都有同樣的功能,但是current_app和app不是一個對象。

from flask import Flask, current_app

app = Flask(__name__)

@app.route('/')
def hello_world():
    print(f"{app}  {type(app)}")
    print(f"{current_app}  {type(current_app)}")
    return f"Hello world!"

if __name__ == '__main__':
    app.run()
<Flask 'flask_demo'>  <class 'flask.app.Flask'>
<Flask 'flask_demo'>  <class 'werkzeug.local.LocalProxy'>

可以看出app和current_app都是flask的實例,但是兩者的類型是不一樣的。app就是flask實例本尊,是真的美猴王,而current_app是werkzeug模塊裏的本地代理對象,一個假美猴王。

爲了探究這個問題,可以從current_app的定義入手。
從current_app的定義來就能知道,不光是current_app是本地代理對象,request,session,g等都是代理對象。

def _lookup_req_object(name):
    top = _request_ctx_stack.top
    if top is None:
        raise RuntimeError(_request_ctx_err_msg)
    return getattr(top, name)


def _lookup_app_object(name):
    top = _app_ctx_stack.top
    if top is None:
        raise RuntimeError(_app_ctx_err_msg)
    return getattr(top, name)
    
def _find_app():
    top = _app_ctx_stack.top
    if top is None:
        raise RuntimeError(_app_ctx_err_msg)
    return top.app
    
_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'))

這裏出現一個重要的概念本地代理,首先要搞明白什麼是本地代理。

LocalProxy 介紹

LocalProxy 中文本地代理,和前面介紹的本地線程和本地棧類似,都可以根據線程隔離變量,同時還有代理的功能。
代理在生活中及其常見,比如現在的美團跑腿,你上班時間時間需要送一份文件給朋友,自己又抽不開身,這時可以預約一個全城送,付錢讓他給你送文件。這個代送小哥就是一個代理。

廣義的代理就是訪問對象無法直接訪問目標對象,代理對象作爲中介,將訪問傳遞給目標對象。

不使用代理
localstack獲取棧頂元素的方法就是通過一個函數返回localstack的top。current_app的定義中的_find_app就是獲取棧頂元素的函數。首先模擬不使用代理的方法來獲取棧頂元素。

from werkzeug.local import LocalStack, LocalProxy
test_stack = LocalStack()
test_stack.push({'abc': '123'})

def get_stack_top():
    return test_stack.top


item = get_stack_top()
print(item)

# 有新的元素入棧
test_stack.push({'abc': '1234'})
print(item)
{'abc': '123'}
{'abc': '123'}

當調用get_stack_top時,獲取的最新的棧頂元素item。有元素入棧之後,item就不再是棧頂元素了。根據flask中current_app的使用,無論何時current_app, 都是獲取棧頂元素。所以需要再次調用函數去獲取棧頂元素。

使用本地代理
使用本地代理就不需要每次調用函數去獲取棧頂元素,可以做到在app1入棧時current_app棧頂是app1,app2入棧時棧頂是app2。

from werkzeug.local import LocalStack, LocalProxy
test_stack = LocalStack()
test_stack.push({'abc': '123'})

def get_stack_top():
    return test_stack.top

item = LocalProxy(get_stack_top)
print(item)

# 當棧發生變化時,再次使用使用代理對象,仍然可以獲取到棧頂元素。
# 原理時每次訪問item時,localproxy都會調用get_stack_top去獲取棧頂
test_stack.push({'abc': '1234'})
print(item)
{'abc': '123'}
{'abc': '1234'}

將獲取棧頂元素的函數傳入LocalProxy,然後訪問LocalProxy實例item,每次都能獲取棧頂元素,而不需要再次調用獲取棧頂的函數get_stack_top。原理在於每次去訪問item時,本地代理都是調用get_stack_top函數去獲取棧頂,相當於幫我們去調用了函數。

小結

本地代理的使用是傳入要獲取棧頂的函數,當每次訪問本地代理對象時,本地代理調用函數獲取最新棧頂,減少了業務中調用函數的過程,是獲取棧頂元素的一種優雅方法。

上下文中的4種全局變量都是本地代理對象,任何時候訪問這4中變量時,本地代理能返回最新的棧頂元素。

系列總結

寫到這裏就將Flask中最核心的請求處理介紹完了。這個系列由淺入深,分別介紹了:

  1. 最簡單Flask程序
  2. Flask依賴的核心模塊werkzeug
  3. Flask請求數據的優雅傳遞
  4. Flask中本地棧的使用
  5. Flask中本地代理的使用

可以這個主題是Flask最值得探討賞析的技術之一,通過這個系列我學習到一些全新的知識,如全局變量的線程隔離,代理模式的優雅,Flask請求和非請求場景下業務的統一等。能夠弄明白這些知識雖然也花費了很多時間但是很值得。所謂山有路勤爲徑,學海無涯苦作舟。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章