Werkzeugs 是 Flask 的底層WSGI庫。
什麼是WSGI?
一段簡單的app:
def dispath_request(self, request):
return Response('Hello World!')
def wsgi_app(self, environ, start_response):
request = Request(environ)
response = self.dispath_request(request)
return response(environ, start_response)
environ: 包含全部的HTTP請求信息的字典,由WSGI Server
解包 HTTP 請求生成。
start_response:一個WSGI Server
提供的函數,調用可以返回相應的狀態嗎和HTTP報文頭,函數在返回前必須調用一次。
Flask的上下文對象
Flask有兩種Context(上下文),分別是
-
RequestContext 請求上下文;
- Request 請求的對象,封裝了Http請求(environ)的內容,生命週期請求處理完就結束了;
- Session 根據請求中的cookie,重新載入該訪問者相關的會話信息;
-
AppContext 應用上下文;
- g 處理請求時用作臨時存儲的對象。每次請求都會重設這個變量, 生命週期請求處理完就結束了;
- current_app 當前激活程序的程序實例,只要當前程序還在運行就不會失效。
flask 處理請求和響應的流程:
在 'flask/globals.py' 代碼中:
# context locals
_request_ctx_stack = LocalStack()
# LocalStack 是由werkzeug提供的棧結構類提供了push、pop等方法
# 並且Local對象是werkzeug開發的類似 thinking.local(用於隔離不同線程間的全局變量) 對象,實現了在同一個協程中數據的隔離和全局性,具體怎麼實現看源代碼,暫時沒看明白
_app_ctx_stack = LocalStack()
current_app = LocalProxy(_find_app)
# partial(_lookup_req_object, 'request') 總是返回 LocalStack 棧頂對象的 request 屬性
# LocalProxy 用於代理Local對象和LocalStack對象,至於爲什麼使用代理。。
request = LocalProxy(partial(_lookup_req_object, 'request'))
session = LocalProxy(partial(_lookup_req_object, 'session'))
g = LocalProxy(partial(_lookup_app_object, 'g'))
flask local https://www.jianshu.com/p/3f38b777a621
爲什麼要使用 LocalProxy 而不直接使用 Local 和 LocalStack
# AppContext 應用上下文
#
# 在flask/app.py下:
#
def app_context(self): # self 就是app對象
return AppContext(self)
#
# 在 `flask/ctx.py` 代碼中
#
class AppContext(object):
def __init__(self, app):
self.app = app
self.url_adapter = app.create_url_adapter(None)
self.g = app.app_ctx_globals_class()
# Like request context, app contexts can be pushed multiple times
# but there a basic "refcount" is enough to track them.
self._refcnt = 0
def push(self): # 這個方法就是把應用上下文push到LocalStack,AppContext類有__enter__方法
"""Binds the app context to the current context."""
self._refcnt += 1
if hasattr(sys, 'exc_clear'):
sys.exc_clear()
_app_ctx_stack.push(self)
appcontext_pushed.send(self.app)
#
# 在 flask/cli.py 中有
#
def with_appcontext(f):
@click.pass_context
def decorator(__ctx, *args, **kwargs): # decorator 被裝飾器後 _ctx 參數是 threading 的 local() 對象
with __ctx.ensure_object(ScriptInfo).load_app().app_context(): # 在這裏就把 應用上下文push到了LocalStack
return __ctx.invoke(f, *args, **kwargs)
return update_wrapper(decorator, f)
# RequestContext 請求上下文
#
#在flask/app.py下
#
def request_context(self, environ): # 一次請求的環境變量
return RequestContext(self, environ)
#
# 在flask/ctx.py下:
#
class RequestContext(object):
def __init__(self, app, environ, request=None):
self.app = app
if request is None:
request = app.request_class(environ)
self.request = request
self.url_adapter = app.create_url_adapter(self.request)
self.flashes = None
self.session = None
...
...
def push(self):
...
_request_ctx_stack.push(self)
#
#在flask/app.py下
#
def wsgi_app(self, environ, start_response): # 這裏類似上面的那小段簡單 Werkzeugs app
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:
error = sys.exc_info()[1]
raise
return response(environ, start_response)
finally:
if self.should_ignore_error(error):
error = None
ctx.auto_pop(error)
到此從 AppContext 到 RequestContext 梳理完
app.py
源碼中有這麼幾個裝飾器:
-
before_request(self, f)
# 註冊一個在請求到達前要執行的函數 -
before_first_request(self, f)
# 註冊在整個應用實例第一個請求到達前要執行的函數 -
after_request(self, f)
# 註冊一個在請求處理後的函數 -
teardown_request(self, f)
# 註冊在請求上下文棧彈出後要執行的函數 -
teardown_appcontext(self, f)
# 註冊在應用上下文結束時要執行的函數 -
context_processor(self, f)
# 向模板上下文中注入變量和方法,這個f
必須返回一個字典, 在渲染的模板中使用 -
url_defaults(self, f)
# 爲應用程序中的所有視圖函數 註冊URL值預處理器函數。 這些函數將在:before_request
函數之前調用。 -
url_value_preprocessor(self, f)
# 註冊一個 ‘在請求的url匹配後視圖函數執行前,把環境中的某些變量換個位置存儲的’ 函數
# url_defaults(self, f) 和 url_value_preprocessor(self, f) 的使用
from flask import Blueprint, render_template
profile = Blueprint('profile', __name__, url_prefix='/<user_url_slug>')
@profile.url_defaults
def add_user_url_slug(endpoint, values):
values.setdefault('user_url_slug', g.user_url_slug)
@profile.url_value_preprocessor
def pull_user_url_slug(endpoint, values):
g.user_url_slug = values.pop('user_url_slug')
query = User.query.filter_by(url_slug=g.user_url_slug)
g.profile_owner = query.first_or_404()
@profile.route('/')
def timeline():
return render_template('profile/timeline.html')
@profile.route('/photos')
def photos():
return render_template('profile/photos.html')
@profile.route('/about')
def about():
return render_template('profile/about.html')
方法:
-
dispatch_request(self)
: 匹配路由,返回視圖函數或者錯誤處理函數的返回值,並且檢測是否爲option
請求,如果是則構造默認的 ‘options response’ 響應。構造過程首先是Request
uri 所支持的方法集(get、post、等),然後更新Response
中allow
屬性(set類型),最後返回Response
對象,若不是option
請求則執行視圖函數; -
make_response(self, rv)
:rv
是視圖函數的返回值,在python3中,rv可以使一個元組(body、status、headers)、Response
類對象、 或者一個返回Response
類對象的回調函數。這個函數的功能就是把視圖函數返回的 status headers 綁定到 Response; -
create_url_adapter(self, request)
:url_map 適配器。對werkzeug的Map的bind
和bind_to_environ
兩個方法進行了封裝。bind
: 綁定一個主機地址,並返回MapAdapter對象 ;bind_to_environ
( 將MAP綁定到WSGI環境中,並返回MapAdapter對象(參數script_name在進行重定向時會用到); -
try_trigger_before_first_request_functions(self)
:在該實例第一個請求到達時把當前線程加鎖,然後依次執行被before_first_request(self, f)
裝飾過得函數,然後釋放鎖並把_got_first_request
置爲True,再次就直接return; -
preprocess_request(self)
:該函數就是執行被url_value_preprocessor
和before_request
裝飾過的函數; -
full_dispatch_request(self)
: 先執行try_trigger_before_first_request_functions
,然後執行preprocess_request
,若before_request
中的函數有返回值則爲其構造Response
,然後跳過排在此函數後邊的函數包括視圖函數,若before_request
的函數都返回的None或沒有函數就執行dispatch_request(self)
; -
inject_url_defaults(self, error, endpoint, values)
: 執行被'url_defaults' 裝飾的函數 -
process_response(self, response)
: 同preprocess_request(self)
通過查看源碼瞭解到的幾個工具:
click 包 把python代碼打包成命令行工具
mimetypes 包 查看文件類型
itsdangerous 簽名 序列化字符串
參考資料:
Flask的核心機制!關於請求處理流程和上下文
Werkzeug(Flask)之Local、LocalStack和LocalProxy