1. 程式人生 > >菜鳥學習nginx之HTTP請求處理(1)

菜鳥學習nginx之HTTP請求處理(1)

上一篇主要介紹Nginx是如何處理HTTP Header。由於HTTP請求處理這部分程式碼是Nginx核心內容,打算用兩篇文章深入介紹。本篇先介紹HTTP處理的11個階段。

一、HTTP處理11階段

1.1、為什麼要有11階段?

這個得從Nginx設計出發。首先Nginx是單執行緒且完全非同步框架,Nginx把所有功能都統一抽象成模組,即“萬物皆模組”。每個模組僅僅完成一個獨立、簡單的功能(說白點,就是一個功能模組不能佔用cpu時間太長)。對於一個HTTP請求來說,需要我們考慮的內容很多,因此Nginx將HTTP請求劃分成11個階段,每個階段只處理很簡單的功能,例如:ACCESS訪問許可權。

1.2、11階段定義

Nginx使用列舉定義11個階段,具體每個階段的含義已經在註釋中表明。(註釋摘自《深入理解Nginx模組開發與架構解析》)

typedef enum {
    /**
     * 在接收到完整的HTTP頭部後處理的HTTP階段
     */
    NGX_HTTP_POST_READ_PHASE = 0,

    /**
     * 在將請求的URI與location表示式匹配前,修改請求的URI(所謂的重定向)是一個獨立的HTTP階段
     */
    NGX_HTTP_SERVER_REWRITE_PHASE,

    /**
     * 根據請求的URI尋找匹配的location表示式,這個階段只能由ngx_http_core_module模組實現,
     * 不建議其他HTTP模組重新定義這一階段的行為
     */
    NGX_HTTP_FIND_CONFIG_PHASE,
    
    /**
     * 在NGX_HTTP_FIND_CONFIG_PHASE階段尋找到匹配的location之後再修改請求的URI
     */
    NGX_HTTP_REWRITE_PHASE,

    /**
     * 這一階段是用於在rewrite重寫URL後,防止錯誤的nginx.conf配置導致死迴圈(遞迴地修改URI),
     * 因此,這一階段僅由ngx_http_core_module模組處理。目前,控制死迴圈的方式很簡單,首先檢查
     * rewrite的次數,如果一個請求超過10次重定向,就認為進入了rewrite死迴圈,這時在
     * NGX_HTTP_POST_REWRITE_PHASE階段就會向用戶返回500,表示伺服器內部錯誤
     */
    NGX_HTTP_POST_REWRITE_PHASE,

    /**
     * 表示在處理NGX_HTTP_ACCESS_PHASE階段決定請求的訪問許可權前,HTTP模組可以介入的處理階段
     */
    NGX_HTTP_PREACCESS_PHASE,

    /*
     * 這個階段用於讓HTTP模組判斷是否允許這個請求訪問Nginx伺服器
     */
    NGX_HTTP_ACCESS_PHASE,
    
    /**
     * 在NGX_HTTP_ACCESS_PHASE階段中,當HTTP模組的handler處理函式返回不允許訪問的錯誤碼時(實際就是
     * NGX_HTTP_FORBIDDEN或者NGX_HTTP_UNAUTHORIZED),這裡將負責向用戶傳送拒絕服務的錯誤響應。因此,
     * 這個階段實際上用於給NGX_HTTP_ACCESS_PHASE階段收尾
     */
    NGX_HTTP_POST_ACCESS_PHASE,

    /**
     * 這個階段完全是為try_files配置項而設立的,當HTTP請求訪問靜態檔案資源時,
     * try_files配置項可以使這個請求順序地訪問多個靜態檔案資源,如果某一次訪問失敗,則繼續訪問
     * try_files中指定的下一個靜態資源。這個功能完全是在
     * NGX_HTTP_TRY_FILES_PHASE階段中實現的
     */
    NGX_HTTP_TRY_FILES_PHASE,

    /**
     * 用於處理HTTP請求內容的階段,這是大部分HTTP模組最願意介入的階段
     */
    NGX_HTTP_CONTENT_PHASE,
    
    /**
     * 處理完請求後記錄日誌的階段。例如,ngx_http_log_module模組就在這個階段中加入了一個
     * handler處理方法,使得每個HTTP請求處理完畢後會記錄access_log訪問日誌
     */
    NGX_HTTP_LOG_PHASE
} ngx_http_phases;

