1. 程式人生 > >flask請求和應用上下文

flask請求和應用上下文

返回 結構 完成後 ont shu oba 流程 err 報文頭

關於WSGI

WSGI(全稱Web Server Gateway Interface),是為 Python 語言定義的Web服務器Web應用程序之間的一種簡單而通用的接口,它封裝了接受HTTP請求解析HTTP請求發送HTTP響應等等的這些底層的代碼和操作,使開發者可以高效的編寫Web應用。

一個簡單的使用WSGI的App例子:

def application(environ, start_response): 
    start_response(‘200 OK‘, [(‘Content-Type‘, ‘text/html‘)]) 
    return [b‘<h1>Hello, I Am WSGI!</h1>‘]
  • environ: 一個包含全部HTTP請求信息的字典,由WSGI Server解包HTTP請求生成。
  • start_response: 一個WSGI Server提供的函數,調用可以發送響應的狀態碼和HTTP報文頭, 函數在返回前必須調用一次start_response()
  • application()應當返回一個可以叠代的對象(HTTP正文)。
  • application()函數由WSGI Server直接調用和提供參數。
  • Python內置了一個WSGIREFWSGI Server,不過性能不是很好,一般只用在開發環境。可以選擇其他的如Gunicorn
技術分享圖片 WSGI Server 和 App交互圖

Flask的上下文對象

Flask有兩種Context(上下文),分別是

  • RequestContext 請求上下文
  • Request 請求的對象,封裝了Http請求(environ)的內容
  • Session 根據請求中的cookie,重新載入該訪問者相關的會話信息。
  • AppContext 程序上下文
  • g 處理請求時用作臨時存儲的對象。每次請求都會重設這個變量
  • current_app 當前激活程序的程序實例

生命周期:

  • current_app的生命周期最長,只要當前程序實例還在運行,都不會失效。
  • Requestg的生命周期為一次請求期間,當請求處理完成後,生命周期也就完結了
  • Session
    就是傳統意義上的session了。只要它還未失效(用戶未關閉瀏覽器、沒有超過設定的失效時間),那麽不同的請求會共用同樣的session。

Flask處理流程

技術分享圖片 Flask處理請求流程

第一步:創建上下文

Flask根據WSGI Server封裝的請求等的信息(environ)新建RequestContext對象AppContext對象

# 聲明對象
# LocalStack  LocalProxy 都由Werkzeug提供
# 我們不深究他的細節,那又是另外一個故事了,我們只需知道他的作用就行了
# LocalStack 是棧結構,可以將對象推入、彈出
# 也可以快速拿到棧頂對象。當然,所有的修改都只在本線程可見。
_request_ctx_stack = LocalStack()
_app_ctx_stack = LocalStack()
# 如果調用一個LocalStack實例, 能返回一個 LocalProxy 對象
# 這個對象始終指向 這個LocalStack實例的棧頂元素。
# 如果棧頂元素不存在,訪問這個 LocalProxy 的時候會拋出 RuntimeError異常
# LocalProxy對象你只需暫時理解為棧裏面的元素即可了
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‘))
# RequestContext
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
#AppContext
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()    
        self._refcnt = 0

這裏需要註意的是,RequestContext在初始化的時候,當前Flask的實例作為參數被傳進來。雖然每次的請求處理都會創建一個RequestContext對象,但是每一次傳入的app參數卻是同一個。通過這個機制,可以使得:

由同一個Flask實例所創建的RequestContext,其成員變量app都是同一個Flask實例對象 。實現了多個RequestContext對應同一個current_app 的目的。

第二步:入棧


RequestContext對象push進_request_ctx_stack裏面。在這次請求期間,訪問request對象session對象將指向這個棧的棧頂元素

class RequestContext(object):
    def push(self):   
        ....
        _app_ctx_stack.push(self)   
        appcontext_pushed.send(self.app)

AppContext對象push進_app_ctx_stack裏面。在這次請求期間,訪問g對象將指向這個棧的棧頂元素

class AppContext(object):
    def push(self):   
        ....
        _request_ctx_stack.push(self)

第三步:請求分發

response = self.full_dispatch_request()
Flask將調用full_dispatch_request函數進行請求的分發,之所以不用給參數,是因為我們可以通過request對象獲得這次請求的信息。full_dispatch_request將根據請求的url找到對應的藍本裏面的視圖函數,並生成一個response對象。註意的是,在請求之外的時間,訪問request對象是無效的,因為request對象依賴請求期間的_request_ctx_stack棧。

第四步:上下文對象出棧

這次HTTP的響應已經生成了,就不需要兩個上下文對象了。分別將兩個上下文對象出棧,為下一次的HTTP請求做出準備。

第五步:響應WSGI

調用Response對象,向WSGI Server返回其結果作為HTTP正文。Response對象是一個 可調用對象,當調用發生時,將首先執行WSGI服務器傳入的start_response()函數 發送狀態碼和HTTP報文頭。

最後附上Flask處理請求的wsgi_app函數

# environ: WSGI Server封裝的HTTP請求信息
# start_response: WSGI Server提供的函數,調用可以發送狀態碼和HTTP報文頭
def wsgi_app(self, environ, start_response):
    # 根據environ創建上下文
    ctx = self.request_context(environ)
    # 把當前的request context,app context綁定到當前的context
    ctx.push()
    error = None
    try:
        try:
            #根據請求的URL,分發請求,經過視圖函數處理後返回響應對象
            response = self.full_dispatch_request()    
        except Exception as e:        
            error = e        
            response = self.make_response(self.handle_exception(e))   
        return response(environ, start_response)
    finally:   
        if self.should_ignore_error(error):        
          error = None    
       # 最後出棧
        ctx.auto_pop(error)




flask請求和應用上下文