Nginx重要結構request_t解析之http請求的獲取


請在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。

本文主要參考為《深入理解nginx模塊開發與架構解析》一書,處理用戶請求部分,是一篇包含作者理解的讀書筆記。歡迎指正,討論。

handler函數的定義模型如下:

1 static ngx_int_t
2 ngx_http_hello_handler(ngx_http_request_t *r)
3 {}

請求的所有信息都可以在傳入的ngx_http_request_t類型指針參數 r 中獲得。Ngx_http_request_t結構體包含的內容很多,這里只討論其中獲取HTTP請求的部分,相關定義如下:

 1 struct ngx_http_request_s {
 2     ...
 3   //請求頭
 4     ngx_buf_t                        *header_in;
 5 
 6     ngx_http_headers_in_t             headers_in;
 7     ngx_http_headers_out_t            headers_out;
 8   //請求體
 9     ngx_http_request_body_t          *request_body;
10   //請求行
11     ngx_uint_t                        method;
12     ngx_uint_t                        http_version;
13 
14     ngx_str_t                         request_line;
15     ngx_str_t                         uri;
16     ngx_str_t                         args;
17     ngx_str_t                         exten;
18     ngx_str_t                         unparsed_uri;
19 
20     ngx_str_t                         method_name;
21     ngx_str_t                         http_protocol;
22     ...
23     /*
24      * a memory that can be reused after parsing a request line
25      * via ngx_http_ephemeral_t
26      */
27 
28     u_char                           *uri_start;
29     u_char                           *uri_end;
30     u_char                           *uri_ext;
31     u_char                           *args_start;
32     u_char                           *request_start;
33     u_char                           *request_end;
34     u_char                           *method_end;
35     u_char                           *schema_start;
36     u_char                           *schema_end;
37     u_char                           *host_start;
38     u_char                           *host_end;
39     u_char                           *port_start;
40     u_char                           *port_end;
41     ... 
42 };

相關定義全在上面列出,

下面,我們先從請求行的獲取來解釋:

http請求行定義如下:

<method><request-URL><version>

首先,需要解析method:

  • method的定義類型為ngx_uint_t,Nginx中通過宏定義,給所有method賦予了不同的整型值。這里的method是Nginx解析了用戶請求之后得到的整型值,可以用來判斷method。
  • method_name則是ngx_str_t類型,內容是方法名字符串,其使用方式區別於常用str類型,這點一定要注意。
  • 還可以使用request_start和method_end指針取得方法名,request_start指向用戶請求的首地址,同時也是方法名的地址,method_end則指向方法名的最后一個字符(注意:這點與其他xxx_end指針不同)。

然后,解析URI:

  • ngx_str_t類型的uri指向用戶請求的URI。
  • uchar*類型的uri_start和uri_end也和method的用法類似。不同的是,method_end指向的是方法名的最后一個字符,而uri_end指向URI結束之后的下一個字符。也就是最后一個字符的下一個字符地址。Nginx中大部分的u_char*類型指針變量中的“xxx_start”和“xxx_end”都是這樣使用的。

  • ngx_str_t類型的extern類型指向用戶請求的文件的擴展名。例如:在訪問“GET /a.txt HTTP/1.1”時,extern的值為{len=3,data=“txt”}。

  • uri_ext指針指向的地質與extern.data相同unparsed_uri表示沒有進行uri解碼的原始請求。例如:“/a b”的原始請求為“/a%20b”(空格字符的編碼為%20)。

接着,解析URI參數:

  • Arg指向用戶請求中的URL參數。

  • Args_start和配合uri_end使用可以獲得URL的參數。

最后,協議版本的獲取:

  • http_protocol指向用戶請求中的HTTP的起始地址。

  • http_version是nginx解析過得協議版本,他的取值范圍如下:

  

#define NGX_HTTP_VERSION_9                 9
#define NGX_HTTP_VERSION_10                1000
#define NGX_HTTP_VERSION_11                1001

 

  • 建議使用http_version分析HTTP協議的版本。

  • 最后使用request_start 和 request_end可以獲取原始的用戶請求。

請求行的獲取到此結束,下面是請求頭的獲取:

head_in指向nginx收到的未經解析的HTTP頭部。這里先不關注。

