Nginx 中處理 HTTP 請求
概述
在 Nginx 的初始化啟動過程中,worker 工作程序會呼叫事件模組的ngx_event_process_init 方法為每個監聽套接字ngx_listening_t 分配一個 ngx_connection_t 連線,並設定該連線上讀事件的回撥方法handler 為ngx_event_accept,同時將讀事件掛載到epoll 事件機制中等待監聽套接字連線上的可讀事件發生,到此,Nginx 就可以接收並處理來自客戶端的請求。當監聽套接字連線上的可讀事件發生時,即該連線上有來自客戶端發出的連線請求,則會啟動讀事件的handler 回撥方法ngx_event_accept
接收 HTTP 請求報文
在接收 HTTP 請求之前,首先會初始化已成功建立的連線;ngx_http_init_connection 函式的功能是設定讀、寫事件的回撥方法,而實際上寫事件的回撥方法並不進行任何操作,讀事件的回撥方法是對HTTP
ngx_http_init_connection 函式的執行流程:
- 設定當前連線上寫事件的回撥方法 handler 為 ngx_http_empty_handler(實際上該方法不進行任何操作);
- 設定當前連線上讀事件的回撥方法 handler 為 ngx_http_wait_request_handler;
- 檢查當前連線上讀事件是否準備就緒(即 ready 標誌位為1):
- 若讀事件 ready 標誌位為1,表示當前連線上有可讀的TCP 流,則執行讀事件的回撥方法ngx_http_wait_request_handler;
- 若讀事件 ready 標誌位為0,表示當前連線上沒有可讀的TCP
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結構體空間 */
hc = ngx_pcalloc(c->pool, sizeof(ngx_http_connection_t));
if (hc == NULL) {
ngx_http_close_connection(c);
return;
}
c->data = hc;
/* find the server configuration for the address:port */
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)
...
#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 {
switch (c->local_sockaddr->sa_family) {
#if (NGX_HAVE_INET6)
...
#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;
/* 設定當前連線讀、寫事件的handler處理方法 */
rev = c->read;
/* 設定當前連線讀事件的處理方法handler為ngx_http_wait_request_handler */
rev->handler = ngx_http_wait_request_handler;
/*
* 設定當前連線寫事件的處理方法handler為ngx_http_empty_handler,
* 該方法不執行任何實際操作,只記錄日誌;
* 因為處理請求的過程不需要write方法;
*/
c->write->handler = ngx_http_empty_handler;
#if (NGX_HTTP_SPDY)
...
#endif
#if (NGX_HTTP_SSL)
...
#endif
if (hc->addr_conf->proxy_protocol) {
hc->proxy_protocol = 1;
c->log->action = "reading PROXY protocol";
}
/* 若讀事件準備就緒,則判斷是否使用同步鎖,
* 根據同步鎖情況判斷決定是否立即處理該事件;
*/
if (rev->ready) {
/* the deferred accept(), rtsig, aio, iocp */
/*
* 若使用了同步鎖ngx_use_accept_mutex,
* 則將該讀事件新增到待處理事件佇列ngx_post_event中,
* 直到退出鎖時,才處理該讀事件;
*/
if (ngx_use_accept_mutex) {
ngx_post_event(rev, &ngx_posted_events);
return;
}
/* 若沒有使用同步鎖,則直接處理該讀事件;
* 讀事件的處理函式handler為ngx_http_wait_request_handler;
*/
rev->handler(rev);
return;
}
/*
* 若當前連線的讀事件未準備就緒,
* 則將其新增到定時器事件機制,並註冊到epoll事件機制中;
*/
/* 將當前連線的讀事件新增到定時器機制中 */
ngx_add_timer(rev, c->listening->post_accept_timeout);
ngx_reusable_connection(c, 1);
/* 將當前連線的讀事件註冊到epoll事件機制中 */
if (ngx_handle_read_event(rev, 0) != NGX_OK) {
ngx_http_close_connection(c);
return;
}
}
當連線上第一次出現可讀事件時,會呼叫 ngx_http_wait_request_handler 函式,該函式的功能是初始化HTTP 請求,但是它並不會在成功建立連線之後就立刻初始化請求,而是在當前連線所對應的套接字緩衝區上確定接收到來自客戶端的實際請求資料時才真正進行初始化工作,這樣做可以減少不必要的記憶體消耗(若當成功建立連線之後,客戶端並不進行實際資料通訊,而此時Nginx 卻因為初始化工作分配記憶體)。
ngx_http_wait_request_handler 函式的執行流程:
- 首先判斷當前讀事件是否超時(即讀事件的 timedout 標誌位是否為1):
- 若 timedout 標誌位為1,表示當前讀事件已經超時,則呼叫ngx_http_close_connection 方法關閉當前連線,return 從當前函式返回;
- 若 timedout 標誌位為0,表示當前讀事件還未超時,則繼續檢查當前連線的close標誌位;
- 若當前連線的 close 標誌位為1,表示當前連線要關閉,則呼叫ngx_http_close_connection 方法關閉當前連線,return 從當前函式返回;
- 若當前連線的 close 標誌位為0,表示不需要關閉當前連線,進而呼叫recv() 函式嘗試從當前連線所對應的套接字緩衝區中接收資料,這個步驟是為了確定客戶端是否真正的傳送請求資料,以免因為客戶端不傳送實際請求資料,出現初始化請求而導致記憶體被消耗。根據所讀取的資料情況n
來判斷是否要真正進行初始化請求工作:
- 若 n = NGX_AGAIN,表示客戶端發起連線請求,但是暫時還沒傳送實際的資料,則將當前連線上的讀事件新增到定時器機制中,同時將讀事件註冊到epoll 事件機制中,return 從當前函式返回;
- 若 n = NGX_ERROR,表示當前連接出錯,則直接呼叫ngx_http_close_connection 關閉當前連線,return 從當前函式返回;
- 若 n = 0,表示客戶端已經主動關閉當前連線,所有伺服器端呼叫ngx_http_close_connection 關閉當前連線,return 從當前函式返回;
- 若 n 大於 0,表示讀取到實際的請求資料,因此決定開始初始化當前請求,繼續往下執行;
- 呼叫 ngx_http_create_request 方法構造ngx_http_request_t 請求結構體,並設定到當前連線的data 成員;
- 設定當前讀事件的回撥方法為 ngx_http_process_request_line,並執行該回調方法開始接收並解析請求行;
/* 處理連線的可讀事件 */
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;
/* 獲取讀事件所對應的連線ngx_connection_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;
}
/* 若當前讀事件所對應的連線設定關閉連線標誌位,則關閉該連結 */
if (c->close) {
ngx_http_close_connection(c);
return;
}
/* 若當前讀事件不超時,且其所對應的連線不設定close標誌位,則繼續指向以下語句 */
hc = c->data;
/* 獲取當前讀事件請求的相關配置項結構 */
cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module);
size = cscf->client_header_buffer_size;
/* 以下內容是接收緩衝區的操作 */
b = c->buffer;
/* 若當前連線的接收緩衝區不存在,則建立該接收緩衝區 */
if (b == NULL) {
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;
}
/* 在當前連線上開始接收HTTP請求資料 */
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;
}
/* 若接收HTTP請求資料成功,則調整接收緩衝區成員指標 */
b->last += n;
if (hc->proxy_protocol) {
hc->proxy_protocol = 0;
p = ngx_proxy_protocol_parse(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";
ngx_reusable_connection(c, 0);
/* 為當前連線建立一個請求結構體ngx_http_request_t */
c->data = ngx_http_create_request(c);
if (c->data == NULL) {
ngx_http_close_connection(c);
return;
}
/* 設定當前讀事件的處理方法為ngx_http_process_request_line */
rev->handler = ngx_http_process_request_line;
/* 執行該讀事件的處理方法ngx_http_process_request_line,接收HTTP請求行 */
ngx_http_process_request_line(rev);
}
接收 HTTP 請求行
HTTP 請求的初始化完成之後會呼叫 ngx_http_process_request_line 方法開始接收並解析 HTTP 請求行。在 HTTP 協議中我們可以知道,請求行的長度並不是固定的,它與URI 長度相關,若當核心套接字緩衝區不能一次性完整的接收HTTP 請求行時,會多次呼叫ngx_http_process_request_line 方法繼續接收,即ngx_http_process_request_line 方法重新作為當前連線上讀事件的回撥方法,必要時將讀事件新增到定時器機制,註冊到epoll 事件機制,直到接收並解析出完整的HTTP 請求行。
ngx_http_process_request_line 處理HTTP 請求行函式執行流程:
- 首先,判斷當前請求是否超時,若超時(即讀事件的 timedout 標誌位為1),則設定當前連線的超時標誌位為1(c->timedout = 1),呼叫ngx_http_close_request 方法關閉該請求,並return 從當前函式返回;
- 若當前請求未超時(讀事件的 timedout 標誌位為
0),呼叫 ngx_http_read_request_header 方法開始讀取當前請求行,根據該函式的返回值n 進行以下判斷:
- 若返回值 n = NGX_AGAIN,表示當前連線上套接字緩衝區不存在可讀TCP 流,則需將當前讀事件新增到定時器機制,註冊到epoll 事件機制中,等待可讀事件發生。return 從當前函式返回;
- 若返回值 n = NGX_ERROR,表示當前連接出錯,則呼叫ngx_http_finalize_request 方法結束請求,return 從當前函式返回;
- 若返回值 n 大於 0,表示讀取請求行成功,呼叫函式 ngx_http_parse_request_line 開始解析由函式ngx_http_read_request_header 讀取所返回的請求行,根據函式ngx_http_parse_request_line 函式返回值rc 不同進行判斷;
- 若返回值 rc = NGX_ERROR,表示解析請求行時出錯,此時,呼叫ngx_http_finalize_request 方法終止該請求,並return 從當前函式返回;
- 若返回值 rc = NGX_AGAIN,表示沒有解析到完整的請求行,即仍需接收請求行,首先根據要求調整接收緩衝區header_in 的記憶體空間,則繼續呼叫函式ngx_http_read_request_header 讀取請求資料進入請求行自動處理機制,直到請求行解析完畢;
- 若返回值 rc = NGX_OK,表示解析到完整的
HTTP 請求行,則設定請求行的成員資訊(例如:方法名稱、URI 引數、HTTP 版本等資訊);
- 若 HTTP 協議版本小於 1.0 版本,表示不需要處理 HTTP 請求頭部,則直接呼叫函式ngx_http_process_request 處理該請求,return 從當前函式返回;
- 若HTTP協議版本不小於 1.0 版本,表示需要處理HTTP請求頭部:
- 呼叫函式 ngx_list_init 初始化儲存 HTTP 請求頭部的結構體 ngx_http_request_t 中成員headers_in 連結串列容器(該連結串列緩衝區是儲存所接收到的HTTP 請求資料);
- 設定當前讀事件的回撥方法為 ngx_http_process_request_headers 方法,並呼叫該方法ngx_http_process_request_headers 開始處理HTTP 請求頭部。return 從當前函式返回;
/* 處理HTTP請求行 */
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;
/* 獲取當前讀事件所對應的連線 */
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;
}
/* 設定NGX_AGAIN標誌,表示請求行還沒解析完畢 */
rc = NGX_AGAIN;
for ( ;; ) {
/* 若請求行還沒解析完畢,則繼續解析 */
if (rc == NGX_AGAIN) {
/* 讀取當前請求未解析的資料 */
n = ngx_http_read_request_header(r);
/* 若沒有資料,或讀取失敗,則直接退出 */
if (n == NGX_AGAIN || n == NGX_ERROR) {
return;
}
}
/* 解析接收緩衝區header_in中的請求行 */
rc = ngx_http_parse_request_line(r, r->header_in);
/* 若請求行解析完畢 */
if (rc == NGX_OK) {
/* the request line has been parsed successfully */
/* 設定請求行的成員,請求行是ngx_str_t型別 */
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);
/* 設定請求方法名稱字串 */
r->method_name.len = r->method_end - r->request_start + 1;
r->method_name.data = r->request_line.data;
/* 設定HTTP請求協議 */
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;
}
/* 若HTTP版本小於1.0版本,則表示不需要接收HTTP請求頭部,則直接處理請求 */
ngx_http_process_request(r);
return;
}
/* 初始化連結串列容器,為接收HTTP請求頭部做準備 */
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;
/* 開始處理HTTP請求頭部 */
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]);
ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
return;
}
/* 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;
}
}
}
}
在接收並解析請求行的過程中會呼叫 ngx_http_read_request_header 讀取請求資料,我們看下該函式是如何讀取到請求資料的。
ngx_http_read_request_header 讀取請求資料函式執行流程:
- 檢測當前請求的接收緩衝區 header_in 是否有資料,若有直接返回該資料n;
- 若接收緩衝區 header_in 沒有資料,檢查當前讀事件是否準備就緒(即判斷ready 標誌位是否為0 ):
- 若當前讀事件未準備就緒(即當前讀事件 ready 標誌位為0),則設定返回值n= NGX_AGAIN;
- 若當前讀事件已經準備就緒(即 ready 標誌位為 1),則呼叫 recv() 方法從當前連線套接字中讀取資料並儲存到接收緩衝區header_in 中,並設定n 為recv()方法所讀取的資料的返回值;
- 下面根據 n 的取值執行不同的操作:
- 若 n = NGX_AGAIN(此時,n 的值可能當前事件未準備就緒而設定的NGX_AGAIN,也可能是recv()方法返回的NGX_AGAIN 值,但是隻能是其中一種情況),將當前讀事件新增到定時器事件機制中, 將當前讀事件註冊到epoll 事件機制中,等待事件可讀,n 從當前函式返回;
- 若 n = 0 或 n = ERROR,則呼叫 ngx_http_finalize_request 結束請求,並返回NGX_ERROR 退出當前函式;
static ssize_t
ngx_http_read_request_header(ngx_http_request_t *r)
{
ssize_t n;
ngx_event_t *rev;
ngx_connection_t *c;
ngx_http_core_srv_conf_t *cscf;
/* 獲取當前請求所對應的連線 */
c = r->connection;
/* 獲取當前連線的讀事件 */
rev = c->read;
/* 獲取當前請求接收緩衝區的資料,header_in 是ngx_buf_t型別 */
n = r->header_in->last - r->header_in->pos;
/* 若接收緩衝區有資料,則直接返回該資料 */
if (n > 0) {
return n;
}
/* 若當前接收緩衝區沒有資料,首先判斷當前讀事件是否準備就緒 */
if (rev->ready) {
/* 若當前讀事件已準備就緒,則從其所對應的連線套接字讀取資料,並儲存到接收緩衝區中 */
n = c->recv(c, r->header_in->last,
r->header_in->end - r->header_in->last);
} else {
/* 若接收緩衝區沒有資料,且讀事件未準備就緒,則設定為NGX_AGAIN */
n = NGX_AGAIN;
}
/* 若接收緩衝區沒有資料,且讀事件未準備就緒,則設定為NGX_AGAIN */
/* 將當前讀事件新增到定時器機制;
* 將當前讀事件註冊到epoll事件機制;
*/
if (n == NGX_AGAIN) {
if (!rev->timer_set) {
cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
/* 將當前讀事件新增到定時器機制中 */
ngx_add_timer(rev, cscf->client_header_timeout);
}
/* 將當前讀事件註冊到epoll事件機制中 */
if (ngx_handle_read_event(rev, 0) != NGX_OK) {
ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return NGX_ERROR;
}
return NGX_AGAIN;
}
if (n == 0) {
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"client prematurely closed connection");
}
if (n == 0 || n == NGX_ERROR) {
c->error = 1;
c->log->action = "reading client request headers";
ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
return NGX_ERROR;
}
r->header_in->last += n;
return n;
}
接收 HTTP 請求頭部
前面已經成功接收並解析了 HTTP 請求行,這裡根據讀事件的回撥方法ngx_http_process_request_headers 開始接收並解析HTTP 請求頭部,但是並不一定能夠一次性接收到完整的HTTP 請求頭部,因此,可以多次呼叫該函式,直到接收到完整的HTTP 請求頭部。
ngx_http_process_request_headers 處理HTTP 請求頭部函式執行流程:
- 首先,判斷當前請求讀事件是否超時,若超時(即讀事件的 timedout 標誌位為1),則設定當前連線超時標誌位為1(c->timedout = 1),並呼叫ngx_http_close_request 方法關閉該請求,並return 從當前函式返回;
- 若當前請求讀事件未超時(即讀事件的 timedout 標誌位為0),檢查接收HTTP 請求頭部的header_in 緩衝區是否有剩餘記憶體空間,若沒有剩餘的記憶體空間,則呼叫ngx_http_alloc_large_header_buffer 方法分配更大的緩衝區。若有剩餘的記憶體,則無需再分配記憶體空間。
- 呼叫 ngx_http_read_request_header 方法開始讀取當前請求頭部儲存到header_in 緩衝區中,根據該函式的返回值 n 進行以下判斷:
- 若返回值 n = NGX_AGAIN,表示當前連線上套接字緩衝區不存在可讀TCP 流,則需將當前讀事件新增到定時器機制,註冊到epoll 事件機制中,等待可讀事件發生。return 從當前函式返回;
- 若返回值 n = NGX_ERROR,表示當前連接出錯,則呼叫ngx_http_finalize_request 方法結束請求,return 從當前函式返回;
- 若返回值 n 大於 0,表示讀取請求頭部成功,呼叫函式 ngx_http_parse_request_line 開始解析由函式ngx_http_read_request_header 讀取所返回的請求頭部,根據函式ngx_http_parse_request_line 函式返回值rc不同進行判斷;
- 若返回值 rc = NGX_ERROR,表示解析請求行時出錯,此時,呼叫ngx_http_finalize_request 方法終止該請求,並return 從當前函式返回;
- 若返回值 rc = NGX_AGAIN,表示沒有解析到完整一行的請求頭部,仍需繼續接收TCP 字元流才能夠是完整一行的請求頭部,則continue 繼續呼叫函式ngx_http_read_request_header 和ngx_http_parse_request_line 方法讀取並解析下一行請求頭部,直到全部請求頭部解析完畢;
- 若返回值 rc = NGX_OK,表示解析出一行 HTTP 請求頭部(注意:一行請求頭部只是整個請求頭部的一部分),判斷當前解析出來的一行請求頭部是否合法,若非法,則忽略當前一行請求頭部,繼續讀取並解析下一行請求頭部。若合法,則呼叫ngx_list_push 方法將該行請求頭部設定到當前請求ngx_http_request_t 結構體 header_in 緩衝區成員的headers 連結串列中,設定請求頭部名稱的hash 值,並continue 繼續呼叫函式ngx_http_read_request_header 和ngx_http_parse_request_line 方法讀取並解析下一行請求頭部,直到全部請求頭部解析完畢;
- 若返回值 rc = NGX_HTTP_PARSE_HEADER_DONE,則表示已經讀取並解析出全部請求頭部,此時,呼叫ngx_http_process_request 方法開始處理請求,return 從當前函式返回;
函式 ngx_http_process_request_headers 在檔案src/http/ngx_http_request.c 中定義如下:
/* 處理HTTP請求頭部 */
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;
/* 獲取當前連線的HTTP請求 */
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;
}
/* 獲取ngx_http_core_module模組的main級別配置項結構 */
cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
/* 表示當前請求頭部未解析完畢 */
rc = NGX_AGAIN;
for ( ;; ) {
if (rc == NGX_AGAIN) {
/* 若當前請求頭部未解析完畢,則首先判斷接收緩衝區是否有記憶體空間再次接收請求資料 */
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;
}
}
/* the host header could change the server configuration context */
/* 獲取ngx_http_core_module模組的srv級別配置項結構 */
cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
/* 開始解析HTTP請求頭部 */
rc = ngx_http_parse_header_line(r, r->header_in,
cscf->underscores_in_headers);
/* 解析出一行請求頭部(注意:一行請求頭部只是HTTP請求頭部的一部分) */
if (rc == NGX_OK) {
/* 設定當前請求的長度 */
r->request_length += r->header_in->pos - r->header_name_start;
/*
* 若當前解析出來的一行請求頭部是非法的,或Nginx當前版本不支援,
* 則記錄錯誤日誌,並繼續解析下一行請求頭部;
*/
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;
}
/* 設定請求頭部名稱的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);
}
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;
/*
* 呼叫該函式主要目的有兩個:
* 1、根據HTTP頭部的host欄位,呼叫ngx_http_find_virtual_server查詢虛擬主機的配置塊;
* 2、對HTTP請求頭部協議版本進行檢查,例如http1.1版本,host頭部不能為空,否則會返回400 Bad Request錯誤;
*/
rc = ngx_http_process_request_header(r);
if (rc != NGX_OK) {
return;
}
/* 開始處理當前請求 */
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: "\r" is not followed by "\n" */
/* 解析請求頭部出錯,則關閉該請求,並退出 */
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"client sent invalid header line: \"%*s\\r...\"",
r->header_end - r->header_name_start,
r->header_name_start);
ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
return;
}
}
處理 HTTP 請求
前面的步驟已經接收到完整的 HTTP 請求頭部,此時,已經有足夠的資訊開始處理HTTP 請求。處理HTTP 請求的過程有11 個 HTTP 階段,在不同的階段由各個 HTTP 模組進行處理。有關多階段處理請求的描述可參考《》;
ngx_http_process_request 處理HTTP 請求函式執行流程:
- 若當前讀事件在定時器機制中,則呼叫 ngx_del_timer 函式將其從定時器機制中移除,因為在處理HTTP 請求時不存在接收HTTP 請求頭部超時的問題;
- 由於處理 HTTP 請求不需要再接收 HTTP 請求行或頭部,則需重新設定當前連線讀、寫事件的回撥方法,讀、寫事件的回撥方法都設定為 ngx_http_request_handler,即後續處理 HTTP 請求的過程都是通過該方法進行;
- 設定當前請求 ngx_http_request_t 結構體中的成員read_event_handler 的回撥方法為ngx_http_block_reading,該回調方法實際不做任何操作,即在處理請求時不會對請求的讀事件進行任何處理,除非某個HTTP模組重新設定該回調方法;
- 接下來呼叫函式 ngx_http_handler 開始處理HTTP 請求;
- 呼叫函式 ngx_http_run_posted_requests 處理post 子請求;
/* 處理HTTP請求 */
void
ngx_http_process_request(ngx_http_request_t *r)
{
ngx_connection_t *c;
/* 獲取當前請求所對應的連線 */
c = r->connection;
#if (NGX_HTTP_SSL)
...
#endif
/*
* 由於現在不需要再接收HTTP請求頭部超時問題,
* 則需要把當前連線的讀事件從定時器機制中刪除;
* timer_set為1表示讀事件已新增到定時器機制中,
* 則將其從定時器機制中刪除,0表示不在定時器機制中;
*/
if (c->read->timer_set) {
ngx_del_timer(c->read);
}
#if (NGX_STAT_STUB)
...
#endif
/* 重新設定當前連線的讀、寫事件的回撥方法 */
c->read->handler = ngx_http_request_handler;
c->write->handler = ngx_http_request_handler;
/*
* 設定請求讀事件的回撥方法,
* 其實ngx_http_block_reading函式實際對讀事件不做任何處理;
* 即在處理請求時,不會對讀事件任何操作,除非有HTTP模組重新設定處理方法;
*/
r->read_event_handler = ngx_http_block_reading;
/* 開始處理各個HTTP模組的handler方法,該函式定義於ngx_http_core_module.c中*/
ngx_http_handler(r);
/* 處理post請求 */
ngx_http_run_posted_requests(c);
}
ngx_http_handler 函式的執行流程:
- 檢查當前請求 ngx_http_request_t 的
internal 標誌位:
- 若 internal 標誌位為 0,表示當前請求不需要重定向,判斷是否使用 keepalive 機制,並設定phase_handler 序號為0,表示執行ngx_http_phase_engine_t 結構成員ngx_http_phase_handler_t *handlers陣列中的第一個回撥方法;
- 若 internal 標誌位為 1,表示需要將當前請求做內部跳轉,並將 phase_handler 設定為server_rewriter_index,表示執行ngx_http_phase_engine_t 結構成員ngx_http_phase_handler_t *handlers 陣列在NGX_HTTP_SERVER_REWRITE_PHASE 處理階段的第一個回撥方法;
- 設定當前請求 ngx_http_request_t 的成員寫事件write_event_handler 為ngx_http_core_run_phases;
- 執行n gx_http_core_run_phases 方法;
void
ngx_http_handler(ngx_http_request_t *r)
{
ngx_http_core_main_conf_t *cmcf;
r->connection->log->action = NULL;
r->connection->unexpected_eof = 0;
/* 若當前請求的internal標誌位為0,表示不需要重定向 */
if (!r->internal) {
/* 下面語句是決定是否使用keepalive機制 */
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->headers_in.chunked);
/*
* phase_handler序號設定為0,表示執行ngx_http_phase_engine_t結構體成員
* ngx_http_phase_handler_t *handlers陣列中的第一個回撥方法;
*/
r->phase_handler = 0;
} else {
/* 若當前請求的internal標誌位為1,表示需要做內部跳轉 */
/* 獲取ngx_http_core_module模組的main級別的配置項結構 */
cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
/*
* 將phase_handler序號設為server_rewriter_index,
* 該phase_handler序號是作為ngx_http_phase_engine_t結構中成員
* ngx_http_phase_handler_t *handlers回撥方法陣列的序號,
* 即表示回撥方法在該陣列中所處的位置;
*
* server_rewrite_index則是handlers陣列中NGX_HTTP_SERVER_REWRITE_PHASE階段的
* 第一個ngx_http_phase_handler_t回撥的方法;
*/
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模組共同處理當前請求,
* 各個HTTP模組按照11個HTTP階段進行處理;
*/
ngx_http_core_run_phases(r);
}
ngx_http_core_run_phases 函式的執行流程:
- 判斷每個 ngx_http_phase_handler_t 處理階段是否實現checker 方法:
- 若實現 checker 方法,則執行 phase_handler 序號在 ngx_http_phase_handler_t *handlers陣列中指定的checker 方法;執行完checker 方法,若返回NGX_OK 則退出;若返回非NGX_OK,則繼續執行下一個HTTP 模組在該階段的checker 方法;
- 若沒有實現 checker 方法,則直接退出;
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;
/* 獲取ngx_http_core_module模組的main級別的配置項結構體 */
cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
/* 獲取各個HTTP模組處理請求的回撥方法陣列 */
ph = cmcf->phase_engine.handlers;
/* 若實現了checker方法 */
while (ph[r->phase_handler].checker) {
/* 執行phase_handler序號在陣列中指定的checker方法 */
rc = ph[r->phase_handler].checker(r, &ph[r->phase_handler]);
/* 成功執行checker方法,則退出,否則繼續執行下一個HTTP模組的checker方法 */
if (rc == NGX_OK) {
return;
}
}
}
處理子請求
post 子請求是基於 subrequest 機制的,首先看下 post 子請求結構體型別:
/* 子請求的單鏈表結構 */
typedef struct ngx_http_posted_request_s ngx_http_posted_request_t;
struct ngx_http_posted_request_s {
/* 指向當前待處理子請求的ngx_http_request_t結構體 */
ngx_http_request_t *request;
/* 指向下一個子請求 */
ngx_http_posted_request_t *next;
};
在請求結構體 ngx_http_request_t 中有一個與post 子請求相關的成員posted_requests,該成員把各個post 子請求按照子請求結構體ngx_http_posted_request_t 的結構連線成單鏈表的形式,請求結構體ngx_http_request_t 中main 成員是子請求的原始請求,parent 成員是子請求的父請求。下面是子請求的處理過程。
ngx_http_run_posted_requests 函式執行流程:
- 判斷當前連線是否已被銷燬(即標誌位 destroyed 是否為0),若被銷燬則直接return 退出,否則繼續執行;
- 獲取原始請求的子請求連結串列,若子請求連結串列為空(表示沒有 post 請求)則直接return 退出,否則繼續執行;
- 遍歷子請求連結串列,執行每個 post 請求的寫事件回撥方法write_event_handler;
void
ngx_http_run_posted_requests(ngx_connection_t *c)
{
ngx_http_request_t *r;
ngx_http_log_ctx_t *ctx;
ngx_http_posted_request_t *pr;
for ( ;; ) {
/* 若當前連線已被銷燬,則直接退出 */
if (c->destroyed) {
return;
}
/* 獲取當前連線所對應的請求 */
r = c->data;
/* 獲取原始請求的子請求單鏈表 */
pr = r->main->posted_requests;
/* 若子請求單鏈表為空,則直接退出 */
if (pr == NULL) {
return;
}
/* 將原始請求的posted_requests指向單鏈表的下一個post請求 */
r->main->posted_requests = pr->next;
/* 獲取子請求連結串列中的第一個post請求 */
r = pr->request;
ctx = c->log->data;
ctx->current_request = r;
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http posted request: \"%V?%V\"", &r->uri, &r->args);
/*
* 呼叫當前post請求寫事件的回撥方法write_event_handler;
* 子請求不被網路事件驅動,因此不需要呼叫read_event_handler;
*/
r->write_event_handler(r);
}
}
處理 HTTP 請求包體
下面開始要分析 HTTP 框架是如何處理HTTP 請求包體,HTTP 框架有兩種處理請求包體的方法:接收請求包體、丟棄請求包體;但是必須要注意的是丟棄請求包體並不意味著就不接受請求包體,只是把接收到的請求包體進行丟棄,不進一步對其進行處理。
其中有一個很重要的成員就是請求結構體 ngx_http_request_t 中的引用計數count,引用計數是用來決定是否真正結束當前請求,若引用計數為0 時,表示沒有其他動作在處理該請求,則可以終止該請求;若引用計數不為0 時,表示當前請求還有其他動作在操作,因此不能結束當前請求,以免發生錯誤;那怎麼樣控制這個引用計數呢?例如,當一個請求新增新事件,或是把一些原本從定時器、epoll 事件機制中移除的事件從新加入到其中等等,出現這些情況都是要對引用計數增加1;當要結束請求時,首先會把引用計數減1,並判斷該引用計數是否為0,再進一步判斷是否決定真的結束當前請求。
接收 HTTP 請求包體