30.Nginx HTTP之讀取處理請求頭函式ngx_http_process_request_headers
阿新 • • 發佈:2019-02-06
在HTTP >= 1.0的版本中,讀取處理完HTTP請求行後,緊接著就應該是讀取處理HTTP請求頭了,Nginx使用ngx_http_process_request_headers來完成這個工作。
/* http/ngx_http_request.c */ /* 處理HTTP請求頭 * param r: 待處理的HTTP請求 * return : */ static ngx_int_t ngx_http_process_request_header(ngx_http_request_t *r) { u_char *ua, *user_agent; size_t len; ngx_uint_t i; ngx_http_server_name_t *name; ngx_http_core_srv_conf_t *cscf; ngx_http_core_loc_conf_t *clcf; if (r->headers_in.host) { // 如果r->headers_in的host成員變數不為空 // 從Host頭部欄位的值中找出主機名 // 由於值中可能包含埠號, 所以需要將埠號排除在外 for (len = 0; len < r->headers_in.host->value.len; len++) { if (r->headers_in.host->value.data[len] == ':') { break; } } // len就是主機名的長度 r->headers_in.host_name_len = len; // 找到基於虛擬主機名稱的server配置 name = r->virtual_names->elts; // 對請求r對應的虛擬主機名稱陣列進行遍歷查詢 for (i = 0; i < r->virtual_names->nelts; i++) { if (r->headers_in.host_name_len != name[i].name.len) { // 如果長度不等, 那麼字串必不相等, 也就不需要進行字串比較, 直接跳過 continue; } if (ngx_strncasecmp(r->headers_in.host->value.data, name[i].name.data, r->headers_in.host_name_len) == 0) { // 對虛擬主機名稱和Host頭部欄位提供的主機名進行不區分大小寫的字串比較, 如果相等, // 那麼這就是我們要找的虛擬主機名稱 // 記錄虛擬主機名稱對應的server配置 r->srv_conf = name[i].core_srv_conf->ctx->srv_conf; r->loc_conf = name[i].core_srv_conf->ctx->loc_conf; // 記錄請求r的主機名稱 r->server_name = &name[i].name; clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); r->connection->log->file = clcf->err_log->file; if (!(r->connection->log->log_level & NGX_LOG_DEBUG_CONNECTION)) { r->connection->log->log_level = clcf->err_log->log_level; } break; } } if (i == r->virtual_names->nelts) { // 如果i等於r->virtual_names的元素個數, 說明沒有找到匹配的虛擬主機名稱 // 獲取ngx_http_core_module模組的server層次配置上下文cscf cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); if (cscf->restrict_host_names != NGX_HTTP_RESTRICT_HOST_OFF) { // 如果restrict_host_names不為off // 說明這是個無效的主機名, 返回NGX_HTTP_PARSE_INVALID_HOST return NGX_HTTP_PARSE_INVALID_HOST; } } } else { // 如果r->headers_in的host成員變數為空 if (r->http_version > NGX_HTTP_VERSION_10) { // 而當前HTTP請求的版本大於1.0, 說明Host頭部欄位是必需的 // 返回NGX_HTTP_PARSE_NO_HOST_HEADER return NGX_HTTP_PARSE_NO_HOST_HEADER; } // 置r->headers_in.host_name_len為0 r->headers_in.host_name_len = 0; } if (r->headers_in.content_length) { // 如果r->headers_in的content_length成員變數不為空 // 將Content-Length頭部欄位的字串值轉換為整型, 並記錄在r->headers_in的content_length_n成員變數中 r->headers_in.content_length_n = ngx_atoi(r->headers_in.content_length->value.data, r->headers_in.content_length->value.len); if (r->headers_in.content_length_n == NGX_ERROR) { return NGX_HTTP_PARSE_INVALID_CL_HEADER; } } if (r->method == NGX_HTTP_POST && r->headers_in.content_length_n <= 0) { // 如果請求方法為POST且content_length_n不大於0, 說明有誤 // 返回NGX_HTTP_PARSE_POST_WO_CL_HEADER return NGX_HTTP_PARSE_POST_WO_CL_HEADER; } if (r->plain_http) { // 如果r->plain_http為1 return NGX_HTTP_PARSE_HTTP_TO_HTTPS; } if (r->headers_in.connection) { // 如果r->headers_in的connection成員變數不為空 if (r->headers_in.connection->value.len == 5 && ngx_strcasecmp(r->headers_in.connection->value.data, "close") == 0) { // 如果Connection頭部欄位的值為close // 置r->headers_in的connection_type成員變數為NGX_HTTP_CONNECTION_CLOSE, // 即連線會在請求處理完畢後斷開 r->headers_in.connection_type = NGX_HTTP_CONNECTION_CLOSE; } else if (r->headers_in.connection->value.len == 10 && ngx_strcasecmp(r->headers_in.connection->value.data, "keep-alive") == 0) { // 如果Connection頭部欄位的值為keep-alive // 置r->headers_in的connection_type成員變數為NGX_HTTP_CONNECTION_KEEP_ALIVE, // 即連線型別為長連線 r->headers_in.connection_type = NGX_HTTP_CONNECTION_KEEP_ALIVE; if (r->headers_in.keep_alive) { // keep_alive_n是指keep-alive超時時間??? r->headers_in.keep_alive_n = ngx_atoi(r->headers_in.keep_alive->value.data, r->headers_in.keep_alive->value.len); } } } if (r->headers_in.user_agent) { // 如果r->headers_in的user_agent成員變數不為空 // 獲取User-Agent頭部欄位的值user_agent user_agent = r->headers_in.user_agent->value.data; // 判斷MSIE是否為user_agent的子串 ua = (u_char *) ngx_strstr(user_agent, "MSIE"); if (ua && ua + 8 < user_agent + r->headers_in.user_agent->value.len) { // 置r->headers_in的msie標誌位為1, 即客戶端的瀏覽器為MSIE r->headers_in.msie = 1; if (ua[4] == ' ' && ua[5] == '4' && ua[6] == '.') { // 判斷MSIE版本是否為4, 如果是 // 置r->headers_in的msie4標誌位為1 r->headers_in.msie4 = 1; } #if 0 ngx_ssl_set_nosendshut(r->connection->ssl); #endif } if (ngx_strstr(user_agent, "Opera")) { // 如果Opera是user_agent的子串, 說明客戶端的瀏覽器為Opera // 置r->headers_in的opera為1, msie和msie4為0 r->headers_in.opera = 1; r->headers_in.msie = 0; r->headers_in.msie4 = 0; } if (!r->headers_in.msie && !r->headers_in.opera) { // 如果客戶端的瀏覽器不是MSIE和Opera if (ngx_strstr(user_agent, "Gecko/")) { // 如果Gecko/是user_agent的子串, 說明客戶端的瀏覽器核心為Gecko // 置r->headers_in的gecko標誌位為1 r->headers_in.gecko = 1; } else if (ngx_strstr(user_agent, "Konqueror")) { // 如果Konqueror是user_agent的子串, 說明客戶端的瀏覽器為Konqueror // 置r->headers_in的konqueror標誌位為1 r->headers_in.konqueror = 1; } } } // 處理完畢返回NGX_OK return NGX_OK; } /* 讀取和處理HTTP請求頭 * param rev: 待處理的讀事件 */ static void ngx_http_process_request_headers(ngx_event_t *rev) { /* HTTP請求頭格式, 頭部欄位名不區分大小寫: * [頭部欄位名]:[值][回車符][換行符] * ...... * [頭部欄位名]:[值][回車符][換行符] * [回車符][換行符] */ ssize_t n; ngx_int_t rc, rv, i; ngx_table_elt_t *h, **cookie; ngx_connection_t *c; ngx_http_request_t *r; // 讀事件的custom data為其所屬的連線c c = rev->data; // 連線c的custom data為其對應的HTTP請求r r = c->data; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, "http process request header line"); if (rev->timedout) { // 如果rev->timedout為1, 說明客戶端超時 ngx_http_client_error(r, 0, NGX_HTTP_REQUEST_TIME_OUT); return; } rc = NGX_AGAIN; for ( ;; ) { if (rc == NGX_AGAIN) { if (r->header_in->pos == r->header_in->end) { // 如果r->header_in緩衝區的pos等於end, 說明緩衝區被耗盡; // note: r->header_in緩衝區用來同時存放請求行和請求頭 // 嘗試分配更大的緩衝區來存放請求頭 rv = ngx_http_alloc_large_header_buffer(r, 0); if (rv == NGX_ERROR) { ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); ngx_http_close_connection(c); return; } if (rv == NGX_DECLINED) { ngx_http_client_error(r, NGX_HTTP_PARSE_TOO_LONG_HEADER, NGX_HTTP_BAD_REQUEST); return; } } // 讀取HTTP請求頭 n = ngx_http_read_request_header(r); if (n == NGX_AGAIN || n == NGX_ERROR) { return; } } // 對讀取的請求頭進行解析 // 當解析完請求頭的一行, 返回NGX_OK; // 當解析完請求頭, 返回NGX_HTTP_PARSE_HEADER_DONE; // 出錯返回NGX_HTTP_PARSE_INVALID_HEADER; 其他返回NGX_AGAIN, 表示需要讀取新的請求頭內容 rc = ngx_http_parse_header_line(r, r->header_in); if (rc == NGX_OK) { // 如果解析完請求頭的一行 // r->headers_n用來統計請求頭的行數, 加1 r->headers_n++; // 從單向連結串列r->headers_in.headers申請一個元素的記憶體空間h if (!(h = ngx_list_push(&r->headers_in.headers))) { ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); ngx_http_close_connection(c); return; } // 前面在ngx_http_process_request_line中我們看到, r->headers_in.headers的元素型別為ngx_table_elt_t(鍵值對) // 記錄解析的請求頭當前行的欄位名和對應的值, 注意這裡不會進行記憶體拷貝 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'; if (h->key.len == sizeof("Cookie") - 1 && ngx_strcasecmp(h->key.data, "Cookie") == 0) { // 如果解析的請求頭當前行的欄位名為Cookie(不區分大小寫) // 從陣列r->headers_in.cookies申請一個元素(型別為ngx_table_elt_t指標)的記憶體空間cookie if (!(cookie = ngx_array_push(&r->headers_in.cookies))) { ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); ngx_http_close_connection(c); return; } // cookie指向h, 即記錄剛解析的Cookie *cookie = h; } else { // 遍歷全域性陣列ngx_http_headers_in, 裡面存放了已知的請求頭欄位名, 例如Host、Connection等 for (i = 0; ngx_http_headers_in[i].name.len != 0; i++) { if (ngx_http_headers_in[i].name.len != h->key.len) { // 如果長度都不相等, 那麼兩個字串必不相等了, 直接跳過 continue; } if (ngx_strcasecmp(ngx_http_headers_in[i].name.data, h->key.data) == 0) { // 對解析的欄位名與已知的欄位名進行不區分大小寫的字串比較, 如果相等 // 將h記錄到r->headers_in對應的成員變數中, 注意這裡用了offset來計算成員變數在r->headers_in結構體中的偏移量 *((ngx_table_elt_t **) ((char *) &r->headers_in + ngx_http_headers_in[i].offset)) = h; break; } } // note: 從這裡可以看出, 對於未知的欄位名, Nginx直接忽略掉 } ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http header: \"%s: %s\"", h->key.data, h->value.data); continue; } else if (rc == NGX_HTTP_PARSE_HEADER_DONE) { // 如果解析完整個請求頭 ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http header done"); // 置r->http_state為NGX_HTTP_PROCESS_REQUEST_STATE r->http_state = NGX_HTTP_PROCESS_REQUEST_STATE; // 對請求頭進行處理 rc = ngx_http_process_request_header(r); if (rc != NGX_OK) { ngx_http_client_error(r, rc, NGX_HTTP_BAD_REQUEST); return; } if (rev->timer_set) { // 如果將讀事件rev註冊進了定時器紅黑樹, // 那麼將其從紅黑樹中移除 ngx_del_timer(rev); } #if (NGX_STAT_STUB) (*ngx_stat_reading)--; r->stat_reading = 0; (*ngx_stat_writing)++; r->stat_writing = 1; #endif // 置讀事件rev的事件處理函式為ngx_http_block_read, 該函式會將事件從epoll控制代碼中刪除, 可以看出讀完請求頭後,Nginx不會馬上讀取請求正文 rev->event_handler = ngx_http_block_read; // 呼叫ngx_http_handler對請求r進行處理 ngx_http_handler(r); return; } else if (rc != NGX_AGAIN) { // 如果解析過程有誤 #if (NGX_DEBUG) if (rc == NGX_HTTP_PARSE_INVALID_HEADER && (rev->log->log_level & NGX_LOG_DEBUG_HTTP)) { u_char *p; for (p = r->header_name_start; p < r->header_in->last - 1; p++) { if (*p == LF) { break; } } *p = '\0'; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, rev->log, 0, "http invalid header: \"%s\"", r->header_name_start); } #endif // 記錄錯誤資訊並將錯誤提示傳送給客戶端 ngx_http_client_error(r, rc, NGX_HTTP_BAD_REQUEST); return; } } }