1. 程式人生 > >nginx的請求接收流程(二)

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 
版權宣告:本文為博主原創文章,轉載請附上博文連結!