五分鐘學 NGINX - 詳解 Nginx 如何處理 HTTP 頭部

    Nginx 作爲高性能的 HTTP 服務器和反向代理服務器,在處理 HTTP 請求時,對 HTTP 頭部的處理是至關重要的一環。

接收請求事件模塊

    Nginx 使用了一個事件驅動的架構,這使得它能夠高效地處理大量的併發連接。下面是 Nginx 處理 HTTP 請求的詳細流程:

1. 建立連接

三次握手:客戶端通過發送 SYN 包開始與 Nginx 建立 TCP 連接。Nginx 響應 SYN+ACK 包,客戶端再次發送 ACK 包完成握手。

負載均衡:如果有多個 worker 進程在監聽相同的端口,操作系統的負載均衡機制會選擇一個 worker 進程來處理新的連接。

讀事件:一旦連接建立,Nginx 的事件模塊會監聽來自客戶端的數據。當有數據到達時,操作系統會通知 Nginx。

Nginx Worker 負載均衡選擇

    Nginx 使用事件驅動和異步 I/O 來處理請求,它的工作進程(worker processes)可以並行處理多個客戶端連接。當多個工作進程監聽相同的端口時,操作系統的負載均衡機制會介入:

讀事件監聽與處理

    一旦 TCP 連接建立,Nginx 需要準備接收客戶端發送的數據。這是通過 Nginx 的事件模塊來實現的:

  1. I/O 多路複用:Nginx 使用 epoll(或其他類似的 I/O 多路複用技術)來同時監控多個網絡套接字上的事件。epoll 允許 Nginx 以非阻塞的方式檢測哪些套接字上有數據可讀。

  2. 事件通知:當操作系統檢測到某個網絡套接字上有數據到達時,epoll 會通知 Nginx。Nginx 的事件模塊會捕獲這個事件,並將事件加入到事件隊列中。

  3. 事件處理:Nginx 會從事件隊列中取出事件,並調用相應的處理函數來讀取數據。在 Nginx 的源碼中,這個處理函數通常是 ngx_event_accept,它會處理新的連接請求。

2. 接收請求

epoll_wait:Nginx 使用 epoll_wait 系統調用來等待 I/O 事件的發生,如客戶端發送的數據到達。

讀取請求:當 epoll_wait 檢測到讀事件時,Nginx 會調用 ngx_http_wait_request_handler 來讀取客戶端發送的 HTTP 請求數據。

epoll_wait 系統調用

    epoll_wait 是 Linux 系統中用於等待 I/O 事件的系統調用,它是 epoll I/O 多路複用機制的一部分。Nginx 使用 epoll 來監控大量的網絡套接字,以檢測哪些套接字上有數據可讀或可寫。

讀取請求數據

    一旦 epoll_wait 檢測到讀事件,Nginx 將調用相應的處理函數來讀取客戶端發送的數據。這個過程在 Nginx 源碼中是由 ngx_http_wait_request_handler 函數負責的。

3. 分配內存資源

分配連接內存池:Nginx 會爲每個新的連接分配一個連接內存池,其大小由 connection_pool_size 配置指令指定,默認爲 512 字節。

設置回調方法:Nginx 會設置回調方法 ngx_http_init_connection 來處理新的連接,並將其讀事件加入到 epoll 監控中。

添加超時定時器:爲了防止客戶端長時間不發送請求,Nginx 會添加一個超時定時器 client_header_timeout,默認爲 60 秒。

分配連接內存池

    Nginx 使用內存池來管理連接相關的數據,這樣可以提高內存使用的效率並減少內存分配和釋放的開銷。

設置回調方法

    Nginx 通過設置回調方法來處理新的連接,這是事件驅動編程的一個重要部分。

添加超時定時器

    爲了防止客戶端長時間不發送請求,Nginx 會爲每個連接設置一個超時定時器。

4. 用戶正式請求

讀取數據:Nginx 讀取客戶端發送的數據,並將其存儲在讀緩衝區中。

分配讀緩衝區:讀緩衝區的大小由 client_header_buffer_size 配置指令指定,默認爲 1KB。(這個值也不是越大越好,因爲當用戶有一個請求進來的時候,nignx 就會分配 1kb 內存出來,)

    在 Nginx 處理用戶正式請求的過程中,讀取數據和分配讀緩衝區是兩個基礎而關鍵的步驟。這兩個步驟確保了 Nginx 能夠接收和存儲客戶端發送的 HTTP 請求數據。以下是結合 Nginx 底層原理與源碼對這兩個步驟的詳細展開:

讀取數據

    Nginx 通過讀取客戶端發送的數據來開始處理 HTTP 請求。這個過程是在 I/O 事件觸發時進行的,通常是在 epoll 事件循環中,當檢測到讀事件(即客戶端發送數據)時,Nginx 會執行以下操作:

