在ngx_http_process_request_line函數中,解析完請求行之后,如果請求行的uri里面包含了域名部分,則將其保持在請求結構的headers_in成員的server字段,headers_in用來保存所有請求頭,它的類型為ngx_http_headers_in_t:
- <span style="font-size: 18px; ">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;</span>
接着,該函數會檢查進來的請求是否使用的是http0.9,如果是的話則使用從請求行里得到的域名,調用ngx_http_find_virtual_server()函數來查找用來處理該請求的虛擬服務器配置,之前通過端口和地址找到的默認配置不再使用,找到相應的配置之后,則直接調用ngx_http_process_request()函數處理該請求,因為http0.9是最原始的http協議,它里面沒有定義任何請求頭,顯然就不需要讀取請求頭的操作。
- <span style="font-size:18px;"> 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;
- }</span>
當然,如果是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,並隨后馬上調用了該函數。
- <span style="font-size:18px;"> 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);</span>
ngx_http_process_request_headers函數循環的讀取所有的請求頭,並保存和初始化和請求頭相關的結構,下面詳細分析一下該函數:
因為nginx對讀取請求頭有超時限制,ngx_http_process_request_headers函數作為讀事件處理函數,一並處理了超時事件,如果讀超時了,nginx直接給該請求返回408錯誤:
- <span style="font-size:18px;"> 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;
- }</span>
讀取和解析請求頭的邏輯和處理請求行差不多,總的流程也是循環的調用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全局數組中:
- <span style="font-size:18px;">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 }
- };</span>
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函數:
- <span style="font-size:18px;">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;
- }</span>
這個函數只是簡單將該請求頭在ngx_http_headers_in_t結構中保存一份引用。ngx_http_process_unique_header_line功能類似,不同點在於該函數會檢查這個請求頭是否是重復的,如果是的話,則給該請求返回400錯誤。
ngx_http_headers_in數組中剩下的請求頭都有自己特殊的處理函數,這些特殊的函數根據對應的請求頭有一些特殊的處理,下面我們拿Host頭的處理函數ngx_http_process_host做一下介紹:
- <span style="font-size:18px;">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;
- }</span>
此函數的目的也是保存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函數:
- <span style="font-size:18px;">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;
- }</span>
當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接收請求接收流程就介紹完畢。