flask 原始碼解析:響應

發表於2017-02-21

這是 flask 原始碼解析系列文章的其中一篇,本系列所有文章列表:

response 簡介

在 flask 應用中,我們只需要編寫 view 函式,並不需要直接和響應(response)打交道,flask 會自動生成響應返回給客戶端。

The return value from a view function is automatically converted into a response object for you.
—— Flask docs

我們知道 HTTP 響應分為三個部分:
狀態列(HTTP 版本、狀態碼和說明)、頭部(以冒號隔開的字元對,用於各種控制和協商)、body(服務端返回的資料)。比如下面訪問部落格首頁的響應:

flask 自然也會提供所有這些資料的操作,檢視函式就支援返回三個值:第一個是返回的資料,第二個是狀態碼,第三個是頭部字典。比如:

這篇文章就講講這背後的魔法。

flask 響應(response)

flask 原始碼解析:應用啟動流程 的最後,我們講到 full_dsipatch_request 在呼叫路由到檢視函式之後,會呼叫 finalize_request 進行最後的處理,在這個方法裡就包含了 response 物件的生成和處理邏輯。

finalize_request 的程式碼如下:

裡面有兩個方法呼叫:make_response 根據檢視函式的返回值生成 response 物件,process_response 對 response 做一些後續的處理(比如執行 hooks 函式)。我們先來看看 make_response

make_response 是檢視函式能返回多個不同數量和型別值的關鍵,因為它能處理這些情況,統一把它們轉換成 response。
如果返回值本身就是 Response 例項,就直接使用它;如果返回值是字串型別,就把它作為響應的 body,並自動設定狀態碼和頭部資訊;
如果返回值是 tuple,會嘗試用 (response, status, headers) 或者 (response, headers) 去解析。

NOTE:因為檢視函式可以返回 Response 物件,因此我們可以直接操作 Response

不管檢視函式返回的是什麼,最終都會變成 Response 物件,那麼我們就來看看 Response 的定義:

Flask 的 Response 類非常簡單,它只是繼承了 werkzeug.wrappers:Response,然後設定預設返回型別為 html。
不過從註釋中,我們得到兩條很有用的資訊:

  1. 一般情況下不要直接操作 Response 物件,而是使用 make_response 方法來生成它
  2. 如果需要使用自定義的響應物件,可以覆蓋 flask app 物件的 response_class 屬性。

繼續,下面就要分析 werkzeug 對應的程式碼了。

werkzeug response

werkzeug 實現的 response 定義在 werkzeug/wrappers.py 檔案中:

和我們在 flask 請求分析的 Request 類一樣,這裡使用了 Mixin 機制。BaseResponse 精簡後的大概框架如下:

BaseResponse 有一些類屬性,定義了預設的值,比如預設字元編碼是 utf-8,預設狀態碼是 200 等。例項化的時候接受的引數有:

  • response: 字串或者其他 iterable 物件,作為響應的 body
  • status: 狀態碼,可以是整數,也可以是字串
  • headers: 響應的頭部,可以是個列表,也可以是 werkzeug.datastructures.Headers 物件
  • mimetype: mimetype 型別,告訴客戶端響應 body 的格式,預設是文字格式
  • content_type: 響應頭部的 Content-Type 內容

所有這些引數都是可選的,預設情況下會生成一個狀態碼為 200,沒有任何 body 的響應。status、status_code 作為 Response 的屬性,可以直接讀取和修改。body 資料在內部儲存為 iterable 的型別,
但是對外也提供了直接讀寫的介面 self.data

body 字元的編碼和長度都是自動設定的,使用者不需要手動處理。

至於頭部的儲存,werkzeug 使用的是類似於字典的 werkzeug.datastructures:Headers 類。在flask 原始碼解析:請求這篇文章中,我們沒有詳細
解釋頭部的儲存,那麼這篇文章就具體分析一下吧。

Headers 這個類的提供了很多和字典相同的介面:keys、values、iterms,但是和字典的區別在於它儲存的值是有序的,而且允許相同 key 的值存在。
為什麼這麼設計呢?因為著更符合 HTTP 頭部的特性。先來看看有序,在 HTTP 傳送的過程中,如果頭部各個 key-value 鍵值對順序發生變化,有些代理或者客戶端等元件會認為請求被篡改而丟棄或者拒絕請求的處理,所以最好把頭部設定為有序的,使用者按照什麼順序設定的,就按照什麼順序儲存;再說說相同 key 的問題,這是因為 HTTP 頭部同一個 key 可能有多個 value(比如 Accept、SetCookie頭部)。那麼這個看起比較特殊的字典是怎麼實現的呢?來看程式碼:

可以看到,頭部資訊在內部儲存為二元組構成的列表,這樣就能同時保證它的有序性和重複性。一個核心的方法是 __getitem__,它定義瞭如何獲取頭部中的資訊:

  • 通過下標 header[3],直接返回對應未知儲存的鍵值對元組
  • 通過 key,返回 value header['Accept'],返回匹配的第一個 value 值
  • 通過 slice header[3:7],返回另外一個 Headers 物件,儲存了 slice 中所有的資料

然後實現 keys()items()pop()setdefault() 等方法讓它表現出來字典的特性,除此之外還有 add()extend()add_header() 等和字典無關的方法方便操作。

自定義 response

如果需要擴充套件 flask Response 的功能,或者乾脆把它替換掉,只要修改 flask app 的 response_class 屬性就可以了,比如:

更多可能的用法,可以參考文章末尾的參考資料。

參考資料

請使用手機”掃一掃”x

相關文章