分配讀緩衝區

    讀緩衝區是用來臨時存儲從客戶端接收到的數據的內存區域。Nginx 爲每個連接分配一個讀緩衝區,以便存儲請求頭和請求體。

    上面是 nginx 處理連接,下面我們來看下 nginx 處理請求的過程,處理請求的過程跟處理連接是不一樣的,因爲系統需要進行大量的上下文分析,分析 http 協議跟 http 的 header 信息。

接收請求 HTTP 模塊

1. 解析請求

狀態機解析請求行:Nginx 使用狀態機來解析客戶端發送的 HTTP 請求行,這包括請求方法、URI 和 HTTP 版本。

接收 URI 和 Header:Nginx 繼續讀取請求的 URI 和 Header 信息。

    在 Nginx 的工作流程中,解析請求是一個至關重要的步驟,它涉及到從客戶端接收的原始 HTTP 請求中提取出有用的信息,如請求方法、URI 和 HTTP 版本等。這一過程是通過狀態機來實現的,狀態機是一種編程模式,用於按順序處理輸入數據,這裏是指 HTTP 請求的不同部分。以下是結合 Nginx 底層原理與源碼對解析請求過程的詳細展開:

狀態機解析請求行

    Nginx 使用內部的狀態機來逐行解析客戶端發送的 HTTP 請求。狀態機的主要任務是識別請求行中的各個組成部分,並將其存儲在相應的數據結構中。

接收 URI 和 Header

    在請求行被成功解析之後,Nginx 會繼續讀取請求的 URI 和 Header 信息。

2. 分配內存資源

分配請求內存池:爲了存儲請求數據,Nginx 會分配一個請求內存池,其大小由 request_pool_size 指令指定,默認爲 4KB。

分配大內存:如果請求行或 Header 超過了基礎的內存池大小,Nginx 會根據 large_client_header_buffers 指令分配更大的內存塊,該指令默認設置爲 4 個 8KB 的內存塊。

分配請求內存池

    當一個 HTTP 請求到達 Nginx 時,Nginx 需要一塊內存區域來存儲請求的各個部分,包括請求行(包含方法、URI 和 HTTP 版本)、請求頭(包含各種頭部字段)以及可能的請求體(例如 POST 請求中的數據)。爲了高效地管理這些數據,Nginx 使用了一個稱爲 “內存池” 的機制。

分配大內存

    在某些情況下,請求的頭部或請求行可能會非常大,超出了默認的 4KB 內存池的限制。例如,如果客戶端發送了一個包含大量頭部字段的請求,或者 URI 非常長,那麼就需要更多的內存來存儲這些數據。

狀態機解析請求行

    Nginx 使用內部的狀態機來解析客戶端發送的 HTTP 請求行和請求頭。狀態機是一種編程模型,它根據輸入數據(在這種情況下是 HTTP 請求的各個部分)和當前的狀態來決定下一步的操作。

    通過這種機制,Nginx 能夠靈活地處理各種大小的 HTTP 請求,同時保持內存使用的高效性。狀態機的解析過程是 Nginx 請求處理的核心部分,它確保了請求數據的正確解析和後續處理的順利進行。

3. 標識 URI、狀態機解析 Header 和分配大內存

    在 Nginx 處理 HTTP 請求的過程中,標識 URI、狀態機解析 Header 和分配大內存是關鍵步驟,這些步驟確保了 Nginx 能夠正確解析客戶端請求併爲後續處理做好準備。以下是結合 Nginx 底層原理與源碼對這些步驟的詳細展開:

標識 URI

  1. 解析請求行:Nginx 首先使用狀態機解析請求行,這包括識別請求方法(如 GET 或 POST)、URI 和 HTTP 版本。

  2. URI 處理:解析出的 URI 會被進一步處理,Nginx 會根據配置的路由規則和重寫規則來確定最終的請求目標。

  3. 位置匹配:Nginx 會查找與請求的 URI 匹配的 location 塊,這決定了請求將如何被處理,例如轉發到代理服務器或直接提供靜態文件。

狀態機解析 Header

  1. 讀取請求頭:在請求行被解析之後,Nginx 繼續讀取請求頭。請求頭包含了客戶端傳遞的元數據,如 HostUser-AgentContent-Type 等。

  2. 狀態機:Nginx 使用一個內部狀態機來逐行解析請求頭。狀態機根據 HTTP 協議規範和請求頭的格式來逐個處理頭部字段。

  3. 存儲頭部信息:解析出的頭部信息被存儲在 ngx_http_request_t 結構體中,以便在後續的請求處理階段中使用。