1.3、流水線處理

既然Nginx設計了11個階段,那麼Nginx是如何管理的呢?對於流程化管理,目前業界有兩種比較好的方式:狀態機和流水線。Nginx採用流水線方式(Netty也採用流水線方式)。那麼Nginx具體是如何實現的呢?

typedef struct ngx_http_phase_handler_s  ngx_http_phase_handler_t;

typedef ngx_int_t (*ngx_http_phase_handler_pt)(ngx_http_request_t *r,
    ngx_http_phase_handler_t *ph);
/**
 * http階段 流水線處理
 */
struct ngx_http_phase_handler_s {
    ngx_http_phase_handler_pt  checker; /* 當前階段checker函式 */
    ngx_http_handler_pt        handler; /* 當前階段處理函式 在checker函式中呼叫 */
    ngx_uint_t                 next; /* 下一處理階段 */
};

說明:

1、checker函式只能由Nginx框架實現,使用者不能修改。 

2、handler函式,可以由使用者設定,handler回撥函式只能在checker函式中呼叫。

3、next指定下一個階段序號。Nginx雖然指定了11階段,但是它允許跳躍執行。例如:當ACCESS階段校驗不通過,可直接結束當前流程,而不必繼續向下執行。

4、checker回撥函式設定在函式ngx_http_init_phase_handlers中。

1.4、如何介入

雖然Nginx定義了11個階段,但並不是每一個階段使用者自定義模組都可以進入,具體可參考下面:

  名稱 備註
使用者模組可介入

NGX_HTTP_POST_READ_PHASE

NGX_HTTP_SERVER_REWRITE_PHASE

NGX_HTTP_REWRITE_PHASE

NGX_HTTP_PREACCESS_PHASE

NGX_HTTP_ACCESS_PHASE

NGX_HTTP_CONTENT_PHASE

NGX_HTTP_LOG_PHASE

使用者自定義的模組,可以介入這些模組。其中NGX_HTTP_CONTENT_PHASE是最常見介入階段。
使用者模組不可介入

NGX_HTTP_FIND_CONFIG_PHASE

NGX_HTTP_POST_REWIRTE_PHASE

NGX_HTTP_POST_ACESS_PHASE

NGX_HTTP_TRY_FILES_PHASE

使用者自定義模組不可以介入,這部分程式碼只能有HTTP框架介入。

那麼如何介入呢?Nginx有兩種方式:

1、設定postconfiguration回撥函式,向全域性的ngx_http_core_main_conf_t結構體的phase[NGX_HTTP_xx_PHASE]動態陣列中新增ngx_http_handler_pt處理方法。例如ACCESS模組新增:

static ngx_int_t
ngx_http_access_init(ngx_conf_t *cf)
{
    ngx_http_handler_pt        *h;
    ngx_http_core_main_conf_t  *cmcf;

    cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);

    h = ngx_array_push(&cmcf->phases[NGX_HTTP_ACCESS_PHASE].handlers);
    if (h == NULL) {
        return NGX_ERROR;
    }

    *h = ngx_http_access_handler;

    return NGX_OK;
}

2、設定ngx_http_core_loc_conf_t結構中handler回撥函式。Nginx知道大部分都是介入NGX_HTTP_CONTENT_PHASE階段,為了方便,Nginx在ngx_http_core_loc_conf_t中增了一個回撥函式。而且這種方式是NGX_HTTP_CONTENT_PHASE階段獨有的。若仍然不清楚,可參考本篇《菜鳥學習Nginx之入門開發留言板》中函式ngx_http_webservice_login。

兩種方式,第一種方式適用於所有階段,而第二種方式僅適用於NGX_HTTP_CONTENT_PHASE階段。

二、總結

本篇只簡單介紹Nginx為了解決複雜的HTTP請求,定義了11個階段以及如何介入11個階段。下一篇正式介紹HTTP請求如何處理