注:當前分析基於 Nginx之搭建反向代理實現tomcat分布式集群 的配置。
1. 用到的指令
下面介紹在上面的配置中用到的指令。
upstream 指令
語法:upstream name { ... }
默認值:none
使用環境:http
該指令用於設置一組可以在 proxy_pass 和 fastcgi_pass 指令中使用的代理服務器,默認的負載均衡方式為輪詢。示例如下:
upstream backend {
server backend1.example.com weight = 5;
server 127.0.0.1:8080 max_fails=3 fail_timeout=30s;
server unix:/tmp/backend3;
}
server 指令
語法:server name [parameters]
默認值:none
使用環境:upstream
該指令用於指定后端服務器的名稱和參數。服務器的名稱可以是一個域名、一個 IP 地址、端口號或 UNIX Socket。
在后端服務器名稱之后,可以跟以下參數:
- weight=NUMBER:設置服務器的權重,權重數值越高,被分配到的客戶端請求數越多。如果沒有設置權重,則為默認權重 1.
- max_fails=NUMBER:在參數 fail_timeout 指定的時間內對后端服務器請求失敗的次數,如果檢測到后端服務器無法連接及發生服務器錯誤(404錯誤除外),則標記為失敗。如果沒有設置,則為默認值 1。設為數組 0 將關閉這項檢查。
- fail_timeout=TIME:在經歷參數 max_fails 設置的失敗次數后,暫停的時間。
- down:標記服務器為永久離線狀態,用於 ip_hash 指令。
- backup:僅僅在非 backup 服務器全部宕機或繁忙的時候才啟用。
proxy_pass 指令
將指定請求傳遞到上游服務器,格式為 URL。
2. 數據結構
2.1 ngx_http_upstream_t
typedef struct ngx_http_upstream_s ngx_http_upstream_t;
struct ngx_http_upstream_s {
/*
* 處理讀事件的回調方法,每一個階段都有不同的 read_event_handler
*/
ngx_http_upstream_handler_pt read_event_handler;
/*
* 處理寫事件的回調方法,每一個階段都有不同的 write_event_handler
*/
ngx_http_upstream_handler_pt write_event_handler;
/*
* 表示主動向上游服務器發起的連接
*/
ngx_peer_connection_t peer;
/*
* 當向下游客戶端轉發響應時(ngx_http_request_t 結構體中的 subrequest_in_memory
* 標志位為 0),如果打開了緩存且認為上游網速更快(conf 配置中的 buffering 標志
* 位為 1),這時會使用 pipe 成員來轉發響應。在使用這種方式轉發響應時,必須由
* HTTP 模塊在使用 upstream 機制前構造 pipe 結構體,否則會出現嚴重的 coredump
* 錯誤.
*/
ngx_event_pipe_t *pipe;
/*
* request_bufs 以鏈表的方式把 ngx_buf_t 緩存區鏈接起來,它表示所有需要發送到
* 上游服務器的請求內容。所以,HTTP 模塊實現的 create_request 回調方法就在於
* 構造 request_bufs 鏈表
*/
ngx_chain_t *request_bufs;
/*
* 定義了向下游發送響應的方式
*/
ngx_output_chain_ctx_t output;
ngx_chain_writer_ctx_t writer;
/*
* 使用 upstream 機制時的各種配置
*/
ngx_http_upstream_conf_t *conf;
ngx_http_upstream_srv_conf_t *upstream;
#if (NGX_HTTP_CACHE)
ngx_array_t *caches;
#endif
/*
* HTTP 模塊在實現 process_header 方法時,如果希望 upstream 直接轉發響應,
* 就需要把解析出的響應頭部適配為 HTTP 的響應頭部,同時需要把包頭中的信息
* 設置到 headers_in 結構體,這樣,會把 headers_in 中設置的頭部添加到要發
* 送到下游客戶端的響應頭部 headers_out 中
*/
ngx_http_upstream_headers_in_t headers_in;
/*
* 用於解析主機域名
*/
ngx_http_upstream_resolved_t *resolved;
ngx_buf_t from_client;
/*
* 接收上游服務器響應包頭的緩沖區,在不需要把響應直接轉發給客戶端,
* 或者 buffering 標志位為 0 的情況下轉發包體時,接收包體的緩沖區
* 仍然使用 buffer。注意,如果沒有自定義 input_filter 方法處理包體,
* 將會使用 buffer 存儲全部的包體,這時 buffer 必須足夠大,它的大小
* 由 ngx_http_upstream_conf_t 配置結構體中的 buffer_size 成員決定
*/
ngx_buf_t buffer;
/*
* 表示來自上游服務器的響應包體的長度
*/
off_t length;
/*
* out_bufs 在兩種場景下有不同的意義:1. 當不需要轉發包體,且使用默認
* 的 input_filter 方法(也就是 ngx_http_upstream_non_buffered_filter
* 方法)處理包體時,out_bufs 將會指向響應包體,事實上,out_bufs 鏈表
* 中會產生多個 ngx_buf_t 緩沖區,每個緩沖區都指向 buffer 緩存中的一部
* 分,而這里的一部分就是每次調用 recv 方法接收到的一段 TCP 流。2. 當
* 需要轉發響應包體到下游時(buffering 標志位為 0,即以下游網速優先),
* 這個鏈表指向上一次向下游轉發響應到現在這段時間內接收自上游的緩存響應
*/
ngx_chain_t *out_bufs;
/*
* 當需要轉發響應包體到下游時(buffering 標志位為 0,即以下游網速優先),
* 它表示上一次向下游轉發響應時沒有發送完的內容
*/
ngx_chain_t *busy_bufs;
/*
* 這個鏈表將用於回收 out_bufs 中已經發送給下游的 ngx_buf_t 結構體,這
* 同樣應用在 buffering 標志位為 0 即以下游網速優先的場景
*/
ngx_chain_t *free_bufs;
/*
* 處理包體前的初始化方法,其中 data 參數用於傳遞用戶數據結構,它實際上
* 就是下面的 input_filter_ctx 指針
*/
ngx_int_t (*input_filter_init)(void *data);
/*
* 處理包體的方法,其中 data 參數用於傳遞用戶數據結構,它實際上就是下面的
* input_filter_ctx 指針,而 bytes 表示本次接收到的包體長度。返回 NGX_ERROR
* 時表示處理包體錯誤,請求需要結束,否則都將繼續 upstream 流程
*/
ngx_int_t (*input_filter)(void *data, ssize_t bytes);
/*
* 用於傳遞 HTTP 模塊自定義的數據結構,在 input_filter_init 和 input_filter
* 方法被回調時會作為參數傳遞過去
*/
void *input_filter_ctx;
#if (NGX_HTTP_CACHE)
ngx_int_t (*create_key)(ngx_http_request_t *r);
#endif
/*
* HTTP 模塊實現的 create_request 方法用於構造發往上游服務器的請求
*/
ngx_int_t (*create_request)(ngx_http_request_t *r);
/*
* 與上游服務器的通信失敗后,如果按照重試規則還需要再次向上游服務器發起
* 連接,則會調用 reinit_request 方法
*/
ngx_int_t (*reinit_request)(ngx_http_request_t *r);
/*
* 解析上游服務器返回響應的包頭,返回 NGX_AGAIN 表示包頭還沒有接收完整,
* 返回 NGX_HTTP_UPSTREAM_INVALID_HEADER 表示包頭不合法,返回 NGX_ERROR
* 表示出現錯誤,返回 NGX_OK 表示解析到完整的包頭.
*/
ngx_int_t (*process_header)(ngx_http_request_t *r);
void (*abort_request)(ngx_http_request_t *r);
/*
* 請求結束時會調用
*/
void (*finalize_request)(ngx_http_request_t *r,
ngx_int_t rc);
/*
* 在上游返回的響應出現 Location 或者 Refresh 頭部時表示重定向時,會通過
* ngx_http_upstream_process_headers 方法調用到可由 HTTP 模塊實現的
* rewrite_redirect 方法
*/
ngx_int_t (*rewrite_redirect)(ngx_http_request_t *r,
ngx_table_elt_t *h, size_t prefix);
ngx_int_t (*rewrite_cookie)(ngx_http_request_t *r,
ngx_table_elt_t *h);
ngx_msec_t timeout;
/*
* 用於表示上游響應的錯誤碼、包體長度等信息
*/
ngx_http_upstream_state_t *state;
ngx_str_t method;
/*
* schema 和 uri 成員僅在記錄日志時會用到,除此之外沒有意義
*/
ngx_str_t schema;
ngx_str_t uri;
#if (NGX_HTTP_SSL || NGX_COMPAT)
ngx_str_t ssl_name;
#endif
ngx_http_cleanup_pt *cleanup;
/*
* 是否指定文件緩存路徑的標志位
*/
unsigned store:1;
/*
* 是否啟用文件緩存
*/
unsigned cacheable:1;
unsigned accel:1;
/*
* 是否基於 SSL 協議訪問上游服務器
*/
unsigned ssl:1;
#if (NGX_HTTP_CACHE)
unsigned cache_status:3;
#endif
/*
* 向下游轉發上游的響應包體時,是否開啟更大的內存及臨時磁盤文件用於
* 緩存來不及發送到下游的響應包體.
*/
unsigned buffering:1;
unsigned keepalive:1;
unsigned upgrade:1;
/*
* request_sent 表示是否已經向上游服務器發送了請求,當 request_sent 為
* 1 時,表示 upstream 機制已經向上游服務器發送了全部或者部分的請求。
* 事實上,這個標志位更多的是為了使用 ngx_output_chain 方法發送請求,
* 因為該方法發送請求時會自動把未發送完的 request_bufs 鏈表記錄下來,
* 為了防止反復發送重復請求,必須有 request_sent 標志位記錄是否調用過
* ngx_output_chain 方法
*/
unsigned request_sent:1;
unsigned request_body_sent:1;
/*
* 將上游服務器的響應划分為包頭和包尾,如果把響應直接轉發給客戶端,
* header_sent 標志位表示包頭是否發送,header_sent 為 1 時表示已經
* 把包頭轉發給客戶端了。如果不轉發響應到客戶端,則 header_sent
* 沒有意義.
*/
unsigned header_sent:1;
};
2.2 ngx_http_upstream_conf_t
typedef struct {
/*
* 當在 ngx_http_upstream_t 結構體中沒有實現 resolved 成員時,upstream 這個
* 結構體才會生效,它會定義上游服務器的配置
*/
ngx_http_upstream_srv_conf_t *upstream;
/*
* 建立 TCP 連接的超時時間,實際上就是寫事件添加到定時器中設置的超時時間
*/
ngx_msec_t connect_timeout;
/*
* 發送請求的超時時間。通常就是寫事件添加到定時器中設置的超時時間
*/
ngx_msec_t send_timeout;
/*
* 接收響應的超時時間。通常就是讀事件添加到定時器中設置的超時時間
*/
ngx_msec_t read_timeout;
ngx_msec_t next_upstream_timeout;
/*
* TCP 的 SO_SNOLOWAT 選項,表示發送緩沖區的下限
*/
size_t send_lowat;
/*
* 定義了接收頭部的緩沖區分配的內存大小(ngx_http_upstream_t 中的 buffer
* 緩沖區),當不轉發響應給下游或者在 buffering 標志位為 0 的情況下轉發
* 響應時,它同樣表示接收包體的緩沖區大小
*/
size_t buffer_size;
size_t limit_rate;
/*
* 僅當 buffering 標志位為 1,並且向下游轉發響應時生效。它會設置到
* ngx_event_pipe_t 結構體的 busy_size 成員中
*/
size_t busy_buffers_size;
/*
* 在 buffering 標志位為 1 時,如果上游速度快於下游速度,將有可能把來自上游的
* 響應存儲到臨時文件中,而 max_temp_file_size 指定了臨時文件的最大長度。實際
* 上,它將限制 ngx_event_pipe_t 結構體中的 temp_file
*/
size_t max_temp_file_size;
/*
* 表示將緩沖區中的響應寫入臨時文件時一次寫入字符流的最大長度
*/
size_t temp_file_write_size;
size_t busy_buffers_size_conf;
size_t max_temp_file_size_conf;
size_t temp_file_write_size_conf;
/*
* 以緩存響應的方式轉發上游服務器的包體時所使用的內存大小
*/
ngx_bufs_t bufs;
/*
* 針對 ngx_http_upstream_t 結構體中保存解析完的包頭的 headers_in 成員,
* ignore_headers 可以按照二進制位使得 upstream 在轉發包頭時跳過對某些
* 頭部的處理。作為 32 位整型,理論上 ignore_headers 最多可以表示 32 個
* 需要跳過不予處理的頭部
*/
ngx_uint_t ignore_headers;
/*
* 以二進制位來表示一些錯誤碼,如果處理上游響應時發現這些錯誤碼,那么在
* 沒有將響應轉發給下游客戶端時,將會選擇下一個上游服務器來重發請求
*/
ngx_uint_t next_upstream;
/*
* 在 buffering 標志位為 1 的情況下轉發響應時,將有可能把響應存放到臨時文件
* 中。在 ngx_http_upstream_t 中的 store 標志位為 1 時,store_access 表示
* 所創建的目錄、文件的權限.
*/
ngx_uint_t store_access;
ngx_uint_t next_upstream_tries;
/*
* 決定轉發響應方式的標志位,buffering 為 1 時表示打開緩存,這時認為上游
* 的網速快於下游的網速,會盡量地在內存或者磁盤中緩存來自上游的響應;如果
* buffering 為 0,僅會開辟一塊固定大小的內存塊作為緩存來轉發響應
*/
ngx_flag_t buffering;
ngx_flag_t request_buffering;
ngx_flag_t pass_request_headers;
ngx_flag_t pass_request_body;
/*
* 標志位,為 1 時表示與上游服務器交互時將不檢查 Nginx 與下游客戶端間的連接
* 是否斷開。也就是說,即使下游客戶端主動關閉了連接,也不會中斷與上游服務器
* 間的交互.
*/
ngx_flag_t ignore_client_abort;
/*
* 當解析上游響應的包頭時,如果解析后設置到 headers_in 結構體中的 status_n
* 錯誤碼大於 400,則會試圖把它與 error_page 中指定的錯誤碼相匹配,如果匹配
* 上,則發送 error_page 中指定的響應,否則繼續返回上游服務器的錯誤碼.
*/
ngx_flag_t intercept_errors;
/*
* buffering 標志位為 1 的情況下轉發響應時才有意義。這時,如果 cyclic_temp_file
* 為 1,則會試圖復用臨時文件中已經使用過的空間。不建議將 cyclic_temp_file
* 設為 1.
*/
ngx_flag_t cyclic_temp_file;
ngx_flag_t force_ranges;
/*
* 在 buffering 標志位為 1 的情況下轉發響應時,存放臨時文件的路徑
*/
ngx_path_t *temp_path;
/*
* 不轉發的頭部。實際上是通過 ngx_http_upstream_hide_hash 方法,
* 根據 hide_headers 和 pass_headers 動態數組構造出的需要隱藏的
* HTTP 頭部散列表
*/
ngx_hash_t hide_headers_hash;
/*
* 當轉發上游響應頭部(ngx_http_upstream_t 中 headers_in 結構體中的頭部)
* 給下游客戶端時,如果不希望某些頭部轉發給下游,就設置到 hide_headers
* 動態數組中
*/
ngx_array_t *hide_headers;
/*
* 當轉發上游響應頭部(ngx_http_upstream_t 中的 headers_in 結構體中的頭部)
* 給下游客戶端時,upstream 機制默認不會轉發如 "Data"、"Server" 之類的頭部,
* 如果確實希望直接轉發它們到下游,就設置到 pass_headers 動態數組中
*/
ngx_array_t *pass_headers;
/*
* 連接上游服務器時使用的本機地址
*/
ngx_http_upstream_local_t *local;
#if (NGX_HTTP_CACHE)
ngx_shm_zone_t *cache_zone;
ngx_http_complex_value_t *cache_value;
ngx_uint_t cache_min_uses;
ngx_uint_t cache_use_stale;
ngx_uint_t cache_methods;
off_t cache_max_range_offset;
ngx_flag_t cache_lock;
ngx_msec_t cache_lock_timeout;
ngx_msec_t cache_lock_age;
ngx_flag_t cache_revalidate;
ngx_flag_t cache_convert_head;
ngx_flag_t cache_background_update;
ngx_array_t *cache_valid;
ngx_array_t *cache_bypass;
ngx_array_t *cache_purge;
ngx_array_t *no_cache;
#endif
/*
* 當 ngx_http_upstream_t 中的 store 標志位為 1 時,如果需要將上游的響應
* 存放到文件中,store_lengths 將表示存放路徑的長度,而 store_values
* 表示存放路徑
*/
ngx_array_t *store_lengths;
ngx_array_t *store_values;
#if (NGX_HTTP_CACHE)
signed cache:2;
#endif
signed store:2;
/*
* 上面的 intercept_errors 標志位定義了 400 以上的錯誤碼將會與 error_page
* 比較后再行處理,實際上這個規則是可以有一個例外情況,如果將 intercept_404
* 標志位設為 1,當上游返回 404 時會直接轉發這個錯誤碼給下游,而不會去與
* errpr_page 進行比較.
*/
unsigned intercept_404:1;
/*
* 為 1 時,將會根據 ngx_http_upstream_t 中 headers_in 結構體里的 X-Accel-Buffering
* 頭部(它的值會是 yes 和 no)來改變 buffering 標志位,當其值為 yes 時,buffering
* 標志位為 1。因此,change_buffering 為 1 時將有可能根據上游服務器返回的響應頭部,
* 動態地決定是以上游網速優先還是以下游網速優先
*/
unsigned change_buffering:1;
#if (NGX_HTTP_SSL || NGX_COMPAT)
ngx_ssl_t *ssl;
ngx_flag_t ssl_session_reuse;
ngx_http_complex_value_t *ssl_name;
ngx_flag_t ssl_server_name;
ngx_flag_t ssl_verify;
#endif
ngx_str_t module;
NGX_COMPAT_BEGIN(2)
NGX_COMPAT_END
} ngx_http_upstream_conf_t;
3. upstream 的解析
static ngx_command_t ngx_http_upstream_commands[] = {
{ ngx_string("upstream"),
NGX_HTTP_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE1,
ngx_http_upstream,
0,
0,
NULL },
{ ngx_string("server"),
NGX_HTTP_UPS_CONF|NGX_CONF_1MORE,
ngx_http_upstream_server,
NGX_HTTP_SRV_CONF_OFFSET,
0,
NULL },
ngx_null_command
};
3.1 ngx_http_upstream
假設 upstream{} 的配置如下:
upstream rong {
server 192.168.56.101:8080;
server 192.168.56.101:8081;
}
則調用該函數進行解析:
static char *
ngx_http_upstream(ngx_conf_t *cf, ngx_command_t *cmd, void *dummy)
{
char *rv;
void *mconf;
ngx_str_t *value;
ngx_url_t u;
ngx_uint_t m;
ngx_conf_t pcf;
ngx_http_module_t *module;
ngx_http_conf_ctx_t *ctx, *http_ctx;
ngx_http_upstream_srv_conf_t *uscf;
ngx_memzero(&u, sizeof(ngx_url_t));
value = cf->args->elts;
u.host = value[1];
u.no_resolve = 1;
u.no_port = 1;
/* 先從 upstreams 數組中檢測是否已有相同的項存在,若沒有則新創建一個
* ngx_http_upstream_srv_conf_t,該結構體代表一個上游服務器,
* 然后將其添加到 upstreams 數組中 */
uscf = ngx_http_upstream_add(cf, &u, NGX_HTTP_UPSTREAM_CREATE
|NGX_HTTP_UPSTREAM_WEIGHT
|NGX_HTTP_UPSTREAM_MAX_CONNS
|NGX_HTTP_UPSTREAM_MAX_FAILS
|NGX_HTTP_UPSTREAM_FAIL_TIMEOUT
|NGX_HTTP_UPSTREAM_DOWN
|NGX_HTTP_UPSTREAM_BACKUP);
if (uscf == NULL) {
return NGX_CONF_ERROR;
}
/* 對於 http{} 中的每一個 block,都要構建屬於該 block 的 ngx_http_conf_ctx_t,
* 該結構體中包含三個指針數組成員:main_conf、srv_conf、loc_conf,存放着該
* block 解析到的所有配置 */
ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t));
if (ctx == NULL) {
return NGX_CONF_ERROR;
}
http_ctx = cf->ctx;
/* 當前 block 的 main_conf 指向上一級的 block 的 main_conf,對於 upstream,
* 上一級的 block 即為 http{} */
ctx->main_conf = http_ctx->main_conf;
/* 下面是分配創建屬於該 upstream{} 的 srv_conf 和 loc_conf */
/* the upstream{}'s srv_conf */
ctx->srv_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
if (ctx->srv_conf == NULL) {
return NGX_CONF_ERROR;
}
/* 將上面創建的 ngx_http_upstream_srv_conf_t 的首地址存放到當前 upstream 模塊
* 在 srv_conf 數組中的索引處
* 注:當前 ngx_http_upstream_module 模塊沒有實現 create_srv_conf 函數,因此
* 下面調用的 create_srv_conf 不會導致這里的值被覆蓋了 */
ctx->srv_conf[ngx_http_upstream_module.ctx_index] = uscf;
uscf->srv_conf = ctx->srv_conf;
/* the upstream{}'s loc_conf */
ctx->loc_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
if (ctx->loc_conf == NULL) {
return NGX_CONF_ERROR;
}
for (m = 0; cf->cycle->modules[m]; m++) {
if (cf->cycle->modules[m]->type != NGX_HTTP_MODULE) {
continue;
}
module = cf->cycle->modules[m]->ctx;
if (module->create_srv_conf) {
mconf = module->create_srv_conf(cf);
if (mconf == NULL) {
return NGX_CONF_ERROR;
}
ctx->srv_conf[cf->cycle->modules[m]->ctx_index] = mconf;
}
if (module->create_loc_conf) {
mconf = module->create_loc_conf(cf);
if (mconf == NULL) {
return NGX_CONF_ERROR;
}
ctx->loc_conf[cf->cycle->modules[m]->ctx_index] = mconf;
}
}
/* 創建 servers 數組,該數組中每一個元素都為 ngx_http_upstream_server_t
* 類型的結構體,該結構體代表 upstream{} 中的 server 指令 */
uscf->servers = ngx_array_create(cf->pool, 4,
sizeof(ngx_http_upstream_server_t));
if (uscf->servers == NULL) {
return NGX_CONF_ERROR;
}
/* parse inside upstream{} */
pcf = *cf;
cf->ctx = ctx;
cf->cmd_type = NGX_HTTP_UPS_CONF;
/* 這里開始解析 upstream{} */
rv = ngx_conf_parse(cf, NULL);
*cf = pcf;
if (rv != NGX_CONF_OK) {
return rv;
}
/* 若 upstream{} 中沒有配置 server 指令,則表明發生錯誤了 */
if (uscf->servers->nelts == 0) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"no servers are inside upstream");
return NGX_CONF_ERROR;
}
return rv;
}
3.1.1 ngx_http_upstream_add
ngx_http_upstream_srv_conf_t *
ngx_http_upstream_add(ngx_conf_t *cf, ngx_url_t *u, ngx_uint_t flags)
{
ngx_uint_t i;
ngx_http_upstream_server_t *us;
ngx_http_upstream_srv_conf_t *uscf, **uscfp;
ngx_http_upstream_main_conf_t *umcf;
if (!(flags & NGX_HTTP_UPSTREAM_CREATE)) {
if (ngx_parse_url(cf->pool, u) != NGX_OK) {
if (u->err) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"%s in upstream \"%V\"", u->err, &u->url);
}
return NULL;
}
}
/* 獲取 ngx_http_upstream_module 模塊在 main 級別的配置結構體 */
umcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_upstream_module);
/* upstreams 是一個數組,每一個數組元素的類型為 ngx_http_upstream_srv_conf_t */
uscfp = umcf->upstreams.elts;
/* 遍歷該數組,檢測是否已有相同的存在,若是,則返回該已添加到 upstreams 數組中的
* ngx_http_upstream_srv_conf_t 結構體 */
for (i = 0; i < umcf->upstreams.nelts; i++) {
if (uscfp[i]->host.len != u->host.len
|| ngx_strncasecmp(uscfp[i]->host.data, u->host.data, u->host.len)
!= 0)
{
continue;
}
if ((flags & NGX_HTTP_UPSTREAM_CREATE)
&& (uscfp[i]->flags & NGX_HTTP_UPSTREAM_CREATE))
{
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"duplicate upstream \"%V\"", &u->host);
return NULL;
}
if ((uscfp[i]->flags & NGX_HTTP_UPSTREAM_CREATE) && !u->no_port) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"upstream \"%V\" may not have port %d",
&u->host, u->port);
return NULL;
}
if ((flags & NGX_HTTP_UPSTREAM_CREATE) && !uscfp[i]->no_port) {
ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
"upstream \"%V\" may not have port %d in %s:%ui",
&u->host, uscfp[i]->port,
uscfp[i]->file_name, uscfp[i]->line);
return NULL;
}
if (uscfp[i]->port && u->port
&& uscfp[i]->port != u->port)
{
continue;
}
if (flags & NGX_HTTP_UPSTREAM_CREATE) {
uscfp[i]->flags = flags;
uscfp[i]->port = 0;
}
return uscfp[i];
}
/* 若upstreams數組中沒有,則新創建一個 ngx_http_upstream_srv_conf_t */
uscf = ngx_pcalloc(cf->pool, sizeof(ngx_http_upstream_srv_conf_t));
if (uscf == NULL) {
return NULL;
}
uscf->flags = flags;
/* 該代理服務器的對應的域名,對於上面的示例則為 rong */
uscf->host = u->host;
/* 配置文件的絕對路徑 */
uscf->file_name = cf->conf_file->file.name.data;
/* 記錄 upstream {} 在配置文件中的行號 */
uscf->line = cf->conf_file->line;
uscf->port = u->port;
uscf->no_port = u->no_port;
if (u->naddrs == 1 && (u->port || u->family == AF_UNIX)) {
uscf->servers = ngx_array_create(cf->pool, 1,
sizeof(ngx_http_upstream_server_t));
if (uscf->servers == NULL) {
return NULL;
}
us = ngx_array_push(uscf->servers);
if (us == NULL) {
return NULL;
}
ngx_memzero(us, sizeof(ngx_http_upstream_server_t));
us->addrs = u->addrs;
us->naddrs = 1;
}
/* 將該新構建的 ngx_http_upstream_srv_conf_t 添加到 upstreams 數組中
* 每一個 ngx_http_upstream_srv_conf_t 代表一個代理服務器 */
uscfp = ngx_array_push(&umcf->upstreams);
if (uscfp == NULL) {
return NULL;
}
*uscfp = uscf;
return uscf;
}
3.2 ngx_http_upstream_server
server 指令就是為 upstream 定義一個服務器地址(帶有端口號的域名、IP 地址,或者是 UNIX 套接字)和一個可選的參數。參數如下:
- weight: 設置一個服務器的優先級優先於其他服務器,默認為 1.
- max_fails:設置在 fail_timeout 時間之內嘗試對一個服務器連接的最大次數,如果超過這個次數,那么就會被標記為 down。
- fail_timeout:在這個指定的時間內服務器必須提供響應,如果在這個時間內沒有收到響應,那么服務器將會被標記為 down 狀態。
- max_conns:該服務器的最大連接數。
- backup:一旦其他服務器宕機,那么有該標記的機器就會接收請求。
- down:標記為一個服務器不再接受任何請求。
static char *
ngx_http_upstream_server(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_http_upstream_srv_conf_t *uscf = conf;
time_t fail_timeout;
ngx_str_t *value, s;
ngx_url_t u;
ngx_int_t weight, max_conns, max_fails;
ngx_uint_t i;
ngx_http_upstream_server_t *us;
/* 從 servers 數組中取出一個已經分配好的 ngx_http_upstream_server_t
* 該數組中每一個 ngx_http_upstream_server_t 結構體代表一個 server */
us = ngx_array_push(uscf->servers);
if (us == NULL) {
return NGX_CONF_ERROR;
}
ngx_memzero(us, sizeof(ngx_http_upstream_server_t));
value = cf->args->elts;
/* 默認權重為 1,值越大表示該服務器的優先級越高 */
weight = 1;
max_conns = 0;
max_fails = 1;
fail_timeout = 10;
/* 若該 server 指令有第 3 個以上的參數,則進行解析 */
for (i = 2; i < cf->args->nelts; i++) {
if (ngx_strncmp(value[i].data, "weight=", 7) == 0) {
if (!(uscf->flags & NGX_HTTP_UPSTREAM_WEIGHT)) {
goto not_supported;
}
weight = ngx_atoi(&value[i].data[7], value[i].len - 7);
if (weight == NGX_ERROR || weight == 0) {
goto invalid;
}
continue;
}
if (ngx_strncmp(value[i].data, "max_conns=", 10) == 0) {
if (!(uscf->flags & NGX_HTTP_UPSTREAM_MAX_CONNS)) {
goto not_supported;
}
max_conns = ngx_atoi(&value[i].data[10], value[i].len - 10);
if (max_conns == NGX_ERROR) {
goto invalid;
}
continue;
}
if (ngx_strncmp(value[i].data, "max_fails=", 10) == 0) {
if (!(uscf->flags & NGX_HTTP_UPSTREAM_MAX_FAILS)) {
goto not_supported;
}
max_fails = ngx_atoi(&value[i].data[10], value[i].len - 10);
if (max_fails == NGX_ERROR) {
goto invalid;
}
continue;
}
if (ngx_strncmp(value[i].data, "fail_timeout=", 13) == 0) {
if (!(uscf->flags & NGX_HTTP_UPSTREAM_FAIL_TIMEOUT)) {
goto not_supported;
}
s.len = value[i].len - 13;
s.data = &value[i].data[13];
fail_timeout = ngx_parse_time(&s, 1);
if (fail_timeout == (time_t) NGX_ERROR) {
goto invalid;
}
continue;
}
if (ngx_strcmp(value[i].data, "backup") == 0) {
if (!(uscf->flags & NGX_HTTP_UPSTREAM_BACKUP)) {
goto not_supported;
}
us->backup = 1;
continue;
}
if (ngx_strcmp(value[i].data, "down") == 0) {
if (!(uscf->flags & NGX_HTTP_UPSTREAM_DOWN)) {
goto not_supported;
}
us->down = 1;
continue;
}
goto invalid;
}
ngx_memzero(&u, sizeof(ngx_url_t));
/* 該服務器的 url */
u.url = value[1];
/* 若沒有指定端口,則該服務器的默認端口為 80 */
u.default_port = 80;
/* 解析該 url */
if (ngx_parse_url(cf->pool, &u) != NGX_OK) {
if (u.err) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"%s in upstream \"%V\"", u.err, &u.url);
}
return NGX_CONF_ERROR;
}
us->name = u.url;
us->addrs = u.addrs;
us->naddrs = u.naddrs;
us->weight = weight;
us->max_conns = max_conns;
us->max_fails = max_fails;
us->fail_timeout = fail_timeout;
return NGX_CONF_OK;
invalid:
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid parameter \"%V\"", &value[i]);
return NGX_CONF_ERROR;
not_supported:
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"balancing method does not support parameter \"%V\"",
&value[i]);
return NGX_CONF_ERROR;
}
3.2.1 ngx_parse_url
ngx_int_t
ngx_parse_url(ngx_pool_t *pool, ngx_url_t *u)
{
u_char *p;
size_t len;
p = u->url.data;
len = u->url.len;
/* unix */
if (len >= 5 && ngx_strncasecmp(p, (u_char *) "unix:", 5) == 0) {
return ngx_parse_unix_domain_url(pool, u);
}
/* IPv6 */
if (len && p[0] == '[') {
return ngx_parse_inet6_url(pool, u);
}
/* IPv4 */
return ngx_parse_inet_url(pool, u);
}
3.2.2 ngx_parse_inet_url
假設當前 server 指定的 url 為: "192.168.56.101:8080"
static ngx_int_t
ngx_parse_inet_url(ngx_pool_t *pool, ngx_url_t *u)
{
u_char *p, *host, *port, *last, *uri, *args;
size_t len;
ngx_int_t n;
struct sockaddr_in *sin;
#if (NGX_HAVE_INET6)
struct sockaddr_in6 *sin6;
#endif
u->socklen = sizeof(struct sockaddr_in);
sin = (struct sockaddr_in *) &u->sockaddr;
sin->sin_family = AF_INET;
u->family = AF_INET;
host = u->url.data;
last = host + u->url.len;
port = ngx_strlchr(host, last, ':');
uri = ngx_strlchr(host, last, '/');
args = ngx_strlchr(host, last, '?');
if (args) {
if (uri == NULL || args < uri) {
uri = args;
}
}
if (uri) {
if (u->listen || !u->uri_part) {
u->err = "invalid host";
return NGX_ERROR;
}
u->uri.len = last - uri;
u->uri.data = uri;
last = uri;
if (uri < port) {
port = NULL;
}
}
/* 存在端口號 */
if (port) {
port++;
len = last - port;
/* 將端口號轉化為整型值 */
n = ngx_atoi(port, len);
if (n < 1 || n > 65535) {
u->err = "invalid port";
return NGX_ERROR;
}
u->port = (in_port_t) n;
/* 將主機字節序的端口號轉化為網絡字節序 */
sin->sin_port = htons((in_port_t) n);
/* 存放字符串形式的端口號 */
u->port_text.len = len;
u->port_text.data = port;
last = port - 1;
} else {
if (uri == NULL) {
if (u->listen) {
/* test value as port only */
n = ngx_atoi(host, last - host);
if (n != NGX_ERROR) {
if (n < 1 || n > 65535) {
u->err = "invalid port";
return NGX_ERROR;
}
u->port = (in_port_t) n;
sin->sin_port = htons((in_port_t) n);
u->port_text.len = last - host;
u->port_text.data = host;
u->wildcard = 1;
return NGX_OK;
}
}
}
u->no_port = 1;
u->port = u->default_port;
sin->sin_port = htons(u->default_port);
}
len = last - host;
if (len == 0) {
u->err = "no host";
return NGX_ERROR;
}
u->host.len = len;
u->host.data = host;
if (u->listen && len == 1 && *host == '*') {
sin->sin_addr.s_addr = INADDR_ANY;
u->wildcard = 1;
return NGX_OK;
}
sin->sin_addr.s_addr = ngx_inet_addr(host, len);
if (sin->sin_addr.s_addr != INADDR_NONE) {
if (sin->sin_addr.s_addr == INADDR_ANY) {
u->wildcard = 1;
}
u->naddrs = 1;
u->addrs = ngx_pcalloc(pool, sizeof(ngx_addr_t));
if (u->addrs == NULL) {
return NGX_ERROR;
}
sin = ngx_pcalloc(pool, sizeof(struct sockaddr_in));
if (sin == NULL) {
return NGX_ERROR;
}
ngx_memcpy(sin, &u->sockaddr, sizeof(struct sockaddr_in));
u->addrs[0].sockaddr = (struct sockaddr *) sin;
u->addrs[0].socklen = sizeof(struct sockaddr_in);
p = ngx_pnalloc(pool, u->host.len + sizeof(":65535") - 1);
if (p == NULL) {
return NGX_ERROR;
}
u->addrs[0].name.len = ngx_sprintf(p, "%V:%d",
&u->host, u->port) - p;
u->addrs[0].name.data = p;
return NGX_OK;
}
if (u->no_resolve) {
return NGX_OK;
}
if (ngx_inet_resolve_host(pool, u) != NGX_OK) {
return NGX_ERROR;
}
u->family = u->addrs[0].sockaddr->sa_family;
u->socklen = u->addrs[0].socklen;
ngx_memcpy(&u->sockaddr, u->addrs[0].sockaddr, u->addrs[0].socklen);
switch (u->family) {
#if (NGX_HAVE_INET6)
case AF_INET6:
sin6 = (struct sockaddr_in6 *) &u->sockaddr;
if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) {
u->wildcard = 1;
}
break;
#endif
default: /* AF_INET */
sin = (struct sockaddr_in *) &u->sockaddr;
if (sin->sin_addr.s_addr == INADDR_ANY) {
u->wildcard = 1;
}
break;
}
return NGX_OK;
}
4. proxy_pass 的解析
該指令指定請求被傳遞到上游服務器,格式為 ULR。
static ngx_command_t ngx_http_proxy_commands[] = {
{ ngx_string("proxy_pass"),
NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_HTTP_LMT_CONF|NGX_CONF_TAKE1,
ngx_http_proxy_pass,
NGX_HTTP_LOC_CONF_OFFSET,
0,
NULL },
...
};
當在配置文件中檢測到有 proxy_pass 指令時,會調用 ngx_http_proxy_pass 函數進行解析:
static char *
ngx_http_proxy_pass(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_http_proxy_loc_conf_t *plcf = conf;
size_t add;
u_short port;
ngx_str_t *value, *url;
ngx_url_t u;
ngx_uint_t n;
ngx_http_core_loc_conf_t *clcf;
ngx_http_script_compile_t sc;
if (plcf->upstream.upstream || plcf->proxy_lengths) {
return "is duplicate";
}
/* 獲取 ngx_http_core_module 模塊 loc 級別的配置結構體 */
clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
/* 設置 handler 的回調函數 */
clcf->handler = ngx_http_proxy_handler;
/* 該 Location 的名稱 */
if (clcf->name.data[clcf->name.len - 1] == '/') {
clcf->auto_redirect = 1;
}
/* value = "proxy_pass" */
value = cf->args->elts;
/* proxy_pass 的值,即要將請求傳遞給上游服務器的 url */
url = &value[1];
/* 檢測是否有腳本變量,返回腳本變量的個數 */
n = ngx_http_script_variables_count(url);
if (n) {
ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));
sc.cf = cf;
sc.source = url;
sc.lengths = &plcf->proxy_lengths;
sc.values = &plcf->proxy_values;
sc.variables = n;
sc.complete_lengths = 1;
sc.complete_values = 1;
if (ngx_http_script_compile(&sc) != NGX_OK) {
return NGX_CONF_ERROR;
}
#if (NGX_HTTP_SSL)
plcf->ssl = 1;
#endif
return NGX_CONF_OK;
}
if (ngx_strncasecmp(url->data, (u_char *) "http://", 7) == 0) {
add = 7;
port = 80;
} else if (ngx_strncasecmp(url->data, (u_char *) "https://", 8) == 0) {
#if (NGX_HTTP_SSL)
plcf->ssl = 1;
add = 8;
port = 443;
#else
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"https protocol requires SSL support");
return NGX_CONF_ERROR;
#endif
} else {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid URL prefix");
return NGX_CONF_ERROR;
}
ngx_memzero(&u, sizeof(ngx_url_t));
/* 去掉 "http://" 或 "https://" 后的長度 */
u.url.len = url->len - add;
u.url.data = url->data + add;
u.default_port = port;
u.uri_part = 1;
u.no_resolve = 1;
/* 根據該 proxy_pass 指定的 url 名稱,從 ngx_http_upstream_main_conf_t
* 結構體的 upstreams 數組中找到該 url 對應的 upstream */
plcf->upstream.upstream = ngx_http_upstream_add(cf, &u, 0);
if (plcf->upstream.upstream == NULL) {
return NGX_CONF_ERROR;
}
plcf->vars.schema.len = add;
plcf->vars.schema.data = url->data;
plcf->vars.key_start = plcf->vars.schema;
ngx_http_proxy_set_vars(&u, &plcf->vars);
/* 當前 location{} 的名稱 */
plcf->location = clcf->name;
if (clcf->named
#if (NGX_PCRE)
|| clcf->regex
#endif
|| clcf->noname)
{
if (plcf->vars.uri.len) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"\"proxy_pass\" cannot have URI part in "
"location given by regular expression, "
"or inside named location, "
"or inside \"if\" statement, "
"or inside \"limit_except\" block");
return NGX_CONF_ERROR;
}
plcf->location.len = 0;
}
plcf->url = *url;
return NGX_CONF_OK;
}
4.1 ngx_http_proxy_set_vars
static void
ngx_http_proxy_set_vars(ngx_url_t *u, ngx_http_proxy_vars_t *v)
{
if (u->family != AF_UNIX) {
if (u->no_port || u->port == u->default_port) {
v->host_header = u->host;
if (u->default_port == 80) {
ngx_str_set(&v->port, "80");
} else {
ngx_str_set(&v->port, "443");
}
} else {
v->host_header.len = u->host.len + 1 + u->port_text.len;
v->host_header.data = u->host.data;
v->port = u->port_text;
}
v->key_start.len += v->host_header.len;
} else {
ngx_str_set(&v->host_header, "localhost");
ngx_str_null(&v->port);
v->key_start.len += sizeof("unix:") - 1 + u->host.len + 1;
}
v->uri = u->uri;
}
5. ngx_http_init_connection
對於 http 連接事件,當監聽的客戶端連接請求並接受連接后,第一個調用的函數都為該 ngx_http_init_connection 函數。該函數構建了該服務器與客戶端之間的連接 ngx_connection_t 結構體,並將讀事件添加到定時器和 epoll 事件監控機制中。當監聽到客戶端發送的數據到達時,即會調用回調函數 ngx_http_wait_request_handler 進行處理。
void
ngx_http_init_connection(ngx_connection_t *c)
{
ngx_uint_t i;
ngx_event_t *rev;
struct sockaddr_in *sin;
ngx_http_port_t *port;
ngx_http_in_addr_t *addr;
ngx_http_log_ctx_t *ctx;
ngx_http_connection_t *hc;
#if (NGX_HAVE_INET6)
struct sockaddr_in6 *sin6;
ngx_http_in6_addr_t *addr6;
#endif
/* 為當前的 HTTP 連接創建一個 ngx_http_connection_t 結構體,該結構體
* 代表當前的 HTTP 連接 */
hc = ngx_pcalloc(c->pool, sizeof(ngx_http_connection_t));
if (hc == NULL) {
ngx_http_close_connection(c);
return;
}
/* 將 data 指針指向表示當前 HTTP 連接的 ngx_http_connection_t */
c->data = hc;
/* find the server configuration for the address:port */
/* listening:這個連接對應的 ngx_listening_t 監聽對象,此連接由 listening
* 監聽端口的事件建立.
* servers: 對於 HTTP 模塊,該指針指向 ngx_http_port_t 結構體,該結構體
* 實際保存着當前監聽端口的地址信息.
*/
port = c->listening->servers;
/* 若該端口對應主機上的多個地址 */
if (port->naddrs > 1) {
/*
* there are several addresses on this port and one of them
* is an "*:port" wildcard so getsockname() in ngx_http_server_addr()
* is required to determine a server address
*/
if (ngx_connection_local_sockaddr(c, NULL, 0) != NGX_OK) {
ngx_http_close_connection(c);
return;
}
switch (c->local_sockaddr->sa_family) {
#if (NGX_HAVE_INET6)
case AF_INET6:
sin6 = (struct sockaddr_in6 *) c->local_sockaddr;
addr6 = port->addrs;
/* the last address is "*" */
for (i = 0; i < port->naddrs - 1; i++) {
if (ngx_memcmp(&addr6[i].addr6, &sin6->sin6_addr, 16) == 0) {
break;
}
}
hc->addr_conf = &addr6[i].conf;
break;
#endif
default: /* AF_INET */
sin = (struct sockaddr_in *) c->local_sockaddr;
addr = port->addrs;
/* the last address is "*" */
for (i = 0; i < port->naddrs - 1; i++) {
if (addr[i].addr == sin->sin_addr.s_addr) {
break;
}
}
hc->addr_conf = &addr[i].conf;
break;
}
} else {
/* 本機的監聽端口對應的 sockaddr 結構體 */
switch (c->local_sockaddr->sa_family) {
#if (NGX_HAVE_INET6)
case AF_INET6:
addr6 = port->addrs;
hc->addr_conf = &addr6[0].conf;
break;
#endif
default: /* AF_INET */
addr = port->addrs;
hc->addr_conf = &addr[0].conf;
break;
}
}
/* the default server configuration for the address:port */
hc->conf_ctx = hc->addr_conf->default_server->ctx;
ctx = ngx_palloc(c->pool, sizeof(ngx_http_log_ctx_t));
if (ctx == NULL) {
ngx_http_close_connection(c);
return;
}
ctx->connection = c;
ctx->request = NULL;
ctx->current_request = NULL;
c->log->connection = c->number;
c->log->handler = ngx_http_log_error;
c->log->data = ctx;
c->log->action = "waiting for request";
c->log_error = NGX_ERROR_INFO;
/* 連接對應的讀事件 */
rev = c->read;
/* 為該連接的讀事件設置回調處理函數 */
rev->handler = ngx_http_wait_request_handler;
/* 為該連接的寫事件設置回調處理函數,該函數為一個空函數,什么也不做 */
c->write->handler = ngx_http_empty_handler;
#if (NGX_HTTP_V2)
if (hc->addr_conf->http2) {
rev->handler = ngx_http_v2_init;
}
#endif
#if (NGX_HTTP_SSL)
{
ngx_http_ssl_srv_conf_t *sscf;
sscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_ssl_module);
if (sscf->enable || hc->addr_conf->ssl) {
c->log->action = "SSL handshaking";
if (hc->addr_conf->ssl && sscf->ssl.ctx == NULL) {
ngx_log_error(NGX_LOG_ERR, c->log, 0,
"no \"ssl_certificate\" is defined "
"in server listening on SSL port");
ngx_http_close_connection(c);
return;
}
hc->ssl = 1;
rev->handler = ngx_http_ssl_handshake;
}
}
#endif
if (hc->addr_conf->proxy_protocol) {
hc->proxy_protocol = 1;
c->log->action = "reading PROXY protocol";
}
/*
* 標志位,為1時表示當前事件已經准備就緒,也就是說,允許這個事件的消費者模塊
* 處理這個事件。在HTTP框架中,經常會檢查事件的ready標志位以確定是否可以接收
* 請求或者發送響應 */
if (rev->ready) {
/* the deferred accept(), iocp */
/* 為 1,表示開啟了負載均衡機制,此時不會立刻執行該讀事件,而是將當前的
* 讀事件添加到 ngx_posted_events 延遲執行隊列中 */
if (ngx_use_accept_mutex) {
ngx_post_event(rev, &ngx_posted_events);
return;
}
/* 若沒有開啟負載均衡機制,則直接處理該讀事件 */
rev->handler(rev);
return;
}
/* 將讀事件添加到定時器中,超時時間為 post_accept_timeout 毫秒
* post_accept_timeout 在配置文件中沒有配置的話,默認為 60000
* 毫秒 */
ngx_add_timer(rev, c->listening->post_accept_timeout);
/* 將該連接添加到可重用雙向鏈表的頭部 */
ngx_reusable_connection(c, 1);
/* 將該讀事件添加到事件驅動模塊中,這樣當該事件對應的 TCP 連接上
* 一旦出現可讀事件(如接收到 TCP 連接的另一端發送來的字節流)就會
* 回調該事件的 handler 方法 */
if (ngx_handle_read_event(rev, 0) != NGX_OK) {
ngx_http_close_connection(c);
return;
}
}
6. ngx_http_wait_request_handler
當監聽到服務器與客戶端之間的套接字可讀,即客戶端發送數據給服務器時,即會調用該 ngx_http_wait_request_handler 函數進行處理。
static void
ngx_http_wait_request_handler(ngx_event_t *rev)
{
u_char *p;
size_t size;
ssize_t n;
ngx_buf_t *b;
ngx_connection_t *c;
ngx_http_connection_t *hc;
ngx_http_core_srv_conf_t *cscf;
/* 事件相關的對象。通常 data 都是指向 ngx_connection_t 連接對象。
* 開啟文件異步 I/O 時,它可能會指向 ngx_event_aio_t 結構體 */
c = rev->data;
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http wait request handler");
/* 檢查該讀事件是否已經超時,若超時,則關閉該連接 */
if (rev->timedout) {
ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
ngx_http_close_connection(c);
return;
}
/* 標志位,為 1 時表示連接關閉 */
if (c->close) {
ngx_http_close_connection(c);
return;
}
/* 由 ngx_http_init_connection 函數知,此時該 data 指針指向
* ngx_http_connection_t 結構體 */
hc = c->data;
/* 獲取該 server{} 對應的配置項結構體 */
cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module);
size = cscf->client_header_buffer_size;
/* 用於接收、緩存客戶端發來的字節流,每個事件消費模塊可自由決定從連接池中
* 分配多大的空間給 buffer 這個接收緩存字段。例如,在 HTTP 模塊中,它的大小
* 決定於 client_header_buffer_size 配置項 */
b = c->buffer;
/* 若沒有為當前連接的接收/發送緩存分配內存 */
if (b == NULL) {
/* 分配一個 size 大小的臨時緩存(表示該緩存中的數據在內存中且
* 該緩存中的數據可以被修改) */
b = ngx_create_temp_buf(c->pool, size);
if (b == NULL) {
ngx_http_close_connection(c);
return;
}
c->buffer = b;
} else if (b->start == NULL) {
b->start = ngx_palloc(c->pool, size);
if (b->start == NULL) {
ngx_http_close_connection(c);
return;
}
b->pos = b->start;
b->last = b->start;
b->end = b->last + size;
}
/* 調用接收字節流的回調函數 ngx_unix_recv 接收客戶端發送的數據 */
n = c->recv(c, b->last, size);
if (n == NGX_AGAIN) {
if (!rev->timer_set) {
ngx_add_timer(rev, c->listening->post_accept_timeout);
ngx_reusable_connection(c, 1);
}
if (ngx_handle_read_event(rev, 0) != NGX_OK) {
ngx_http_close_connection(c);
return;
}
/*
* We are trying to not hold c->buffer's memory for an idle connection.
*/
if (ngx_pfree(c->pool, b->start) == NGX_OK) {
b->start = NULL;
}
return;
}
if (n == NGX_ERROR) {
ngx_http_close_connection(c);
return;
}
if (n == 0) {
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"client closed connection");
ngx_http_close_connection(c);
return;
}
/* last 指向緩存中有效數據的末尾 */
b->last += n;
if (hc->proxy_protocol) {
hc->proxy_protocol = 0;
p = ngx_proxy_protocol_read(c, b->pos, b->last);
if (p == NULL) {
ngx_http_close_connection(c);
return;
}
b->pos = p;
if (b->pos == b->last) {
c->log->action = "waiting for request";
b->pos = b->start;
b->last = b->start;
ngx_post_event(rev, &ngx_posted_events);
return;
}
}
c->log->action = "reading client request line";
/* 將該連接從 reusable_connections_queue 可重用雙向鏈表中刪除 */
ngx_reusable_connection(c, 0);
/* 為當前客戶端連接創建並初始化一個 ngx_http_request_t 結構體
* 並將 c->data 指向該結構體 */
c->data = ngx_http_create_request(c);
if (c->data == NULL) {
ngx_http_close_connection(c);
return;
}
/* 設置該讀事件的回調處理函數 */
rev->handler = ngx_http_process_request_line;
/* 開始解析該客戶端請求的請求行 */
ngx_http_process_request_line(rev);
}
該函數主要是接收該客戶端發送的數據,然后調用 ngx_http_create_request 函數為該客戶端的請求創建一個 ngx_http_request_t 結構體,用於專門處理此次客戶端的請求。
6.1 ngx_http_create_request
ngx_http_request_t *
ngx_http_create_request(ngx_connection_t *c)
{
ngx_pool_t *pool;
ngx_time_t *tp;
ngx_http_request_t *r;
ngx_http_log_ctx_t *ctx;
ngx_http_connection_t *hc;
ngx_http_core_srv_conf_t *cscf;
ngx_http_core_loc_conf_t *clcf;
ngx_http_core_main_conf_t *cmcf;
/* 處理請求的次數加 1 */
c->requests++;
/* 在該函數返回前,data 還是指向 ngx_http_connection_t 結構體 */
hc = c->data;
/* 獲取當前 server{} 下的配置結構體 */
cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module);
/* 為該客戶端請求分配一個內存池 */
pool = ngx_create_pool(cscf->request_pool_size, c->log);
if (pool == NULL) {
return NULL;
}
/* 從內存池 pool 中為 ngx_http_request_t 結構體分配內存 */
r = ngx_pcalloc(pool, sizeof(ngx_http_request_t));
if (r == NULL) {
ngx_destroy_pool(pool);
return NULL;
}
/* 該請求的內存池,在 ngx_http_free_request 方法中銷毀。
* 它與 ngx_connection_t 中的內存池意義不同,當請求釋放時,TCP 連接
* 可能並沒有關閉,這時請求的內存池會銷毀,但 ngx_connection_t 的
* 內存池並不會銷毀. */
r->pool = pool;
/* 代表當前 HTTP 連接 */
r->http_connection = hc;
r->signature = NGX_HTTP_MODULE;
/* 指向這個請求對應的客戶端連接 */
r->connection = c;
/* 存放請求對應的存放 main 級別配置結構體的指針數組 */
r->main_conf = hc->conf_ctx->main_conf;
/* 存放請求對應的存放 srv 級別配置結構體的指針數組 */
r->srv_conf = hc->conf_ctx->srv_conf;
/* 存放請求對應的存放 loc 級別配置結構體的指針數組 */
r->loc_conf = hc->conf_ctx->loc_conf;
/* 在接收完 HTTP 頭部,第一次在業務上處理 HTTP 請求時,HTTP 框架提供的
* 處理方法是 ngx_http_process_request。但如果該方法無法一次處理完該
* 請求的全部業務,在歸還控制權到 epoll 事件模塊后,該請求回調時,
* 將通過 ngx_http_request_handler 方法來處理,而這個方法中對於可讀
* 事件的處理就是調用 read_event_handler 處理請求,也就是說,HTTP 模塊
* 希望在底層處理請求的讀事件時,重新實現 read_event_handler 方法 */
r->read_event_handler = ngx_http_block_reading;
clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
ngx_set_connection_log(r->connection, clcf->error_log);
/* header_in: 存儲讀取到的 HTTP 頭部數據 */
r->header_in = hc->busy ? hc->busy->buf : c->buffer;
/* headers_out: HTTP 模塊會把想要發送的 HTTP 響應信息放到 headers_out 中,
* 期望 HTTP 框架將 headers_out 中的成員序列化為 HTTP 響應包發送給用戶 */
if (ngx_list_init(&r->headers_out.headers, r->pool, 20,
sizeof(ngx_table_elt_t))
!= NGX_OK)
{
ngx_destroy_pool(r->pool);
return NULL;
}
if (ngx_list_init(&r->headers_out.trailers, r->pool, 4,
sizeof(ngx_table_elt_t))
!= NGX_OK)
{
ngx_destroy_pool(r->pool);
return NULL;
}
/* 存放指向所有的 HTTP 模塊的上下文結構體的指針數組 */
r->ctx = ngx_pcalloc(r->pool, sizeof(void *) * ngx_http_max_module);
if (r->ctx == NULL) {
ngx_destroy_pool(r->pool);
return NULL;
}
cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
r->variables = ngx_pcalloc(r->pool, cmcf->variables.nelts
* sizeof(ngx_http_variable_value_t));
if (r->variables == NULL) {
ngx_destroy_pool(r->pool);
return NULL;
}
#if (NGX_HTTP_SSL)
if (c->ssl) {
r->main_filter_need_in_memory = 1;
}
#endif
/* 當前請求既可能是用戶發來的請求,也可能是派生出的子請求,而 main
* 則標識一系列相關的派生子請求的原始請求,一般可通過 main 和當前
* 請求的地址是否相等來判斷當前請求是否為用戶發來的原始請求 */
r->main = r;
/* 表示當前請求的引用次數。例如,在使用 subrequest 功能時,依附在
* 這個請求上的子請求數目會返回到 count 上,每增加一個子請求,count
* 數就要加 1. 其中任何一個子請求派生出新的子請求時,對應的原始請求
*(main 指針指向的請求)的 count 值都要加 1。又如,當我們接收 HTTP
* 包體時,由於這也是一個異步調用,所有 count 上也需要加 1,這樣在結束
* 請求時,就不會在 count 引用計數未清零時銷毀請求。
*
* 在 HTTP 模塊中每進行一類新的操作,包括為一個請求添加新的事件,或者把
* 一些已經由定時器、epoll 中移除的事件重新加入其中,都需要把這個請求的
* 引用計數加 1,這是因為需要讓 HTTP 框架知道,HTTP 模塊對於該請求有
* 獨立的異步處理機制,將由該 HTTP 模塊決定這個操作什么時候結束,防止
* 在這個操作還未結束時 HTTP 框架卻把這個請求銷毀了 */
r->count = 1;
tp = ngx_timeofday();
/* 當前請求初始化時的時間。start_sec是格林威治時間1970年1月1日0:0:0到當前時間的秒數。
* 如果這個請求是子請求,則該時間是子請求的生成時間;如果這個請求是用戶發來的請求,
* 則是在建立起TCP連接后,第一次接收到可讀事件時的時間 */
r->start_sec = tp->sec;
/* 與start_sec配合使用,表示相對於start_sec秒的毫秒偏移量 */
r->start_msec = tp->msec;
r->method = NGX_HTTP_UNKNOWN;
r->http_version = NGX_HTTP_VERSION_10;
/* ngx_http_process_request_headers 方法在接收、解析完 HTTP 請求的
* 頭部后,會把解析完的每一個HTTP頭部加入到 headers_in 的 headers 鏈表中,
* 同時會構造 headers_in 中的其他成員 */
r->headers_in.content_length_n = -1;
r->headers_in.keep_alive_n = -1;
r->headers_out.content_length_n = -1;
r->headers_out.last_modified_time = -1;
/* 表示使用 rewrite 重寫 URL 的次數。因為目前最多可以更改 10 次,
* 所以 uri_changes 初始化為 11,而每重寫 URL 一次就把 uri_changes
* 減 1,一旦 uri_changes 等於 0,則向用戶返回失敗 */
r->uri_changes = NGX_HTTP_MAX_URI_CHANGES + 1;
/* 表示允許派生子請求的個數,當前最多可為 50,因此該值初始化為 51 */
r->subrequests = NGX_HTTP_MAX_SUBREQUESTS + 1;
/* 設置當前請求的狀態為正在讀取請求的狀態 */
r->http_state = NGX_HTTP_READING_REQUEST_STATE;
ctx = c->log->data;
ctx->request = r;
ctx->current_request = r;
r->log_handler = ngx_http_log_error_handler;
#if (NGX_STAT_STUB)
(void) ngx_atomic_fetch_add(ngx_stat_reading, 1);
r->stat_reading = 1;
(void) ngx_atomic_fetch_add(ngx_stat_requests, 1);
#endif
return r;
}
7. ngx_http_process_request_line
當為該客戶端的請求創建好 ngx_http_request_t 結構體后,調用 ngx_http_process_request_line 函數處理該請求的請求行。
static void
ngx_http_process_request_line(ngx_event_t *rev)
{
ssize_t n;
ngx_int_t rc, rv;
ngx_str_t host;
ngx_connection_t *c;
ngx_http_request_t *r;
/* rev->data 指向當前客戶端連接對象 ngx_connection_t */
c = rev->data;
/* 由前面知,當接收到客戶端的請求數據並為該請求創建一個
* ngx_http_request_t 結構體后,c->data 就重新設置為指向
* 該結構體 */
r = c->data;
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0,
"http process request line");
/* 檢測該讀事件是否已經超時 */
if (rev->timedout) {
ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
c->timedout = 1;
ngx_http_close_request(r, NGX_HTTP_REQUEST_TIME_OUT);
return;
}
rc = NGX_AGAIN;
for ( ;; ) {
if (rc == NGX_AGAIN) {
/* 讀取客戶端的請求數據到 header_in 指向的緩存中,若該緩存中
* 已有數據,則直接返回該緩存中數據的大小 */
n = ngx_http_read_request_header(r);
if (n == NGX_AGAIN || n == NGX_ERROR) {
return;
}
}
/* 該函數僅解析請求消息中的第一個行,即請求行 */
rc = ngx_http_parse_request_line(r, r->header_in);
/* 解析請求行成功 */
if (rc == NGX_OK) {
/* the request line has been parsed successfully */
/* 請求行的大小 */
r->request_line.len = r->request_end - r->request_start;
/* 指向接收緩沖區中請求行的起始地址,注意,這里並沒有內存分配/拷貝 */
r->request_line.data = r->request_start;
r->request_length = r->header_in->pos - r->request_start;
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http request line: \"%V\"", &r->request_line);
/* 該請求的方法名,GET 或 POST 或其他 */
r->method_name.len = r->method_end - r->request_start + 1;
r->method_name.data = r->request_line.data;
/* http_protocol.data = "HTTP/1.1" */
if (r->http_protocol.data) {
r->http_protocol.len = r->request_end - r->http_protocol.data;
}
/* 解析該請求的 uri */
if (ngx_http_process_request_uri(r) != NGX_OK) {
return;
}
if (r->host_start && r->host_end) {
host.len = r->host_end - r->host_start;
host.data = r->host_start;
rc = ngx_http_validate_host(&host, r->pool, 0);
if (rc == NGX_DECLINED) {
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"client sent invalid host in request line");
ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
return;
}
if (rc == NGX_ERROR) {
ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return;
}
if (ngx_http_set_virtual_server(r, &host) == NGX_ERROR) {
return;
}
r->headers_in.server = host;
}
if (r->http_version < NGX_HTTP_VERSION_10) {
if (r->headers_in.server.len == 0
&& ngx_http_set_virtual_server(r, &r->headers_in.server)
== NGX_ERROR)
{
return;
}
ngx_http_process_request(r);
return;
}
/* 初始化該 header_in.headers 鏈表 */
if (ngx_list_init(&r->headers_in.headers, r->pool, 20,
sizeof(ngx_table_elt_t))
!= NGX_OK)
{
ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return;
}
c->log->action = "reading client request headers";
/* 上面解析完請求行后,開始處理請求消息的請求頭部 */
rev->handler = ngx_http_process_request_headers;
ngx_http_process_request_headers(rev);
return;
}
if (rc != NGX_AGAIN) {
/* there was error while a request line parsing */
ngx_log_error(NGX_LOG_INFO, c->log, 0,
ngx_http_client_errors[rc - NGX_HTTP_CLIENT_ERROR]);
if (rc == NGX_HTTP_PARSE_INVALID_VERSION) {
ngx_http_finalize_request(r, NGX_HTTP_VERSION_NOT_SUPPORTED);
} else {
ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
}
return;
}
/* NGX_AGAIN: a request line parsing is still incomplete */
/* ngx_http_parse_reqeust_line 方法返回NGX_AGAIN,則表示需要接收更多的字符流,
* 這時需要對header_in緩沖區做判斷,檢查是否還有空閑的內存,如果還有未使用的
* 內存可以繼續接收字符流,否則調用ngx_http_alloc_large_header_buffer方法
* 分配更多的接收緩沖區。到底是分配多大?這有nginx.conf文件中的
* large_client_header_buffers 配置項指定 */
if (r->header_in->pos == r->header_in->end) {
rv = ngx_http_alloc_large_header_buffer(r, 1);
if (rv == NGX_ERROR) {
ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return;
}
if (rv == NGX_DECLINED) {
r->request_line.len = r->header_in->end - r->request_start;
r->request_line.data = r->request_start;
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"client sent too long URI");
ngx_http_finalize_request(r, NGX_HTTP_REQUEST_URI_TOO_LARGE);
return;
}
}
}
}
8. ngx_http_process_request_headers
當處理完請求消息的請求行后,就會調用 ngx_http_process_request_headers 函數開始處理請求消息的請求頭部。
static void
ngx_http_process_request_headers(ngx_event_t *rev)
{
u_char *p;
size_t len;
ssize_t n;
ngx_int_t rc, rv;
ngx_table_elt_t *h;
ngx_connection_t *c;
ngx_http_header_t *hh;
ngx_http_request_t *r;
ngx_http_core_srv_conf_t *cscf;
ngx_http_core_main_conf_t *cmcf;
c = rev->data;
r = c->data;
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0,
"http process request header line");
/* 檢測該讀事件是否超時 */
if (rev->timedout) {
ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
c->timedout = 1;
ngx_http_close_request(r, NGX_HTTP_REQUEST_TIME_OUT);
return;
}
/* 獲取 main 級別的配置 */
cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
rc = NGX_AGAIN;
/* 在該循環中,將 HTTP 請求頭一個個的解析出來,並添加到
* headers_in.header 鏈表中 */
for ( ;; ) {
if (rc == NGX_AGAIN) {
/* 若當前 heder_in 指向的緩存已全部使用完,則需要分配更多的內存 */
if (r->header_in->pos == r->header_in->end) {
/* 為該緩存分配更多的內存 */
rv = ngx_http_alloc_large_header_buffer(r, 0);
if (rv == NGX_ERROR) {
ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return;
}
if (rv == NGX_DECLINED) {
p = r->header_name_start;
r->lingering_close = 1;
if (p == NULL) {
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"client sent too large request");
ngx_http_finalize_request(r,
NGX_HTTP_REQUEST_HEADER_TOO_LARGE);
return;
}
len = r->header_in->end - p;
if (len > NGX_MAX_ERROR_STR - 300) {
len = NGX_MAX_ERROR_STR - 300;
}
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"client sent too long header line: \"%*s...\"",
len, r->header_name_start);
ngx_http_finalize_request(r,
NGX_HTTP_REQUEST_HEADER_TOO_LARGE);
return;
}
}
/* 讀取數據,若 header_in 指向的緩存中仍然有未處理的數據,則
* 直接返回,否則需要從 socket 中讀取數據 */
n = ngx_http_read_request_header(r);
if (n == NGX_AGAIN || n == NGX_ERROR) {
return;
}
}
/* the host header could change the server configuration context */
/* 獲取當前 server{} 下的配置 */
cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
/* 該函數是將頭部數據解析出一行 */
rc = ngx_http_parse_header_line(r, r->header_in,
cscf->underscores_in_headers);
if (rc == NGX_OK) {
r->request_length += r->header_in->pos - r->header_name_start;
if (r->invalid_header && cscf->ignore_invalid_headers) {
/* there was error while a header line parsing */
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"client sent invalid header line: \"%*s\"",
r->header_end - r->header_name_start,
r->header_name_start);
continue;
}
/* a header line has been parsed successfully */
/* 將解析出來的請求頭存入到 headers_in.headers 鏈表中 */
h = ngx_list_push(&r->headers_in.headers);
if (h == NULL) {
ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return;
}
h->hash = r->header_hash;
/* 頭部名稱,如 "Host" */
h->key.len = r->header_name_end - r->header_name_start;
h->key.data = r->header_name_start;
h->key.data[h->key.len] = '\0';
/* 該頭部對應的值,如 "192.168.56.101" */
h->value.len = r->header_end - r->header_start;
h->value.data = r->header_start;
h->value.data[h->value.len] = '\0';
h->lowcase_key = ngx_pnalloc(r->pool, h->key.len);
if (h->lowcase_key == NULL) {
ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return;
}
if (h->key.len == r->lowcase_index) {
/* r->lowcase_header 存放的上面解析出來的 h->key.data
* 的小寫字符串 */
ngx_memcpy(h->lowcase_key, r->lowcase_header, h->key.len);
} else {
ngx_strlow(h->lowcase_key, h->key.data, h->key.len);
}
/* 在 headers_in_hash 指向的 hash 表中尋找是否與該 lowcase_key
* 相同的項 */
hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash,
h->lowcase_key, h->key.len);
/* 若能找到,則調用該頭部對應的的處理方法 */
if (hh && hh->handler(r, h, hh->offset) != NGX_OK) {
return;
}
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http header: \"%V: %V\"",
&h->key, &h->value);
continue;
}
/* 請求消息的請求頭部解析完成 */
if (rc == NGX_HTTP_PARSE_HEADER_DONE) {
/* a whole header has been parsed successfully */
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http header done");
r->request_length += r->header_in->pos - r->header_name_start;
r->http_state = NGX_HTTP_PROCESS_REQUEST_STATE;
/* 對解析后的 HTTP 頭部字段的一些處理,檢測解析出來的請求頭是否正確 */
rc = ngx_http_process_request_header(r);
if (rc != NGX_OK) {
return;
}
/* 在解析並處理 HTTP 的頭部數據后,開始處理該 HTTP 請求 */
ngx_http_process_request(r);
return;
}
if (rc == NGX_AGAIN) {
/* a header line parsing is still not complete */
continue;
}
/* rc == NGX_HTTP_PARSE_INVALID_HEADER */
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"client sent invalid header line");
ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
return;
}
}
9. ngx_http_process_request
void
ngx_http_process_request(ngx_http_request_t *r)
{
ngx_connection_t *c;
c = r->connection;
#if (NGX_HTTP_SSL)
if (r->http_connection->ssl) {
long rc;
X509 *cert;
ngx_http_ssl_srv_conf_t *sscf;
if (c->ssl == NULL) {
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"client sent plain HTTP request to HTTPS port");
ngx_http_finalize_request(r, NGX_HTTP_TO_HTTPS);
return;
}
sscf = ngx_http_get_module_srv_conf(r, ngx_http_ssl_module);
if (sscf->verify) {
rc = SSL_get_verify_result(c->ssl->connection);
if (rc != X509_V_OK
&& (sscf->verify != 3 || !ngx_ssl_verify_error_optional(rc)))
{
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"client SSL certificate verify error: (%l:%s)",
rc, X509_verify_cert_error_string(rc));
ngx_ssl_remove_cached_session(sscf->ssl.ctx,
(SSL_get0_session(c->ssl->connection)));
ngx_http_finalize_request(r, NGX_HTTPS_CERT_ERROR);
return;
}
if (sscf->verify == 1) {
cert = SSL_get_peer_certificate(c->ssl->connection);
if (cert == NULL) {
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"client sent no required SSL certificate");
ngx_ssl_remove_cached_session(sscf->ssl.ctx,
(SSL_get0_session(c->ssl->connection)));
ngx_http_finalize_request(r, NGX_HTTPS_NO_CERT);
return;
}
X509_free(cert);
}
}
}
#endif
/* 由於現在已經開始准備調用各 HTTP 模塊處理請求了,不再存在
* 接收 HTTP 請求頭部超時的問題,因此需要從定時器中將當前
* 連接的讀事件移除
*/
if (c->read->timer_set) {
ngx_del_timer(c->read);
}
#if (NGX_STAT_STUB)
(void) ngx_atomic_fetch_add(ngx_stat_reading, -1);
r->stat_reading = 0;
(void) ngx_atomic_fetch_add(ngx_stat_writing, 1);
r->stat_writing = 1;
#endif
/* 設置該讀、寫事件的回調函數 */
c->read->handler = ngx_http_request_handler;
c->write->handler = ngx_http_request_handler;
/*
* 設置 ngx_http_request_t 結構體的 read_event_handler 方法為
* ngx_http_block_reading。當再次有讀事件到來時,這個方法可以
* 認為不做任何事,它的意義在於,目前已經開始處理 HTTP 請求,
* 除非某個 HTTP 模塊重新設置了 read_event_handler 方法,否則
* 任何讀事件都將得不到處理,也可以認為讀事件被阻塞了
*/
r->read_event_handler = ngx_http_block_reading;
/* 該函數確定 phase_handler 的值,即從 ngx_http_phase_engine_t 指定
* 數組的第幾個回調函數開始執行,然后依次執行 HTTP 的各個階段 */
ngx_http_handler(r);
ngx_http_run_posted_requests(c);
}
9.1 ngx_http_handler
void
ngx_http_handler(ngx_http_request_t *r)
{
ngx_http_core_main_conf_t *cmcf;
r->connection->log->action = NULL;
/* 如果 internal 標志位為 1,則表示當前需要做內部跳轉,將要把
* 結構體中的 phase_handler 序號置為 server_rewrite_index. */
if (!r->internal) {
/* 當 internal 標志位為 0 時,表示不需要重定向(如剛開始處理請求時),
* 將 phase_handler 序號置為 0,意味着從 ngx_http_phase_engine_t 指定
* 數組的第一個回調方法開始執行 */
switch (r->headers_in.connection_type) {
case 0:
r->keepalive = (r->http_version > NGX_HTTP_VERSION_10);
break;
case NGX_HTTP_CONNECTION_CLOSE:
r->keepalive = 0;
break;
case NGX_HTTP_CONNECTION_KEEP_ALIVE:
/* 標志位,為 1 表示當前請求是 keepalive 請求,即長連接 */
r->keepalive = 1;
break;
}
/* 延遲關閉標志位,為 1 時表示需要延遲關閉。例如,在接收完
* HTTP 頭部時如果發現包體存在,該標志位為設為 1,而放棄接收
* 包體時會設為 0 */
r->lingering_close = (r->headers_in.content_length_n > 0
|| r->headers_in.chunked);
/* 置為 0,表示從 ngx_http_phase_engine_t 指定數組的第一個回調
* 方法開始執行 */
r->phase_handler = 0;
} else {
cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
/* 這里,把phase_handler序號設為server_rewrite_index,這意味着
* 無論之前執行到哪一個階段,馬上都要重新從NGX_HTTP_SERVER_REWRITE_PHASE
* 階段開始再次執行,這是Nginx的請求可以反復rewrite重定向的基礎 */
r->phase_handler = cmcf->phase_engine.server_rewrite_index;
}
r->valid_location = 1;
#if (NGX_HTTP_GZIP)
r->gzip_tested = 0;
r->gzip_ok = 0;
r->gzip_vary = 0;
#endif
r->write_event_handler = ngx_http_core_run_phases;
/* 開始執行 HTTP 請求的各個階段 */
ngx_http_core_run_phases(r);
}
10. ngx_http_core_run_phases
/* ngx_http_phase_engine_t結構體就是所有的ngx_http_phase_handler_t組成的數組 */
typedef struct {
/* handlers是由ngx_http_phase_handler_t構成的數組首地址,它表示一個請求可能
* 經歷的所有ngx_http_handler_pt處理方法 */
ngx_http_phase_handler_t *handlers;
/* 表示NGX_HTTP_SERVER_REWRITE_PHASE階段第1個ngx_http_phase_handler_t處理方法
* 在handlers數組中的序號,用於在執行HTTP請求的任何階段中快速跳轉到
* NGX_HTTP_SERVER_REWRITE_PHASE階段處理請求 */
ngx_uint_t server_rewrite_index;
/* 表示NGX_HTTP_REWRITE_PHASE階段第1個ngx_http_phase_handler_t處理方法
* 在handlers數組中的序號,用於在執行HTTP請求的任何階段中快速跳轉到
* NGX_HTTP_REWRITE_PHASE階段處理請求 */
ngx_uint_t location_rewrite_index;
}ngx_http_phase_engine_t;
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);
/* handlers 是由 ngx_http_phase_handler_t 構成的數組首地址,它表示
* 一個請求可能經歷的所有 ngx_http_handler_pt 處理方法 */
ph = cmcf->phase_engine.handlers;
/*
* 在處理到某一個 HTTP 階段時,HTTP 框架將會在 checker 方法已實現的前提下
* 首先調用 checket 方法來處理請求,而不會直接調用任何階段中的handler方法,
* 只有在checket方法中才會去調用handler方法。因此,事實上所有的checker方法
* 都是由框架中的 ngx_http_core_module 模塊實現的,且普通的 HTTP 模塊無法
* 重定義 checket 方法 */
while (ph[r->phase_handler].checker) {
rc = ph[r->phase_handler].checker(r, &ph[r->phase_handler]);
if (rc == NGX_OK) {
return;
}
}
}
下面 HTTP 各個階段的分析可參考 HTTP 階段執行
執行 HTTP 各個階段中,在 NGX_HTTP_CONTENT_PHASE 階段之前的所有階段都可參考 HTTP 階段執行,當到了 NGX_HTTP_CONTENT_PHASE 階段之后,就會開始進行與 upstream 相關的處理。
11. NGX_HTTP_CONTENT_PHASE
該階段實現的 checker 方法為 ngx_http_core_content_phase:
ngx_int_t
ngx_http_core_content_phase(ngx_http_request_t *r,
ngx_http_phase_handler_t *ph)
{
size_t root;
ngx_int_t rc;
ngx_str_t path;
/* r->content_handler 是在執行 NGX_HTTP_FIND_CONFIG_PHASE 階段中找到匹配的
* location 后,調用 ngx_http_update_location_config 函數進行設置的,而該函數
* 中賦給 r->content_handler 的 clcf->handler 又是在解析 proxy_pass 設置的,
* 指向的回調函數為 ngx_http_proxy_handler 函數 */
if (r->content_handler) {
/* 指向的回調函數為一個空函數,什么也不處理 */
r->write_event_handler = ngx_http_request_empty_handler;
/* 因此,這里會先調用 ngx_http_proxy_handler 函數進行處理 */
ngx_http_finalize_request(r, r->content_handler(r));
return NGX_OK;
}
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"content phase: %ui", r->phase_handler);
rc = ph->handler(r);
if (rc != NGX_DECLINED) {
ngx_http_finalize_request(r, rc);
return NGX_OK;
}
/* rc == NGX_DECLINED */
ph++;
if (ph->checker) {
r->phase_handler++;
return NGX_AGAIN;
}
/* no content handler was found */
if (r->uri.data[r->uri.len - 1] == '/') {
if (ngx_http_map_uri_to_path(r, &path, &root, 0) != NULL) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"directory index of \"%s\" is forbidden", path.data);
}
ngx_http_finalize_request(r, NGX_HTTP_FORBIDDEN);
return NGX_OK;
}
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "no handler found");
ngx_http_finalize_request(r, NGX_HTTP_NOT_FOUND);
return NGX_OK;
}