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 theMETA
keyHTTP_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
。
無時無刻遵守好習慣,你的隊友——包括未來的自己——會感謝現在的你。