1. 程式人生 > >nginx http請求的PHASE深度解析

nginx http請求的PHASE深度解析

原文出處:http://www.xuebuyuan.com/1722415.html

當客戶端的一個request到達伺服器的時候,可以想象一下,一個伺服器應該怎麼處理這個請求呢?nginx又是怎麼處理請求的呢?


客戶端一個請求到達nginx後,一個worker程序accept後開始處理,首先解析此次請求的請求行(request line),然後處理請求頭(request headers),然後再經過http各功能模組,實現對不同請求的特定處理,最後將result返回給客戶端。

**一:那麼各個模組是怎麼work的呢?**

nginx在這裡採用了PHASE狀態機來實現的,每個phase階段由checker函式和handler函式來控制。先看一下nginx的11個PHASE,有個直觀的印象。

typedef enum {
    NGX_HTTP_POST_READ_PHASE = 0,


    NGX_HTTP_SERVER_REWRITE_PHASE,

    NGX_HTTP_FIND_CONFIG_PHASE,
    NGX_HTTP_REWRITE_PHASE,
    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_phases;
上面列舉的就是nginx的所有phase,每個phase只有一個checker,checker是來控制請求階段走向的,通過cheker函式來判斷是繼續在本phase進行下一個handler,還是到下一個phase的handler進行處理,或者直接跳到某個phase的某個handler處理,後續會詳細看一下REWRITE_PHASE的checker函式:ngx_http_core_rewrite_phase 的工作過程。

每個phase都有哪些模組的handler掛載了呢?

當一個nginx程序在處理完請求行和請求頭之後,就會到達phase狀態機的入口函式:ngx_http_core_run_phases

這個函式囊括了整個狀態機的執行。

void
ngx_http_core_run_phases(ngx_http_request_t *r)
{
    ngx_int_t                   rc;

    ngx_http_phase_handler_t   *ph;
    ngx_http_core_main_conf_t  *cmcf;

    cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);

/*
ph就儲存了各phase的checker和handler資訊。
while迴圈就是順序執行這些checker,checker是舵手,根據handler處理結果或者相關配置來掌控下一步走法
*/
    ph = cmcf->phase_engine.handlers;

    while (ph[r->phase_handler].checker) {


        rc = ph[r->phase_handler].checker(r, &ph[r->phase_handler]);

        if (rc == NGX_OK) {
            return;
        }
    }
}
採用gdb除錯nginx時,列印一下ngx_http_core_run_phases函式中的ph變數,可以看到當前編譯的nginx在phase中註冊的handler,通過handler的名稱我們就能判斷出handler的隸屬模組,下面是筆者所用nginx打印出的ph值。

ph[0]= {checker = 0x43b8e1 <ngx_http_core_rewrite_phase>, handler = 0x48c0c0 <ngx_http_rewrite_handler>, next = 1}
ph[1]= {checker = 0x43b997 <ngx_http_core_find_config_phase>, handler = 0, next = 0}
ph[2]= {checker = 0x43b8e1 <ngx_http_core_rewrite_phase>, handler = 0x4a76a2 <ngx_http_xxx_xxx_handler>, next = 4}//自己開發的第三方擴充套件模組
ph[3]= {checker = 0x43b8e1 <ngx_http_core_rewrite_phase>, handler = 0x48c0c0 <ngx_http_rewrite_handler>, next = 4}
ph[4]= {checker = 0x43bdb5 <ngx_http_core_post_rewrite_phase>, handler = 0, next = 1}
ph[5]= {checker = 0x43b7fe <ngx_http_core_generic_phase>, handler = 0x484b98 <ngx_http_limit_req_handler>, next = 7}
ph[6]= {checker = 0x43b7fe <ngx_http_core_generic_phase>, handler = 0x4837f8 <ngx_http_limit_conn_handler>, next = 7}
ph[7]= {checker = 0x43bf99 <ngx_http_core_access_phase>, handler = 0x483334 <ngx_http_access_handler>, next = 10}
ph[8]= {checker = 0x43bf99 <ngx_http_core_access_phase>, handler = 0x48272c <ngx_http_auth_basic_handler>, next = 10}
ph[9]= {checker = 0x43c16c <ngx_http_core_post_access_phase>, handler = 0, next = 10}
ph[10]={checker = 0x43cae6 <ngx_http_core_content_phase>, handler = 0x467eb8 <ngx_http_index_handler>, next = 13}
ph[11]={checker = 0x43cae6 <ngx_http_core_content_phase>, handler = 0x480d5c <ngx_http_autoindex_handler>, next = 13}
ph[12]={checker = 0x43cae6 <ngx_http_core_content_phase>, handler = 0x467600 <ngx_http_static_handler>, next = 13}
可見phase狀態機最後的形式是一個指向ngx_http_phase_handler_t結構的指標,cmcf->phase_engine.handlers。

