1. 程式人生 > >30.Nginx HTTP之讀取處理請求頭函式ngx_http_process_request_headers

30.Nginx HTTP之讀取處理請求頭函式ngx_http_process_request_headers

在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;
        }
    }
}