nginx原始碼分析(7)——請求處理
在建立連線過程中,對於nginx監聽到的每個客戶端連線,都會將它的讀事件的handler設定為ngx_http_init_request函式,這個函式就是請求處理的入口。在處理請求時,主要就是要解析http請求,比如:uri,請求行等,然後再根據請求生成響應。下面看一下nginx處理的具體過程。
1. ngx_http_init_request
在ngx_http_init_connection函式中,將連線的讀事件的handler設定為這個函式,在客戶端傳送請求時會被呼叫。
/* ngx_event_t的data域存放事件對應的連線控制代碼 */ c = rev->data; /* 在ngx_init_connection中對讀事件添加了timer,超時直接返回*/ if (rev->timedout) { ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out"); ngx_http_close_connection(c); return; } /* 連線處理的request的計數 */ c->requests++; /* ngx_http_connection_t */ hc = c->data; if (hc == NULL) { hc = ngx_pcalloc(c->pool, sizeof(ngx_http_connection_t)); if (hc == NULL) { ngx_http_close_connection(c); return; } } r = hc->request; if (r) { ngx_memzero(r, sizeof(ngx_http_request_t)); r->pipeline = hc->pipeline; if (hc->nbusy) { r->header_in = hc->busy[0]; } } else { /* 為request分配空間 */ r = ngx_pcalloc(c->pool, sizeof(ngx_http_request_t)); if (r == NULL) { ngx_http_close_connection(c); return; } hc->request = r; } c->data = r; r->http_connection = hc; c->sent = 0; r->signature = NGX_HTTP_MODULE;
上面一段程式碼完成獲取連線、請求並進行一部分初始化工作,比如會為新的請求分配記憶體。
/** * ngx_listening_t的servers存放監聽同一埠的server,但它們的監聽的地址可以不同。 * * port * / | \ * addr1 addr2 addr3 * | | | * conf1 conf2 conf3 */ port = c->listening->servers; r->connection = c; 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 */ /* 獲取連線c的socket繫結的本地地址 */ if (ngx_connection_local_sockaddr(c, NULL, 0) != NGX_OK) { ngx_http_close_connection(c); return; } /* 根據連線c的socket地址匹配port->addrs,找到對應的address:port */ 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; } } 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; } } addr_conf = &addr[i].conf; break; } } else { switch (c->local_sockaddr->sa_family) { #if (NGX_HAVE_INET6) case AF_INET6: addr6 = port->addrs; addr_conf = &addr6[0].conf; break; #endif default: /* AF_INET */ addr = port->addrs; addr_conf = &addr[0].conf; break; } } /* virtual hosts based on the address:port */ r->virtual_names = addr_conf->virtual_names; /* the default server configuration for the address:port */ cscf = addr_conf->default_server; /* 初始化為default server的配置,後續虛擬主機匹配成功會採用對應的配置 */ r->main_conf = cscf->ctx->main_conf; r->srv_conf = cscf->ctx->srv_conf; r->loc_conf = cscf->ctx->loc_conf;
註釋中解釋的很清楚,這段程式碼是為地址addr:port匹配server config,從而確定該請求的配置。由於nginx支援虛擬主機,所以這裡確定了r->virtual_names是該addr:port對應的虛擬主機陣列,後面會根據請求的HOST匹配對應的虛擬主機從而確定最終的配置。nginx的每個請求都有執行環境,這個環境就是ngx_request_t請求的main_conf、srv_conf和loc_conf。請求的執行環境就是nginx各個模組的配置資訊,根據這些資訊的不同請求的處理效果是不一樣的。待解釋完虛擬主機匹配後,再詳細說明執行環境查詢。
將連線讀事件的handler設定為ngx_http_process_request_line,在本方法的最後會直接呼叫rev->handler(rev)。為什麼這麼做?rev->handler = ngx_http_process_request_line; r->read_event_handler = ngx_http_block_reading;
我的猜測是,在客戶端第一次請求時,該連線的讀事件的handler是ngx_init_request,用於處理話請求,後續請求直接複用,所以不需要執行ngx_init_request,所以需要將rev->handler設定成ngx_http_process_request_line,最後直接呼叫handler是為了在ngx_init_request中處理第一個請求。不知道這樣對不對?
clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
c->log->file = clcf->error_log->file;
if (!(c->log->log_level & NGX_LOG_DEBUG_CONNECTION)) {
c->log->log_level = clcf->error_log->log_level;
}
/* initialise the temporary buffer for the request's connection */
if (c->buffer == NULL) {
c->buffer = ngx_create_temp_buf(c->pool,
cscf->client_header_buffer_size);
if (c->buffer == NULL) {
ngx_http_close_connection(c);
return;
}
}
/* raw request header string will be stored in the connection's buffer */
if (r->header_in == NULL) {
r->header_in = c->buffer;
}
/* initialise the memory pool of the request */
r->pool = ngx_create_pool(cscf->request_pool_size, c->log);
if (r->pool == NULL) {
ngx_http_close_connection(c);
return;
}
初始化連線的日誌資訊,為連線分配臨時buffer,將request的header_in指向該臨時buffer,後面會看到header_in用於存放請求頭資訊。然後為該請求分配記憶體池。 if (ngx_list_init(&r->headers_out.headers, r->pool, 20,
sizeof(ngx_table_elt_t))
!= NGX_OK)
{
ngx_destroy_pool(r->pool);
ngx_http_close_connection(c);
return;
}
// ?
r->ctx = ngx_pcalloc(r->pool, sizeof(void *) * ngx_http_max_module);
if (r->ctx == NULL) {
ngx_destroy_pool(r->pool);
ngx_http_close_connection(c);
return;
}
為http響應header分配空間,request的ctx屬性目前不知道幹什麼用的。。。
cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
/**
* 為所有變數在此請求中分配空間
* Allocate memory space for the variable values of the request.
* The type of variables array is ngx_http_variable_value_t.
*/
r->variables = ngx_pcalloc(r->pool, cmcf->variables.nelts
* sizeof(ngx_http_variable_value_t));
if (r->variables == NULL) {
ngx_destroy_pool(r->pool);
ngx_http_close_connection(c);
return;
}
nginx中的變數的生命週期是基於請求的,也就是說請求之間的變數時獨立的。request的variables相當於變數的值,cmcf->variables相當於變數的定義。為了處理變數,nginx實現了一個小型的指令碼引擎,也就是一個直譯器。在解析配置檔案時生成了程式碼,在處理請求時去執行這個程式碼,具體就是在ngx_http_rewrite_module註冊的phase handler中處理的,後面介紹nginx變數機制時再詳細說明。
這裡就是為請求分配所有變數的佔用空間。
c->single_connection = 1;
c->destroyed = 0;
/* 主請求 */
r->main = r;
r->count = 1;
/* Store start time of the request */
tp = ngx_timeofday();
r->start_sec = tp->sec;
r->start_msec = tp->msec;
/* HTTP method, e.g. POST, GET, PUT */
r->method = NGX_HTTP_UNKNOWN;
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;
r->uri_changes = NGX_HTTP_MAX_URI_CHANGES + 1;
/* 子請求個數的限制 */
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
/* Start processing request */
rev->handler(rev);
對連線和請求的屬性進行初始化,後面講解subrequest時再具體分析r->main和r->subrequests。最後呼叫rev->handler(rev)實際就是呼叫ngx_http_process_request_line。
2. ngx_http_process_request_line
這個函式是用來處理請求行的,會不斷呼叫ngx_http_read_request_header從socket讀取頭部並儲存在request的header_in欄位中,實際上header_in是一個緩衝區,就是指向連線的臨時緩衝區。
c = rev->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;
}
從rev中獲取連線以及請求,並判斷是否超時。
http請求行格式是:
GET /index.php HTTP/1.1
接下來的for迴圈就是試圖從socket中讀取足夠的資訊,然後解析出HTTP Method、URI以及HTTP版本等資訊。 if (rc == NGX_AGAIN) {
/**
* 讀取請求行,儲存到r->header_in緩衝區
*/
n = ngx_http_read_request_header(r);
if (n == NGX_AGAIN || n == NGX_ERROR) {
return;
}
}
讀取request line並儲存在header_in緩衝區。
/**
* 解析請求行,解析後的資訊的是以r->uri_start,r->uri_end,r->arg_start等
* 一些指標儲存的,這些指標指向r->header_in
*/
rc = ngx_http_parse_request_line(r, r->header_in);
解析請求行,ngx_http_request_t中比如uri_start、uri_end、arg_start、request_start、request_end等指標用於請求行的解析,識別各個屬性在header_in緩衝區中的指標的位置。ngx_http_parse_request_line在ngx_http_parse.c中,用於解析請求行,實際上就是一個狀態機的實現,所有的狀態包括:
enum {
sw_start = 0,
sw_method,
sw_spaces_before_uri,
sw_schema,
sw_schema_slash,
sw_schema_slash_slash,
sw_host,
sw_port,
sw_host_http_09,
sw_after_slash_in_uri,
sw_check_uri,
sw_check_uri_http_09,
sw_uri,
sw_http_09,
sw_http_H,
sw_http_HT,
sw_http_HTT,
sw_http_HTTP,
sw_first_major_digit,
sw_major_digit,
sw_first_minor_digit,
sw_minor_digit,
sw_spaces_after_digit,
sw_almost_done
} state;
每個狀態表示解析到哪一步,根據輸入字元決定下一狀態。這裡不糾結於狀態機的實現,只要記住在本方法呼叫完後請求行解析完畢,所有資訊儲存在request的欄位中;要不就是解析沒有完成,進行下一次迭代;要不就是解析出錯。
解析成功後,會根據ngx_http_request_t中用於解析的指標去初始化request中的屬性,比如uri、args等。如果使用的是complex uri會進入其解析過程。
/* the request line has been parsed successfully */
r->request_line.len = r->request_end - r->request_start;
r->request_line.data = r->request_start;
if (r->args_start) {
r->uri.len = r->args_start - 1 - r->uri_start;
} else {
r->uri.len = r->uri_end - r->uri_start;
}
初始化request_line屬性,是一個ngx_str_t,然後初始化uri的長度。
if (r->complex_uri || r->quoted_uri) {
r->uri.data = ngx_pnalloc(r->pool, r->uri.len + 1);
if (r->uri.data == NULL) {
ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return;
}
cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
rc = ngx_http_parse_complex_uri(r, cscf->merge_slashes);
if (rc == NGX_HTTP_PARSE_INVALID_REQUEST) {
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"client sent invalid request");
ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
return;
}
} else {
r->uri.data = r->uri_start;
}
初始化uri的data欄位,如果使用complex uri就進入其解析過程,否則直接賦值為uri_start。
/* 未解析的uri */
r->unparsed_uri.len = r->uri_end - r->uri_start;
r->unparsed_uri.data = r->uri_start;
r->valid_unparsed_uri = r->space_in_uri ? 0 : 1;
/* http method */
r->method_name.len = r->method_end - r->request_start + 1;
r->method_name.data = r->request_line.data;
if (r->http_protocol.data) {
r->http_protocol.len = r->request_end - r->http_protocol.data;
}
if (r->uri_ext) {
if (r->args_start) {
r->exten.len = r->args_start - 1 - r->uri_ext;
} else {
r->exten.len = r->uri_end - r->uri_ext;
}
r->exten.data = r->uri_ext;
}
/* 請求引數 */
if (r->args_start && r->uri_end > r->args_start) {
r->args.len = r->uri_end - r->args_start;
r->args.data = r->args_start;
}
接下來對其他的屬性初始化。
/* 解析並驗證host */
if (r->host_start && r->host_end) {
host = r->host_start;
n = ngx_http_validate_host(r, &host,
r->host_end - r->host_start, 0);
if (n == 0) {
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 (n < 0) {
ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return;
}
/* 初始化request header的server欄位 */
r->headers_in.server.len = n;
r->headers_in.server.data = host;
}
解析並驗證host,然後初始化request header的server欄位。
if (r->http_version < NGX_HTTP_VERSION_10) {
/**
* 根據請求的host,即虛擬主機名,去查詢對應的server
*/
if (ngx_http_find_virtual_server(r, r->headers_in.server.data,
r->headers_in.server.len)
== NGX_ERROR)
{
ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return;
}
ngx_http_process_request(r);
return;
}
如果使用的http協議版本小於1.0,那麼不支援header以及請求體,所以接下來直接呼叫ngx_http_process_request處理請求。前面已經介紹過根據請求的addr:port確定了該請求的虛擬主機集合,接下來就要根據請求的host確定具體是哪個虛擬主機來處理該請求,通過ngx_http_find_virtual_server處理,r->headers_in.server存放的就是host。這個函式處理的很簡單,因為已經將該addr:port對應的虛擬主機集合組織成map,首先是根據host從這個map中get,如果有對應的虛擬主機直接返回。如果沒有,但是nginx提供正則表示式支援,則逐一進行匹配直到找一個為止。找到host對應的虛擬主機後,最重要的是將request的執行環境設定為該虛擬主機對應的配置。ngx_http_process_request後面會詳說。對於http0.9然後會直接返回,而1.0或1.1還有沒有處理完。
/* Initialise list for request 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;
}
/* Initialise array for request cookies */
if (ngx_array_init(&r->headers_in.cookies, r->pool, 2,
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";
因為http1.0和http1.1支援header和cookie,這裡先為它們分配空間,後面再解析這些資料。
/* process request headers */
rev->handler = ngx_http_process_request_headers;
ngx_http_process_request_headers(rev);
return;
設定讀事件rev的handler為ngx_http_process_request_headers,然後呼叫ngx_http_process_request_headers進行header解析。
下面先看看for迴圈的最後部分,主要是錯誤處理和緩衝區擴容。
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]);
ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
return;
}
請求解析出錯,這裡會終結這個請求,並log。
/* NGX_AGAIN: a request line parsing is still incomplete */
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;
}
}
rc等於NGX_AGAIN,說起request line沒有處理完畢,如果header_in大小不夠,則呼叫ngx_http_alloc_large_header_buffer進行擴容。
3. ngx_http_process_request_headers
這個函式處理request header。
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;
}
獲取連線和請求,並判斷超時。接下來for迴圈會一次解析一個header。
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;
p[len++] = '.'; p[len++] = '.'; p[len++] = '.';
}
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;
}
}
n = ngx_http_read_request_header(r);
if (n == NGX_AGAIN || n == NGX_ERROR) {
return;
}
首先還是先判斷緩衝區大小是否夠用,如果不夠用就擴容,擴容失敗就會退出。然後呼叫ngx_http_read_request_header讀取請求並存到header_in緩衝區。
rc = ngx_http_parse_header_line(r, r->header_in,
cscf->underscores_in_headers);
呼叫ngx_http_parse_header_line解析header,其內部實現和ngx_http_parse_request_line一樣,都是狀態機,這個函式會一次解析一個header,實際上就是一行。如果解析成功,即rc等NGX_OK,會執行:
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;
}
解析到不合法的header,log然後解析下一個header。否則,說明成功解析了一個header,會進行header的初始化。
/* r->header_hash是當前解析得到的header的hash值 */
h->hash = r->header_hash;
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';
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) {
ngx_memcpy(h->lowcase_key, r->lowcase_header, h->key.len);
} else {
ngx_strlow(h->lowcase_key, h->key.data, h->key.len);
}
把解析出的header新增到request的headers_in.header連結串列中,然後對其初始化,包括:header的name、value等。
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;
}
在http模組的初始化函式ngx_http_block中,會呼叫ngx_http_init_headers_in_hash將所有可能的header初始化成hash map,然後儲存至ngx_http_core_main_conf_t的headers_in_hash欄位中。在ngx_http_request.c中,定義的ngx_http_headers_in陣列就是所有可能的header。這裡會檢視解析出的header是否在這個map,如果在的話會呼叫對應的handler進行初始化。
如果rc等於NGX_HTTP_PARSE_HEADER_DONE,說明header解析完畢,接下來會執行:
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_in->start;
r->http_state = NGX_HTTP_PROCESS_REQUEST_STATE;
/* 根據host匹配虛擬主機,並對一些header初始化 */
rc = ngx_http_process_request_header(r);
if (rc != NGX_OK) {
return;
}
ngx_http_process_request(r);
return;
}
會呼叫ngx_http_process_request_header函式解析虛擬主機,並對一些header初始化。然後呼叫ngx_http_process_request進行後續的請求處理。ngx_http_process_request_headers的最後部分就是錯誤處理,這裡不詳述,下面看一下請求的處理部分。
4. ngx_http_process_request
這個函式驅動請求的處理,併為處理做些準備。
/* remove timer of read event of the connection */
if (c->read->timer_set) {
ngx_del_timer(c->read);
}
刪除掉之前設定的讀事件的timer。
c->read->handler = ngx_http_request_handler;
c->write->handler = ngx_http_request_handler;
r->read_event_handler = ngx_http_block_reading;
設定讀寫事件的handler為ngx_http_request_handler,這個函式貌似是處理subrequest的,還不敢確定。
ngx_http_handler(r);
// 處理subrequest
ngx_http_run_posted_requests(c);
呼叫ngx_http_handler繼續處理請求,ngx_http_run_posted_requests函式是處理子請求的。
5. ngx_http_handler
為跑一遍所有phase hander做準備。
r->connection->log->action = NULL;
r->connection->unexpected_eof = 0;
/**
* 初始化r->phase_handler
*/
if (!r->internal) {
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:
r->keepalive = 1;
break;
}
r->lingering_close = (r->headers_in.content_length_n > 0);
r->phase_handler = 0;
} else {
cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
r->phase_handler = cmcf->phase_engine.server_rewrite_index;
}
初始化request的phase_handler,實際上就是第一個執行的phase handler在handlers陣列中的下標。
r->write_event_handler = ngx_http_core_run_phases;
ngx_http_core_run_phases(r);
設定request的write_event_handler為ngx_http_core_run_phases,然後再呼叫這個函式把請求對應的phase handler執行一遍。下面看這個函式。
6. ngx_http_core_run_phases
nginx把請求的處理劃分成11個階段,其中一些階段可以自定義新增handler,這個函式就是執行請求對應對應所有的phase handler。對應nginx對phase handler的處理會在之後的文章中分析。
cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
ph = cmcf->phase_engine.handlers;
ngx_http_core_main_conf_t的phase_engine的handlers就是所有的phase handler組成的陣列。
/* 遍歷phase上註冊的所有handler,這裡是以r->phase_handler為索引組成的連結串列 */
while (ph[r->phase_handler].checker) {
rc = ph[r->phase_handler].checker(r, &ph[r->phase_handler]);
if (rc == NGX_OK) {
return;
}
}
遍歷phase上註冊的所有handler,這裡是以r->phase_handler為索引組成的連結串列。在呼叫每個handler的checker時會更新request的phase_handler,從而實現一個handler的連結串列。
在跑完所有的phase handler之後,這個請求就被處理完畢。實際的響應內容的輸出,是在content phase的handler中呼叫filter輸出實現的。下一篇文章會介紹nginx的phase handler的處理,後面還會介紹filter實現。