那麼cmcf->phase_engine.handlers這個指標指向的陣列的賦值就成了關鍵。

**二:這些handler是怎麼新增到cmcf->phase_engine.handlers指向的陣列中去的呢?**

先看一下cmcf,cmcf結構是ngx_http_core_main_conf_t

ngx_http_core_main_conf_t有兩個phase相關的成員變數:

typedef struct {

    ngx_http_phase_engine_t    phase_engine;

    ngx_http_phase_t           phases[NGX_HTTP_LOG_PHASE + 1];

} ngx_http_core_main_conf_t;
在ngx_http_block函式中,依次呼叫了各模組的 postconfiguration 函式,而玄機就在這些模組的postconfiguration函式裡

static char *
ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ......

    for (m = 0; ngx_modules[m]; m++) {
        if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
            continue;
        }

        module = ngx_modules[m]->ctx;

        if (module->postconfiguration) {
            if (module->postconfiguration(cf) != NGX_OK) {
                return NGX_CONF_ERROR;
            }
        }
    }

    if (ngx_http_variables_init_vars(cf) != NGX_OK) {
        return NGX_CONF_ERROR;
    }

    *cf = pcf;


    if (ngx_http_init_phase_handlers(cf, cmcf) != NGX_OK) {
        return NGX_CONF_ERROR;
    }

    ......
}
例如:看一下訪問控制模組ngx_http_access_module的postconfiguration函式

static ngx_http_module_t  ngx_http_access_module_ctx = {
    NULL,                                  /* preconfiguration */
    ngx_http_access_init,                  /* postconfiguration */

    NULL,                                  /* create main configuration */
    NULL,                                  /* init main configuration */

    NULL,                                  /* create server configuration */
    NULL,                                  /* merge server configuration */

    ngx_http_access_create_loc_conf,       /* create location configuration */
    ngx_http_access_merge_loc_conf         /* merge location configuration */
};
可以看到ngx_http_access_module模組postconfiguration函式是ngx_http_access_init,在ngx_http_access_init函式中,完成了新增access模組handler的第一步:

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);
    /*將本模組的handler:ngx_http_access_handler放入到NGX_HTTP_ACCESS_PHASE階段。
      存在了cmcf->phases[NGX_HTTP_ACCESS_PHASE].handlers這個array中
    */
    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;
}
即呼叫訪問控制模組的postconfiguration函式,將本模組的handler新增到cmcf->phases中NGX_HTTP_ACCESS_PHASE階段的陣列中。

隨著各個模組的postconfiguration函式呼叫,最後,各http模組均將自己的handler註冊到了cmcf->phases中對應的phase陣列中。下面列舉了各PHASE一般包含的http功能模組,“:”左邊是階段(PHASE)名稱,“:”右邊是http功能模組,

POST_READ_PHASE:realip         
SERVER_REWRITE_PHASE:rewrite
FIND_CONFIG_PHASE:NULL
REWRITE_PHASE:rewrite
POST_REWRITE_PHASE:NULL
PREACCESS_PHASE:limit_zone、limit_req、realip
ACCESS_PHASE:auth_basic 、access         
POST_ACCESS_PHASE:NULL
TRY_FILES_PHASE:NULL
CONTENT_PHASE:index、autoindex、static、dav、gzip_static、random_index
LOG_PHASE:log

需要提到兩個點:

1:為什麼realip存在兩個phase中

POST_READ_PHASE階段的作用範圍為server,使用realip的相關指令,可以對rewrite造成影響

PREACCESS_PHASE階段的作用範圍為location

2:怎麼控制一個phase內執行不同模組執行的handler的順序

執行順序是按照模組的註冊順序的倒序,在進行phase_handlers初始化的時候實現的倒序,這個在後面會提到。

