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