源碼:nginx 1.12.0
一、nginx http模塊簡介
由於nginx的性能優勢,現在已經有越來越多的單位、個人采用nginx或者openresty、tengine等衍生版來作為WEB服務器、負載均衡服務器、安全網關來使用。在這些場景下,依賴的就是nginx的http模塊,nginx的設計者采用模塊化的設計思路,允許用戶在http請求處理的各個階段添加自己設計的模塊來實現自己的一些邏輯,擴充一些功能。
二、http模塊功能介紹
http模塊的豐富功能其實是由一個個http_module來共同實現的,每個module提供單獨的功能方便開發、維護。nginx通過配置文件中的一個個配置項的設置來實現對相關模塊功能的調用,如下示例:
http { server { location ~ \.php$ { #proxy_pass命令在http_proxy_module中的被ngx_http_proxy_pass函數實現 proxy_pass http://127.0.0.1; } } }
http模塊對一個完整http請求的處理流程如下:


上面的流程中,http_handler、output_filter這兩步分別是執行phase_handler、filter兩類http module的位置。在講這兩類模塊之前,需要先理清這些模塊是如何加載到添加到nginx中的。
在nginx啟動時,會將各個module加載到一個數組中,然后調用ngx_parse_conf尋找配置文件中特定類型的command,然后根據command找到包含該command的module,並執行command對應的函數(這些函數通常是將對應command的配置結構中設置一些變量或者將函數賦值給相關的handler函數指針,以便在處理對應command請求時可以直接調用)。
在處理http這個command時,對應函數會初始化所有類型為HTTP_CORE_MODULE的模塊,處理流程如下:


三、phase handlers請求處理階段
nginx對http的請求處理分成了POST_READ, SERVER_REWRITE, FIND_CONFIG, REWRITE, POST_REWRITE, PREACCESS, ACCESS, POST_ACCESS, TRY_FILES, CONTENT, LOG這11個階段,除了FIND_CONFIG, POST_REWRITE, POST_ACCESS, TRY_FILES這4個階段用戶不能添加自定義的處理函數,其余每個階段都通過ngx_http_init_phases函數初始化了一個數組用於保存本階段的處理函數指針。如下:
////// nginx/src/http/ngx_http.c //////// static ngx_int_t ngx_http_init_phases(ngx_conf_t *cf, ngx_http_core_main_conf_t *cmcf) { //申請指針數組,用於存儲該階段的處理函數指針 if (ngx_array_init(&cmcf->phases[NGX_HTTP_POST_READ_PHASE].handlers, cf->pool, 1, sizeof(ngx_http_handler_pt)) != NGX_OK) { return NGX_ERROR; } ..... return NGX_OK; } ///// nginx/src/http/modules/ngx_http_rewrite_module.c ///// // 該函數是在rewrite的postconfiguration階段被調用 static ngx_int_t ngx_http_rewrite_init(ngx_conf_t *cf) { ..... //獲取rewrite命令對應的core_module配置結構 cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); //在&cmcf->phases[NGX_HTTP_SERVER_REWRITE_PHASE].handlers指向的數組中申請一個函數指針空間 h = ngx_array_push(&cmcf->phases[NGX_HTTP_SERVER_REWRITE_PHASE].handlers); if (h == NULL) { return NGX_ERROR; } //將函數添加到數組中新申請的元素中 *h = ngx_http_rewrite_handler; ...... return NGX_OK; } ////// nginx/src/http/ngx_http.c //////// //遍歷各個PHASE,給每個PHASE添加相關的checker函數,並將各個phase階段注冊的函數按照phase+數組index的順序統一添加到一個phase_engine中,每個phase handler函數都會由唯一的index標識,便於之后的遍歷使用 static ngx_int_t ngx_http_init_phase_handlers(ngx_conf_t *cf, ngx_http_core_main_conf_t *cmcf) { 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; } //根據phase階段確定每個階段對應的checker checker = ngx_http_core_rewrite_phase; break; ..... ..... default: checker = ngx_http_core_generic_phase; } n += cmcf->phases[i].handlers.nelts; //遍歷各個phase handlers數組中的注冊函數並統一添加到cmcf->phase_engine.handlers中 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; }
在查看module源碼的時候發現一些沒有注冊postconfiguration函數的模塊,也就是說這些模塊並沒有通過ngx_array_push函數將handler添加到對應的phase中,例如http_memcached_module。但是這些模塊通過command對應的函數將真正的handler賦值給了該command所在location對應的conf結構的handler指針,這些handler指針在ngx_http_update_location_config函數(該函數被ngx_http_core_find_config_phase調用)中被賦值給了r->content_handler,然后在http_core_content_phase函數中被執行了(但是也跳過了該phase中其他的handler)。由此可見,這些沒有通過ngx_array_push顯示的加入到某一phase的handler,都通過這種方式加入到了content_phase中。
//////// nginx/src/http/ngx_http_core_module.c ///// void ngx_http_update_location_config(ngx_http_request_t *r) { ngx_http_core_loc_conf_t *clcf; clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); .... //如果對應command存在直接的handler函數,就直接調用 if (clcf->handler) { r->content_handler = clcf->handler; } } ngx_int_t ngx_http_core_content_phase(ngx_http_request_t *r, ngx_http_phase_handler_t *ph) { .... if (r->content_handler) { r->write_event_handler = ngx_http_request_empty_handler; //如果存在的話就直接執行該handler函數並跳過content階段的其他處理函數 ngx_http_finalize_request(r, r->content_handler(r)); return NGX_OK; } ..... } //調用各個所有phase中的函數完成對request的處理 void ngx_http_core_run_phases(ngx_http_request_t *r) { .... while (ph[r->phase_handler].checker) { //根據請求中的phase索引確定執行的checker函數 //checker函數根據處理結果來決定是結束處理還是繼續下一個phase handler rc = ph[r->phase_handler].checker(r, &ph[r->phase_handler]); if (rc == NGX_OK) { return; } } }
綜合上述兩種request處理module處理方法,如果需要在特定的PHASE階段執行的話,就用ngx_array_push這種方式;r->content_handler這種方式只能在CONTENT PHASE階段執行,且導致該階段的其他handler無法執行,這需要開發者考慮。
四、filter輸出處理模塊
filter輸出處理主要是在輸出的時候對輸出內容進行處理,filter handler調用順序與phase handler類型(根據phase階段順序)不同,filter類型的各個handler是通過鏈表的形式聯系到一塊的,只有鏈表頭ngx_http_top_body_filter 函數指針是全局的。在函數
ngx_http_output_filter中,有一個調用filter handler的入口,這個函數可以在正常的ngx_http_send_response函數中被調用,也可以在特定的phase handler中被調用。
在各個module初始化的時候,會將ngx_http_top_body_filter指針的值保存到ngx_http_next_body_filter局部變量中,然后把當前filter的處理函數賦值給ngx_http_top_body_filter,同時每個filter的處理函數中都將ngx_http_next_body_filter的值賦值給ctx->output_filter,以便可以順序遍歷各個filter。
對於ngx_http_write_filter_module、ngx_http_header_filter_module兩個模塊中沒有ngx_http_next_body_filter變量,是因為ngx_http_write_filter_module是最后一個filter模塊,因此不用next。ngx_http_header_filter_module雖然是倒數第二個模塊,但是filter函數中調用了write_filter的函數,因此也沒有使用next。
///// nginx/src/http/ngx_http_core_module.c /////// ngx_int_t ngx_http_output_filter(ngx_http_request_t *r, ngx_chain_t *in) { ..... //通過鏈表接口來一次遍歷各個filter模塊進行處理 rc = ngx_http_top_body_filter(r, in); ..... return rc; } ///// nginx/src/http/ngx_http_copy_filter_module.c ////////// //在postconfiguration階段被調用,初始化filter handler鏈表 static ngx_int_t ngx_http_copy_filter_init(ngx_conf_t *cf) { ngx_http_next_body_filter = ngx_http_top_body_filter; ngx_http_top_body_filter = ngx_http_copy_filter; return NGX_OK; } //將本階段輸出執行鏈表中下一個filter handler函數 static ngx_int_t ngx_http_copy_filter(ngx_http_request_t *r, ngx_chain_t *in) { ..... ..... ngx_http_set_ctx(r, ctx, ngx_http_copy_filter_module); //記錄下一個要執行的filter ctx->output_filter = (ngx_output_chain_filter_pt) ngx_http_next_body_filter; ctx->filter_ctx = r; .... .... rc = ngx_output_chain(ctx, in); ..... return rc; }