ngx_http_request_t中ngx_http_headers_in_t類型的headers_in則儲存已經解析過得HTTP頭部。結構體定義如下:

 1 typedef struct {
 2     ngx_list_t                        headers;
 3 
 4     ngx_table_elt_t                  *host;
 5     ngx_table_elt_t                  *connection;
 6     ngx_table_elt_t                  *if_modified_since;
 7     ngx_table_elt_t                  *if_unmodified_since;
 8     ngx_table_elt_t                  *if_match;
 9     ngx_table_elt_t                  *if_none_match;
10     ngx_table_elt_t                  *user_agent;
11     ngx_table_elt_t                  *referer;
12     ngx_table_elt_t                  *content_length;
13     ngx_table_elt_t                  *content_type;
14 
15     ngx_table_elt_t                  *range;
16     ngx_table_elt_t                  *if_range;
17 
18     ngx_table_elt_t                  *transfer_encoding;
19     ngx_table_elt_t                  *expect;
20     ngx_table_elt_t                  *upgrade;
21 
22 #if (NGX_HTTP_GZIP)
23     ngx_table_elt_t                  *accept_encoding;
24     ngx_table_elt_t                  *via;
25 #endif
26 
27     ngx_table_elt_t                  *authorization;
28 
29     ngx_table_elt_t                  *keep_alive;
30 
31 #if (NGX_HTTP_X_FORWARDED_FOR)
32     ngx_array_t                       x_forwarded_for;
33 #endif
34 
35 #if (NGX_HTTP_REALIP)
36     ngx_table_elt_t                  *x_real_ip;
37 #endif
38 
39 #if (NGX_HTTP_HEADERS)
40     ngx_table_elt_t                  *accept;
41     ngx_table_elt_t                  *accept_language;
42 #endif
43 
44 #if (NGX_HTTP_DAV)
45     ngx_table_elt_t                  *depth;
46     ngx_table_elt_t                  *destination;
47     ngx_table_elt_t                  *overwrite;
48     ngx_table_elt_t                  *date;
49 #endif
50 
51     ngx_str_t                         user;
52     ngx_str_t                         passwd;
53 
54     ngx_array_t                       cookies;
55 
56     ngx_str_t                         server;
57     off_t                             content_length_n;
58     time_t                            keep_alive_n;
59 
60     unsigned                          connection_type:2;
61     unsigned                          chunked:1;
62     unsigned                          msie:1;
63     unsigned                          msie6:1;
64     unsigned                          opera:1;
65     unsigned                          gecko:1;
66     unsigned                          chrome:1;
67     unsigned                          safari:1;
68     unsigned                          konqueror:1;
69 } ngx_http_headers_in_t;

結構體中,后面定義了很多ngx_table_elt_t類型的指針變量,ngx_table_elt_t類型是Nginx定義的字典類型。內容都指向Nginx已經解析過得標准常見的HTTP頭部,可以直接使用。這些解析過了的頭部其實都儲存在headers鏈表中,所以,對於headers_in結構體中未定義的不常用的HTTP頭部,就需要遍歷headers鏈表,解析其值。

下面是一個解析的例子(源於《深入理解》一書):

下面嘗試在一個用戶請求中找到“Rpc-Description”頭部,首先判斷其值是否為“uploadFile”,再決定后續的服務器行為。

ngx_list_part_t *part = &r->headers_in.headers.part;
ngx_table_elt_t *header = part->elts;

//開始遍歷鏈表
for(i = 0;/*void*/;i++){
    //判斷是否到達鏈表中當前數組結尾
    if(i>=part->nelts){
        //是否還有下一個鏈表數組元素
        if(part->next == NULL){
            break;
        }
        //part設置為next來訪問下一個鏈表數組;header也指向下一個鏈表數組的首地址,設置i為0,表示從頭開始遍歷新的鏈表數組。
        part = part->next;
        header = part->elts;
        i=0;
    }
    //hash為0表示不是合法的頭部
    if(header[i].hash == 0){
        continue;
    }

    //判斷當前頭部是否是“Rpc-Description”,如果想要忽略大小寫,則應先用header[i].lowcase_key代替header[i].key.data,然后比較字符串。

    if(0 == ngx_strncasecmp(header[i].key.data,
                    (u_char*) "Rpc-Description",
                    header[i].key.len))
    {
        //判斷頭部的值是否是“uploadFile”
        if(0 == ngx_strncmp(header[i].key.data,
                "uploadFile",
                header[i].value.len))
                {
                //找到之后繼續處理
                }
    }
}

獲取headers的方法講完了,下面是獲取請求體(包體)的方法:

HTTP框架提供了一種方法可以異步接受包體:

ngx_int_t
ngx_http_read_client_request_body(ngx_http_request_t *r,
    ngx_http_client_body_handler_pt post_handler)

 

它的返回並不表示已經接受完成所有包體,只表示Nginx已經知道這個任務。所以一般返回為NGX_DONE(含義請自查,不贅述)。接受完所有包體之后,會調用post_handler函數指針指向的函數。其原型定義如下:

typedef void (*ngx_http_client_body_handler_pt)(ngx_http_request_t *r);

如果不想處理包體內容,可用如下方式將包體內容丟掉:

ngx_int_t rc = ngx_http_discard_request_body(r);
    if (rc != NGX_OK) {
        return rc;
    }

以上。歡迎留言討論,聯系方式:rwhsysu@163.com

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM