Flask 框架理解(一)
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 時,會存在一定問題。