Flask 框架理解(一)
web 服務器 , web 框架 以及 WSGI
這里說的 web 服務器特指純粹的 python HTTP 服務器(比如 Gunicorn,而不是 Apache,Nginx這些)
一般來說, web 服務器負責建立並管理監聽套接字,並且解析所有收到的 HTTP 請求。Web 框架負責 URL 分發,根據 HTTP 中請求路徑生成響應。 而 WSGI(pep 333) 則是 web 服務器調用 web 框架的一個調用慣例。用來解偶 web 服務器和 web 框架。
通過 flask 的源碼能比較清晰的看到這三者關系
def wsgi_app(self, environ, start_response):
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)
def __call__(self, environ, start_response):
"""The WSGI server calls the Flask application object as the
WSGI application. This calls :meth:`wsgi_app` which can be
wrapped to applying middleware."""
return self.wsgi_app(environ, start_response)
從這里可以看出, flask 的 application 是一個 WSGI 可調用對象。如果去看 Flask Appication 的 run 方法, 就能發現最終是啟動一個 Werkzeug serving.py 中的一個 基於BaseHTTPServer.HTTPServer 實現的 WSGIServer,也就是 web 服務器。證實 web 服務器通過 WSGI 接口調用 web 框架作出響應。這里也能很清楚的看到 web 框架的基礎功能就是分發請求,作出響應。
理解全局變量
在 flask 中有 4 個方便好用的全局變量 current_app, request, session, g。嚴格意義上來說是線程級別的全局變量(具體可看 Werkzeug 的 LocalStack 源碼),也就是只有當前線程能訪問。正因為用的多, 更應該花點時間去理解這幾個變量的含義以及生命周期。
# context locals
_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'))
對於請求-響應模式, 響應需要根據具體的請求來做出合理的響應。 這四個全局變量就是用來提供請求的具體信息,即請求上下文。從上面 wsgi_app 函數中也可以看出,有請求過來, 調用 wsig_app 時, 會構建一個當前請求的上下文實例(RequestContext),將 http 請求原始信息存到 request 屬性, 並將其 push 到當前線程的全局棧 _request_ctx_stack。接着將當前 flask 實例 push 到 _app_ctx_stack, 存儲到上下文實例的 app 屬性中。最后解析請求中的 cookie, 構建 Session 實例存到上下文實例的 session 屬性中。 這樣每個請求的上下文中就攜帶了, http 請求的原始信息, 當前 flask 實例上下文信息, 以及 cookie 提取的 session 信息。 一個請求所需要的信息基本就全部包含了。至於 g 只是當前 flask 實例上下文對象的一個屬性。在響應構造完成后, 會把當前上下文 session 信息存到 cookie,所以裸 flask 框架的 session 全部信息是存在客戶端的 cookie 里面。接着彈出當前請求的上下文, 彈出當前請求的 flask 實例上下文。
def push(self):
"""Binds the request context to the current context."""
top = _request_ctx_stack.top
if top is not None and top.preserved:
top.pop(top._preserved_exc)
# Before we push the request context we have to ensure that there
# is an application context.
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)
if hasattr(sys, 'exc_clear'):
sys.exc_clear()
_request_ctx_stack.push(self)
if self.session is None:
session_interface = self.app.session_interface
self.session = session_interface.open_session(
self.app, self.request
)
if self.session is None:
self.session = session_interface.make_null_session(self.app)
備注:
之前有提到過, web 服務器與 web 框架是分開的,而且 WSGI 也不支持異步服務器。當 web 服務器是異步服務器時,因為 flask 存在線程級全局變量, 且是基於 Werkzeug 中基於 greenlet 的 LocalStack 上實現,所以當異步服務器不是基於 greenlet 時,會存在一定問題。