最近面試的時候,被面試官問道一個問題,就是 request.user 裡面的 user 是怎樣得到的,這個問題當時沒有回答上來,可以說是非常的尷尬,所以趕快查了一些資料,看了一些原始碼,特地來總結一下這個問題。
要想回答為什麼可以直接通過 request.user 得到請求的使用者,應該先來看看請求被處理以及如何返回響應的流程。今天先總結一下 django 從請求到響應都進行了哪些過程。
WSGI
當客戶端傳送一次請求後,最先處理請求的實際上是 web 伺服器就是我們經常說的 nginx、Apache 這類的 web 伺服器,而 WSGI 的作用就是把 web 伺服器和 web 框架 (Django) 連線起來。WSGI 被分為了兩個部分:服務端和應用端。為了處理一個 WSGI 的響應,服務端執行應用程式並嚮應用端提供一個回撥函式,應用端處理請求並使用提供的回撥將響應返回給服務端。本質上來講,我覺得 WSGI 就是 web 伺服器和 django 應用之間的一個聯絡人。
資料流
當使用者向你的應用傳送一個請求的時候,一個 WSGI handler 將會被初始化,它會完成以下工作:
- 匯入 settings.py 和 django 的異常類
- 使用 load_middleware 方法載入 settings.py 中 MIDDLEWARE_CLASSES 或者 MIDDLEWARES 元組中所用的 middleware classes.
- 建立四個列表 (_request_middleware,_view_middleware, _response_middleware, _exception_middleware),裡面分別包含處理 request,view,response 和 exception 的方法。
- WSGI Handler 將例項化一個 django.http.HTTPRequest 物件的子類,django.core.handlers.wsgi.WSGIRequest.
- 迴圈遍歷處理 request 的方法 (_request_middleware 列表),並按照順序呼叫他們
- 解析請求的 url
- 迴圈遍歷每個處理 view 的方法 (_view_middleware 列表)
- 如果找的到的話,就呼叫檢視函式
- 處理任何異常的方法 (_exception_middleware 列表)
- 迴圈遍歷每個處理響應的方法 (_response_middleware 列表),(從內向外,與請求中介軟體的順序相反)
- 最後得到一個響應,並呼叫 web server 提供的回撥函式
中介軟體
中介軟體被用在了 django 的許多關鍵功能中:例如,使用 CSRF 中間鍵來防止跨站請求偽造攻擊。它們也被用來處理會話資料,身份認證和授權同樣是由中介軟體來完成的。我們也可以自己編寫中介軟體來調整或者(短路)通過應用程式的資料流。
django 的中介軟體至少含有以下四個方法中的一個:process_request, process_response, process_view, process_exception。這些方法會被 WSGI handler 收集並按照順序呼叫。
process_request
我們可以先來看看 django.contrib.auth.middleware.AuthenticationMiddleware:
def get_user(request):
if not hasattr(request, '_cached_user'):
request._cached_user = auth.get_user(request)
return request._cached_user
class AuthenticationMiddleware(MiddlewareMixin):
def process_request(self, request):
assert hasattr(request, 'session'), (
"The Django authentication middleware requires session middleware "
"to be installed. Edit your MIDDLEWARE%s setting to insert "
"'django.contrib.sessions.middleware.SessionMiddleware' before "
"'django.contrib.auth.middleware.AuthenticationMiddleware'."
) % ("_CLASSES" if settings.MIDDLEWARE is None else "")
request.user = SimpleLazyObject(lambda: get_user(request))
複製程式碼
這裡我們可以發現 request.user 這個屬性是在 AuthenticationMiddleware 中產生的。這個我們稍後再說。
這裡我們可以發現,這個中介軟體只有 process_request,說明它只在 request 這一步處理流入和流出 django 應用的資料流。這個中介軟體會首先驗證會話中介軟體是否被使用,然後通過呼叫 get_user 函式來設定使用者。當 WSGI 處理程式迭代 process_request 方法列表的時候,它將會構建這個最終會被傳遞給檢視函式的請求物件,並能夠使你引用 request.user。一些中介軟體沒有 process_request 方法,在這個階段,會被跳過。
process_request 應該返回 None 或者 HTTPResponse 物件。當返回 None 時,WSGI handler 會繼續載入 process_request 裡面的方法,但是後一種情況會短路處理過程並進入 process_response 迴圈。
解析 url
當所有的 process_request 被呼叫完之後,我們就會得到一個將被傳遞給檢視函式的 request 物件。當這個事件發生之前,django 必須解析 url 並決定呼叫哪一個檢視函式。這個過程非常簡單,只需要使用正則匹配即可。settings.py 中有一個 ROOT_URLCONF 鍵來指定根 url.py,在這裡會包含你所有 app 的 urls.py 檔案。如果沒有匹配成功,將會丟擲一個異常 django.core.urlresolvers.Resolver404, 這是 django.http.HTTP404 的子類。
process_view
到這一步之後 WSGI handler 知道了呼叫哪一個檢視函式,以及傳遞哪些引數。它會再一次呼叫中介軟體列表裡面的方法,這次是_view_middleware 列表。所有 Django 中介軟體的 process_view 方法將會被這樣宣告:
process_view(request, view_function, view_args, view_kwargs)
和 process_request 一樣,process_view 函式必須返回 None 或者 HTTPResponse 物件,使得 WSGI handler 繼續處理檢視或者’短路’處理流程並返回一個響應。在 CSRF middleware 中存在一個 process_view 的方法。作用是當 CSRF cookies 出現時,process_view 方法將會返回 None, 檢視函式將會繼續的執行。如果不是這樣,請求將會被拒絕,處理流程將會被’短路’,會生成一個錯誤的資訊。
進入檢視函式
一個檢視函式需要滿足三個條件:
- 必須是可以呼叫的。這可以是基於函式的檢視或者是 class-based 的檢視(繼承自 View 並且使用 as_view() 方法來使它成為可呼叫的。這些方法的呼叫依賴 HTTP verb(GET, POST, etc))
- 必須接受一個 HTTPRequest 物件作為第一個位置引數。這個 HTTPRequest 物件是被所有 process_request 和 process_view 中介軟體方法處理的結果。
- 必須返回一個 HTTPResponse 物件,或者丟擲一個異常。就是用這個 response 物件來開啟 WSGI handler 的 process_view 迴圈。
process_exception
如果檢視函式丟擲一個異常,Handler 將會迴圈遍歷_exception_middleware 列表,這些方法按照相反的順序執行,從 settings.py 裡面列出來的最後一箇中介軟體到第一個。如果一個異常被丟擲,處理過程將會被短路,其他的 process_exception 將不會被執行。通常我們依賴 Djnago's BaseHandler 提供的異常處理程式,但是我們也可以使用自定義的異常處理中介軟體。
process_response
在這個階段,我們得到了一個 HTTPResponse 物件,這個物件可能是 process_view 返回的,也可能是檢視函式返回的。現在我們將迴圈訪問響應中介軟體。這是中介軟體調整資料的最後的機會。執行的順序是從內向外執行。 以 cache middleware 的 process_response 為例:它依賴於你的 app 裡面的不同的狀態(快取是否開啟或者關閉,是否在處理一個資料流),來決定是否快取你的響應。
注意
django 1.10 和之前版本的區別: 在舊版本的 MIDDLEWARE_CLASSES 中,就算一箇中介軟體”短路”了執行過程,所有的中介軟體都會呼叫它們的 process_response 方法。而在新的 MIDDLEWARES 版本中,只有這個中介軟體和在它之前執行的中介軟體才會呼叫 process_response 方法。
總結
以上就是 django 在處理一個請求的基本的過程,最後 django 的 WSGI Handler 會建立一個來自 HTTPResponse 的返回值,而且會呼叫回撥函式把資料傳遞給 web server, 最後返回給使用者。
以下是兩個關鍵點:
- 我們現在知道了檢視函式是如何和 url 解析器匹配以及什麼在呼叫它 (WSGI Handler)
- 有四個關鍵的地方可以讓你掛鉤到請求 / 響應週期:process_request, process_response, process_view, process_exception。請求中介軟體是從外部向內執行,最後抵達到檢視函式,然後通過響應中介軟體從內向外返回。
參考資料
————————————————————————————————————— 關注微信公眾號:創宇前端(KnownsecFED),碼上獲取更多優質乾貨!