在ngx_http_block依次呼叫所有模組的postconfiguration函式後,11個phase的handler已經註冊完畢,那麼怎麼對應到phase_engine上的呢?

ngx_http_block接著呼叫了函式ngx_http_init_phase_handlers,

static ngx_int_t
ngx_http_init_phase_handlers(ngx_conf_t *cf, ngx_http_core_main_conf_t *cmcf)
{
    ngx_int_t                   j;
    ngx_uint_t                  i, n;
    ngx_uint_t                  find_config_index, use_rewrite, use_access;
    ngx_http_handler_pt        *h;
    ngx_http_phase_handler_t   *ph;
    ngx_http_phase_handler_pt   checker;
   //這三個index記錄了rewrite後的下一步,和rewrite命令結尾字元如break,last有關
    cmcf->phase_engine.server_rewrite_index = (ngx_uint_t) -1;
    cmcf->phase_engine.location_rewrite_index = (ngx_uint_t) -1;
    find_config_index = 0;
    /*use_rewrite用來標註REWRITE_PHASE不將phases[NGX_HTTP_POST_REWRITE_PHASE]陣列中的handler放到phase_engine
    ,而是進行特殊處理;
      use_access用來標註POST_ACCESS_PHASE不將phases[NGX_HTTP_POST_ACCESS_PHASE]陣列中的handler放到phase_engine
    ,而是進行特殊處理;
      同樣的還有try_files階段和find_location階段;
      可以看到在接下來的switch程式碼段的時候,case match後continue的階段就是剛提到的進行特殊處理的四個階段
    */
    use_rewrite = cmcf->phases[NGX_HTTP_REWRITE_PHASE].handlers.nelts ? 1 : 0;
    use_access = cmcf->phases[NGX_HTTP_ACCESS_PHASE].handlers.nelts ? 1 : 0;

    n = use_rewrite + use_access + cmcf->try_files + 1 /* find config phase */;

    for (i = 0; i < NGX_HTTP_LOG_PHASE; i++) {
        n += cmcf->phases[i].handlers.nelts;
    }

    ph = ngx_pcalloc(cf->pool,
                     n * sizeof(ngx_http_phase_handler_t) + sizeof(void *));
    if (ph == NULL) {
        return NGX_ERROR;
    }

    cmcf->phase_engine.handlers = ph;
    n = 0;
/*
for迴圈就是將phases的handler放到phase_engine陣列中
*/
    for (i = 0; i < NGX_HTTP_LOG_PHASE; i++) {
        h = cmcf->phases[i].handlers.elts;

        switch (i) {

        case NGX_HTTP_SERVER_REWRITE_PHASE:
            if (cmcf->phase_engine.server_rewrite_index == (ngx_uint_t) -1) {
                cmcf->phase_engine.server_rewrite_index = n;
            }
            checker = ngx_http_core_rewrite_phase;

            break;

        case NGX_HTTP_FIND_CONFIG_PHASE:
            find_config_index = n;

            ph->checker = ngx_http_core_find_config_phase;
            n++;
            ph++;

            continue;

        case NGX_HTTP_REWRITE_PHASE:
            if (cmcf->phase_engine.location_rewrite_index == (ngx_uint_t) -1) {
                cmcf->phase_engine.location_rewrite_index = n;
            }
            checker = ngx_http_core_rewrite_phase;

            break;

        case NGX_HTTP_POST_REWRITE_PHASE:
            if (use_rewrite) {
                ph->checker = ngx_http_core_post_rewrite_phase;
                ph->next = find_config_index;
                n++;
                ph++;
            }

            continue;

        case NGX_HTTP_ACCESS_PHASE:
            checker = ngx_http_core_access_phase;
            n++;
            break;

        case NGX_HTTP_POST_ACCESS_PHASE:
            if (use_access) {
                ph->checker = ngx_http_core_post_access_phase;
                ph->next = n;
                ph++;
            }

            continue;

        case NGX_HTTP_TRY_FILES_PHASE:
            if (cmcf->try_files) {
                ph->checker = ngx_http_core_try_files_phase;
                n++;
                ph++;
            }

            continue;

        case NGX_HTTP_CONTENT_PHASE:
            checker = ngx_http_core_content_phase;
            break;

        default:
            checker = ngx_http_core_generic_phase;
        }

        n += cmcf->phases[i].handlers.nelts;
/*
在這裡採用了倒敘的方法,即同一個phase中先註冊模組的handler會在後面
*/
        for (j = cmcf->phases[i].handlers.nelts - 1; j >=0; j--) {
            ph->checker = checker;
            ph->handler = h[j];
            ph->next = n;
            ph++;
        }
    }

    return NGX_OK;
}
通過程式碼發現,如果要編寫第三方模組的話,根據需求可以將自己模組的handler註冊到某個phase;也可以更改nginx原始碼,支援一個新phase。

