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-Benderwould be mapped to theMETAkeyHTTP_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.headersis 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。
無時無刻遵守好習慣,你的隊友——包括未來的自己——會感謝現在的你。