Nginx - HTTP請求處理
解析HTTP請求頭
上一篇文章中說到接收到HTTP請求之後會直接呼叫ngx_http_wait_request_handler
函式來處理,此函式的作用是首先分配一塊記憶體緩衝區來存放請求頭,
然後呼叫c->recv
函式來接收資料,接著為這個請求建立一個ngx_http_request_t
結構體,然後設定rev->handler
指標為ngx_http_process_request_line
函式,
將讀事件處理函式設定為此函式,當本次沒有接受完全部請求資料時下次資料到來時直接呼叫ngx_http_process_request_line
函式,
最後直接呼叫ngx_http_process_request_line
函式來處理請求行。
ngx_http_process_request_line
函式首先判斷是不是超時事件,如果是則直接關閉這個請求,然後返回。如果不是則迴圈解析請求行,
迴圈中首先會呼叫ngx_http_read_request_header
函式,它首先檢查緩衝區中是否有新增的資料,如果有則直接返回,如果沒有則呼叫c->recv
函式來接收資料並返回。
然後呼叫ngx_http_parse_request_line
函式來解析請求行,如果解析成功則接下來解析請求頭。如果返回NGX_AGAIN
則說明請求行資料還沒有接收完整,
接下來先判斷分配的緩衝區是否已滿,如果滿了則分配一個更大的緩衝區,然後進行下一輪迴圈,呼叫ngx_http_read_request_header
函式接收資料,繼續解析請求行。
static void ngx_http_process_request_line(ngx_event_t *rev) { ssize_tn; ngx_int_trc, rv; ngx_str_thost; ngx_connection_t*c; ngx_http_request_t*r; c = rev->data; r = c->data; /* 如果是超時事件則關閉請求,直接返回 */ 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; } } /* 解析請求行 */ rc = ngx_http_parse_request_line(r, r->header_in); if (rc == NGX_OK) { /* 請求行解析成功,檢查引數是否正確,進一步解析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; } ngx_http_process_request(r); return; } 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; 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]); if (rc == NGX_HTTP_PARSE_INVALID_VERSION) { ngx_http_finalize_request(r, NGX_HTTP_VERSION_NOT_SUPPORTED); } else { ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); } return; } /* 返回的是 NGX_AGAIN 說明請求行資料還沒有完全收到,判斷緩衝區是否用完,如果用完了, 則分配一塊更大的記憶體,然後進行下一輪接收資料並處理 */ 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_parse_request_line
函式則會返回NGX_OK
,接下來判斷請求行是否合法,如果一切正常則設定rev->handler
指標為ngx_http_process_request_headers
函式,
將讀事件處理函式設定為此函式,如果還有資料沒有接受完,則會直接呼叫ngx_http_process_request_headers
函式來處理請求頭,因為請求行已經處理完畢。
請求頭的處理和請求行的處理類似,也是迴圈解析請求頭,如果資料沒有接受完則還是呼叫ngx_http_read_request_header
函式接收資料,
不同的是每解析到一個請求頭都會呼叫註冊到HTTP模組的對應的請求頭處理函式,來處理。請求頭處理函式在ngx_http_headers_in[]
陣列中註冊,並存放到headers_in_hash
雜湊表中。
請求頭全部解析完成之後,則會呼叫ngx_http_process_request
函式,來處理HTTP請求。
static void ngx_http_process_request_headers(ngx_event_t *rev) { u_char*p; size_tlen; ssize_tn; ngx_int_trc, 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; r = c->data; /* 如果是超時事件則關閉請求,直接返回 */ 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; } cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); /* 設定為 NGX_AGAIN */ 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; } 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; } } /* 然後呼叫 ngx_http_read_request_header 來接收資料 */ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, "*** http process request header line NGX_AGAIN"); n = ngx_http_read_request_header(r); if (n == NGX_AGAIN || n == NGX_ERROR) { return; } } /* the host header could change the server configuration context */ cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); /* 解析請求頭 */ rc = ngx_http_parse_header_line(r, r->header_in, cscf->underscores_in_headers); /* 如果請求頭解析成功,則呼叫對應的請求頭處理函式,並continue繼續下一個解析 */ if (rc == NGX_OK) { r->request_length += r->header_in->pos - r->header_name_start; 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 */ h = ngx_list_push(&r->headers_in.headers); if (h == NULL) { ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } 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); } /* 在 headers_in_hash 雜湊表中查詢對應的請求頭處理函式 */ 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; } /* 請求頭全部解析完成,則呼叫 ngx_http_process_request 處理HTTP請求 */ 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; rc = ngx_http_process_request_header(r); if (rc != NGX_OK) { return; } ngx_http_process_request(r); return; } /* 如果返回NGX_AGAIN,說明還有資料沒有接受完,繼續下一輪接收資料並處理 */ if (rc == NGX_AGAIN) { /* a header line parsing is still not complete */ continue; } /* 請求頭解析出錯 */ /* rc == NGX_HTTP_PARSE_INVALID_HEADER */ ngx_log_error(NGX_LOG_INFO, c->log, 0, "client sent invalid header line"); ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); return; } }
處理HTTP請求
nginx將HTTP的請求處理分為多個階段,目前分了11個階段,分別如下。我們可以在每個階段中註冊自己的模組,當執行到此階段時就會呼叫到我們註冊的模組處理函式。 整個執行過程會將所有階段的處理函式,以及我們自己註冊的函式組織為一個連結串列,處理請求時遍歷一次這個連結串列就完成了所有階段的處理。
typedef enum { NGX_HTTP_POST_READ_PHASE = 0, NGX_HTTP_SERVER_REWRITE_PHASE, NGX_HTTP_FIND_CONFIG_PHASE, NGX_HTTP_REWRITE_PHASE, NGX_HTTP_POST_REWRITE_PHASE, NGX_HTTP_PREACCESS_PHASE, NGX_HTTP_ACCESS_PHASE, NGX_HTTP_POST_ACCESS_PHASE, NGX_HTTP_PRECONTENT_PHASE, NGX_HTTP_CONTENT_PHASE, NGX_HTTP_LOG_PHASE } ngx_http_phases;
首先來看一下整個執行鏈的初始化過程,首先計算註冊的處理函式的數量,然後為每個處理函式分配一個ngx_http_phase_handler_t
結構體大小的記憶體,
最後按照上面11個階段的順序,將所有處理函式的指標存放到分配的記憶體中,組成一個執行鏈。初始化函式是ngx_http_init_phase_handlers
。
static ngx_int_t ngx_http_init_phase_handlers(ngx_conf_t *cf, ngx_http_core_main_conf_t *cmcf) { ngx_int_tj; ngx_uint_ti, n; ngx_uint_tfind_config_index, use_rewrite, use_access; ngx_http_handler_pt*h; ngx_http_phase_handler_t*ph; ngx_http_phase_handler_ptchecker; cmcf->phase_engine.server_rewrite_index = (ngx_uint_t) -1; cmcf->phase_engine.location_rewrite_index = (ngx_uint_t) -1; find_config_index = 0; use_rewrite = cmcf->phases[NGX_HTTP_REWRITE_PHASE].handlers.nelts ? 1 : 0; use_access = cmcf->phases[NGX_HTTP_ACCESS_PHASE].handlers.nelts ? 1 : 0; n = 1/* find config phase */ + use_rewrite/* post rewrite phase */ + use_access;/* post access phase */ /* 計算註冊處理函式的數量 */ for (i = 0; i < NGX_HTTP_LOG_PHASE; i++) { n += cmcf->phases[i].handlers.nelts; } /* 分配記憶體 */ ph = ngx_pcalloc(cf->pool, n * sizeof(ngx_http_phase_handler_t) + sizeof(void *)); if (ph == NULL) { return NGX_ERROR; } cmcf->phase_engine.handlers = ph; n = 0; /* 為每個處理階段指定處理函式,我們自己註冊的函式會在指定階段的處理函式中呼叫執行 */ for (i = 0; i < NGX_HTTP_LOG_PHASE; i++) { h = cmcf->phases[i].handlers.elts; switch (i) { case NGX_HTTP_SERVER_REWRITE_PHASE: if (cmcf->phase_engine.server_rewrite_index == (ngx_uint_t) -1) { cmcf->phase_engine.server_rewrite_index = n; } checker = ngx_http_core_rewrite_phase; break; case NGX_HTTP_FIND_CONFIG_PHASE: find_config_index = n; ph->checker = ngx_http_core_find_config_phase; n++; ph++; /* 直接 continue 的表示這個階段下不允許註冊外部處理函式 */ continue; case NGX_HTTP_REWRITE_PHASE: if (cmcf->phase_engine.location_rewrite_index == (ngx_uint_t) -1) { cmcf->phase_engine.location_rewrite_index = n; } checker = ngx_http_core_rewrite_phase; break; case NGX_HTTP_POST_REWRITE_PHASE: if (use_rewrite) { ph->checker = ngx_http_core_post_rewrite_phase; ph->next = find_config_index; n++; ph++; } continue; case NGX_HTTP_ACCESS_PHASE: checker = ngx_http_core_access_phase; n++; break; case NGX_HTTP_POST_ACCESS_PHASE: if (use_access) { ph->checker = ngx_http_core_post_access_phase; ph->next = n; ph++; } continue; case NGX_HTTP_CONTENT_PHASE: checker = ngx_http_core_content_phase; break; /* 預設其他階段的處理函式都設定為這個 */ default: checker = ngx_http_core_generic_phase; } n += cmcf->phases[i].handlers.nelts; /* 初始化外部註冊的函式,設定執行鏈上的處理函式為對應階段的處理函式,然後在階段處理函式中呼叫外部註冊的處理函式 */ for (j = cmcf->phases[i].handlers.nelts - 1; j >= 0; j--) { ph->checker = checker; ph->handler = h[j]; ph->next = n; ph++; } } return NGX_OK; }
HTTP的處理過程就非常簡單了,直接遍歷一次執行鏈就完成了。
void ngx_http_core_run_phases(ngx_http_request_t *r) { ngx_int_trc; ngx_http_phase_handler_t*ph; ngx_http_core_main_conf_t*cmcf; cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); ph = cmcf->phase_engine.handlers; /* 遍歷執行鏈 */ while (ph[r->phase_handler].checker) { rc = ph[r->phase_handler].checker(r, &ph[r->phase_handler]); if (rc == NGX_OK) { return; } } }
參考
基於nginx-1.14.0原始碼分析
我分析原始碼時不喜歡去找所有結構體欄位的含義,這樣感覺非常枯燥,而且也很難記住所有欄位的含義。
一般是通過除錯日誌和看程式碼來梳理程式的整個執行流程。在分析程式執行流程中來理解結構體中重要欄位的含義,其他的細枝末節就直接忽略掉了。