但是有四個phase是不支援新增http功能的handler的,這四個階段分別是FIND_CONFIG_PHASE、POST_REWRITE_PHASE、POST_ACCESS_PHASE、TRY_FILES_PHASE。

**三:checker是怎麼工作的呢?**

那個比較有代表性的checker討論討論,NGX_HTTP_POST_REWRITE_PHASE的checker:ngx_http_core_post_rewrite_phase

ngx_http_request_t結構有個成員變數phase_handler標誌著走到了cmcf->phase_engine.handlers陣列的哪一個元素,即到了狀態機的哪一步。

 ph = cmcf->phase_engine.handlers;

    while (ph[r->phase_handler].checker) {

        rc = ph[r->phase_handler].checker(r, &ph[r->phase_handler]);

        if (rc == NGX_OK) {
            return;
        }
    }
當前request到了狀態機的哪一步,就呼叫對應的checker,假設走到了上面所列狀態機的第五步,r->phase_handler = 4,

ph[4]= {checker = 0x43bdb5 <ngx_http_core_post_rewrite_phase>, handler = 0, next = 1}
那麼就會執行ngx_http_core_post_rewrite_phase這個checker。

注意,每個phase_handler還有一個變數時next,在這裡next=1,表明了requset在某些情況下從當前狀態直接跳到find_config階段

ph[1]= {checker = 0x43b997 <ngx_http_core_find_config_phase>, handler = 0, next = 0}
ngx_int_t
ngx_http_core_post_rewrite_phase(ngx_http_request_t *r,
    ngx_http_phase_handler_t *ph)
{
    ngx_http_core_srv_conf_t  *cscf;

    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "post rewrite phase: %ui", r->phase_handler);
    /*
    不管是server_rewrite階段,還是rewrite階段,都會進行url的rewrite重寫,
    當使用last的時候,uri_changed=1,當使用break的時候,uri_changed=0;
    也就是說當使用break時候,是走到狀態機的下一步。
    */
    if (!r->uri_changed) {
        r->phase_handler++;
        return NGX_AGAIN;
    }

    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "uri changes: %d", r->uri_changes);
    /*
      如果使用last,則uri_changes會減去1
      uri_changes初始化的值為NGX_HTTP_MAX_URI_CHANGES + 1=11次
      也就是說在nginx內部url 的rewrite最多迴圈重定向11次,就會結束這個請求,並報500的錯誤碼

    */

    r->uri_changes--;

    if (r->uri_changes == 0) {
        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                      "rewrite or internal redirection cycle "
                      "while processing \"%V\"", &r->uri);

        ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
        return NGX_OK;
    }
    /*
      使用last的時候,就會改變狀態機的當前狀態,使得狀態機回到find_config階段,ph->next在這裡指向的就是find_config階段
   */
    r->phase_handler = ph->next;

    cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
    r->loc_conf = cscf->ctx->loc_conf;

    return NGX_AGAIN;

}

 一般而言handler返回:

    NGX_OK:    表示該階段已經處理完成,需要轉入下一個階段;
    NG_DECLINED:    表示需要轉入本階段的下一個handler繼續處理;
    NGX_AGAIN, NGX_DONE:
         表示需要等待某個事件發生才能繼續處理(比如等待網路IO),此時Nginx為了不阻塞其他請求的處理,必須中斷當前請求的執行鏈,等待事件發生之後繼續執行該handler;
    NGX_ERROR:    表示發生了錯誤,需要結束該請求。
    checker函式根據handler函式的不同返回值,給上一層的ngx_http_core_run_phases函式返回NGX_AGAIN或者NGX_OK,如果期望上一層繼續執行後面的phase則需要確保checker函式不是返回NGX_OK,不同checker函式對handler函式的返回值處理還不太一樣,開發模組時需要確保相應階段的checker函式對返回值的處理在你的預期之內。

--------The End