今天我們講 request,在 Nginx 中我們指的是 http 請求,具體到 Nginx 中的資料結構是ngx_http_request_t。 ngx_http_request_t 是對一個 http 請求的封裝。我們知道,一個 http 請求,包含請求行、請求頭、請求體、回應行、回應頭、回應體。
http 請求是典型的請求-回應類型的網路協議,而http 是文字協議,所以我們在分析請求行與請求頭,以及輸出回應行與回應頭,往往是一行一行的進行處理。 ( 建議學習:nginx使用 )
如果我們自己來寫一個 http 伺服器,通常在一個連線建立好後,客戶端會發送請求過來。然後我們讀取一行數據,分析出請求行中包含的 method、uri、http_version 資訊。
然後再一行一行處理請求頭,並根據請求 method 與請求頭的資訊來決定是否有請求體以及請求體的長度,然後再去讀取請求體。
得到請求後,我們處理請求產生需要輸出的數據,然後再產生回應行,回應頭以及回應體。
在將回應傳送給客戶端之後,一個完整的請求就處理完了。當然這是最簡單的 webserver 的處理方式,其實 Nginx 也是這樣做的,只是有一些小小的區別,比如,當請求頭讀取完成後,就開始進行請求的處理了。 Nginx 透過 ngx_http_request_t 來保存解析請求與輸出回應相關的資料。
那接下來,簡單講講 Nginx 是如何處理一個完整的請求的。 對於 Nginx 來說,一個請求是從ngx_http_init_request 開始的,在這個函數中,會設定讀取事件為 ngx_http_process_request_line,也就是說,接下來的網路事件,會由 ngx_http_process_request_line 來執行。
從ngx_http_process_request_line 的函數名,我們可以看到,這就是來處理請求行的,正好與之前講的,處理請求的第一件事就是處理請求行是一致的。
透過 ngx_http_read_request_header 來讀取請求資料。然後呼叫 ngx_http_parse_request_line 函數來解析請求行。 Nginx 為提高效率,採用狀態機來解析請求行,而且在進行method 的比較時,沒有直接使用字串比較,而是將四個字元轉換成一個整數,然後一次比較以減少cpu 的指令數,這個前面有說過。
很多人可能很清楚一個請求行包含請求的方法,uri,版本,卻不知道其實在請求行中,也是可以包含有 host 的。例如一個請求GET http://www.taobao.com/uri HTTP/1.0 這樣一個請求行也是合法的,而且host 是www.taobao.com,這個時候,Nginx 會忽略請求頭中的host 域,而以請求行中的這個為準來查找虛擬主機。
另外,對於 http0.9 版來說,是不支援請求頭的,所以這裡也是要特別的處理。所以,在後面解析請求頭時,協定版本都是 1.0 或 1.1。整個請求行解析到的參數,會儲存到 ngx_http_request_t 結構當中。
在解析完請求行後,Nginx 會設定讀取事件的 handler 為 ngx_http_process_request_headers,然後後續的請求就在 ngx_http_process_request_headers 中進行讀取與解析。
ngx_http_process_request_headers 函數用來讀取請求頭,跟請求行一樣,還是呼叫ngx_http_read_request_header 來讀取請求頭,呼叫ngx_http_parse_header_line 來解析一行請求頭,解析到的請求頭會保存到ngx_http_request_in 的域中,headers_in 是一個鍊錶結構,保存所有的請求頭。
而HTTP 中有些請求是需要特別處理的,這些請求頭與請求處理函數存放在一個映射表裡面,即ngx_http_headers_in,在初始化時,會產生一個hash 表,當每解析到一個請求頭後,就會先在這個hash 表中查找,如果有找到,則呼叫對應的處理函數來處理這個請求頭。例如:Host 頭的處理函數是 ngx_http_process_host。
當 Nginx 解析到兩個回車換行符時,就表示請求頭的結束,此時就會呼叫 ngx_http_process_request 來處理請求了。
ngx_http_process_request 會設定目前的連線的讀寫事件處理函式為 ngx_http_request_handler,然後再呼叫 ngx_http_handler 來真正開始處理一個完整的http請求。
這裡可能比較奇怪,讀寫事件處理函式都是ngx_http_request_handler,其實在這個函式中,會根據目前事件是讀取事件還是寫事件,分別呼叫 ngx_http_request_t 中的 read_event_handler 或是 write_event_handler。
由於此時,我們的請求頭已經讀取完成了,之前有說過,Nginx 的做法是先不讀取請求body,所以這裡面我們設定read_event_handler 為ngx_http_block_reading,即不讀取數據了。
剛才說到,真正開始處理數據,是在 ngx_http_handler 這個函數裡面,這個函數會設定 write_event_handler 為 ngx_http_core_run_phases,並執行 ngx_http_core_run_phases 函數。
ngx_http_core_run_phases 這個函數會執行多階段請求處理,Nginx 將一個 http 請求的處理分成多個階段,那麼這個函數就是執行這些階段來產生資料。
因為 ngx_http_core_run_phases 最後會產生數據,所以我們就很容易理解,為什麼設定寫事件的處理函數為 ngx_http_core_run_phases 了。
在這裡,我簡單說明了一下函數的呼叫邏輯,我們需要明白最終是呼叫ngx_http_core_run_phases 來處理請求,產生的回應頭會放在ngx_http_request_t 的headers_out 中,這一部分內容,我會放在請求處理流程裡面去講。 Nginx 的各種階段會對請求進行處理,最後會呼叫 filter 來過濾數據,對數據進行加工,如 truncked 傳輸、gzip 壓縮等。
這裡的 filter 包含 header filter 與 body filter,即對回應頭或回應體進行處理。 filter 是鍊錶結構,分別有 header filter 與 body filter,先執行 header filter 中的所有 filter,然後再執行 body filter 中的所有 filter。
在 header filter 中的最後一個 filter,即 ngx_http_header_filter,這個 filter 將會遍歷所有的響應頭,最後需要輸出的響應頭在一個連續的內存,然後調用 ngx_http_write_filter 進行輸出。
ngx_http_write_filter 是 body filter 中的最後一個,所以 Nginx 首先的 body 訊息,在經過一系列的 body filter 之後,最後也會呼叫 ngx_http_write_filter 來進行輸出(有圖來說明)。
這裡要注意的是,Nginx 會將整個請求頭都放在一個buffer 裡面,這個buffer 的大小透過設定項client_header_buffer_size 來設置,如果使用者的請求頭太大,這個buffer 裝不下,那Nginx 就會重新分配一個新的更大的buffer 來裝請求頭,這個大buffer 可以透過large_client_header_buffers 來設置,這個large_buffer 這一組buffer,例如配置48k,就是表示有四個8k 大小的buffer 可以用。
注意,為了保存請求行或請求頭的完整性,一個完整的請求行或請求頭,需要放在一個連續的記憶體裡面,所以,一個完整的請求行或請求頭,只會保存在一個buffer 裡面。
這樣,如果請求行大於一個 buffer 的大小,就會回傳 414 錯誤,如果一個請求頭大小大於一個 buffer 大小,就會回傳 400 錯誤。在了解了這些參數的值,以及 Nginx 實際的做法之後,在應用場景,我們就需要根據實際的需求來調整這些參數,來最佳化我們的程式了。
處理流程圖:
#以上這些,就是 Nginx 中一個 http 請求的生命週期了。
以上是nginx的請求如何處理?的詳細內容。更多資訊請關注PHP中文網其他相關文章!