Django:以 request.headers 而非 META 獲取 HTTP header 資訊
文章目錄

我們知道,Django 提供了專屬的 HttpRequest 類別,把從前端(主要為瀏覽器)傳過來的 HTTP 請求重新封裝成 OOP 物件,方便我們在 view 中操作、使用。
而這個 HttpRequest 物件,也就是開發時,前端傳給 view 函式的第一位置參數——request。
我們經常使用 request,在 view 函式(或類別)內部獲得本次 HTTP 請求的相關資訊,比如所使用的 HTTP 方法,或下面提到的 header 相關資訊等等。
1 | if request.method == 'GET': |
場景需求:欲取得特定 request header 內容
在開發內部 API 相關功能時,一個很常見的需求,就是取得本次請求所帶的「驗證 token」 內容,加以重新封裝成一個新的請求,並轉發給內部 API。
通常 token 會附在請求的 header 中,以下列鍵值對形式存在:
1 | ... |
其中 Authorization,就是我們要的,有關驗證 token 的 header 欄位。
所以我們的需求是:原封不動地取得它的值——「'Bearer <token_string>'」,並複製到我們重新封裝的 HTTP 請求的 header 中,以維持認證的有效性。
以往做法:使用 request.META
過去撰寫內部 API 的相關請求,驗證 token 部分通常是透過上述「request.META」取得 request header 裡的 token 值。
申言之,HttpRequest 有一個物件屬性叫 META,其值為一個 Python 字典,並以字典中的每一個鍵值對代表 HTTP header 相關資訊。
典型的 HTTP header 欄位名稱看起來像這樣:
Accept-Language– List of acceptable human languages for response.Authorization– Authentication credentials for HTTP authentication.
但實際上,META 字典中的鍵名,並非上述格式,因為 request.META 在封裝 HTTP 請求的 header 時,會對「欄位名稱」進行格式轉換(mapping),規則如下:
- 加上
HTTP_前綴。 - 轉為全大寫。
- 將
-替換為_(底線)。
So, for example, a header called **
X-Bender** would be mapped to the **META** key **HTTP_X_BENDER**.
因此,程式中取值的寫法差不多都是這樣,以獲得驗證 token 為例(注意名稱):
1 | headers = {'Authorization': request.META.get('HTTP_AUTHORIZATION')} |
request.META 缺點
我覺得大致有下:
- Header 欄位名稱被強制轉換,與原生的 HTTP header 並不相同。
- 比如
Accept-Language→HTTP_ACCEPT_LANGUAGE。 - 這樣的轉換雖然遵循一定規律,但畢竟和原來不同,所以並不直觀。
- 比如
META這個詞,太籠統太抽象:request.META究竟代表什麼?說真的第一時間你不太能和 HTTP header 聯想,多少會影響程式的可讀性。
所以我一直不太喜歡「request.META」這個寫法——不易理解且存在較多出錯可能。
更好的做法:使用 request.headers
其實仔細想想就會覺得:使用 META 來取 header 資訊的做法實在很不直覺、不自然,對於一個成熟的框架,理論上應該要再給出「語法糖」加以簡化。
Django 2.2 更新
這個需求終於在 Django 2.2 獲得實現:加入了 request.headers。Django 文件也建議你,如果只是想單純取得 header 資訊,可以使用更簡潔的 headers:
**
HttpRequest.headers** is a simpler way to access all HTTP-prefixed headers
HttpRequest.headers 特色
- 依舊為 Python 字典格式。
- Header 鍵名維持原來的名稱,不再進行轉換。
- 使用鍵名取值時,無須區分大小寫。
1 | >>> request.headers |
這個「不區分大小寫」的設計,主要是考量 header 欄位名稱十分多樣,尤其是自訂的 header 名稱,根本沒有一定的標準。
使用 request.headers 後的改善
可以看看原來程式碼的寫法變化:
Before
1 | headers = {'Authorization': request.META.get('HTTP_AUTHORIZATION')} |
After
1 | headers = {'Authorization': request.headers.get('Authorization')} |
改變雖小,卻直觀了很多!
換句話說,其實就是改善 META 既有的缺點:
- HTTP header 鍵名不必強制前綴
HTTP_和全大寫了!只要使用原來的名稱即可,這才是符合人類直覺得設計!至於不區分大小寫我覺得還好,畢竟最好還是使用一致的命名,如上述程式碼,比較不易混淆。 headers這個屬性名稱比META更加直觀、可讀,容易聯想到 HTTP headers。
小結:可讀性為王
簡潔、直觀、易讀,都是 Clean Code 的重要元素。
因此,哪怕只是很小的改動,我仍然強烈建議你:在不影響原有需求的前提下,盡可能使用 request.headers 取代 request.META。
無時無刻遵守好習慣,你的隊友——包括未來的自己——會感謝現在的你。