flask 原始碼解析:請求

發表於2017-02-21

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

簡介

對於物理鏈路來說,請求只是不同電壓訊號,它根本不知道也不需要知道請求格式和內容到底是怎樣的;
對於 TCP 層來說,請求就是傳輸的資料(二進位制的資料流),它只要傳送給對應的應用程式就行了;
對於 HTTP 層的伺服器來說,請求必須是符合 HTTP 協議的內容;
對於 WSGI server 來說,請求又變成了檔案流,它要讀取其中的內容,把 HTTP 請求包含的各種資訊儲存到一個字典中,呼叫 WSGI app;
對於 flask app 來說,請求就是一個物件,當需要某些資訊的時候,只需要讀取該物件的屬性或者方法就行了。

可以看到,雖然是同樣的請求資料,在不同的階段和不同元件看來,是完全不同的形式。因為每個元件都有它本身的目的和功能,這和生活中的事情一個道理:對於同樣的事情,不同的人或者同一個人不同人生階段的理解是不一樣的。

這篇文章呢,我們只考慮最後一個內容,flask 怎麼看待請求。

請求

我們知道要訪問 flask 的請求物件非常簡單,只需要 from flask import request

前面一篇文章 已經介紹了這個神奇的變數是怎麼工作的,它最後對應了 flask.wrappers:Request 類的物件。
這個類內部的實現雖然我們還不清楚,但是我們知道它接受 WSGI server 傳遞過來的 environ 字典變數,並提供了很多常用的屬性和方法可以使用,比如請求的 method、path、args 等。
請求還有一個不那麼明顯的特性——它不能被應用修改,應用只能讀取請求的資料。

這個類的定義很簡單,它繼承了 werkzeug.wrappers:Request,然後新增了一些屬性,這些屬性和 flask 的邏輯有關,比如 view_args、blueprint、json 處理等。它的程式碼如下:

這段程式碼沒有什難理解的地方,唯一需要說明的就是 @property 裝飾符能夠把類的方法變成屬性,這是 python 中經常見到的用法。

接著我們就要看 werkzeug.wrappers:Request

這個方法有一點比較特殊,它沒有任何的 body。但是有多個基類,第一個是 BaseRequest,其他的都是各種 Mixin
這裡要講一下 Mixin 機制,這是 python 多繼承的一種方式,如果你希望某個類可以自行組合它的特性(比如這裡的情況),或者希望某個特性用在多個類中,就可以使用 Mixin。
如果我們只需要能處理各種 Accept 頭部的請求,可以這樣做:

但是不要濫用 Mixin,在大多數情況下子類繼承了父類,然後實現需要的邏輯就能滿足需求。

我們先來看看 BaseRequest:

能看到例項化需要的唯一變數是 environ,它只是簡單地把變數儲存下來,並沒有做進一步的處理。Request 的內容很多,其中相當一部分是被 @cached_property 裝飾的方法,比如下面這種:

@cached_property 從名字就能看出來,它是 @property 的升級版,新增了快取功能。我們知道
@property 能把某個方法轉換成屬性,每次訪問屬性的時候,它都會執行底層的方法作為結果返回。
@cached_property 也一樣,區別是隻有第一次訪問的時候才會呼叫底層的方法,後續的方法會直接使用之前返回的值。
那麼它是如何實現的呢?我們能在 werkzeug.utils 找到它的定義:

這個裝飾器同時也是實現了 __set____get__ 方法的描述器
訪問它裝飾的屬性,就會呼叫 __get__ 方法,這個方法先在 obj.__dict__ 中尋找是否已經存在對應的值。如果存在,就直接返回;如果不存在,呼叫底層的函式
self.func,並把得到的值儲存起來,再返回。這也是它能實現快取的原因:因為它會把函式的值作為屬性儲存到物件中。

關於 Request 內部各種屬性的實現,就不分析了,因為它們每個具體的實現都不太一樣,也不復雜,無外乎對 environ 字典中某些欄位做一些處理和計算。
接下來回過頭來看看 Mixin,這裡只用 AcceptMixin 作為例子:

AcceptMixin 實現了請求內容協商的部分,比如請求接受的語言、編碼格式、相應內容等。
它也是定義了很多 @cached_property 方法,雖然自己沒有 __init__ 方法,但是也直接使用了
self.environ,因此它並不能直接使用,只能和 BaseRequest 一起出現。

參考資料

請使用手機”掃一掃”x

相關文章