分配大內存

  1. 默認內存池:每個請求都會分配一個默認大小的內存池,由 request_pool_size 指令指定,默認爲 4KB。

  2. 大內存需求:如果請求頭的大小超過了默認內存池的容量,Nginx 需要分配額外的內存來存儲這些數據。

  3. 動態內存分配:Nginx 根據 large_client_header_buffers 指令動態分配額外的內存。這個指令定義了可以分配的最大內存塊的數量和大小,通常設置爲 4 個 8KB 的內存塊。

標識 Header

  1. 處理頭部字段:一旦請求頭被讀取和解析,Nginx 會根據頭部字段的內容執行特定的操作。例如,如果存在 Content-Length 字段,Nginx 會知道請求體的大小,並準備讀取相應的數據。

  2. 變量賦值:Nginx 會將請求頭中的某些值賦給內部變量,這些變量可以在配置文件中引用,用於重寫規則、日誌記錄等。

  3. 模塊處理:不同的 Nginx 模塊可能會對請求頭進行特定的處理。例如,安全模塊可能會檢查 Sec-WebSocket-Key 字段以支持 WebSocket 連接。

4. 處理請求

移除超時定時器:在請求行和 Header 被成功解析之後,Nginx 會移除之前設置的 client_header_timeout 超時定時器,該定時器默認設置爲 60 秒,用於檢測客戶端是否在超時時間內發送完整的請求頭。

開始 11 個階段的 HTTP 請求處理:Nginx 將請求處理分爲 11 個階段,每個階段可以包含多個模塊的處理函數。這些階段包括重寫、權限檢查、內容生成、日誌記錄等。

    處理請求是 Nginx 接收到客戶端 HTTP 請求後的核心環節,涉及到多個階段的執行和多個模塊的參與。以下是結合 Nginx 底層原理與源碼對處理請求過程的詳細展開:

移除超時定時器

    在 Nginx 配置中,client_header_timeout 指令用於設置讀取客戶端請求頭的超時時間。如果在指定時間內,Nginx 未能接收到完整的請求頭,那麼連接將被關閉以避免資源佔用。

11 個階段的 HTTP 請求處理

    Nginx 將每個請求的處理分爲 11 個階段,每個階段負責處理特定的任務。這種模塊化的設計使得 Nginx 能夠靈活地配置和擴展其功能。

  1. 服務器重寫階段 (NGX_HTTP_SERVER_REWRITE_PHASE):在這個階段,Nginx 可以重寫請求的 URI 和請求方法。

  2. 查找配置階段 (NGX_HTTP_FIND_CONFIG_PHASE):Nginx 根據請求的 URI 查找最合適的服務器配置。

  3. 重寫階段 (NGX_HTTP_REWRITE_PHASE):在這個階段,Nginx 可以根據 location 塊進一步重寫 URI。

  4. 權限檢查階段 (NGX_HTTP_ACCESS_PHASE):Nginx 檢查客戶端是否有權限訪問請求的資源。

  5. 內容生成階段 (NGX_HTTP_CONTENT_PHASE):在這個階段,Nginx 調用內容生成模塊來生成響應內容。

  6. 日誌記錄階段 (NGX_HTTP_LOG_PHASE):Nginx 記錄請求的日誌信息。

  7. 其他階段:還包括嘗試文件階段、文件查找階段、錯誤處理階段等。

上面的所有步驟都是 nginx 的框架執行的,後面的 11 個階段的 HTTP 請求處理 是 nginx http 模塊的執行流程

5. 詳細處理流程

NGX_HTTP_SERVER_REWRITE_PHASE:服務器重寫階段,用於修改請求的 URI。
NGX_HTTP_FIND_CONFIG_PHASE:查找配置階段,用於確定請求應該由哪個 server 塊處理。
NGX_HTTP_REWRITE_PHASE:重寫階段,用於修改請求的 URI 和頭部。
NGX_HTTP_POST_REWRITE_PHASE:重寫後階段,用於處理重寫後的結果。
NGX_HTTP_PREACCESS_PHASE:訪問前階段,用於進行權限檢查。
NGX_HTTP_ACCESS_PHASE:訪問階段,用於執行訪問控制。
NGX_HTTP_POST_ACCESS_PHASE:訪問後階段,用於執行訪問控制後的清理工作。
NGX_HTTP_TRY_FILES_PHASE:嘗試文件階段,用於嘗試查找請求的文件。
NGX_HTTP_CONTENT_PHASE:內容生成階段,用於生成響應的內容。
NGX_HTTP_LOG_PHASE:日誌記錄階段,用於記錄請求的日誌。
NGX_HTTP_CLOSE_REQUEST_PHASE:關閉請求階段,用於清理請求資源。

6. 結束處理

Nginx 處理 HTTP 頭部的過程是高效且靈活的,它通過精細的內存管理和狀態機解析,確保了在各種情況下都能快速準確地處理客戶端請求。我們在下一篇詳細的學習 Nginx 處理 HTTP 請求的 11 個階段的過程與原理。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/DFvayYOXY7kuhouLAAy61A