nginx的請求接收流程(二)
在ngx_http_process_request_line函式中,解析完請求行之後,如果請求行的uri裡面包含了域名部分,則將其保持在請求結構的headers_in成員的server欄位,headers_in用來儲存所有請求頭,它的型別為ngx_http_headers_in_t:
typedef struct { ngx_list_t headers; ngx_table_elt_t *host; ngx_table_elt_t *connection; ngx_table_elt_t *if_modified_since; ngx_table_elt_t *if_unmodified_since; ngx_table_elt_t *user_agent; ngx_table_elt_t *referer; ngx_table_elt_t *content_length; ngx_table_elt_t *content_type; ngx_table_elt_t *range; ngx_table_elt_t *if_range; ngx_table_elt_t *transfer_encoding; ngx_table_elt_t *expect; #if (NGX_HTTP_GZIP) ngx_table_elt_t *accept_encoding; ngx_table_elt_t *via; #endif ngx_table_elt_t *authorization; ngx_table_elt_t *keep_alive; #if (NGX_HTTP_PROXY || NGX_HTTP_REALIP || NGX_HTTP_GEO) ngx_table_elt_t *x_forwarded_for; #endif #if (NGX_HTTP_REALIP) ngx_table_elt_t *x_real_ip; #endif #if (NGX_HTTP_HEADERS) ngx_table_elt_t *accept; ngx_table_elt_t *accept_language; #endif #if (NGX_HTTP_DAV) ngx_table_elt_t *depth; ngx_table_elt_t *destination; ngx_table_elt_t *overwrite; ngx_table_elt_t *date; #endif ngx_str_t user; ngx_str_t passwd; ngx_array_t cookies; ngx_str_t server; off_t content_length_n; time_t keep_alive_n; unsigned connection_type:2; unsigned msie:1; unsigned msie6:1; unsigned opera:1; unsigned gecko:1; unsigned chrome:1; unsigned safari:1; unsigned konqueror:1; } ngx_http_headers_in_t;
接著,該函式會檢查進來的請求是否使用的是http0.9,如果是的話則使用從請求行裡得到的域名,呼叫ngx_http_find_virtual_server()函式來查詢用來處理該請求的虛擬伺服器配置,之前通過埠和地址找到的預設配置不再使用,找到相應的配置之後,則直接呼叫ngx_http_process_request()函式處理該請求,因為http0.9是最原始的http協議,它裡面沒有定義任何請求頭,顯然就不需要讀取請求頭的操作。
if (r->host_start && r->host_end) { host = r->host_start; n = ngx_http_validate_host(r, &host, r->host_end - r->host_start, 0); if (n == 0) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "client sent invalid host in request line"); ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); return; } if (n < 0) { ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } r->headers_in.server.len = n; r->headers_in.server.data = host; } if (r->http_version < NGX_HTTP_VERSION_10) { if (ngx_http_find_virtual_server(r, r->headers_in.server.data, r->headers_in.server.len) == NGX_ERROR) { ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } ngx_http_process_request(r); return; }
當然,如果是1.0或者更新的http協議,接下來要做的就是讀取請求頭了,首先nginx會為請求頭分配空間,ngx_http_headers_in_t結構的headers欄位為一個連結串列結構,它被用來儲存所有請求頭,初始為它分配了20個節點,每個節點的型別為ngx_table_elt_t,儲存請求頭的name/value值對,還可以看到ngx_http_headers_in_t結構有很多型別為ngx_table_elt_t*的指標成員,而且從它們的命名可以看出是一些常見的請求頭名字,nginx對這些常用的請求頭在ngx_http_headers_in_t結構裡面儲存了一份引用,後續需要使用的話,可以直接通過這些成員得到,另外也事先為cookie頭分配了2個元素的陣列空間,做完這些記憶體準備工作之後,該請求對應的讀事件結構的處理函式被設定為ngx_http_process_request_headers,並隨後馬上呼叫了該函式。
if (ngx_list_init(&r->headers_in.headers, r->pool, 20,
sizeof(ngx_table_elt_t))
!= NGX_OK)
{
ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return;
}
if (ngx_array_init(&r->headers_in.cookies, r->pool, 2,
sizeof(ngx_table_elt_t *))
!= NGX_OK)
{
ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return;
}
c->log->action = "reading client request headers";
rev->handler = ngx_http_process_request_headers;
ngx_http_process_request_headers(rev);
ngx_http_process_request_headers函式迴圈的讀取所有的請求頭,並儲存和初始化和請求頭相關的結構,下面詳細分析一下該函式:
因為nginx對讀取請求頭有超時限制,ngx_http_process_request_headers函式作為讀事件處理函式,一併處理了超時事件,如果讀超時了,nginx直接給該請求返回408錯誤:
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_read_request_header()函式讀取資料,然後再呼叫一個解析函式來從讀取的資料中解析請求頭,直到解析完所有請求頭,或者發生解析錯誤為主。當然由於涉及到網路io,這個流程可能發生在多個io事件的上下文中。
接著來細看該函式,先呼叫了ngx_http_read_request_header()函式讀取資料,如果當前連線並沒有資料過來,再直接返回,等待下一次讀事件到來,如果讀到了一些資料則呼叫ngx_http_parse_header_line()函式來解析,同樣的該解析函式實現為一個有限狀態機,邏輯很簡單,只是根據http協議的解析一個請求頭的name/vale對,每次呼叫該函式最多解析出一個請求頭,該函式返回4種不同返回值,表示不同解析結果:
1,返回NGX_OK,表示解析出了一行請求頭,這時還要判斷解析出的請求頭名字裡面是否有非法字元,名字裡面合法的字元包括字母,數字和連字元(-),另外如果設定了underscores_in_headers指令為on,則下劃線也是合法字元,但是nginx預設下劃線不合法,當請求頭裡麵包含了非法的字元,nginx預設只是忽略這一行請求頭;如果一切都正常,nginx會將該請求頭及請求頭名字的hash值儲存在請求結構體的headers_in成員的headers連結串列,而且對於一些常見的請求頭,如Host,Connection,nginx採用了類似於配置指令的方式,事先給這些請求頭分配了一個處理函式,當解析出一個請求頭時,會檢查該請求頭是否有設定處理函式,有的話則呼叫之,nginx所有有處理函式的請求頭都記錄在ngx_http_headers_in全域性陣列中:
typedef struct {
ngx_str_t name;
ngx_uint_t offset;
ngx_http_header_handler_pt handler;
} ngx_http_header_t;
ngx_http_header_t ngx_http_headers_in[] = {
{ ngx_string("Host"), offsetof(ngx_http_headers_in_t, host),
ngx_http_process_host },
{ ngx_string("Connection"), offsetof(ngx_http_headers_in_t, connection),
ngx_http_process_connection },
{ ngx_string("If-Modified-Since"),
offsetof(ngx_http_headers_in_t, if_modified_since),
ngx_http_process_unique_header_line },
{ ngx_string("If-Unmodified-Since"),
offsetof(ngx_http_headers_in_t, if_unmodified_since),
ngx_http_process_unique_header_line },
{ ngx_string("User-Agent"), offsetof(ngx_http_headers_in_t, user_agent),
ngx_http_process_user_agent },
{ ngx_string("Referer"), offsetof(ngx_http_headers_in_t, referer),
ngx_http_process_header_line },
{ ngx_string("Content-Length"),
offsetof(ngx_http_headers_in_t, content_length),
ngx_http_process_unique_header_line },
{ ngx_string("Content-Type"),
offsetof(ngx_http_headers_in_t, content_type),
ngx_http_process_header_line },
{ ngx_string("Range"), offsetof(ngx_http_headers_in_t, range),
ngx_http_process_header_line },
{ ngx_string("If-Range"),
offsetof(ngx_http_headers_in_t, if_range),
ngx_http_process_unique_header_line },
{ ngx_string("Transfer-Encoding"),
offsetof(ngx_http_headers_in_t, transfer_encoding),
ngx_http_process_header_line },
{ ngx_string("Expect"),
offsetof(ngx_http_headers_in_t, expect),
ngx_http_process_unique_header_line },
#if (NGX_HTTP_GZIP)
{ ngx_string("Accept-Encoding"),
offsetof(ngx_http_headers_in_t, accept_encoding),
ngx_http_process_header_line },
{ ngx_string("Via"), offsetof(ngx_http_headers_in_t, via),
ngx_http_process_header_line },
#endif
{ ngx_string("Authorization"),
offsetof(ngx_http_headers_in_t, authorization),
ngx_http_process_unique_header_line },
{ ngx_string("Keep-Alive"), offsetof(ngx_http_headers_in_t, keep_alive),
ngx_http_process_header_line },
#if (NGX_HTTP_PROXY || NGX_HTTP_REALIP || NGX_HTTP_GEO)
{ ngx_string("X-Forwarded-For"),
offsetof(ngx_http_headers_in_t, x_forwarded_for),
ngx_http_process_header_line },
#endif
#if (NGX_HTTP_REALIP)
{ ngx_string("X-Real-IP"),
offsetof(ngx_http_headers_in_t, x_real_ip),
ngx_http_process_header_line },
#endif
#if (NGX_HTTP_HEADERS)
{ ngx_string("Accept"), offsetof(ngx_http_headers_in_t, accept),
ngx_http_process_header_line },
{ ngx_string("Accept-Language"),
offsetof(ngx_http_headers_in_t, accept_language),
ngx_http_process_header_line },
#endif
#if (NGX_HTTP_DAV)
{ ngx_string("Depth"), offsetof(ngx_http_headers_in_t, depth),
ngx_http_process_header_line },
{ ngx_string("Destination"), offsetof(ngx_http_headers_in_t, destination),
ngx_http_process_header_line },
{ ngx_string("Overwrite"), offsetof(ngx_http_headers_in_t, overwrite),
ngx_http_process_header_line },
{ ngx_string("Date"), offsetof(ngx_http_headers_in_t, date),
ngx_http_process_header_line },
#endif
{ ngx_string("Cookie"), 0, ngx_http_process_cookie },
{ ngx_null_string, 0, NULL }
};
ngx_http_headers_in陣列當前包含了25個常用的請求頭,每個請求頭都設定了一個處理函式,當前其中一部分請求頭設定的是公共的處理函式,這裡有2個公共的處理函式,ngx_http_process_header_line和ngx_http_process_unique_header_line。
先來看一下處理函式的函式指標定義:
typedef ngx_int_t (*ngx_http_header_handler_pt)(ngx_http_request_t *r,
ngx_table_elt_t *h, ngx_uint_t offset);
它有3個引數,r為對應的請求結構,h為該請求頭在headers_in.headers連結串列節點的指標,offset為該請求頭的引用在ngx_http_headers_in_t結構中的偏移。
再來看ngx_http_process_header_line函式:
static ngx_int_t
ngx_http_process_header_line(ngx_http_request_t *r, ngx_table_elt_t *h,
ngx_uint_t offset)
{
ngx_table_elt_t **ph;
ph = (ngx_table_elt_t **) ((char *) &r->headers_in + offset);
if (*ph == NULL) {
*ph = h;
}
return NGX_OK;
}
這個函式只是簡單將該請求頭在ngx_http_headers_in_t結構中儲存一份引用。ngx_http_process_unique_header_line功能類似,不同點在於該函式會檢查這個請求頭是否是重複的,如果是的話,則給該請求返回400錯誤。
ngx_http_headers_in陣列中剩下的請求頭都有自己特殊的處理函式,這些特殊的函式根據對應的請求頭有一些特殊的處理,下面我們拿Host頭的處理函式ngx_http_process_host做一下介紹:
static ngx_int_t
ngx_http_process_host(ngx_http_request_t *r, ngx_table_elt_t *h,
ngx_uint_t offset)
{
u_char *host;
ssize_t len;
if (r->headers_in.host == NULL) {
r->headers_in.host = h;
}
host = h->value.data;
len = ngx_http_validate_host(r, &host, h->value.len, 0);
if (len == 0) {
ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
"client sent invalid host header");
ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
return NGX_ERROR;
}
if (len < 0) {
ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return NGX_ERROR;
}
if (r->headers_in.server.len) {
return NGX_OK;
}
r->headers_in.server.len = len;
r->headers_in.server.data = host;
return NGX_OK;
}
此函式的目的也是儲存Host頭的快速引用,它會對Host頭的值做一些合法性檢查,並從中解析出域名,儲存在headers_in.server欄位,實際上前面在解析請求行時,headers_in.server可能已經被賦值為從請求行中解析出來的域名,根據http協議的規範,如果請求行中的uri帶有域名的話,則域名以它為準,所以這裡需檢查一下headers_in.server是否為空,如果不為空則不需要再賦值。
其他請求頭的特殊處理函式,不再做介紹,大致都是根據該請求頭在http協議中規定的意義及其值設定請求的一些屬性,必備後續使用。
對一個合法的請求頭的處理大致為如上所述;
2,返回NGX_AGAIN,表示當前接收到的資料不夠,一行請求頭還未結束,需要繼續下一輪迴圈。在下一輪迴圈中,nginx首先檢查請求頭緩衝區header_in是否已滿,如夠滿了,則呼叫ngx_http_alloc_large_header_buffer()函式分配更多緩衝區,下面分析一下ngx_http_alloc_large_header_buffer函式:
static ngx_int_t
ngx_http_alloc_large_header_buffer(ngx_http_request_t *r,
ngx_uint_t request_line)
{
u_char *old, *new;
ngx_buf_t *b;
ngx_http_connection_t *hc;
ngx_http_core_srv_conf_t *cscf;
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http alloc large header buffer");
/*
* 在解析請求行階段,如果客戶端在傳送請求行之前傳送了大量回車換行符將
* 緩衝區塞滿了,針對這種情況,nginx只是簡單的重置緩衝區,丟棄這些垃圾
* 資料,不需要分配更大的記憶體。
*/
if (request_line && r->state == 0) {
/* the client fills up the buffer with "\r\n" */
r->request_length += r->header_in->end - r->header_in->start;
r->header_in->pos = r->header_in->start;
r->header_in->last = r->header_in->start;
return NGX_OK;
}
/* 儲存請求行或者請求頭在舊緩衝區中的起始地址 */
old = request_line ? r->request_start : r->header_name_start;
cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
/* 如果一個大緩衝區還裝不下請求行或者一個請求頭,則返回錯誤 */
if (r->state != 0
&& (size_t) (r->header_in->pos - old)
>= cscf->large_client_header_buffers.size)
{
return NGX_DECLINED;
}
hc = r->http_connection;
/* 首先在ngx_http_connection_t結構中查詢是否有空閒緩衝區,有的話,直接取之 */
if (hc->nfree) {
b = hc->free[--hc->nfree];
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http large header free: %p %uz",
b->pos, b->end - b->last);
/* 檢查給該請求分配的請求頭緩衝區個數是否已經超過限制,預設最大個數為4個 */
} else if (hc->nbusy < cscf->large_client_header_buffers.num) {
if (hc->busy == NULL) {
hc->busy = ngx_palloc(r->connection->pool,
cscf->large_client_header_buffers.num * sizeof(ngx_buf_t *));
if (hc->busy == NULL) {
return NGX_ERROR;
}
}
/* 如果還沒有達到最大分配數量,則分配一個新的大緩衝區 */
b = ngx_create_temp_buf(r->connection->pool,
cscf->large_client_header_buffers.size);
if (b == NULL) {
return NGX_ERROR;
}
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http large header alloc: %p %uz",
b->pos, b->end - b->last);
} else {
/* 如果已經達到最大的分配限制,則返回錯誤 */
return NGX_DECLINED;
}
/* 將從空閒佇列取得的或者新分配的緩衝區加入已使用佇列 */
hc->busy[hc->nbusy++] = b;
/*
* 因為nginx中,所有的請求頭的儲存形式都是指標(起始和結束地址),
* 所以一行完整的請求頭必須放在連續的記憶體塊中。如果舊的緩衝區不能
* 再放下整行請求頭,則分配新緩衝區,並從舊緩衝區拷貝已經讀取的部分請求頭,
* 拷貝完之後,需要修改所有相關指標指向到新緩衝區。
* status為0表示解析完一行請求頭之後,緩衝區正好被用完,這種情況不需要拷貝
*/
if (r->state == 0) {
/*
* r->state == 0 means that a header line was parsed successfully
* and we do not need to copy incomplete header line and
* to relocate the parser header pointers
*/
r->request_length += r->header_in->end - r->header_in->start;
r->header_in = b;
return NGX_OK;
}
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http large header copy: %d", r->header_in->pos - old);
r->request_length += old - r->header_in->start;
new = b->start;
/* 拷貝舊緩衝區中不完整的請求頭 */
ngx_memcpy(new, old, r->header_in->pos - old);
b->pos = new + (r->header_in->pos - old);
b->last = new + (r->header_in->pos - old);
/* 修改相應的指標指向新緩衝區 */
if (request_line) {
r->request_start = new;
if (r->request_end) {
r->request_end = new + (r->request_end - old);
}
r->method_end = new + (r->method_end - old);
r->uri_start = new + (r->uri_start - old);
r->uri_end = new + (r->uri_end - old);
if (r->schema_start) {
r->schema_start = new + (r->schema_start - old);
r->schema_end = new + (r->schema_end - old);
}
if (r->host_start) {
r->host_start = new + (r->host_start - old);
if (r->host_end) {
r->host_end = new + (r->host_end - old);
}
}
if (r->port_start) {
r->port_start = new + (r->port_start - old);
r->port_end = new + (r->port_end - old);
}
if (r->uri_ext) {
r->uri_ext = new + (r->uri_ext - old);
}
if (r->args_start) {
r->args_start = new + (r->args_start - old);
}
if (r->http_protocol.data) {
r->http_protocol.data = new + (r->http_protocol.data - old);
}
} else {
r->header_name_start = new;
r->header_name_end = new + (r->header_name_end - old);
r->header_start = new + (r->header_start - old);
r->header_end = new + (r->header_end - old);
}
r->header_in = b;
return NGX_OK;
}
當ngx_http_alloc_large_header_buffer函式返回NGX_DECLINED)時,表示客戶端傳送了過大的一行請求頭,或者是整個請求頭部超過了限制,nginx會返回494錯誤,注意到nginx再返回494錯誤之前將請求的lingering_close標識置為了1,這樣做的目的是在返回響應之丟棄掉客戶端發過來的其他資料;
3,返回NGX_HTTP_PARSE_INVALID_HEADER,表示請求頭解析過程中遇到錯誤,一般為客戶端傳送了不符合協議規範的頭部,此時nginx返回400錯誤。
4,返回NGX_HTTP_PARSE_HEADER_DONE,表示所有請求頭已經成功的解析,這時請求的狀態被設定為NGX_HTTP_PROCESS_REQUEST_STATE,意味著結束了請求讀取階段,正式進入了請求處理階段,但是實際上請求可能含有請求體,nginx在請求讀取階段並不會去讀取請求體,這個工作交給了後續的請求處理階段的模組,這樣做的目的是nginx本身並不知道這些請求體是否有用,如果後續模組並不需要的話,一方面請求體一般較大,如果全部讀取進記憶體,則白白耗費大量的記憶體空間,另一方面即使nginx將請求體寫進磁碟,但是涉及到磁碟io,會耗費比較多時間。所以交由後續模組來決定讀取還是丟棄請求體是最明智的辦法。
讀取完請求頭之後,nginx呼叫了ngx_http_process_request_header()函式,這個函式主要做了兩個方面的事情,一是呼叫ngx_http_find_virtual_server()函式查詢虛擬伺服器配置;二是對一些請求頭做一些協議的檢查。比如對那些使用http1.1協議但是卻沒有傳送Host頭的請求,nginx給這些請求返回400錯誤。還有nginx現在的版本並不支援chunked格式的輸入,如果某些請求申明自己使用了chunked格式的輸入(請求帶有值為chunked的transfer_encoding頭部),nginx給這些請求返回411錯誤。等等。
最後呼叫ngx_http_process_request()函式處理請求;
至此,nginx接收請求接收流程就介紹完畢。
---------------------
作者:fengmo_q
來源:CSDN
原文:https://blog.csdn.net/fengmo_q/article/details/7736695
版權宣告:本文為博主原創文章,轉載請附上博文連結!