### 概述
? ? ? 在 Nginx 的初始化啟動過程中,worker 工作進程會調用事件模塊的ngx_event_process_init 方法為每個監聽套接字ngx_listening_t 分配一個 ngx_connection_t 連接,并設置該連接上讀事件的回調方法handler 為ngx_event_accept,同時將讀事件掛載到epoll 事件機制中等待監聽套接字連接上的可讀事件發生,到此,Nginx?就可以接收并處理來自客戶端的請求。當監聽套接字連接上的可讀事件發生時,即該連接上有來自客戶端發出的連接請求,則會啟動讀事件的handler 回調方法ngx_event_accept,在ngx_event_accept 方法中調用accept() 函數接收來自客戶端的連接請求,成功建立連接之后,ngx_event_accept 函數調用監聽套接字上的handler 回調方法ls->handler(c)(該回調方法就是ngx_http_init_connection)。因此,成功建立連接之后由ngx_http_init_connection 方法開始處理該連接上的請求數據。
### 接收 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 流,則將讀事件添加到定時器事件機制中(監控可讀事件是否超時),同時將讀事件注冊到epoll 事件機制中,等待可讀事件的發生;
函數 ngx_http_init_connection 在文件[src/http/ngx_http_request.c](http://lxr.nginx.org/source/src/http/ngx_http_request.c) 中定義如下:
~~~
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,并執行該回調方法開始接收并解析請求行;
函數 ngx_http_wait_request_handler 在文件[src/http/ngx_http_request.c](http://lxr.nginx.org/source/src/http/ngx_http_request.c) 中定義如下:
~~~
/* 處理連接的可讀事件 */
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 從當前函數返回;
函數 ngx_http_process_request_line 在文件[src/http/ngx_http_request.c](http://lxr.nginx.org/source/src/http/ngx_http_request.c) 中定義如下:
~~~
/* 處理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 退出當前函數;
函數 ngx_http_read_request_header 在文件[src/http/ngx_http_request.c](http://lxr.nginx.org/source/src/http/ngx_http_request.c) 中定義如下:
~~~
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://lxr.nginx.org/source/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?模塊進行處理。有關多階段處理請求的描述可參考《[HTTP request processing phases in Nginx](http://www.nginxguts.com/2011/01/phases/)》;
**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 子請求;
函數 ngx_http_process_request 在文件[src/http/ngx_http_request.c](http://lxr.nginx.org/source/src/http/ngx_http_request.c) 中定義如下:
~~~
/* 處理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 方法;
函數 ngx_http_handler 在文件 [src/http/ngx_http_core_module.c](http://lxr.nginx.org/source/src/http/ngx_http_core_module.c) 中定義如下:
~~~
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 方法,則直接退出;
函數 ngx_http_core_run_phases 在文件[src/http/ngx_http_core_module.c](http://lxr.nginx.org/source/src/http/ngx_http_core_module.c) 中定義如下:
~~~
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;
函數 ngx_http_run_posted_requests 在文件[src/http/ngx_http_request.c](http://lxr.nginx.org/source/src/http/ngx_http_request.c) 中定義如下:
~~~
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 請求包體
? ? ? HTTP 請求包體保存在結構體 ngx_http_request_body_t 中,該結構體是存放在保存著請求結構體 ngx_http_request_t 的成員 request_body 中,該結構體定義如下:
~~~
/* 存儲HTTP請求包體的結構體ngx_http_request_body_t */
typedef struct {
/* 存放HTTP請求包體的臨時文件 */
ngx_temp_file_t *temp_file;
/*
* 指向接收HTTP請求包體的緩沖區鏈表表頭,
* 因為當一個緩沖區ngx_buf_t無法容納所有包體時,就需要多個緩沖區形成鏈表;
*/
ngx_chain_t *bufs;
/* 指向當前保存HTTP請求包體的緩沖區 */
ngx_buf_t *buf;
/*
* 根據content-length頭部和已接收包體長度,計算還需接收的包體長度;
* 即當前剩余的請求包體大小;
*/
off_t rest;
/* 接收HTTP請求包體緩沖區鏈表空閑緩沖區 */
ngx_chain_t *free;
/* 接收HTTP請求包體緩沖區鏈表已使用的緩沖區 */
ngx_chain_t *busy;
/* 保存chunked的解碼狀態,供ngx_http_parse_chunked方法使用 */
ngx_http_chunked_t *chunked;
/*
* HTTP請求包體接收完畢后執行的回調方法;
* 即ngx_http_read_client_request_body方法傳遞的第 2 個參數;
*/
ngx_http_client_body_handler_pt post_handler;
} ngx_http_request_body_t;
~~~
**接收 HTTP?請求包體 ngx_http_read_client_request_body 函數執行流程:**
- 原始請求引用計算 r->main->count 增加 1;引用計數 count 的管理是:當邏輯開啟流程時,引用計數就增加1,結束此流程時,引用計數就減1。在ngx_http_read_client_request_body 函數中,首先將原始請求的引用計數增加1,當遇到異常終止時,引用計數會在該函數返回之前減1;若正常結束時,引用計數由post_handler回調方法繼續維護;
- 判斷當前請求包體是否已被完整接收(r->request_body 為1)或被丟棄(r->discard_body為1),若滿足其中一個則不需要再次接收請求包體,直接執行post_handler 回調方法,并NGX_OK 從當前函數返回;
- 若需要接收 HTTP?請求包體,則首先調用 ngx_http_test_expect 方法,檢查客戶端是否發送 Expect:100-continue 頭部期望發送請求包體,服務器會回復 HTTP/1.1 100 Continue 表示允許客戶端發送請求包體;
- 分配當前請求 ngx_http_request_t 結構體request_body 成員,準備接收請求包體;
- 檢查請求的 content-length 頭部,若請求頭部的 content-length 字段小于0,則表示不需要繼續接收請求包體(即已經接收到完整的請求包體),直接執行post_handler 回調方法,并 NGX_OK 從當前函數返回;
- 若請求頭部的 content-length 字段大于 0,則表示需要繼續接收請求包體。首先判斷當前請求 ngx_http_request_t 的header_in 成員是否存在未處理數據,若存在未被處理的數據,表示該緩沖區header_in 在接收請求頭部期間已經預接收了請求包體,因為在接收HTTP?請求頭部期間有可能預接收請求包體,由于在接收請求包體之前,請求頭部已經被接收完畢,所以若該緩沖區存在未被處理的數據,那就是請求包體。
- 若 header_in 緩沖區存在未被處理的數據,即是預接收的請求包體,首先檢查緩沖區請求包體長度preread 是否大于請求包體長度的content-length 字段,若大于則表示已經接收到完整的HTTP?請求包體,不需要繼續接收,則執行post_handler 回調方法;
- 若 header_in 緩沖區存在未被處理的數據,即是預接收的請求包體,但是緩沖區請求包體長度preread 小于請求包體長度的content-length 字段,表示已接收的請求包體不完整,則需要繼續接收請求包體。調用函數ngx_http_request_body_filte 解析并把已接收的請求包體掛載到請求ngx_http_request_t r 的 request_body->bufs,header_in 緩沖區剩余的空間足夠接收剩余的請求包體大小rest,則不需要分配新的緩沖區,進而設置當前請求ngx_http_request_t ?的 read_event_handler 讀事件回調方法為ngx_http_read_client_request_body_handler,寫事件write_event_handler 回調方法為ngx_http_request_empty_handler (即不執行任何操作),然后調用方法ngx_http_do_read_client_request_body 真正接收HTTP?請求包體,該方法將TCP 連接上的套接字緩沖區中的字符流全部讀取出來,并判斷是否需要寫入到臨時文件,以及是否接收全部的請求包體,同時在接收到完整包體后執行回調方法post_handler;
- 若 header_in 緩沖區存在未被處理的數據,即是預接收的請求包體,但是緩沖區請求包體長度preread 小于請求包體長度的content-length 字段,或者header_in 緩沖區不存在未被處理的數據,且header_in 剩余的空間不足夠接收HTTP?請求包體,則會重新分配接收請求包體的緩沖區,再進而設置當前請求ngx_http_request_t?的read_event_handler 讀事件回調方法為ngx_http_read_client_request_body_handler,寫事件write_event_handler 回調方法為ngx_http_request_empty_handler (即不執行任何操作),然后調用方法ngx_http_do_read_client_request_body 真正接收HTTP?請求包體;
函數 ngx_http_read_client_request_body 在文件[src/http/ngx_http_request_body.c](http://lxr.nginx.org/source/src/http/ngx_http_request_body.c) 中定義如下:
~~~
/* 接收HTTP請求包體 */
ngx_int_t
ngx_http_read_client_request_body(ngx_http_request_t *r,
ngx_http_client_body_handler_pt post_handler)
{
size_t preread;
ssize_t size;
ngx_int_t rc;
ngx_buf_t *b;
ngx_chain_t out, *cl;
ngx_http_request_body_t *rb;
ngx_http_core_loc_conf_t *clcf;
/*
* 當有邏輯開啟流程時,引用計數會增加1,此流程結束時,引用計數將減1;
* 在ngx_http_read_client_request_body方法中,首先將原始請求引用計數增加1,
* 當遇到異常終止時,則在該函數返回前會將引用計數減1,;
* 若正常結束時,引用計數由post_handler方法繼續維護;
*/
/* 原始請求的引用計數count加1 */
r->main->count++;
#if (NGX_HTTP_SPDY)
if (r->spdy_stream && r == r->main) {
rc = ngx_http_spdy_read_request_body(r, post_handler);
goto done;
}
#endif
/* HTTP請求包體未被處理時,request_body結構是不被分配的,只有處理時才會分配 */
/*
* 若當前HTTP請求不是原始請求,或HTTP請求包體已被讀取或被丟棄;
* 則直接執行HTTP模塊的回調方法post_handler,并返回NGX_OK;
*/
if (r != r->main || r->request_body || r->discard_body) {
post_handler(r);
return NGX_OK;
}
/*
* ngx_http_test_expect 用于檢查客戶端是否發送Expect:100-continue頭部,
* 若客戶端已發送該頭部表示期望發送請求包體數據,則服務器回復HTTP/1.1 100 Continue;
* 具體意義是:客戶端期望發送請求包體,服務器允許客戶端發送,
* 該函數返回NGX_OK;
*/
if (ngx_http_test_expect(r) != NGX_OK) {
rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
goto done;
}
/* 只有在確定要接收請求包體時才分配存儲HTTP請求包體的結構體 ngx_http_request_body_t 空間 */
rb = ngx_pcalloc(r->pool, sizeof(ngx_http_request_body_t));
if (rb == NULL) {
rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
goto done;
}
/*
* set by ngx_pcalloc():
*
* rb->bufs = NULL;
* rb->buf = NULL;
* rb->free = NULL;
* rb->busy = NULL;
* rb->chunked = NULL;
*/
/* 初始化存儲請求包體結構成員 */
rb->rest = -1;/* 待接收HTTP請求包體的大小 */
rb->post_handler = post_handler;/* 接收完包體后的回調方法 */
/* 令當前請求的post_body成員指向存儲請求包體結構 */
r->request_body = rb;
/*
* 若指定HTTP請求包體的content_length字段小于0,則表示不需要接收包體;
* 執行post_handler方法,并返回;
*/
if (r->headers_in.content_length_n < 0 && !r->headers_in.chunked) {
post_handler(r);
return NGX_OK;
}
/* 若指定HTTP請求包體的content_length字段大于0,則表示需要接收包體;*/
/*
* 在請求結構ngx_http_request_t 成員中header_in緩沖區保存的是HTTP請求頭部,
* 由于在處理HTTP請求之前,HTTP頭部已被完整接收,所以若header_in緩沖區里面
* 還存在未處理的數據,則證明在接收HTTP請求頭部期間,已經預接收了HTTP請求包體;
*/
preread = r->header_in->last - r->header_in->pos;
/*
* 若header_in緩沖區存在預接收的HTTP請求包體,
* 則計算還需接收HTTP請求包體的大小rest;
*/
if (preread) {
/* there is the pre-read part of the request body */
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http client request body preread %uz", preread);
/* 將out的緩沖區指向header_in緩沖區中的請求包體數據 */
out.buf = r->header_in;
out.next = NULL;
/*
* 將預接收的HTTP請求包體數據添加到r->request_body->bufs中,
* 即將請求包體存儲在新分配的ngx_http_request_body_t rb 結構體的bufs中;
*/
rc = ngx_http_request_body_filter(r, &out);
if (rc != NGX_OK) {
goto done;
}
/* 若ngx_http_request_body_filter返回NGX_OK,則繼續執行以下程序 */
/* 更新當前HTTP請求長度:包括請求頭部與請求包體 */
r->request_length += preread - (r->header_in->last - r->header_in->pos);
/*
* 若已接收的請求包體不完整,即rest大于0,表示需要繼續接收請求包體;
* 若此時header_in緩沖區仍然有足夠的剩余空間接收剩余的請求包體長度,
* 則不再分配緩沖區內存;
*/
if (!r->headers_in.chunked
&& rb->rest > 0
&& rb->rest <= (off_t) (r->header_in->end - r->header_in->last))
{
/* the whole request body may be placed in r->header_in */
b = ngx_calloc_buf(r->pool);
if (b == NULL) {
rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
goto done;
}
b->temporary = 1;
b->start = r->header_in->pos;
b->pos = r->header_in->pos;
b->last = r->header_in->last;
b->end = r->header_in->end;
rb->buf = b;
/* 設置當前請求讀事件的回調方法 */
r->read_event_handler = ngx_http_read_client_request_body_handler;
r->write_event_handler = ngx_http_request_empty_handler;
/*
* 真正開始接收請求包體數據;
* 將TCP套接字連接緩沖區中當前的字符流全部讀取出來,
* 并判斷是否需要寫入臨時文件,以及是否接收全部的請求包體,
* 同時在接收到完整包體后執行回調方法post_handler;
*/
rc = ngx_http_do_read_client_request_body(r);
goto done;
}
} else {
/*
* 若在接收HTTP請求頭部過程沒有預接收HTTP請求包體數據,
* 或者預接收了不完整的HTTP請求包體,但是header_in緩沖區不夠繼續存儲剩余的包體;
* 進一步計算待需接收HTTP請求的大小rest;
*/
/* set rb->rest */
if (ngx_http_request_body_filter(r, NULL) != NGX_OK) {
rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
goto done;
}
}
/* 若rest為0,表示無需繼續接收HTTP請求包體,即已接收到完整的HTTP請求包體 */
if (rb->rest == 0) {/* 若已接收完整的HTTP請求包體 */
/* the whole request body was pre-read */
/*
* 檢查client_body_in_file_only配置項是否打開,若打開,
* 則將r->request_body->bufs中的包體數據寫入到臨時文件;
*/
if (r->request_body_in_file_only) {
if (ngx_http_write_request_body(r) != NGX_OK) {
rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
goto done;
}
if (rb->temp_file->file.offset != 0) {
cl = ngx_chain_get_free_buf(r->pool, &rb->free);
if (cl == NULL) {
rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
goto done;
}
b = cl->buf;
ngx_memzero(b, sizeof(ngx_buf_t));
b->in_file = 1;
b->file_last = rb->temp_file->file.offset;
b->file = &rb->temp_file->file;
rb->bufs = cl;
} else {
rb->bufs = NULL;
}
}
/* 執行回調方法 */
post_handler(r);
return NGX_OK;
}
/* rest小于0表示出錯 */
if (rb->rest < 0) {
ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
"negative request body rest");
rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
goto done;
}
/* 若rest大于0,則表示需要繼續接收HTTP請求包體數據,執行以下程序 */
/* 獲取ngx_http_core_module模塊的loc級別配置項結構 */
clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
/* 獲取緩存請求包體的buffer緩沖區大小 */
size = clcf->client_body_buffer_size;
size += size >> 2;
/* TODO: honor r->request_body_in_single_buf */
if (!r->headers_in.chunked && rb->rest < size) {
size = (ssize_t) rb->rest;
if (r->request_body_in_single_buf) {
size += preread;
}
} else {
size = clcf->client_body_buffer_size;
}
rb->buf = ngx_create_temp_buf(r->pool, size);
if (rb->buf == NULL) {
rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
goto done;
}
/* 設置當前請求讀事件的回調方法 */
r->read_event_handler = ngx_http_read_client_request_body_handler;
r->write_event_handler = ngx_http_request_empty_handler;
/* 接收請求包體 */
rc = ngx_http_do_read_client_request_body(r);
done:
if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
r->main->count--;
}
return rc;
}
~~~
**讀取 HTTP?請求包體 ngx_http_do_read_client_request_body 函數執行流程:**
- 若 request_body->buf 緩沖區沒有剩余的空間,則先調用函數ngx_http_write_request_body 將該緩沖區的數據寫入到文件中;此時,該緩沖區就有空間;或者?request_body->buf 緩沖區有剩余的空間;接著分別計算request_body->buf 緩沖區所剩余的可用空間大小 size、待接收 HTTP?請求包體的長度 rest;若當前緩沖區剩余大小足夠接收HTTP?請求包體,即size > rest,則調用recv 方法從 TCP 連接套接字緩沖區中讀取請求包體數據到當前緩沖區request_body->buf 中,下面根據recv 方法的返回值n 做不同的判斷:
- 返回值 n 為 NGX_AGAIN,表示 TCP 連接套接字緩沖區上的字符流未讀取完畢,則需繼續讀取;
- 返回值 n 為 0 或 NGX_ERROR,表示讀取失敗,設置當前請求的errno 標志位錯誤編碼,并退出;
- 返回值 n 不是以上的值,則表示讀取成功,此時,更新當緩沖區request_body->buf的使用情況,更新當前請求的長度。判斷已成功讀取的長度n 是否等于待接收HTTP?請求包體的長度rest,若n = rest,則將已讀取的請求包體掛載到當前請求的request body->buf鏈表中;并重新更新待接收的剩余請求包體長度rest 值;
- 根據 rest 值判斷是否已經接收到完整的 HTTP?請求包體:
- rest 值大于 0,表示未接收到完整的 HTTP?請求包體,且當前套接字緩沖區已經沒有可讀數據,則需要調用函數ngx_add_timer 將當前連接的讀事件添加到定時器機制,調用函數ngx_handler_read_event 將當前連接讀事件注冊到epoll 事件機制中,等待可讀事件的發生;此時,ngx_http_do_read_client_reuqest_body 返回NGX_AGAIN;
- rest 等于 0,表示已經接收到完整的 HTTP?請求包體,則把讀事件從定時器機制移除,把緩沖區數據寫入到文件中,設置讀事件的回調方法為ngx_http_block_reading(不進行任何操作),最后執行post_handler 回調方法;
函數 ngx_http_do_read_client_request_body 在文件[src/http/ngx_http_request_body.c](http://lxr.nginx.org/source/src/http/ngx_http_request_body.c) 中定義如下:
~~~
/* 讀取HTTP請求包體 */
static ngx_int_t
ngx_http_do_read_client_request_body(ngx_http_request_t *r)
{
off_t rest;
size_t size;
ssize_t n;
ngx_int_t rc;
ngx_buf_t *b;
ngx_chain_t *cl, out;
ngx_connection_t *c;
ngx_http_request_body_t *rb;
ngx_http_core_loc_conf_t *clcf;
/* 獲取當前請求所對應的連接 */
c = r->connection;
/* 獲取當前請求的請求包體結構體 */
rb = r->request_body;
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http read client request body");
for ( ;; ) {
for ( ;; ) {
/* 若當前緩沖區buf已滿 */
if (rb->buf->last == rb->buf->end) {
/* pass buffer to request body filter chain */
out.buf = rb->buf;
out.next = NULL;
rc = ngx_http_request_body_filter(r, &out);
if (rc != NGX_OK) {
return rc;
}
/* write to file */
/* 將緩沖區的字符流寫入文件 */
if (ngx_http_write_request_body(r) != NGX_OK) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
/* update chains */
rc = ngx_http_request_body_filter(r, NULL);
if (rc != NGX_OK) {
return rc;
}
if (rb->busy != NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
/* 由于已經將當前緩沖區的字符流寫入到文件,則該緩沖區有空間繼續使用 */
rb->buf->pos = rb->buf->start;
rb->buf->last = rb->buf->start;
}
/* 計算當前緩沖區剩余的可用空間size */
size = rb->buf->end - rb->buf->last;
/* 計算需要繼續接收請求包體的大小rest */
rest = rb->rest - (rb->buf->last - rb->buf->pos);
/* 若當前緩沖區有足夠的空間接收剩余的請求包體,則不需要再分配緩沖區 */
if ((off_t) size > rest) {
size = (size_t) rest;
}
/* 從TCP連接套接字讀取請求包體,并保存到當前緩沖區 */
n = c->recv(c, rb->buf->last, size);
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http client request body recv %z", n);
/* 若連接上套接字字符流還未讀取完整,則繼續讀取 */
if (n == NGX_AGAIN) {
break;
}
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;
return NGX_HTTP_BAD_REQUEST;
}
/* 調整當前緩沖區的使用情況 */
rb->buf->last += n;
/* 設置已接收HTTP請求長度 */
r->request_length += n;
/* 若已完整接收HTTP請求包體,則將該包體數據存儲到r->request_body->bufs中 */
if (n == rest) {
/* pass buffer to request body filter chain */
out.buf = rb->buf;
out.next = NULL;
/* 將已讀取的請求包體數據掛載到r->request_body->bufs中,并重新計算rest值 */
rc = ngx_http_request_body_filter(r, &out);
if (rc != NGX_OK) {
return rc;
}
}
if (rb->rest == 0) {
break;
}
if (rb->buf->last < rb->buf->end) {
break;
}
}
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http client request body rest %O", rb->rest);
if (rb->rest == 0) {
break;
}
/*
* 若未接接收到完整的HTTP請求包體,且當前連接讀事件未準備就緒,
* 則需將讀事件添加到定時器機制,注冊到epoll事件機制中,等待可讀事件發生;
*/
if (!c->read->ready) {
clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
ngx_add_timer(c->read, clcf->client_body_timeout);
if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
return NGX_AGAIN;
}
}
/* 到此,已經完整接收到HTTP請求,則需要將讀事件從定時器機制中移除 */
if (c->read->timer_set) {
ngx_del_timer(c->read);
}
/* 若設置將請求包體保存到臨時文件,則必須將緩沖區的請求包體數據寫入到文件中 */
if (rb->temp_file || r->request_body_in_file_only) {
/* save the last part */
/* 將緩沖區的請求包體數據寫入到文件中 */
if (ngx_http_write_request_body(r) != NGX_OK) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
if (rb->temp_file->file.offset != 0) {
cl = ngx_chain_get_free_buf(r->pool, &rb->free);
if (cl == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
b = cl->buf;
ngx_memzero(b, sizeof(ngx_buf_t));
b->in_file = 1;
b->file_last = rb->temp_file->file.offset;
b->file = &rb->temp_file->file;
rb->bufs = cl;
} else {
rb->bufs = NULL;
}
}
/*
* 由于已經完成請求包體的接收,則需重新設置讀事件的回調方法;
* read_event_handler 設置為 ngx_http_block_reading 表示阻塞讀事件
* 即再有讀事件發生將不會做任何處理;
*/
r->read_event_handler = ngx_http_block_reading;
/* 接收HTTP請求包體完畢后,調用回調方法post_handler */
rb->post_handler(r);
return NGX_OK;
}
~~~
**ngx_http_read_client_request_body_handler 方法執行流程:**
- 檢查連接上讀事件 timeout 標志位是否超時,若超時則調用函數ngx_http_finalize_request 終止當前請求;
- 若不超時,調用函數 ngx_http_do_read_client_request_body 開始讀取HTTP?請求包體數據;
函數 ngx_http_read_client_request_body_handler 在文件[src/http/ngx_http_request_body.c](http://lxr.nginx.org/source/src/http/ngx_http_request_body.c) 中定義如下:
~~~
static void
ngx_http_read_client_request_body_handler(ngx_http_request_t *r)
{
ngx_int_t rc;
/* 檢查連接上讀事件timeout標志位是否超時,若超時則終止該請求 */
if (r->connection->read->timedout) {
r->connection->timedout = 1;
ngx_http_finalize_request(r, NGX_HTTP_REQUEST_TIME_OUT);
return;
}
/* 開始接收HTTP請求包體數據 */
rc = ngx_http_do_read_client_request_body(r);
if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
ngx_http_finalize_request(r, rc);
}
}
~~~
### 丟棄 HTTP 請求包體
? ? ? 當 HTTP?框架不需要請求包體時,會在接收完該請求包體之后將其丟棄,并不會進行下一步處理,以下是相關函數。
**丟棄 HTTP?請求包體 ngx_http_discard_request_body 函數執行流程:**
- 若當前是子請求,或請求包體已經被完整接收,或請求包體已被丟棄,則不需要繼續,直接返回 NGX_OK 結束該函數;
- 調用函數 ngx_http_test_expect 檢查客戶端是否要發送請求包體,若服務器允許發送,則繼續執行;
- 若當前請求連接上的讀事件在定時器機制中(即 timer_set 標志位為1),則將該讀事件從定時器機制中移除(丟棄請求包體不需要考慮超時問題,除非設置linger_timer);
- 由于此時,待丟棄包體長度 content_length_n 為請求content-length 頭部字段大小,所有判斷content-length頭部字段是否小于0,若小于0,表示已經成功丟棄完整的請求包體,直接返回NGX_OK;若大于0,表示需要繼續丟棄請求包體,則繼續執行;
- 檢查當前請求的 header_in 緩沖區是否預接收了 HTTP?請求,設此時 header_in 緩沖區里面未處理的數據大小為size,若size 不為0,表示已經預接收了HTTP?請求包體數據,則調用函數ngx_http_discard_request_body_filter 將該請求包體丟棄,并根據已經預接收請求包體長度和請求content-length 頭部字段長度,重新計算需要待丟棄請求包體的長度content_length_n 的值;根據ngx_http_discard_request_body_filter 函數的返回值rc 進行不同的判斷:
- 若 rc = NGX_OK,且 content_length_n 的值為 0,則表示已經接收到完整請求包體,并將其丟棄;
- 若 rc != NGX_OK,則表示需要繼續接收請求包體,根據content_length_n 的值來表示待丟棄請求包體的長度;
- 若還需繼續丟棄請求包體,則調用函數 ngx_http_read_discard_request_body 讀取剩余的請求包體數據,并將其丟棄;并根據該函數返回值rc 不同進行判斷:
- 若 rc = NGX_OK,表示已成功丟棄完整的請求包體;
- 若 rc != NGX_OK,則表示接收到請求包體依然不完整,且此時連接套接字上已經沒有剩余數據可讀,則設置當前請求讀事件的回調方法read_event_handler?為ngx_http_discarded_request_body_handler,并調用函數ngx_handle_read_event 將該請求連接上的讀事件注冊到epoll 事件機制中,等待可讀事件發生以便繼續讀取請求包體;同時將引用計數增加1(防止繼續丟棄包體),當前請求的discard_body 標志位設置為1,表示正在丟棄,并返回NGX_OK(這里并不表示已經成功丟棄完整的請求包體,只是表示ngx_http_discard_request_body 執行完畢,接下來的是等待讀事件發生并繼續丟棄包體);
函數 ngx_http_discard_request_body 在文件[src/http/ngx_http_request_body.c](http://lxr.nginx.org/source/src/http/ngx_http_request_body.c) 中定義如下:
~~~
/* 丟棄HTTP請求包體 */
ngx_int_t
ngx_http_discard_request_body(ngx_http_request_t *r)
{
ssize_t size;
ngx_int_t rc;
ngx_event_t *rev;
#if (NGX_HTTP_SPDY)
if (r->spdy_stream && r == r->main) {
r->spdy_stream->skip_data = NGX_SPDY_DATA_DISCARD;
return NGX_OK;
}
#endif
/*
* 若當前HTTP請求不是原始請求,或HTTP請求包體已被讀取或被丟棄;
* 則直接返回NGX_OK;
*/
if (r != r->main || r->request_body || r->discard_body) {
return NGX_OK;
}
/*
* ngx_http_test_expect 用于檢查客戶端是否發送Expect:100-continue頭部,
* 若客戶端已發送該頭部表示期望發送請求包體數據,則服務器回復HTTP/1.1 100 Continue;
* 具體意義是:客戶端期望發送請求包體,服務器允許客戶端發送,
* 該函數返回NGX_OK;
*/
if (ngx_http_test_expect(r) != NGX_OK) {
rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
}
/* 獲取當前連接的讀事件 */
rev = r->connection->read;
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, "http set discard body");
/* 若讀事件在定時器機制中,則將其移除 */
if (rev->timer_set) {
ngx_del_timer(rev);
}
/* 若請求content-length頭部字段小于0,直接返回NGX_OK */
if (r->headers_in.content_length_n <= 0 && !r->headers_in.chunked) {
return NGX_OK;
}
/* 獲取當前請求header_in緩沖區中預接收請求包體數據 */
size = r->header_in->last - r->header_in->pos;
/* 若已經預接收了HTTP請求包體數據 */
if (size || r->headers_in.chunked) {
/*
* 丟棄預接收請求包體數據,并根據預接收請求包體大小與請求content-length頭部大小,
* 重新計算content_length_n的值;
*/
rc = ngx_http_discard_request_body_filter(r, r->header_in);
/* 若rc不為NGX_OK表示預接收的請求包體數據不完整,需繼續接收 */
if (rc != NGX_OK) {
return rc;
}
/* 若返回rc=NGX_OK,且待丟棄請求包體大小content-length_n為0,表示已丟棄完整的請求包體 */
if (r->headers_in.content_length_n == 0) {
return NGX_OK;
}
}
/* 讀取剩余的HTTP請求包體數據,并將其丟棄 */
rc = ngx_http_read_discarded_request_body(r);
/* 若已經讀取到完整請求包體,則返回NGX_OK */
if (rc == NGX_OK) {
r->lingering_close = 0;/* 不需要延遲關閉請求 */
return NGX_OK;
}
if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
return rc;
}
/* rc == NGX_AGAIN */
/*
* 若讀取到的請求包體依然不完整,但此時已經沒有剩余數據可讀,
* 則將當前請求讀事件回調方法設置為ngx_http_discard_request_body_handler,
* 并將讀事件注冊到epoll事件機制中,等待可讀事件發生以便繼續讀取請求包體;
*/
r->read_event_handler = ngx_http_discarded_request_body_handler;
if (ngx_handle_read_event(rev, 0) != NGX_OK) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
/* 由于已將讀事件注冊到epoll事件機制中,則引用計數增加1,discard_body標志為1 */
r->count++;
r->discard_body = 1;
return NGX_OK;
}
~~~
**ngx_http_discarded_request_body_handler 函數執行流程如下:**
- 判斷當前請求連接上的讀事件是否超時,若超時(即標志位 timeout 為1),則調用函數ngx_http_finalize_request 將引用計數減1,若此時引用計數為0,則終止當前請求;
- 調用函數 ngx_http_read_discarded_request_body 開始讀取請求包體,并將所讀取的請求包體丟棄;同時根據該函數的返回值rc 不同進行判斷:
- 若返回值 rc = NGX_OK,表示已經接收到完整請求包體,并成功將其丟棄,則此時設置discard_body 標志位為0,設置lingering_close 標志位為0,并調用函數ngx_http_finalize_request 結束當前請求;
- 若返回值 rc != NGX_OK,則表示讀取的請求包體依舊不完整,調用函數ngx_handle_read_event 將讀事件注冊到epoll 事件機制中,等待可讀事件發生;
函數 ngx_http_discarded_request_body_handler 在文件[src/http/ngx_http_request_body.c](http://lxr.nginx.org/source/src/http/ngx_http_request_body.c) 中定義如下:
~~~
void
ngx_http_discarded_request_body_handler(ngx_http_request_t *r)
{
ngx_int_t rc;
ngx_msec_t timer;
ngx_event_t *rev;
ngx_connection_t *c;
ngx_http_core_loc_conf_t *clcf;
c = r->connection;
rev = c->read;
/*
* 判斷讀事件是否超時,若超時則調用ngx_http_finalize_request方法將引用計數減1,
* 若此時引用計數是0,則直接終止該請求;
*/
if (rev->timedout) {
c->timedout = 1;
c->error = 1;
ngx_http_finalize_request(r, NGX_ERROR);
return;
}
/* 若需要延遲關閉,則設置延遲關閉連接的時間 */
if (r->lingering_time) {
timer = (ngx_msec_t) r->lingering_time - (ngx_msec_t) ngx_time();
if ((ngx_msec_int_t) timer <= 0) {
r->discard_body = 0;
r->lingering_close = 0;
ngx_http_finalize_request(r, NGX_ERROR);
return;
}
} else {
timer = 0;
}
/* 讀取剩余請求包體,并將其丟棄 */
rc = ngx_http_read_discarded_request_body(r);
/* 若返回rc=NGX_OK,則表示已接收到完整請求包體,并成功將其丟棄 */
if (rc == NGX_OK) {
r->discard_body = 0;
r->lingering_close = 0;
ngx_http_finalize_request(r, NGX_DONE);
return;
}
if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
c->error = 1;
ngx_http_finalize_request(r, NGX_ERROR);
return;
}
/* rc == NGX_AGAIN */
/* 若讀取的請求包體依舊不完整,則再次將讀事件注冊到epoll事件機制中 */
if (ngx_handle_read_event(rev, 0) != NGX_OK) {
c->error = 1;
ngx_http_finalize_request(r, NGX_ERROR);
return;
}
/* 若設置了延遲,則將讀事件添加到定時器事件機制中 */
if (timer) {
clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
timer *= 1000;
if (timer > clcf->lingering_timeout) {
timer = clcf->lingering_timeout;
}
ngx_add_timer(rev, timer);
}
}
~~~
**ngx_http_read_discarded_request_body 函數執行流程:**
- 若待丟棄請求包體長度 content_length_n 為0,表示已經接收到完整請求包體,并成功將其丟棄,則此時,設置讀事件的回調方法為ngx_http_block_reading(不進行任何操作),同時返回NGX_OK,表示已成功丟棄完整請求包體;
- 若需要繼續丟棄請求包體數據,且此時,連接上套接字緩沖區沒有可讀數據,即讀事件未準備就緒,則返回 NGX_AGAIN,表示需要等待讀事件再次被觸發時繼續讀取請求包體并丟棄;
- 調用函數 recv 讀取請求包體數據,根據不同返回值 n,進行不同的判斷:
- 若返回值 n = NGX_AGAIN,表示讀取的請求包體依舊不完整,需要等待下次讀事件被觸發,繼續讀取請求包體數據;
- 若 n = NGX_ERROR 或 n = 0,表示客戶端主動關閉當前連接,則不需要讀取請求包體,即直接返回 NGX_OK,表示結束丟棄包體動作;
- 若返回值 n = NGX_OK,則表示讀取請求包體成功,此時調用函數ngx_http_discard_request_body_filter 將已經讀取的請求包體丟棄,并更新content_length_n 的值;根據content_length_n 的值進行判斷是否繼續讀取請求包體數據(此時又回到步驟1,因此是一個for 循環),直到讀取到完整的請求包體,并將其丟棄,才結束for 循環,并從該函數返回;
函數 ngx_http_read_discarded_request_body 在文件[src/http/ngx_http_request_body.c](http://lxr.nginx.org/source/src/http/ngx_http_request_body.c) 中定義如下:
~~~
/* 讀取請求包體,并將其丟棄 */
static ngx_int_t
ngx_http_read_discarded_request_body(ngx_http_request_t *r)
{
size_t size;
ssize_t n;
ngx_int_t rc;
ngx_buf_t b;
u_char buffer[NGX_HTTP_DISCARD_BUFFER_SIZE];
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http read discarded body");
ngx_memzero(&b, sizeof(ngx_buf_t));
b.temporary = 1;
for ( ;; ) {
/* 若待丟棄的請求包體大小content_length_n 為0,表示不需要接收請求包體 */
if (r->headers_in.content_length_n == 0) {
/* 重新設置讀事件的回調方法,其實該回調方法不進行任何操作 */
r->read_event_handler = ngx_http_block_reading;
return NGX_OK;
}
/* 若當前連接的讀事件未準備就緒,則不能讀取數據,即返回NGX_AGAIN */
if (!r->connection->read->ready) {
return NGX_AGAIN;
}
/* 若需要讀取請求包體數據,計算需要讀取請求包體的大小size */
size = (size_t) ngx_min(r->headers_in.content_length_n,
NGX_HTTP_DISCARD_BUFFER_SIZE);
/* 從連接套接字緩沖區讀取請求包體數據 */
n = r->connection->recv(r->connection, buffer, size);
if (n == NGX_ERROR) {
r->connection->error = 1;
return NGX_OK;
}
/* 若讀取的請求包體數據不完整,則繼續讀取 */
if (n == NGX_AGAIN) {
return NGX_AGAIN;
}
/* 若n=0或n=NGX_ERROR表示讀取失敗,即該連接已關閉,則不需要丟棄包體 */
if (n == 0) {
return NGX_OK;
}
/* 若返回n=NGX_OK ,表示讀取到完整的請求包體,則將其丟棄 */
b.pos = buffer;
b.last = buffer + n;
/* 將讀取的完整請求包體丟棄 */
rc = ngx_http_discard_request_body_filter(r, &b);
if (rc != NGX_OK) {
return rc;
}
}
}
~~~
### 發送 HTTP 響應報文
? ? ? HTTP 的響應報文由 Filter 模塊處理并發送,Filter 模塊包括過濾頭部(Header Filter)和過濾包體(Body Filter),Filter 模塊過濾頭部處理 HTTP?響應頭部(HTTP headers),Filter 包體處理HTTP 響應包體(response content)。HTTP 響應報文發送的過程需要經過Nginx 的過濾鏈,所謂的過濾鏈就是由多個過濾模塊組成有序的過濾鏈表,每個鏈表元素就是對應過濾模塊的處理方法。在HTTP 框架中,定義了過濾鏈表表頭(即鏈表的第一個元素,也是處理方法)如下:
~~~
extern ngx_http_output_header_filter_pt ngx_http_top_header_filter;/* 發送響應頭部 */
extern ngx_http_output_body_filter_pt ngx_http_top_body_filter;/* 發送響應包體 */
~~~
其中,ngx_http_output_header_filter_pt 和?ngx_http_output_body_filter_pt 是函數指針,定義如下:
~~~
typedef ngx_int_t (*ngx_http_output_header_filter_pt) (ngx_http_request_t *r);/* 發送響應頭部 */
typedef ngx_int_t (*ngx_http_output_body_filter_pt) (ngx_http_request_t *r, ngx_chain_t *chain);/* 發送響應包體 */
~~~
參數 r 是當前的請求,chain 是待發送的HTTP 響應包體;上面提到的只有過濾鏈表的表頭,那么使用什么把所有過濾模塊連接起來呢?該工作由下面定義完成,即:
~~~
static ngx_http_output_header_filter_pt ngx_http_next_header_filter;
static ngx_http_output_body_filter_pt ngx_http_next_body_filter;
~~~
? ? ? 這樣就可以遍歷整個過濾鏈表,把 HTTP?響應報文發送出去,按照過濾鏈表的順序,調用鏈表元素的回調方法可能會對響應報文數據進行檢測、截取、新增、修改 或 刪除等操作,即FIlter 模塊可以對響應報文進行修改。但是必須注意的是只有最后一個鏈表元素才會真正的發送響應報文。
### 發送 HTTP 響應頭部
? ? ? HTTP 響應狀態行、響應頭部由函數 ngx_http_send_header 發送,該發送函數的執行過程中會遍歷過濾鏈表,該過濾鏈表的過濾模塊是那些對 HTTP?響應頭部感興趣的過濾模塊組成,ngx_http_send_header 函數按照過濾鏈表的順序依次處理響應頭部,直到最后一個鏈表元素處理響應頭部并把該響應頭部發送給客戶端。ngx_http_send_header 函數定義在文件[src/http/ngx_http_core_module.c](http://lxr.nginx.org/source/src/http/ngx_http_core_module.c)?中如下:
~~~
/* 發送 HTTP 響應頭部 */
ngx_int_t
ngx_http_send_header(ngx_http_request_t *r)
{
if (r->header_sent) {
ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
"header already sent");
return NGX_ERROR;
}
if (r->err_status) {
r->headers_out.status = r->err_status;
r->headers_out.status_line.len = 0;
}
return ngx_http_top_header_filter(r);
}
~~~
? ? ? 從函數的實現過程中我們可以知道,該函數調用 ngx_http_top_header_filter 方法開始順序遍歷過濾鏈表的每一個元素處理方法,直到最后一個把響應頭部發送出去為止。在Nginx中,過濾鏈表的順序如下:
~~~
+----------------------------+
|ngx_http_not_modified_filter|
+----------+-----------------+
|
v
+----------+------------+
|ngx_http_headers_filter|
+----------+------------+
|
v
+----------+-----------+
|ngx_http_userid_filter|
+----------+-----------+
|
v
+----------+-------------------+
|ngx_http_charset_header_filter|
+----------+-------------------+
|
v
+----------+---------------+
|ngx_http_ssi_header_filter|
+----------+---------------+
|
v
+----------+----------------+
|ngx_http_gzip_header_filter|
+----------+----------------+
|
v
+----------+-----------------+
|ngx_http_range_header_filter|
+----------+-----------------+
|
v
+----------+-------------------+
|ngx_http_chunked_header_filter|
+----------+-------------------+
|
v
+----------+-----------+
|ngx_http_header_filter|
+----------------------+
~~~
? ? ? 根據發送響應頭部的過濾鏈表順序可以知道,除了最后一個模塊是真正發送響應頭部給客戶端之外,其他模塊都只是對響應頭部進行修改,最后一個過來模塊是ngx_http_header_filter_module,該模塊提供的處理方法是ngx_http_header_filter 根據請求結構體ngx_http_request_t 中的 header_out 成員序列化字符流,并發送序列化之后的響應頭部;
? ? ?ngx_http_header_filter_module 模塊的定義如下:
~~~
ngx_module_t ngx_http_header_filter_module = {
NGX_MODULE_V1,
&ngx_http_header_filter_module_ctx, /* module context */
NULL, /* module directives */
NGX_HTTP_MODULE, /* module type */
NULL, /* init master */
NULL, /* init module */
NULL, /* init process */
NULL, /* init thread */
NULL, /* exit thread */
NULL, /* exit process */
NULL, /* exit master */
NGX_MODULE_V1_PADDING
};
~~~
? ? ? 從該模塊的定義中可以知道,該模塊只調用上下文結構?ngx_http_header_filter_module_ctx,該上下文結構定義如下:
~~~
static ngx_http_module_t ngx_http_header_filter_module_ctx = {
NULL, /* preconfiguration */
ngx_http_header_filter_init, /* postconfiguration */
NULL, /* create main configuration */
NULL, /* init main configuration */
NULL, /* create server configuration */
NULL, /* merge server configuration */
NULL, /* create location configuration */
NULL, /* merge location configuration */
};
~~~
? ? ? 上下文結構指定了?postconfiguration 的方法為?ngx_http_header_filter_init,該方法定義如下:
~~~
/* 初始化ngx_http_header_filter_module模塊 */
static ngx_int_t
ngx_http_header_filter_init(ngx_conf_t *cf)
{
/* 調用ngx_http_header_filter方法發送響應頭部 */
ngx_http_top_header_filter = ngx_http_header_filter;
return NGX_OK;
}
~~~
? ? ? 最終該模塊由方法 ?ngx_http_header_filter 執行即發送HTTP 響應頭部,下面對該方法進行分析;
**ngx_http_header_filter 函數執行流程:**
- 首先檢查當前請求 ngx_http_request_t 結構的header_sent 標志位,若該標志位為1,則表示已經發送過響應頭部,因此,無需重復發送,直接返回NGX_OK 結束該函數;
- 若之前未發送過響應頭部(即 headr_sent 標志位為0),此時,準備發送響應頭部,并設置header_sent 標志位為1(防止重復發送),表示正要發送響應頭部,同時檢查當前請求是否為原始請求,若不是原始請求(即為子請求),則不需要發送響應頭部返回?NGX_OK,因為子請求不存在響應頭部概念。繼而檢查HTTP?協議版本,若HTTP?協議版本小于 ?1.0(即不支持請求頭部,也就沒有所謂的響應頭部)直接返回NGX_OK,若是原始請求且HTTP?協議版本不小于1.0版本,則準備發送響應頭部;
- 根據 HTTP?響應報文的狀態行、響應頭部將字符串序列化為發送響應頭部所需的字節數len,方便下面分配緩沖區空間存在待發送的響應頭部;
- 根據前一步驟計算的 len 值在當前請求內存池中分配用于存儲響應頭部的字符流緩沖區b,并將響應報文的狀態行、響應頭部按照HTTP?規范序列化地復制到剛分配的緩沖區b 中;
- 將待發送響應頭部的緩沖區 b 掛載到鏈表緩沖區 out.buf 中;掛載的目的是:當響應頭部不能一次性發送完畢時,ngx_http_header_filter 方法會返回NGX_AGAIN,表示發送的響應頭部不完整,則把剩余的響應頭部數據保存在out 鏈表緩沖區中,以便調用ngx_http_filter_request 時,再次調用 HTTP?框架將 out 鏈表緩沖區的剩余響應頭部字符流發送出去;
- 調用 ngx_http_writer_filter 方法將out 鏈表緩沖區的響應頭部發送出去,但是不能保證一次性發送完畢;
函數 ngx_http_header_filter 在文件[src/http/ngx_http_header_filter_module.c](http://lxr.nginx.org/source/src/http/ngx_http_header_filter_module.c) 中定義如下:
~~~
/* 發送HTTP響應頭部 */
static ngx_int_t
ngx_http_header_filter(ngx_http_request_t *r)
{
u_char *p;
size_t len;
ngx_str_t host, *status_line;
ngx_buf_t *b;
ngx_uint_t status, i, port;
ngx_chain_t out;
ngx_list_part_t *part;
ngx_table_elt_t *header;
ngx_connection_t *c;
ngx_http_core_loc_conf_t *clcf;
ngx_http_core_srv_conf_t *cscf;
struct sockaddr_in *sin;
#if (NGX_HAVE_INET6)
struct sockaddr_in6 *sin6;
#endif
u_char addr[NGX_SOCKADDR_STRLEN];
/*
* 檢查當前請求結構的header_sent標志位,若該標志位為1,
* 表示已經發送HTTP請求響應,則無需再發送,此時返回NGX_OK;
*/
if (r->header_sent) {
return NGX_OK;
}
/* 若之前未發送HTTP請求響應,則現在準備發送,并設置header_sent標志位 */
r->header_sent = 1;
/* 當前請求不是原始請求,則返回NGX_OK */
if (r != r->main) {
return NGX_OK;
}
/*
* 若HTTP版本為小于1.0 則直接返回NGX_OK;
* 因為這些版本不支持請求頭部,所有就沒有響應頭部;
*/
if (r->http_version < NGX_HTTP_VERSION_10) {
return NGX_OK;
}
if (r->method == NGX_HTTP_HEAD) {
r->header_only = 1;
}
if (r->headers_out.last_modified_time != -1) {
if (r->headers_out.status != NGX_HTTP_OK
&& r->headers_out.status != NGX_HTTP_PARTIAL_CONTENT
&& r->headers_out.status != NGX_HTTP_NOT_MODIFIED)
{
r->headers_out.last_modified_time = -1;
r->headers_out.last_modified = NULL;
}
}
/* 以下是根據HTTP響應報文的狀態行、響應頭部字符串序列化為所需的字節數len */
len = sizeof("HTTP/1.x ") - 1 + sizeof(CRLF) - 1
/* the end of the header */
+ sizeof(CRLF) - 1;
/* status line */
if (r->headers_out.status_line.len) {
len += r->headers_out.status_line.len;
status_line = &r->headers_out.status_line;
...
...
/* 分配用于存儲響應頭部字符流緩沖區 */
b = ngx_create_temp_buf(r->pool, len);
if (b == NULL) {
return NGX_ERROR;
}
/* 將響應報文的狀態行、響應頭部按照HTTP規范序列化地復制到剛分配的緩沖區b中 */
/* "HTTP/1.x " */
b->last = ngx_cpymem(b->last, "HTTP/1.1 ", sizeof("HTTP/1.x ") - 1);
/* status line */
if (status_line) {
b->last = ngx_copy(b->last, status_line->data, status_line->len);
} else {
b->last = ngx_sprintf(b->last, "%03ui ", status);
}
...
...
/*
* 將待發送的響應頭部掛載到out鏈表緩沖區中,
* 掛載的目的是:當響應頭部不能一次性發送完成時,
* ngx_http_header_filter方法返回NGX_AGAIN,表示發送的響應頭部不完整,
* 則把剩余的響應頭部保存在out鏈表中,以便調用ngx_http_finalize_request時,
* 再次調用HTTP框架將out鏈表中剩余的響應頭部字符流繼續發送;
*/
out.buf = b;
out.next = NULL;
/*
* 調用方法ngx_http_write_filter將響應頭部字符流發送出去;
* 所有實際發送響應頭部數據的由ngx_http_write_filter方法實現;
*/
return ngx_http_write_filter(r, &out);
}
~~~
### 發送 HTTP 響應包體
? ? ? HTTP 響應包體由函數 ngx_http_output_filter?發送,該發送函數的執行過程中會遍歷過濾鏈表,該過濾鏈表的過濾模塊是那些對 HTTP?響應包體感興趣的過濾模塊組成,ngx_http_output_filter 函數按照過濾鏈表的順序依次處理響應包體,直到最后一個鏈表元素處理響應包體并把該響應包體發送給客戶端。ngx_http_output_filter 函數定義在文件?[src/http/ngx_http_core_module.c](http://lxr.nginx.org/source/src/http/ngx_http_core_module.c)?中如下:
~~~
/* 發送HTTP 響應包體 */
ngx_int_t
ngx_http_output_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
ngx_int_t rc;
ngx_connection_t *c;
c = r->connection;
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http output filter \"%V?%V\"", &r->uri, &r->args);
rc = ngx_http_top_body_filter(r, in);
if (rc == NGX_ERROR) {
/* NGX_ERROR may be returned by any filter */
c->error = 1;
}
return rc;
}
~~~
? ? ? 從函數的實現過程中我們可以知道,該函數調用 ngx_http_top_body_filter 方法開始順序遍歷過濾鏈表的每一個元素處理方法,直到最后一個把響應包體發送出去為止。在Nginx 中,過濾鏈表的順序如下:
~~~
+--------------------------+
|ngx_http_range_body_filter|
+----------+---------------+
|
v
+----------+---------+
|ngx_http_copy_filter|
+----------+---------+
|
v
+----------+-----------------+
|ngx_http_charset_body_filter|
+----------+-----------------+
|
v
+----------+-------------+
|ngx_http_ssi_body_filter|
+----------+-------------+
|
v
+----------+-------------+
|ngx_http_postpone_filter|
+----------+-------------+
|
v
+----------+--------------+
|ngx_http_gzip_body_filter|
+----------+--------------+
|
v
+----------+-----------------+
|ngx_http_chunked_body_filter|
+----------+-----------------+
|
v
+---------------------+
|ngx_http_write_filter|
+---------------------+
~~~
? ? ? 根據發送響應包體的過濾鏈表順序可以知道,除了最后一個模塊是真正發送響應包體給客戶端之外,其他模塊都只是對響應包體進行修改,最后一個過來模塊是ngx_http_write_filter_module,該模塊提供的處理方法是ngx_http_write_filter;
? ? ??ngx_http_write_filter_module 模塊的定義如下:
~~~
ngx_module_t ngx_http_write_filter_module = {
NGX_MODULE_V1,
&ngx_http_write_filter_module_ctx, /* module context */
NULL, /* module directives */
NGX_HTTP_MODULE, /* module type */
NULL, /* init master */
NULL, /* init module */
NULL, /* init process */
NULL, /* init thread */
NULL, /* exit thread */
NULL, /* exit process */
NULL, /* exit master */
NGX_MODULE_V1_PADDING
};
~~~
? ? ? 該模塊的上下文結構?ngx_http_write_filter_module_ctx 定義如下:
~~~
static ngx_http_module_t ngx_http_write_filter_module_ctx = {
NULL, /* preconfiguration */
ngx_http_write_filter_init, /* postconfiguration */
NULL, /* create main configuration */
NULL, /* init main configuration */
NULL, /* create server configuration */
NULL, /* merge server configuration */
NULL, /* create location configuration */
NULL, /* merge location configuration */
};
~~~
? ? ? 上下文結構中只調用 ngx_http_write_filter_init?方法,該方法定義如下:
~~~
/* 初始化模塊 */
static ngx_int_t
ngx_http_write_filter_init(ngx_conf_t *cf)
{
/* 調用模塊的回調方法 */
ngx_http_top_body_filter = ngx_http_write_filter;
return NGX_OK;
}
~~~
? ? ? 該模塊最終調用?ngx_http_write_filter 方法發送HTTP 響應包體,該方法的實現如下分析;
**ngx_http_writer_filter 函數執行流程:**
- 檢查當前連接的錯誤標志位 error,若該標志位為 1,表示當前請求出粗,則返回 NGX_ERROR 結束該函數,否則繼續;
- 遍歷當前請求 ngx_http_request_t 結構體中的鏈表緩沖區成員out,計算剩余響應報文的長度size。因為當響應報文一次性不能發送完畢時,會把剩余的響應報文保存在out 中,相對于本次待發送的響應報文 in (即是該函數所傳入的參數in )來說,out 鏈表緩沖區保存的是前一次剩余的響應報文;
- 將本次待發送的響應報文的緩沖區 in 添加到 out 鏈表緩沖區的尾部,并計算待發送響應報文的總長度 size;
- 若緩沖區 ngx_buf_t 塊的 last_buf (即 last)、flush 標志位為0,則表示待發送的out 鏈表緩沖區沒有一個是需要立刻發送響應報文,并且本次待發送的in 不為空,且待發送的響應報文數據總長度 size 小于postpone_output 參數(該參數由nginx.conf配置文件中設置),則不需要發送響應報文,即返回NGX_OK 結束該函數;
- 若需要發送響應報文,則檢查當前連接上寫事件的 delayed 標志位,若為1,表示發送響應超速,則需要在epoll 事件機制中減速,所有相當于延遲發送響應報文,則返回NGX_AGIAN;
- 若不需要延遲發送響應報文,檢查當前請求的限速標志位 limit_rate,若該標志位設置為大于0,表示當前發送響應報文的速度不能超過limit_rate 值;
- 根據限速值 r->limit_rate、當前客戶開始接收響應的時間r->start_sec、在當前連接上已發送響應的長度c->sent、和limit_after 值計算本次可以發送的字節數limit,若limit 值不大于0,表示當前連接上發送響應的速度超過limit_rate 限速值,即本次不可以發送響應,因此將寫事件的delayed 標志位設置為1,把寫事件添加到定時器機制,并設置當前連接ngx_connection_t 結構體中的成員buffered 為NGX_HTTP_WRITE_BUFFERED(即可寫狀態),同時返回NGX_AGAIN,表示鏈表緩沖區out 還保存著剩余待發送的響應報文;
- 若 limit 值大于 0,則根據 limit 值、配置項參數 sendfile_max_chunk 和待發送字節數 size 來計算本次發送響應的長度(即三者中的最小值);
- 根據前一步驟計算的可發送響應的長度,再次檢查 limit_rate 標志位,若limit_rate 還是為1,表示繼續需要限速檢查。再按照前面的計算方法判斷是否超過限速值limit_rate,若超過該限速值,則需再次把寫事件添加到定時器機制中,標志位delayed 設置為1;
- 若不會超過限速值,則發送響應,并重新調整鏈表緩沖區 out 的情況,把已發送響應數據的緩沖區進行回收內存;
- 繼續檢查鏈表緩沖區 out 是否還存在數據,若存在數據,則表示未發送完畢,返回NGX_AGAIN,表示等待下次HTTP?框架被調用發送out 緩沖區剩余的響應數據;若不存在數據,則表示成功發送完整的響應數據,并返回NGX_OK;
函數 ngx_http_write_filter 在文件 [src/http/ngx_http_write_filter_module.c](http://lxr.nginx.org/source/src/http/ngx_http_write_filter_module.c) 中定義如下:
~~~
/* 發送響應報文數據 */
/* 參數r是對應的請求,in是保存本次待發送數據的鏈表緩沖區 */
ngx_int_t
ngx_http_write_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
off_t size, sent, nsent, limit;
ngx_uint_t last, flush;
ngx_msec_t delay;
ngx_chain_t *cl, *ln, **ll, *chain;
ngx_connection_t *c;
ngx_http_core_loc_conf_t *clcf;
/* 獲取當前請求所對應的連接 */
c = r->connection;
/*
* 檢查當前連接的錯誤標志位error,若該標志位為1,
* 表示當前請求出錯,返回NGX_ERROR;
*/
if (c->error) {
return NGX_ERROR;
}
size = 0;
flush = 0;
last = 0;
ll = &r->out;
/* find the size, the flush point and the last link of the saved chain */
/*
* 遍歷當前請求out鏈表緩沖區,計算剩余響應報文的長度;
* 因為當響應報文一次性不能發送完成時,會把剩余的響應報文保存在out中,
* 相對于本次發送的響應報文數據in來說(即該方法所傳入的參數in),
* out鏈表緩沖區保存的是前一次剩余的響應報文;
*/
for (cl = r->out; cl; cl = cl->next) {
ll = &cl->next;
ngx_log_debug7(NGX_LOG_DEBUG_EVENT, c->log, 0,
"write old buf t:%d f:%d %p, pos %p, size: %z "
"file: %O, size: %z",
cl->buf->temporary, cl->buf->in_file,
cl->buf->start, cl->buf->pos,
cl->buf->last - cl->buf->pos,
cl->buf->file_pos,
cl->buf->file_last - cl->buf->file_pos);
#if 1
...
#endif
size += ngx_buf_size(cl->buf);
if (cl->buf->flush || cl->buf->recycled) {
flush = 1;
}
if (cl->buf->last_buf) {
last = 1;
}
}
/* add the new chain to the existent one */
/*
* 將本次待發送的響應報文的緩沖區in添加到out鏈表緩沖區的尾部,
* 并計算待發送響應報文總的長度size;
*/
for (ln = in; ln; ln = ln->next) {
cl = ngx_alloc_chain_link(r->pool);
if (cl == NULL) {
return NGX_ERROR;
}
cl->buf = ln->buf;
*ll = cl;/* 由上面可知 ll=&r->out */
ll = &cl->next;
ngx_log_debug7(NGX_LOG_DEBUG_EVENT, c->log, 0,
"write new buf t:%d f:%d %p, pos %p, size: %z "
"file: %O, size: %z",
cl->buf->temporary, cl->buf->in_file,
cl->buf->start, cl->buf->pos,
cl->buf->last - cl->buf->pos,
cl->buf->file_pos,
cl->buf->file_last - cl->buf->file_pos);
#if 1
...
#endif
size += ngx_buf_size(cl->buf);
if (cl->buf->flush || cl->buf->recycled) {
flush = 1;
}
if (cl->buf->last_buf) {
last = 1;
}
}
*ll = NULL;
ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http write filter: l:%d f:%d s:%O", last, flush, size);
/* 獲取ngx_http_core_module模塊的loc級別配置項結構體 */
clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
/*
* avoid the output if there are no last buf, no flush point,
* there are the incoming bufs and the size of all bufs
* is smaller than "postpone_output" directive
*/
/*
* 若out鏈表最后一塊緩沖區last為空,且沒有強制性刷新flush鏈表緩沖區out,
* 且當前有待發響應報文in,但是待發送響應報文總的長度size小于預設可發送條件值postpone_output,
* 則本次不能發送響應報文,繼續保存在out鏈表緩沖區中,以待下次才發送;
* 其中postpone_output預設值我們可以在配置文件nginx.conf中設置;
*/
if (!last && !flush && in && size < (off_t) clcf->postpone_output) {
return NGX_OK;
}
/*
* 檢查當前連接上寫事件的delayed標志位,
* 若該標志位為1,表示需要延遲發送響應報文,
* 因此,返回NGX_AGAIN,表示延遲發送;
*/
if (c->write->delayed) {
c->buffered |= NGX_HTTP_WRITE_BUFFERED;
return NGX_AGAIN;
}
if (size == 0
&& !(c->buffered & NGX_LOWLEVEL_BUFFERED)
&& !(last && c->need_last_buf))
{
if (last || flush) {
for (cl = r->out; cl; /* void */) {
ln = cl;
cl = cl->next;
ngx_free_chain(r->pool, ln);
}
r->out = NULL;
c->buffered &= ~NGX_HTTP_WRITE_BUFFERED;
return NGX_OK;
}
ngx_log_error(NGX_LOG_ALERT, c->log, 0,
"the http output chain is empty");
ngx_debug_point();
return NGX_ERROR;
}
/*
* 檢查當前請求的限速標志位limit_rate,
* 若該標志位為大于0,表示發送響應報文的速度不能超過limit_rate指定的速度;
*/
if (r->limit_rate) {
if (r->limit_rate_after == 0) {
r->limit_rate_after = clcf->limit_rate_after;
}
/* 計算發送速度是否超過限速值 */
limit = (off_t) r->limit_rate * (ngx_time() - r->start_sec + 1)
- (c->sent - r->limit_rate_after);
/*
* 若當前發送響應報文的速度超過限速值,則寫事件標志位delayed設為1,
* 并把該寫事件添加到定時器機制中,并且將buffered設置為可寫狀態,
* 返回NGX_AGAIN,表示鏈表緩沖區out還保存剩余待發送的響應報文;
*/
if (limit <= 0) {
c->write->delayed = 1;
ngx_add_timer(c->write,
(ngx_msec_t) (- limit * 1000 / r->limit_rate + 1));
c->buffered |= NGX_HTTP_WRITE_BUFFERED;
return NGX_AGAIN;
}
if (clcf->sendfile_max_chunk
&& (off_t) clcf->sendfile_max_chunk < limit)
{
limit = clcf->sendfile_max_chunk;
}
} else {
limit = clcf->sendfile_max_chunk;
}
/* 若不需要減速,或沒有設置速度限制,則向客戶端發送響應字符流 */
sent = c->sent;
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http write filter limit %O", limit);
chain = c->send_chain(c, r->out, limit);
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http write filter %p", chain);
if (chain == NGX_CHAIN_ERROR) {
c->error = 1;
return NGX_ERROR;
}
/* 再次檢查limit_rate標志位 */
if (r->limit_rate) {
nsent = c->sent;
if (r->limit_rate_after) {
sent -= r->limit_rate_after;
if (sent < 0) {
sent = 0;
}
nsent -= r->limit_rate_after;
if (nsent < 0) {
nsent = 0;
}
}
/* 再次計算當前發送響應報文速度是否超過限制值 */
delay = (ngx_msec_t) ((nsent - sent) * 1000 / r->limit_rate);
/* 若超過,需要限速,并把寫事件添加到定時器機制中 */
if (delay > 0) {
limit = 0;
c->write->delayed = 1;
ngx_add_timer(c->write, delay);
}
}
if (limit
&& c->write->ready
&& c->sent - sent >= limit - (off_t) (2 * ngx_pagesize))
{
c->write->delayed = 1;
ngx_add_timer(c->write, 1);
}
/* 重新調整鏈表緩沖區out的情況,把已發送數據的緩沖區內存回收 */
for (cl = r->out; cl && cl != chain; /* void */) {
ln = cl;
cl = cl->next;
ngx_free_chain(r->pool, ln);
}
/* 檢查out鏈表緩沖區是否還有數據 */
r->out = chain;
/* 若還有數據,返回NGX_AGAIN,表示還存在待發送的響應報文數據 */
if (chain) {
c->buffered |= NGX_HTTP_WRITE_BUFFERED;
return NGX_AGAIN;
}
c->buffered &= ~NGX_HTTP_WRITE_BUFFERED;
if ((c->buffered & NGX_LOWLEVEL_BUFFERED) && r->postponed == NULL) {
return NGX_AGAIN;
}
/* 若已發送全部數據則返回NGX_OK */
return NGX_OK;
}
~~~
**ngx_http_write 函數執行流程如下:**
- 檢查寫事件的 timedout 標志位,若該標志位為 1(表示超時),進而判斷屬于哪種情況引起的超時(第一種:網絡異常或客戶端長時間不接收響應;第二種:由于響應發送速度超速,導致寫事件被添加到定時器機制(注意一點:delayed 標志位此時是為1),有超速引起的超時,不算真正的響應發送超時);
- 檢查 delayed 標志位,若 delayed 為 0,表示由第一種情況引起的超時,即是真正的響應超時,此時設置timedout 標志位為1,并調用函數ngx_http_finalize_request 結束請求;
- 若 delayed 為 1,表示由第二種情況引起的超時,不算真正的響應超時,此時,把標志位 timedout、delayed 都設置為 0,繼續檢查寫事件的 ready 標志位,若 ready 為 0,表示當前寫事件未準備就緒(即不可寫),因此,將寫事件添加到定時器機制,注冊到epoll 事件機制中,等待可寫事件發送,返回return 結束該方法;
- 若寫事件 timedout 為 0,且 delayed 為 0,且 ready 為 1,則調用函數 ngx_http_output_filter 發送響應;該函數的第二個參數為NULL,表示需要調用各個包體過濾模塊處理鏈表緩沖區out 中剩余的響應,最后由ngx_http_write_filter 方法把響應發送出去;
函數 ngx_http_writer 在文件 [src/http/ngx_http_request.c](http://lxr.nginx.org/source/src/http/ngx_http_request.c) 中定義如下:
~~~
static void
ngx_http_writer(ngx_http_request_t *r)
{
int rc;
ngx_event_t *wev;
ngx_connection_t *c;
ngx_http_core_loc_conf_t *clcf;
/* 獲取當前請求的連接 */
c = r->connection;
/* 獲取連接上的寫事件 */
wev = c->write;
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, wev->log, 0,
"http writer handler: \"%V?%V\"", &r->uri, &r->args);
/* 獲取ngx_http_core_module模塊的loc級別配置項結構 */
clcf = ngx_http_get_module_loc_conf(r->main, ngx_http_core_module);
/*
* 寫事件超時有兩種可能:
* 1、由于網絡異常或客戶端長時間不接收響應,導致真實的發送響應超時;
* 2、由于響應發送速度超過了請求的限速值limit_rate,導致寫事件被添加到定時器機制中,
* 這是由超速引起的,并不是真正的響應發送超時;注意:寫事件被添加到定時器機制時,delayed標志位設置為1;
*/
/* 檢查寫事件是否超時,若超時(即timedout為1),進而判斷屬于哪種情況引起的超時 */
if (wev->timedout) {
/*
* 若是響應真的超時,即網絡異常或客戶端長時間未接收響應引起的超時;
* 則將timedout標志位設置為1,并調用ngx_http_finalize_request結束請求;
* 并return返回結束當前方法;
*/
if (!wev->delayed) {
ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT,
"client timed out");
c->timedout = 1;
ngx_http_finalize_request(r, NGX_HTTP_REQUEST_TIME_OUT);
return;
}
/*
* 若是由超速發送響應引起的超時,則將timedout、delayed標志位都設為0;
* 再繼續檢查寫事件的ready標志位;
*/
wev->timedout = 0;
wev->delayed = 0;
/*
* 檢查寫事件的ready標志位,若寫事件未準備就緒(ready=0),即表示當前寫事件不可寫,
* 則將寫事件添加到定時器機制中,同時將寫事件注冊到epoll事件機制中,等待可寫事件發生;
* 并return結束當前方法;
*/
if (!wev->ready) {
ngx_add_timer(wev, clcf->send_timeout);
if (ngx_handle_write_event(wev, clcf->send_lowat) != NGX_OK) {
ngx_http_close_request(r, 0);
}
return;
}
}
/* 當timedout為0,但是delayed為1或是aio,則將寫事件注冊到epoll事件機制中,并return返回 */
if (wev->delayed || r->aio) {
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, wev->log, 0,
"http writer delayed");
if (ngx_handle_write_event(wev, clcf->send_lowat) != NGX_OK) {
ngx_http_close_request(r, 0);
}
return;
}
/* 若寫事件timedout為0,且delayed為0,且ready為1,則調用ngx_http_output_filter 發送響應報文 */
rc = ngx_http_output_filter(r, NULL);
ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http writer output filter: %d, \"%V?%V\"",
rc, &r->uri, &r->args);
/* 若發送響應錯誤,則調用ngx_http_finalize_request結束請求,并return返回 */
if (rc == NGX_ERROR) {
ngx_http_finalize_request(r, rc);
return;
}
/*
* 若成功發送響應,則檢查當前請求的out鏈表緩沖區是否存在剩余待發送的響應報文,
* 若存在剩余待發送響應,又因為此時寫事件不可寫,則將其添加到定時器機制,注冊到epoll事件機制中,
* 等待可寫事件的發生生;*/
if (r->buffered || r->postponed || (r == r->main && c->buffered)) {
if (!wev->delayed) {
ngx_add_timer(wev, clcf->send_timeout);
}
if (ngx_handle_write_event(wev, clcf->send_lowat) != NGX_OK) {
ngx_http_close_request(r, 0);
}
return;
}
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, wev->log, 0,
"http writer done: \"%V?%V\"", &r->uri, &r->args);
/*
* 若當前out鏈表緩沖區不存在未發送的響應數據,則表示已成功發送完整的響應數據,
* 此時,重新設置寫事件的回調方法為ngx_http_request_empty_handler即不進行任何操作;
*/
r->write_event_handler = ngx_http_request_empty_handler;
/* 最終調用ngx_http_finalize_request結束請求 */
ngx_http_finalize_request(r, rc);
}
~~~
? ? ? 總結:真正發送響應的是 ngx_http_write_filter 函數,但是該函數不能保證一次性把響應發送完畢,若發送不完畢,把剩余的響應保存在out 鏈表緩沖區中,繼而調用ngx_http_writer 把剩余的響應發送出去,函數ngx_http_writer 最終調用的是ngx_http_output_filter 函數發送響應,但是要知道的是ngx_http_output_filter 函數是需要調用個包體過濾模塊來處理剩余響應的out 鏈表緩沖區,并由最后一個過濾模塊 ngx_http_write_filter_module 調用ngx_http_write_filter?方法將響應發送出去;因此,我們可知道,真正發送響應的函數是ngx_http_write_filter;
### 關閉連接請求
? ? ? 當一個動作結束時,會根據引用計數判斷是否結束其處理的請求,以下是有關關閉請求的函數;以下函數均在在文件 [src/http/ngx_http_request.c](http://lxr.nginx.org/source/src/http/ngx_http_request.c) 中定義如下;
**ngx_http_finalize_request 函數執行流程:**
- 若所傳入的參數 rc ?= ?NGX_DONE,則直接調用ngx_http_finalize_connection 函數結束連接,并return 退出當前函數;
- 若參數 rc = NGX_DECLINED,表示需要按照11 個HTTP 階段繼續處理,此時,設置r->content_handler = NULL(為了讓ngx_http_core_content_phase 方法可以繼續調用NGX_HTTP_CONTENT_PHASE 階段的其他處理方法),并設置寫事件的回調方法為ngx_http_core_run_phases,最后調用ngx_http_core_run_phases 方法處理請求,return 從當前函數返回;
- 若 ?rc != NGX_DONE 且 rc != NGX_DECLINED,檢查當前請求是否為子請求:
- 若當前請求是子請求,則調用 post_subrequest 的回調方法handler;
- 若不是子請求則繼續執行以下程序;
- 若 rc 為 NGX_ERROR、NGX_HTTP_REQUEST_TIME_OUT、NGX_HTTP_CLIENT_CLOSED_REQUEST,或當前連接的錯誤碼標志位c->error為1,則調用ngx_http_terminate_request 強制關閉請求,return 從當前函數返回;
- 若 rc 為 NGX_HTTP_CREATED、NGX_HTTP_NO_CONTENT,或rc 不小于NGX_HTTP_SPECIAL_RESPONSE,接著檢查當前請求是否為原始請求,若是原始請求,則檢查讀、寫事件的timer_set 標志位,若 timer_set 為1,將讀、寫事件從定時器機制中移除,重新設置當前連接的讀、寫事件的回調方法都為ngx_http_request_handler,并調用ngx_http_finalize_request,此時應該注意的是ngx_http_finalize_request 函數的第二個參數是ngx_http_special_response_handler(r, rc)函數的返回值,ngx_http_special_response_handler(r, rc) 函數根據參數rc 構造完整的HTTP?響應,根據ngx_http_special_response_handler 函數的返回值調用ngx_http_finalize_request 方法結束請求。return 從當前函數返回;
- 若參數 rc 不是以上步驟所描述的值,檢查當前請求是否為原始請求:
- 若當前請求不是原始請求,
- 若當前請求是原始請求,檢查當前請求的 buffered、postponed、blocked 標志位 或當前連接的buffered 標志位:
- 若這些標志位有一個是 1,則調用 ngx_http_set_write_handler 函數(該函數的功能就是設置當前請求寫事件的回調方法為 ngx_http_writer 發送 out 鏈表緩沖區的剩余響應,若寫事件未準備就緒,則將寫事件添加到定時器機制,注冊到epoll 事件機制中,最終返回NGX_OK),并return 返回當前函數;
- 若這些標志位都不為 1,則檢查讀、寫事件的 timer_set 標志位,若 timer_set 標志位為1,則將讀、寫事件從定時器機制中移除,最后調用ngx_http_finalize_connection 釋放請求,并關閉連接;
~~~
/* 結束請求 */
void
ngx_http_finalize_request(ngx_http_request_t *r, ngx_int_t rc)
{
ngx_connection_t *c;
ngx_http_request_t *pr;
ngx_http_core_loc_conf_t *clcf;
c = r->connection;
ngx_log_debug5(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http finalize request: %d, \"%V?%V\" a:%d, c:%d",
rc, &r->uri, &r->args, r == c->data, r->main->count);
/* 若傳入的參數rc=NGX_DONE,則直接調用ngx_http_finalize_connection方法 */
if (rc == NGX_DONE) {
ngx_http_finalize_connection(r);
return;
}
if (rc == NGX_OK && r->filter_finalize) {
c->error = 1;
}
/*
* 若傳入的參數rc=NGX_DECLINED,則表示需按照11個HTTP階段繼續處理;
* 此時,寫事件調用ngx_http_core_run_phases;
*/
if (rc == NGX_DECLINED) {
r->content_handler = NULL;
r->write_event_handler = ngx_http_core_run_phases;
ngx_http_core_run_phases(r);
return;
}
/* 若傳入的參數rc != NGX_DONE 且 rc != NGX_DECLINED,則執行以下程序 */
/*
* 若當前處理的請求是子請求,且post_subrequest標志位為1,
* 則調用post_subrequest的handler回調方法;
*/
if (r != r->main && r->post_subrequest) {
rc = r->post_subrequest->handler(r, r->post_subrequest->data, rc);
}
/* 若處理的當前請求不是子請求,則執行以下程序 */
/* 若rc是以下這些值,或error標志位為1,則調用ngx_http_terminate_request方法強制關閉請求 */
if (rc == NGX_ERROR
|| rc == NGX_HTTP_REQUEST_TIME_OUT
|| rc == NGX_HTTP_CLIENT_CLOSED_REQUEST
|| c->error)
{
if (ngx_http_post_action(r) == NGX_OK) {
return;
}
if (r->main->blocked) {
r->write_event_handler = ngx_http_request_finalizer;
}
ngx_http_terminate_request(r, rc);
return;
}
/*
* 若rc為以下值,表示請求的動作是上傳文件,
* 或HTTP模塊需要HTTP框架構造并發送響應碼不小于300的特殊響應;
* 則首先檢查當前請求是否為原始請求,若不是則調用ngx_http_terminate_request強制關閉請求,
* 若是原始請求,則將讀、寫事件從定時器機制中移除;
* 并重新設置讀、寫事件的回調方法為ngx_http_request_handler,
* 最后調用ngx_http_finalize_request關閉請求(指定特定的rc參數);
*/
if (rc >= NGX_HTTP_SPECIAL_RESPONSE
|| rc == NGX_HTTP_CREATED
|| rc == NGX_HTTP_NO_CONTENT)
{
if (rc == NGX_HTTP_CLOSE) {
ngx_http_terminate_request(r, rc);
return;
}
if (r == r->main) {
if (c->read->timer_set) {
ngx_del_timer(c->read);
}
if (c->write->timer_set) {
ngx_del_timer(c->write);
}
}
c->read->handler = ngx_http_request_handler;
c->write->handler = ngx_http_request_handler;
ngx_http_finalize_request(r, ngx_http_special_response_handler(r, rc));
return;
}
/* 若rc不是以上的值,則執行以下程序 */
/* 再次檢查當前請求是否為原始請求 */
if (r != r->main) {
/*
* 若當前請求不是原始請求,即當前請求是子請求;
* 若子請求的buffered 或 postponed 標志位為1,
* 則調用 ngx_http_set_write_handler;
*/
if (r->buffered || r->postponed) {
if (ngx_http_set_write_handler(r) != NGX_OK) {
ngx_http_terminate_request(r, 0);
}
return;
}
/*
* 若子請求的buffered且postponed標志位都為0,則找到當前子請求的父親請求;
*/
pr = r->parent;
/*
* 將父親請求放置在ngx_http_posted_request_t結構體中,
* 并將該結構體添加到原始請求的posted_requests鏈表中;
*/
if (r->buffered || r->postponed) {
if (ngx_http_set_write_handler(r) != NGX_OK) {
ngx_http_terminate_request(r, 0);
}
if (r == c->data) {
r->main->count--;
r->main->subrequests++;
if (!r->logged) {
clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
if (clcf->log_subrequest) {
ngx_http_log_request(r);
}
r->logged = 1;
} else {
ngx_log_error(NGX_LOG_ALERT, c->log, 0,
"subrequest: \"%V?%V\" logged again",
&r->uri, &r->args);
}
r->done = 1;
if (pr->postponed && pr->postponed->request == r) {
pr->postponed = pr->postponed->next;
}
c->data = pr;
} else {
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http finalize non-active request: \"%V?%V\"",
&r->uri, &r->args);
r->write_event_handler = ngx_http_request_finalizer;
if (r->waited) {
r->done = 1;
}
}
if (ngx_http_post_request(pr, NULL) != NGX_OK) {
r->main->count++;
ngx_http_terminate_request(r, 0);
return;
}
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http wake parent request: \"%V?%V\"",
&pr->uri, &pr->args);
return;
}
/* 若當前請求是原始請求 */
/*
* 若r->buffered或c->buffered 或 r->postponed 或 r->blocked 標志位為1;
* 則調用ngx_http_set_write_handler方法;
*/
if (r->buffered || c->buffered || r->postponed || r->blocked) {
if (ngx_http_set_write_handler(r) != NGX_OK) {
ngx_http_terminate_request(r, 0);
}
return;
}
if (r != c->data) {
ngx_log_error(NGX_LOG_ALERT, c->log, 0,
"http finalize non-active request: \"%V?%V\"",
&r->uri, &r->args);
return;
}
r->done = 1;
r->write_event_handler = ngx_http_request_empty_handler;
if (!r->post_action) {
r->request_complete = 1;
}
if (ngx_http_post_action(r) == NGX_OK) {
return;
}
/*
* 將讀、寫事件從定時器機制中移除;
*/
if (c->read->timer_set) {
ngx_del_timer(c->read);
}
if (c->write->timer_set) {
c->write->delayed = 0;
ngx_del_timer(c->write);
}
if (c->read->eof) {
ngx_http_close_request(r, 0);
return;
}
/* 關閉連接,并結束請求 */
ngx_http_finalize_connection(r);
}
~~~
**ngx_http_set_write_handler 函數執行流程:**
- 設置當前請求的讀事件回調方法為:ngx_httpp_discarded_request_body_handler(丟棄包體) 或ngx_http_test_reading;
- 設置當前請求的寫事件回調方法為 ngx_http_writer(發送out 鏈表緩沖區剩余的響應);
- 若當前寫事件準備就緒(即 ready 和 delayed 標志位為 1)開始限速的發送 out 鏈表緩沖區中的剩余響應;
- 若當前寫事件未準備就緒,則將寫事件添加到定時器機制,注冊到 epoll 事件機制中;
~~~
static ngx_int_t
ngx_http_set_write_handler(ngx_http_request_t *r)
{
ngx_event_t *wev;
ngx_http_core_loc_conf_t *clcf;
r->http_state = NGX_HTTP_WRITING_REQUEST_STATE;
/* 設置當前請求讀事件的回調方法:丟棄包體或不進行任何操作 */
r->read_event_handler = r->discard_body ?
ngx_http_discarded_request_body_handler:
ngx_http_test_reading;
/* 設置寫事件的回調方法為ngx_http_writer,即發送out鏈表緩沖區剩余的響應 */
r->write_event_handler = ngx_http_writer;
#if (NGX_HTTP_SPDY)
if (r->spdy_stream) {
return NGX_OK;
}
#endif
wev = r->connection->write;
/* 若寫事件的ready標志位和delayed標志為都為1,則返回NGX_OK */
if (wev->ready && wev->delayed) {
return NGX_OK;
}
/*
* 若寫事件的ready標志位為0,或delayed標志位為0,則將寫事件添加到定時器機制中;
* 同時將寫事件注冊到epoll事件機制中;
* 最后返回NGX_OK;
*/
clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
if (!wev->delayed) {
ngx_add_timer(wev, clcf->send_timeout);
}
if (ngx_handle_write_event(wev, clcf->send_lowat) != NGX_OK) {
ngx_http_close_request(r, 0);
return NGX_ERROR;
}
return NGX_OK;
}
~~~
**ngx_http_finalize_connection 函數的執行流程:**
- 檢查原始請求的引用計數,若原始請求的引用計數不為 1,則表示其他動作在操作該請求,檢查當前請求的discard_body 標志位:
- 若 discard_body 標志位為 1,表示當前請求正在丟棄包體,把讀事件的回調方法設為 ngx_http_discarded_request_body_handler 方法,并將讀事件添加到定時器機制中(超時時間為lingering_timeout),最后調用ngx_http_close_request 關閉請求,并 return 從當前函數返回;
- 若 discard_body 標志位為 0,直接調用 ngx_http_close_request 關閉請求,并return 從當前函數返回;
- 若原始請求的引用計數為 1,檢查當前請求的 keepalive 標志位:
- 若 keepalive 標志位為 1,則調用 ngx_http_set_keepalive方 法將當前連接設置為keepalive?狀態,并return 從當前函數返回;
- 若 keepalive?標志位為 0,檢查 lingering_close 標志位:
- 若 lingering_close?標志位為 1,則調用 ngx_http_set_lingering_close 延遲關閉請求,return 從當前函數返回;
- 若 lingering_close?標志位為 0,則調用 ngx_http_close_request 方法關閉請求;
~~~
/* 結束當前連接 */
static void
ngx_http_finalize_connection(ngx_http_request_t *r)
{
ngx_http_core_loc_conf_t *clcf;
#if (NGX_HTTP_SPDY)
if (r->spdy_stream) {
ngx_http_close_request(r, 0);
return;
}
#endif
clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
/*
* 檢查原始請求的引用計數,若原始請求的引用計數不為1,表示有其他動作在操作該請求;
*/
if (r->main->count != 1) {
/*
* 檢查當前請求的discard_body標志位,若該標志位為1,表示當前請求正在丟棄包體;
*/
if (r->discard_body) {
/* 設置當前請求讀事件的回調方法,并將讀事件添加到定時器機制中 */
r->read_event_handler = ngx_http_discarded_request_body_handler;
ngx_add_timer(r->connection->read, clcf->lingering_timeout);
if (r->lingering_time == 0) {
r->lingering_time = ngx_time()
+ (time_t) (clcf->lingering_time / 1000);
}
}
/* 關閉當前請求 */
ngx_http_close_request(r, 0);
return;
}
/* 若原始請求的引用計數為1,則執行以下程序 */
/*
* 若keepalive標志為1,表示只需要釋放請求,但是當前連接需要復用;
* 則調用ngx_http_set_keepalive 設置當前連接為keepalive狀態;
*/
if (!ngx_terminate
&& !ngx_exiting
&& r->keepalive
&& clcf->keepalive_timeout > 0)
{
ngx_http_set_keepalive(r);
return;
}
/*
* 若keepalive標志為0,但是lingering_close標志為1,表示需要延遲關閉連接;
* 則調用ngx_http_set_lingering_close方法延遲關閉請求;
*/
if (clcf->lingering_close == NGX_HTTP_LINGERING_ALWAYS
|| (clcf->lingering_close == NGX_HTTP_LINGERING_ON
&& (r->lingering_close
|| r->header_in->pos < r->header_in->last
|| r->connection->read->ready)))
{
ngx_http_set_lingering_close(r);
return;
}
/* 若keepalive標志為0,且lingering_close標志也為0,則立刻關閉請求 */
ngx_http_close_request(r, 0);
}
~~~
**ngx_http_close_request 函數的執行流程:**
- 將原始請求的引用計數 count 減 1,若此時引用計數 count?不為 0,或當前請求的 blocked 標志位為 1,即不需要正在關閉該請求,因為該請求有其他動作在操作,return 從當前函數返回;
- 若引用計數 count?為 0(表示沒有其他動作操作當前請求),且 blocked 標志位為0(表示沒有HTTP 模塊會處理當前請求),因此,調用ngx_http_free_request 釋放當前請求的結構體ngx_http_request_t,并調用函數ngx_http_close_connection 關閉當前連接;
~~~
/* 關閉當前請求 */
static void
ngx_http_close_request(ngx_http_request_t *r, ngx_int_t rc)
{
ngx_connection_t *c;
r = r->main;
c = r->connection;
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http request count:%d blk:%d", r->count, r->blocked);
if (r->count == 0) {
ngx_log_error(NGX_LOG_ALERT, c->log, 0, "http request count is zero");
}
/* 將原始請求的引用計數減1 */
r->count--;
/*
* 若此時引用計數不為0,或blocked標志位不為0,則該函數到此結束;
* 到此,即ngx_http_close_request方法的功能只是將原始請求引用計數減1;
*/
if (r->count || r->blocked) {
return;
}
#if (NGX_HTTP_SPDY)
if (r->spdy_stream) {
ngx_http_spdy_close_stream(r->spdy_stream, rc);
return;
}
#endif
/*
* 若引用計數此時為0(表示請求沒有其他動作要使用),
* 且blocked也為0(表示沒有HTTP模塊還需要處理請求),
* 則調用ngx_http_free_request釋放請求所對應的結構體ngx_http_request_t,
* 調用ngx_http_close_connection關閉當前連接;
*/
ngx_http_free_request(r, rc);
ngx_http_close_connection(c);
}
~~~
**ngx_http_free_request 函數的執行流程:**
- 調用當前請求的 cleanup 鏈表的回調方法 handler 開始清理工作釋放資源;
- 在 HTTP 的 NGX_HTTP_LOG_PHASE 階段調用所有回調方法記錄日志;
- 調用 ngx_destroy_pool 方法銷毀保存請求結構ngx_http_request_t 的內存池pool;
~~~
/* 釋放當前請求 ngx_http_request_t 的數據結構 */
void
ngx_http_free_request(ngx_http_request_t *r, ngx_int_t rc)
{
ngx_log_t *log;
ngx_pool_t *pool;
struct linger linger;
ngx_http_cleanup_t *cln;
ngx_http_log_ctx_t *ctx;
ngx_http_core_loc_conf_t *clcf;
log = r->connection->log;
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, log, 0, "http close request");
if (r->pool == NULL) {
ngx_log_error(NGX_LOG_ALERT, log, 0, "http request already closed");
return;
}
/* 獲取當前請求的清理cleanup方法 */
cln = r->cleanup;
r->cleanup = NULL;
/* 調用清理方法cleanup的回調方法handler開始清理工作 */
while (cln) {
if (cln->handler) {
cln->handler(cln->data);
}
cln = cln->next;
}
#if (NGX_STAT_STUB)
if (r->stat_reading) {
(void) ngx_atomic_fetch_add(ngx_stat_reading, -1);
}
if (r->stat_writing) {
(void) ngx_atomic_fetch_add(ngx_stat_writing, -1);
}
#endif
/* 記錄日志 */
if (rc > 0 && (r->headers_out.status == 0 || r->connection->sent == 0)) {
r->headers_out.status = rc;
}
log->action = "logging request";
ngx_http_log_request(r);
log->action = "closing request";
if (r->connection->timedout) {
clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
if (clcf->reset_timedout_connection) {
linger.l_onoff = 1;
linger.l_linger = 0;
if (setsockopt(r->connection->fd, SOL_SOCKET, SO_LINGER,
(const void *) &linger, sizeof(struct linger)) == -1)
{
ngx_log_error(NGX_LOG_ALERT, log, ngx_socket_errno,
"setsockopt(SO_LINGER) failed");
}
}
}
/* the various request strings were allocated from r->pool */
ctx = log->data;
ctx->request = NULL;
r->request_line.len = 0;
r->connection->destroyed = 1;
/*
* Setting r->pool to NULL will increase probability to catch double close
* of request since the request object is allocated from its own pool.
*/
pool = r->pool;
r->pool = NULL;
/* 銷毀請求ngx_http_request_t 所對應的內存池 */
ngx_destroy_pool(pool);
}
~~~
**ngx_http_close_connection 函數的執行流程:**
- 將當前連接的 destroyed 標志位設置為 1,表示即將銷毀該連接;
- 調用 ngx_close_connection 方法開始銷毀連接;
- 最后銷毀該鏈接所使用的內存池 pool;
~~~
/* 關閉TCP連接 */
void
ngx_http_close_connection(ngx_connection_t *c)
{
ngx_pool_t *pool;
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"close http connection: %d", c->fd);
#if (NGX_HTTP_SSL)
if (c->ssl) {
if (ngx_ssl_shutdown(c) == NGX_AGAIN) {
c->ssl->handler = ngx_http_close_connection;
return;
}
}
#endif
#if (NGX_STAT_STUB)
(void) ngx_atomic_fetch_add(ngx_stat_active, -1);
#endif
/* 設置當前連接的destroyed標志位為1,表示即將銷毀該連接 */
c->destroyed = 1;
pool = c->pool;
/* 關閉套接字連接 */
ngx_close_connection(c);
/* 銷毀連接所使用的內存池 */
ngx_destroy_pool(pool);
}
~~~
**ngx_close_connection 函數的執行流程:**
- 檢查讀、寫事件的 timer_set 標志位,若該標志位都為1,則調用ngx_del_timer 方法將讀、寫事件從定時器機制中移除;
- 若定義了 ngx_del_conn 宏調用 ngx_del_conn 方法將當前連接上的讀、寫事件從 epoll 事件機制中移除,若沒定義ngx_del_conn 宏,則調用ngx_del_event 方法將讀、寫事件從epoll 事件機制中移除;
- 調用 ngx_free_connection 方法釋放當前連接結構;
- 調用 ngx_close_socket 方法關閉套接字連接;
~~~
/* 關閉套接字連接 */
void
ngx_close_connection(ngx_connection_t *c)
{
ngx_err_t err;
ngx_uint_t log_error, level;
ngx_socket_t fd;
if (c->fd == (ngx_socket_t) -1) {
ngx_log_error(NGX_LOG_ALERT, c->log, 0, "connection already closed");
return;
}
/* 將當前連接的讀、寫事件從定時器機制中移除 */
if (c->read->timer_set) {
ngx_del_timer(c->read);
}
if (c->write->timer_set) {
ngx_del_timer(c->write);
}
/* 將當前連接的讀、寫事件從epoll事件機制中移除 */
if (ngx_del_conn) {
ngx_del_conn(c, NGX_CLOSE_EVENT);
} else {
if (c->read->active || c->read->disabled) {
ngx_del_event(c->read, NGX_READ_EVENT, NGX_CLOSE_EVENT);
}
if (c->write->active || c->write->disabled) {
ngx_del_event(c->write, NGX_WRITE_EVENT, NGX_CLOSE_EVENT);
}
}
#if (NGX_THREADS)
/*
* we have to clean the connection information before the closing
* because another thread may reopen the same file descriptor
* before we clean the connection
*/
ngx_mutex_lock(ngx_posted_events_mutex);
if (c->read->prev) {
ngx_delete_posted_event(c->read);
}
if (c->write->prev) {
ngx_delete_posted_event(c->write);
}
c->read->closed = 1;
c->write->closed = 1;
ngx_unlock(&c->lock);
c->read->locked = 0;
c->write->locked = 0;
ngx_mutex_unlock(ngx_posted_events_mutex);
#else
if (c->read->prev) {
ngx_delete_posted_event(c->read);
}
if (c->write->prev) {
ngx_delete_posted_event(c->write);
}
c->read->closed = 1;
c->write->closed = 1;
#endif
ngx_reusable_connection(c, 0);
log_error = c->log_error;
/* 釋放當前連接結構體 */
ngx_free_connection(c);
fd = c->fd;
c->fd = (ngx_socket_t) -1;
/* 關閉套接字連接 */
if (ngx_close_socket(fd) == -1) {
err = ngx_socket_errno;
if (err == NGX_ECONNRESET || err == NGX_ENOTCONN) {
switch (log_error) {
case NGX_ERROR_INFO:
level = NGX_LOG_INFO;
break;
case NGX_ERROR_ERR:
level = NGX_LOG_ERR;
break;
default:
level = NGX_LOG_CRIT;
}
} else {
level = NGX_LOG_CRIT;
}
/* we use ngx_cycle->log because c->log was in c->pool */
ngx_log_error(level, ngx_cycle->log, err,
ngx_close_socket_n " %d failed", fd);
}
}
~~~
**ngx_http_terminate_request 函數的執行流程:**
- 調用原始請求的 cleanup 鏈表的回調方法 handler 開始清理工作;
- 調用 ngx_http_close_request 方法關閉請求;
~~~
/* 強制關閉連接 */
static void
ngx_http_terminate_request(ngx_http_request_t *r, ngx_int_t rc)
{
ngx_http_cleanup_t *cln;
ngx_http_request_t *mr;
ngx_http_ephemeral_t *e;
mr = r->main;
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http terminate request count:%d", mr->count);
if (rc > 0 && (mr->headers_out.status == 0 || mr->connection->sent == 0)) {
mr->headers_out.status = rc;
}
/* 調用原始請求的cleanup的回調方法,開始清理工作 */
cln = mr->cleanup;
mr->cleanup = NULL;
while (cln) {
if (cln->handler) {
cln->handler(cln->data);
}
cln = cln->next;
}
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http terminate cleanup count:%d blk:%d",
mr->count, mr->blocked);
if (mr->write_event_handler) {
if (mr->blocked) {
return;
}
e = ngx_http_ephemeral(mr);
mr->posted_requests = NULL;
mr->write_event_handler = ngx_http_terminate_handler;
(void) ngx_http_post_request(mr, &e->terminal_posted_request);
return;
}
/* 釋放請求,并關閉連接 */
ngx_http_close_request(mr, rc);
}
~~~
- 前言
- Nginx 配置文件
- Nginx 內存池管理
- Nginx 基本數據結構
- Nginx 數組結構 ngx_array_t
- Nginx 鏈表結構 ngx_list_t
- Nginx 隊列雙向鏈表結構 ngx_queue_t
- Nginx 哈希表結構 ngx_hash_t
- Nginx 紅黑樹結構 ngx_rbtree_t
- Nginx 模塊開發
- Nginx 啟動初始化過程
- Nginx 配置解析
- Nginx 中的 upstream 與 subrequest 機制
- Nginx 源碼結構分析
- Nginx 事件模塊
- Nginx 的 epoll 事件驅動模塊
- Nginx 定時器事件
- Nginx 事件驅動模塊連接處理
- Nginx 中 HTTP 模塊初始化
- Nginx 中處理 HTTP 請求
- Nginx 中 upstream 機制的實現
- Nginx 中 upstream 機制的負載均衡