本文所用 Django
程式碼版本:2.1.3
本文中進行的分析並不侷限於某一個 Django
版本但都會盡量討論版本 2.0+
流程總覽
概述:
Django
和其他 Web
框架的 HTTP
處理的流程大致相同:先通過 Request Middleware
對請求物件做定義處理,然後再通過預設的 URL
指向的方法,最後再通過 Response Middleware
對響應物件做自定義處理。
細則:
- [啟動->WSGI]通過任意方式啟動
Django
建立WSGIServer
類的例項 - 使用者通過瀏覽器請求某個
Django
頁面 - [WSGI]
Django WSGIServer
接收客戶端(瀏覽器)請求初始化WSGIHandler
例項 - [WSGI->載入配置]匯入
setting
配置和Django
異常類 - [WSGI->中介軟體]載入
setting
中設定的中介軟體 - [中介軟體]建立
_request_middleware,_view_middleware,_response_middleware,_exception_middleware
四個列表 - [中介軟體]遍歷執行
_request_middleware
,對request
進行處理:若返回None
進入到 8;若直接返回HttpResponse
物件進入到 12 - [URL Resolver]解析
url
並進行匹配(假設匹配成功) - [中介軟體]遍歷執行
_view_middleware
,對request
進行處理:若返回None
進入到 10;若直接返回HttpResponse
物件進入到 12。 - [中介軟體]實現
url
匹配的view
邏輯:若引發異常進入到 11;若正常返回HttpResponse
物件進入到 12 - [中介軟體]遍歷執行
_exception_middleware
- [中介軟體]遍歷執行
_response_middleware
,對HttpResponse
進行處理並最終返回response
啟動
在開發環境中,我們一般是通過命令列執行 runserver
命令,ruserver
命令是使用 Django
自帶的的 Web Server
,而在正式的環境中,一般會使用 Nginx+uWSGI
模式。
無論通過哪種方式,啟動一個專案時,都會做兩件事:
- 建立一個
WSGIServer
類的例項,來接受使用者的請求。 - 當一個使用者的
HTTP
請求到達的時,為使用者指定一個WSGIHandler
,用於處理使用者請求與響應,這個Handler
是處理整個Request
的核心。
WSGI
WSGI
:全稱 Web Server Gateway Interface
。
WSGI
不是伺服器,Python
模組,框架,API
或者任何軟體,只是一種規範,描述 Web Server
如何與 Web Application
通訊的規範。
WSGI
協議主要包括 server
和 application
兩部分:
WSGI Server
負責從客戶端接收請求,將request
轉發給application
,將application
返回的response
返回給客戶端;WSGI Application
接收由server
轉發的request
,處理請求,並將處理結果返回給server
。application
中可以包括多個棧式的中介軟體(middlewares),這些中介軟體需要同時實現server
與application
,因此可以在WSGI
伺服器與WSGI
應用之間起調節作用:對伺服器來說,中介軟體扮演應用程式,對應用程式來說,中介軟體扮演伺服器。
Django WSGI Application
WSGI Application
應該實現為一個可呼叫物件,例如:函式、方法、類(包含 call
方法)。
需要接收兩個引數:
- 包含客戶端請求的資訊以及其他資訊的字典。可以認為是請求上下文,一般叫做
environment
(編碼中多簡寫為environ
、env
); - 用於傳送
HTTP
響應狀態(HTTP Status
)、響應頭(HTTP Headers
)的回撥函式;
通過回撥函式將響應狀態和響應頭返回給 WSGI Server
,同時返回響應正文,響應正文是可迭代的、幷包含了多個字串。下面是 Django WSGI Application
的具體實現:
class WSGIHandler(base.BaseHandler):
request_class = WSGIRequest
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# 本文作者注:載入中介軟體
self.load_middleware()
def __call__(self, environ, start_response):
set_script_prefix(get_script_name(environ))
# 本文作者注:處理請求前傳送訊號
signals.request_started.send(sender=self.__class__, environ=environ)
request = self.request_class(environ)
response = self.get_response(request)
response._handler_class = self.__class__
status = '%d %s' % (response.status_code, response.reason_phrase)
response_headers = list(response.items())
for c in response.cookies.values():
response_headers.append(('Set-Cookie', c.output(header='')))
# 本文作者注:將響應的 header 和 status 返回給 server
start_response(status, response_headers)
if getattr(response, 'file_to_stream', None) is not None and environ.get('wsgi.file_wrapper'):
response = environ['wsgi.file_wrapper'](response.file_to_stream)
return response
複製程式碼
可以看出 Django WSGI Application
的流程包括:
- 載入所有中介軟體,以及執行框架相關的操作,設定當前執行緒指令碼字首,傳送請求開始訊號;
- 處理請求,呼叫
get_response()
方法處理當前請求,該方法的的主要邏輯是通過urlconf
找到對應的view
和callback
,按順序執行各種middleware
和callback
; - 呼叫由
WSGI Server
傳入的start_response()
方法將響應header
與status
返回給WSGI Server
; - 返回響應正文。
Django WSGI Server
負責獲取 HTTP
請求,將請求傳遞給 Django WSGI Application
,由 Django WSGI Application
處理請求後返回 response
。以 Django
內建 server
為例看一下具體實現。
通過 runserver
命令執行 Django
專案,在啟動時都會呼叫下面的 run
方法,建立一個 WSGIServer
的例項,之後再呼叫其 serve_forever()
方法啟動服務。
def run(addr, port, wsgi_handler, ipv6=False, threading=False, server_cls=WSGIServer):
server_address = (addr, port)
if threading:
httpd_cls = type('WSGIServer', (socketserver.ThreadingMixIn, server_cls), {})
else:
httpd_cls = server_cls
httpd = httpd_cls(server_address, WSGIRequestHandler, ipv6=ipv6)
if threading:
httpd.daemon_threads = True
httpd.set_app(wsgi_handler)
httpd.serve_forever()
複製程式碼
下圖表示 WSGI Server
伺服器處理流程中關鍵的類和方法(來自:參考引用_1)
1. WSGIServer
run()
方法會建立 WSGIServer
例項,主要作用是接收客戶端請求,將請求傳遞給WSGI Application
,然後將 WSGI Application
返回的 response
返回給客戶端。
- 建立例項時會指定
HTTP
請求的handler
:WSGIRequestHandler
類; - 通過
set_app
和get_app
方法設定和獲取WSGIApplication
例項wsgi_handler
; - 處理
HTTP
請求時,呼叫handler_request
方法,會建立WSGIRequestHandler
例項處理HTTP
請求; WSGIServer
中get_request
方法通過socket
接受請求資料;
2. WSGIRequestHandler
- 由
WSGIServer
在呼叫handle_request
時建立例項,傳入request,cient_address,WSGIServer
三個引數,__init__
方法在例項化同時還會呼叫自身的handle
方法; handle
方法會建立ServerHandler
例項,然後呼叫其run
方法處理請求;
3. ServerHandler
WSGIRequestHandler
在其handle
方法中呼叫run
方法,傳入self.server.get_app()
引數,獲取WSGIApplication
,然後呼叫例項(call),獲取response
,其中會傳入start_response
回撥,用來處理返回的header
和status
;- 通過
application
獲取response
以後,通過finish_response
返回response
;
4. WSGIHandler(即 Django WSGI Application)
WSGI
協議中的application
,接收兩個引數,environ
字典包含了客戶端請求的資訊以及其他資訊,可以認為是請求上下文,start_response
用於傳送返回status
和header
的回撥函式
雖然上面一個 Django WSGI Server
涉及到多個類實現以及相互引用,但其實原理還是呼叫WSGIHandler
,傳入請求引數以及回撥方法 start_response()
,並將響應返回給客戶端。
Python wsgiref simple_server
在 Python3.7
的原始碼中給出了一個 simple_server
案例位於 python3.7/wsgiref/simple_server.py
。
模組實現了一個簡單的 HTTP
伺服器,並給出了一個簡單的 demo
,可以直接執行,執行結果會將請求中涉及到的環境變數在瀏覽器中展示出來。其中包括上述描述的整個 HTTP
請求的所有元件:ServerHandler,WSGIServer,WSGIRequestHandler
以及 demo_app
表示的簡易版的 WSGIApplication
。
感興趣的話可以自己去看一下原始碼。
載入配置
Django
的配置都在 {project_name}/settings.py
中定義,可以是 Django
的配置,也可以是自定義的配置,並且都通過 django.conf.settings
訪問。
中介軟體-Middleware
概述:
Django
中的 Middleware
類似底層中一個輕量級的外掛系統,它能夠介入 Django
的請求和響應過程,在全域性修改 Django
的輸入和輸出內容。從流程總覽圖中可以看出 Django
請求處理過程的核心在於 Middleware
,Django
中所有的請求和響應都有 Middleware
的參與。
細則:
一個 HTTP
請求,首先被轉化成一個 HttpRequest
物件,然後該物件被傳遞給 Request Middleware
處理,如果它返回了 HttpResponse
物件,則直接傳遞給 Response Middleware
做收尾處理。否則的話 Request Middleware
將訪問 URL
配置,確定目標 view
來處理 HttpRequest
物件,在確定了 view
,但是還沒有執行時候,系統會把 HttpRequest
物件傳遞給 View Middleware
進行處理,如果它返回了 HttpResponse
物件,那麼該 HttpResponse
物件將被傳遞給 Response Middleware
進行後續處理,否則將執行確定的 view
函式處理並返回 HttpResponse
物件,在整個過程中如果引發了異常並丟擲,會被 Exception Middleware
進行處理。
中介軟體執行順序
在請求階段,呼叫檢視之前,Django
按照 setting.py
設定的順序,自頂向下應用遍歷執行 Request Middleware
。你可以把它想象成一個洋蔥:每個中介軟體類都是一個“層”,它覆蓋了洋蔥的核心。如果請求通過洋蔥的所有層(每一個呼叫 get_response
)以將請求傳遞到下一層,一直到核心的檢視,那麼響應將在返回的過程中通過每個層(以相反的順序)。
如何編寫自己的中介軟體即中介軟體的深入瞭解
編寫一個自己的中介軟體是很容易的,每個中介軟體元件都是一個獨立的 Python Class
,你可以在自定義的 Class
下編寫一個或多個下面的方法:
process_request
函式樣式:process_request(request)
;
引數解析:request
是一個 HTTPRequest
物件;
呼叫時間:在 Django
決定執行哪個 view
之前,process_request()
會被請求呼叫;
產生響應:它應該返回一個 None
或一個 HttpResponse
物件,如果返回 None
,Django
會繼續處理這個請求;如果它返回一個 HTTPResponse
物件,Django
會直接跳轉到 Response Middleware
;
process_view
函式樣式:process_view(request, view_func, view_args, view_kwargs)
;
引數解析:request
是一個 HTTPRequest
物件,view_func
是 Django
會呼叫的一個函式(準確的說是一個函式物件而非一個表示函式名的字串),view_args
是一個會被傳遞到檢視的 *args
,view_kwargs
是一個會被傳遞到檢視的 **kwargs
,view_args
和 view_kwargs
都不包括 request
;
呼叫時間:process_view()
會在 Django
呼叫 view
前被呼叫;
產生響應:它應該返回一個 None
或一個 HttpResponse
物件,如果返回 None
,Django
會繼續處理這個請求;如果它返回一個 HTTPResponse
物件,Django
會直接跳轉到 Response Middleware
;
PS:除 CsrfViewMiddleware
外中介軟體執行時在檢視執行前或在 process_view()
中訪問 request.POST
會使得之後的所有檢視無法修改 request
,所以應該儘量避免。
process_template_response
函式樣式:process_template_response(request, response)
;
引數解析:request
是一個 HttpRequest
物件,response
是一個 TemplateResponse
物件(或類似物件),由 Django
檢視或中介軟體返回;
呼叫時間:如果 response
的例項有 render()
方法,process_template_response()
在檢視剛好執行完畢之後被呼叫,這表明他是一個 TemplateResponse
物件(或類似物件);
產生響應:這個方法必須返回一個實現了 render()
方法的 TemplateResponse
物件(或類似物件),它可以修改給定的 response
物件,也可以建立一個全新的 TemplateResponse
物件(或類似物件);
PS:在響應處理階段,中介軟體以相反的順序執行,包括 process_template_response
;
process_response
函式樣式:process_response(request, response)
;
引數解析:request
是一個 HttpRequest
物件,response
是一個 HttpResponse
物件,由 Django
檢視或中介軟體返回;
呼叫時間:process_request
在所有響應返回客戶端前被呼叫;
產生響應:這個方法必須返回一個 HttpRequest
物件,它可以修改給定的 response
物件,也可以建立一個全新的 HttpRequest
物件;
PS:process_response
總是被呼叫,這意味著你的 process_response
不能依賴 process_request
process_exception
函式樣式:process_exception(request, exception)
;
引數解析:request
是一個 HttpRequest
物件,exception
是一個被檢視丟擲 Exception
物件;
呼叫時間:當一個檢視丟擲異常,Django
會呼叫 process_exception
來處理;
產生響應:它應該返回一個 None
或一個 HttpResponse
物件,如果返回 None
,Django
會繼續處理這個請求;如果它返回一個 HTTPResponse
物件,模板物件和 Response Middleware
會被直接返回給客戶端;否則,將啟動預設異常處理。;
URL Resolver
概述:
假設:中介軟體便利執行完 _request_middleware,_view_middleware
後都返回 None
。
當 Django
遍歷執行完 _request_middleware
後會得到一個經過處理的 request
物件,此時 Django
將按順序進行對 url
進行正則匹配,如果匹配不成功,就會丟擲異常;如果匹配成功,Django
會繼續迴圈執行 _view_middleware
並在執行後繼續執行剛剛匹配成功的 view
。
在 setting
中有一個 ROOT_URLCONF
,它指向 urls.py
檔案,根據這個檔案可以生產一個 urlconf
,本質上,他就是 url
與檢視函式之間的對映表,然後通過 resolver
解析使用者的 url
,找到第一個匹配的 view
。
細則:
重要函式原始碼位置:
_path: django/urls/conf.py
URLPattern: django/urls/resolvers.py
ResolverMatch: django/urls/resolvers.py
URLResolver: django/urls/resolvers.py
複製程式碼
原始碼比較長,就不放出來了,感興趣的話自己去看吧。
- 通過
urlpatterns
的配置執行_path
函式; _path
函式進行判斷:如果是一個list
或者tuple
,就用URLResolver
處理,跳至 4;如果是一個正常的可呼叫的view
函式,則用URLPattern
處理,跳至;如果匹配失敗,丟擲異常;URLPattern
初始化相應值後執行resolve
方法:如果匹配成功,返回ResolverMatch
;如果匹配失敗,丟擲異常;URLResolver
匹配path
如果匹配成功,則繼續匹配它的url_patterns
,跳至 5;匹配失敗,丟擲異常;- 匹配
url_patterns
:若為urlpattern
匹配成功,返回ResolverMatch
;若為URLResolver
遞迴呼叫URLResolver
跳至 4;若匹配失敗,丟擲異常;
可以發現,整個過程的關鍵就是 ResolverMatch
,URLPattern
和 URLResolver
三個類,其中:
ResolverMatch
是匹配結果,包含匹配成功後需要的資訊;
URLPattern
是一個 url
對映資訊的物件,包含了 url
對映對應的可呼叫物件等資訊;
URLResolver
是實現 url
路由,解析 url
的關鍵的地方,它的 url_patterns
既可以是URLPattern
也可以是 URLResolver
,正是因為這種設計, 實現了對 url
的層級解析。
總述
真實的請求響應過程肯定是比我提到的這些還要複雜的多,但是我的能力實在有限,目前僅能理解到這個層面了,如果錯誤歡迎指正。
參考引用: