1.目标
一个HTTP请求会被许多个配置项控制,实际上这是因为一个HTTP请求可以被许多个HTTP模块同时处理。这样一来,肯定会有一个先后问题,也就是说,谁先处理请求谁的“权力”就更大
e.:
ngx_http_access_module模块的deny选项一旦得到满足后,Nginx就会决定拒绝来自某个IP的请求,后面的诸如root这种访问静态文件的处理方式是得不到执行的
实现一个简单的模板:
1)不希望模块对所有的HTTP请求起作用。
2)在nginx.conf文件中的http{}、server{}或者location{}块内定义mytest配置项,如果一个用户请求通过主机域名、URI等匹配上了相应的配置块,而这个配置块下又具有mytest配置项,那么希望mytest模块开始处理请求。(必须在HTTP框架定义的NGX_HTTP_CONTENT_PHASE阶段开始处理请求)
2.处理请求http
1.配置
static ngx_command_t ngx_http_mytest_commands[]={
ngx_string("mytest"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|
NGC_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF|NGX_CONF_NOARGS,
ngx_http_mytest,
NGC_HTTP_LOC_CONF_OFFSET,
0,
NULL},
ngx_null_command
};
static char* ngx_http_mytest(ngx_conf_t* cf,ngx_command_t*cmd,void*conf)
{
ngx_http_core_loc_conf_t*clcf;
clcf=ngx_http_conf_get_module_loc_conf(cf,ngx_http_core_module);
clcf->handler=ngx_http_mytest_handler;
return NGX_CONF_OK;
}
note:
1.ngx_http_mytest是ngx_command_t结构体中的set成员(完整定义为char*(*set(ngx_conf_t*cf,ngx_command_t*cmd,void*conf);)当在某个配置块中出现mytest配置项时
nginx将会调用ngx_http_mytest方法
2.ngx_http_core_loc_conf_t:首先找到mytest配置项所属的配置块,clcf看上去像是location块内的数据结构,其实不然,它可以是main、srv或者loc级别配置项,也就是说,在每个http{}和server{}内也都有一个ngx_http_core_loc_conf_t结构体
3. HTTP框架在处理用户请求进行到NGX_HTTP_CONTENT_PHASE阶段时,如果请求的主机域名、URI与mytest配置项所在的配置块相匹配,就将调用我们实现的ngx_http_mytest_handler方法处理这个请求
2.接口函数:
static ngx_http_module_t ngx_http_mytest_module_ctx={
NULL,
/*preconfiguration*/
NULL,
/*postconfiguration*/
NULL,
/*create main configuration*/
NULL,
/*init main configuration*/
NULL,
/*create server configuration*/
NULL,
/*merge server configuration*/
NULL,
/*create location configuration*/
NULL
/*merge location configuration*/
};//ctxs上下文
ngx_module_t ngx_http_mytest_module={
NGX_MODULE_V1,
&ngx_http_mytest_module_ctx,
/*module context*/
ngx_http_mytest_commands,
/*module directives*/
NGX_HTTP_MODULE,
/*module type*/
NULL,
/*init master*/
NULL,
/*init module*/
NULL,
/*init process*/
NULL,
/*init thread*/
NULL,
/*exit thread*/
NULL,
/*exit process*/
NULL,
/*exit master*/
NGX_MODULE_V1_PADDING
};
3.处理用户请求
介绍:handler成员的原型ngx_http_handler_pt
typedef ngx_int_t(*ngx_http_handler_pt)(ngx_http_request_t*r);
实际处理请求的方法ngx_http_mytest_handler将接收一个ngx_http_request_t类型的参数r,返回一个ngx_int_t
返回值:
/*节选与ngx_http_request.h*/
#define NGX_HTTP_INTERNAL_SERVER_ERROR 500
#define NGX_HTTP_NOT_IMPLEMENTED 501
#define NGX_HTTP_BAD_GATEWAY 502
#define NGX_HTTP_SERVICE_UNAVAILABLE 503
#define NGX_HTTP_GATEWAY_TIME_OUT 504
#define NGX_HTTP_INSUFFICIENT_STORAGE 507
e:
if(r->content_handler){
r->write_event_handler=ngx_http_request_empty_handler;
ngx_http_finalize_request(r,r->content_handler(r));}
return NGX_OK;
}
4.获取URI和参数
介绍一下ngx_http_request_t(节选uri和参数说明)
truct ngx_http_request_s {
...
ngx_uint_t method;
ngx_uint_t http_version;
ngx_str_t request_line;
ngx_str_t uri;
ngx_str_t args;
ngx_str_t exten;
ngx_str_t unparsed_uri;
ngx_str_t method_name;
ngx_str_t http_protocol;
u_cha *uri_start;
u_char *uri_end;
u_char *uri_ext;
u_char *args_start;
u_char *request_start;
u_char *request_end;
u_char *method_end;
u_char *schema_start;
u_char *schema_end;
...
};
1.方法名
method的类型是ngx_uint_t(无符号整型),它是Nginx忽略大小写等情形时解析完用户请求后得到的方法类型,其取值
#define NGX_HTTP_UNKNOWN 0x0001
#define NGX_HTTP_GET 0x0002
#define NGX_HTTP_HEAD 0x0004
#define NGX_HTTP_POST 0x0008
#define NGX_HTTP_PUT 0x0010
#define NGX_HTTP_DELETE 0x0020
#define NGX_HTTP_MKCOL 0x0040
#define NGX_HTTP_COPY 0x0080
#define NGX_HTTP_MOVE 0x0100
#define NGX_HTTP_OPTIONS 0x0200
#define NGX_HTTP_PROPFIND 0x0400
#define NGX_HTTP_PROPPATCH 0x0800
#define NGX_HTTP_LOCK 0x1000
#define NGX_HTTP_UNLOCK 0x2000
#define NGX_HTTP_PATCH 0x4000
#define NGX_HTTP_TRACE 0x8000
当需要了解用户请求中的HTTP方法时,应该使用r->method这个整型成员与以上15个宏进行比较,这样速度是最快的
method_start与method_end的用法也很简单,其中method_start指向用户请求的首地址,同时也是方法名的地址,method_end指向方法名的最后一个字符(注意,这点与其他xxx_end指针不同)
2.uri
ngx_str_t类型的uri成员指向用户请求中的URI。同理,u_char*类型的uri_start和uri_end也与request_start、method_end的用法相似,唯一不同的是,method_end指向方法名的最后一个字符,而uri_end指向URI结束后的下一个地址,也就是最后一个字符的下一个字符地址(HTTP框架的行为)这是大部分u_char*类型指针对”xxx_start”和”xxx_end”变量的用法。
ngx_str_t类型的exten成员指向用户请求的文件扩展名。例如,在访问”GET/a.txt HTTP/1.1″时,extern的值是{len=3,data=”txt”},而在访问”GET/a HTTP/1.1″时,extern的值为空,也就是{len=0,data=0x0}。
unparsed_uri表示没有进行URL解码的原始请求。例如,当uri为”/a b”时,unparsed_uri是”/a%20b”(空格字符做完编码后是%20)。
3.URL参数
arg指向用户请求中的URL参数。
args_start指向URL参数的起始地址,配合uri_end使用也可以获得URL参数。
4.协议版本
http_protocol指向用户请求中HTTP的起始地址。
http_version是Nginx解析过的协议版
最后,使用request_start和request_end可以获取原始的用户请求行。
协议版本
http_protocol的data成员指向用户请求中HTTP协议版本字符串的起始地址,len成员为协 议版本字符串长度。 http_version是Nginx解析过的协议版本,它的取值范围如下:
#define NGX_HTTP_VERSION_9 9
#define NGX_HTTP_VERSION_10 1000
#define NGX_HTTP_VERSION_11 1001
建议使用http_version分析HTTP的协议
5.获取http的头部
struct ngx_http_request_s {
…
ngx_buf_t *header_in;
ngx_http_headers_in_t headers_in; …
};
note:
header_in指向Nginx收到的未经解析的HTTP头部
ngx_http_headers_in_t类型的headers_in 则存储已经解析过的HTTP头部。下面介绍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 *if_match;
ngx_table_elt_t *if_none_match;
ngx_table_elt_t *user_agent;
ngx_table_elt_t *referer;
ngx_table_elt_t *content_length;
ngx_table_elt_t *content_range;
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;
ngx_table_elt_t *upgrade;
#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_X_FORWARDED_FOR)
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;
/*以下
7个标志位是
HTTP框架根据浏览器传来的“
useragent”头部,它们可用来判断浏览器的类型,值为
1时表示是相应的浏览器发来的请求,值为
0时则相反
*/
unsigned chunked:1;
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;
note:
1.所有解析过的 HTTP头部都在 headers链表中,可以使用 的遍历链表的方法来获取所有的 HTTP头部。注意,这里 headers链表的每一个元素都是介绍过的 ngx_table_elt_t成员
2.以下每个 ngx_table_elt_t成员都是 RFC2616规范中定义的 HTTP头部, 它们实际都指向 headers链表中的相应成员。注意,当它们为 NULL空指针时,表示没有解析到相应的 HTTP头部
3.user和 passwd是只有 ngx_http_auth_basic_module才会用到的成员,这里可以忽略
获取HTTP的包体
HTTP包体的长度有可能非常大,如果试图一次性调用并读取完所有的包体,那么多半 会阻塞Nginx进程。HTTP框架提供了一种方法来异步地接收包体
ngx_int_t ngx_http_read_client_request_body(ngx_http_request_t *r, ngx_http_client_body_handler_pt post_handler)
ngx_http_read_client_request_body是一个异步方法,调用它只是说明要求Nginx开始接收 请求的包体,并不表示是否已经接收完,当接收完所有的包体内容后,post_handler指向的回 调方法会被调用。因此,即使在调用了ngx_http_read_client_request_body方法后它已经返回, 也无法确定这时是否已经调用过post_handler指向的方法。换句话说, ngx_http_read_client_request_body返回时既有可能已经接收完请求中所有的包体(假如包体的 长度很小),也有可能还没开始接收包体
参数解析
1.包体接收完毕后的回调方法原型ngx_http_client_body_handler_pt是如何定义
typedef void (*ngx_http_client_body_handler_pt)(ngx_http_request_t *r);
有参数ngx_http_request_t*r,这个请求的信息都可以从r中获得。这样可以定义一 个方法void func(ngx_http_request_t*r),在Nginx接收完包体时调用它
void ngx_http_mytest_body_handler(ngx_http_request_t *r) {
…
}
ngx_http_mytest_body_handler的返回类型是void,Nginx不会根据返回值做一些 收尾工作,因此,我们在该方法里处理完请求时必须要主动调用ngx_http_finalize_request方法 来结束请求。
接收包体时可以这样写:
ngx_int_t rc = ngx_http_read_client_request_body(r, ngx_http_mytest_body_handler);
if (rc->= NGX_HTTP_SPECIAL_RESPONSE)
{ return rc;}
return NGX_DONE
抛弃包体
如果不想处理请求中的包体,那么可以调用ngx_http_discard_request_body方法将接收自 客户端的HTTP包体丢弃掉
ngx_int_t rc = ngx_http_discard_request_body(r);
if (rc != NGX_OK) {
return rc;
}
发送响应
请求处理完毕后,需要向用户发送HTTP响应,告知客户端Nginx的执行结果。HTTP响 应主要包括响应行、响应头部、包体三部分。发送HTTP响应时需要执行发送HTTP头部(发 送HTTP头部时也会发送响应行)和发送HTTP包体两步操作
发送头部
ngx_int_t ngx_http_send_header(ngx_http_request_t *r)
调用ngx_http_send_header时把ngx_http_request_t对象传给它即可,而ngx_http_send_header 的返回值是多样的
ngx_int_t rc = ngx_http_send_header(r);
if (rc == NGX_ERROR || rc > NGX_OK || r->header_only)
{
return rc;
}
头部信息
如同headers_in,ngx_http_request_t也有一个headers_out成员,用来设置响应中的HTTP头 部
struct ngx_http_request_s {
…
ngx_http_headers_in_t headers_in;
ngx_http_headers_out_t headers_out; …
};
ypedef struct {
ngx_list_t headers;
ngx_uint_t status;
ngx_str_t status_line;
//通过过滤模块 可以把它们加入待发送的网络包中
ngx_table_elt_t *server;
ngx_table_elt_t *date;
ngx_table_elt_t *content_length;
ngx_table_elt_t *content_encoding;
ngx_table_elt_t *location;
ngx_table_elt_t *refresh;
ngx_table_elt_t *last_modified;
ngx_table_elt_t *content_range;
ngx_table_elt_t *accept_ranges;
ngx_table_elt_t *www_authenticate;
ngx_table_elt_t *expires;
ngx_table_elt_t *etag;
ngx_str_t *override_charset;
/*ngx_http_set_content_type(r)方法帮助我们设置
Content-Type头部,这个方法会根据
URI中的文件扩展名并对应着
mime.type来设置
Content-Type值
*/
size_t content_type_len;
ngx_str_t content_type;
ngx_str_t charset;
u_char *content_type_lowcase;
ngx_uint_t content_type_hash;
ngx_array_t cache_control;
off_t content_length_n;
off_t content_offset;
time_t date_time;
time_t last_modified_time;
} ngx_http_headers_out_t;
向headers链表中添加自定义的HTTP的头部
ngx_table_elt_t* h = ngx_list_push(&r->headers_out.headers);
if (h == NULL) {
return NGX_ERROR;
}
h->hash = 1;
h->key.len = sizeof("TestHead") - 1;
h->key.data = (u_char *) "TestHead";
h->value.len = sizeof("TestValue") - 1; h->value.data = (u_char *) "TestValue";
结果:TestHead: TestValue\r\n
拓展阅读
1.NGX_HTTP_CONTENT_PHASE阶段
typedef enum{
//在接收到完整的HTTP头部后处理的HTTP阶段
NGX_HTTP_POST_READ_PHASE=0,
/*在还没有查询到URI匹配的location前,这时rewrite重写URL也作为一个独立的HTTP阶段*/
NGX_HTTP_SERVER_REWRITE_PHASE,
/*根据URI寻找匹配的location,这个阶段通常由ngx_http_core_module模块实现,不建议其他HTTP模块重新定义这一阶段的行为*/
NGX_HTTP_FIND_CONFIG_PHASE,
/*在NGX_HTTP_FIND_CONFIG_PHASE阶段之后重写URL的意义与NGX_HTTP_SERVER_REWRITE_PHASE阶段显然是不同的,因为这两者会导致查找到不同的location块(location是与URI进行匹配的)*/
NGX_HTTP_REWRITE_PHASE,
/*这一阶段是用于在rewrite重写URL后重新跳到NGX_HTTP_FIND_CONFIG_PHASE阶段,找到与新的URI匹配的location。所以,这一阶段是无法由第三方HTTP模块处理的,而仅由ngx_http_core_module模块使用*/
NGX_HTTP_POST_REWRITE_PHASE,
//处理NGX_HTTP_ACCESS_PHASE阶段前,HTTP模块可以介入的处理阶段
NGX_HTTP_PREACCESS_PHASE,
/*这个阶段用于让HTTP模块判断是否允许这个请求访问Nginx服务器
NGX_HTTP_ACCESS_PHASE,
/*当NGX_HTTP_ACCESS_PHASE阶段中HTTP模块的handler处理方法返回不允许访问的错误码时(实际是NGX_HTTP_FORBIDDEN或者NGX_HTTP_UNAUTHORIZED),这个阶段将负责构造拒绝服务的用户响应。所以,这个阶段实际上用于给NGX_HTTP_ACCESS_PHASE阶段收尾*/
NGX_HTTP_POST_ACCESS_PHASE,
/*这个阶段完全是为了try_files配置项而设立的。当HTTP请求访问静态文件资源时,try_files配置项可以使这个请求顺序地访问多个静态文件资源,如果某一次访问失败,则继续访问try_files中指定的下一个静态资源。另外,这个功能完全是在NGX_HTTP_TRY_FILES_PHASE阶段中实现的*/
NGX_HTTP_TRY_FILES_PHASE,
//用于处理HTTP请求内容的阶段,这是大部分HTTP模块最喜欢介入的阶段
NGX_HTTP_CONTENT_PHASE,
/*处理完请求后记录日志的阶段。例如,ngx_http_log_module模块就在这个阶段中加入了一个handler处理方法,使得每个HTTP请求处理完毕后会记录access_log日志*/
NGX_HTTP_LOG_PHASE
}ngx_http_phases;
2.ngx_http_finalize_request决定了ngx_http_mytest_handler如何起作用
❑NGX_OK:表示成功。Nginx将会继续执行该请求的后续动作(如执行subrequest或撤销这个请求)。
❑NGX_DECLINED:继续在NGX_HTTP_CONTENT_PHASE阶段寻找下一个对于该请求感兴趣的HTTP模块来再次处理这个请求。
❑NGX_DONE:表示到此为止,同时HTTP框架将暂时不再继续执行这个请求的后续部分。事实上,这时会检查连接的类型,如果是keepalive类型的用户请求,就会保持住HTTP连接,然后把控制权交给Nginx。这个返回码很有用,考虑以下场景:在一个请求中我们必须访问一个耗时极长的操作(比如某个网络调用),这样会阻塞住Nginx,又因为我们没有把控制权交还给Nginx,而是在ngx_http_mytest_handler中让Nginx worker进程休眠了(如等待网络的回包),所以,这就会导致Nginx出现性能问题,该进程上的其他用户请求也得不到响应。可如果我们把这个耗时极长的操作分为上下两个部分(就像Linux内核中对中断处理的划分),上半部分和下半部分都是无阻塞的(耗时很少的操作),这样,在ngx_http_mytest_handler进入时调用上半部分,然后返回NGX_DONE,把控制交还给Nginx,从而让Nginx继续处理其他请求。在下半部分被触发时(这里不探讨具体的实现方式,事实上使用upstream方式做反向代理时用的就是这种思想),再回调下半部分处理方法,这样就可以保证Nginx的高性能特性了。如果需要彻底了解NGX_DONE的意义,其中还涉及请求的引用计数内容。
❑NGX_ERROR:表示错误。这时会调用ngx_http_terminate_request终止请求。如果还有POST子请求,那么将会在执行完POST请求后再终止本次请求。
3.获取header_in的数据
3.1headers是一个ngx_list_t链表,它 存储着解析过的所有HTTP头部,链表中的元素都是ngx_table_elt_t类型。下面尝试在一个用 户请求中找到“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))
{
// 判断这个HTTP头部的值是否是“uploadFile”
if (0 == ngx_strncmp(header[i].value.data, "uploadFile",
header[i].value.len))
{
// 找到了正确的头部,继续向下执行
}
}
}
3.2对于常见的HTTP头部,直接获取r->headers_in中已经由HTTP框架解析过的成员即可, 而对于不常见的HTTP头部,需要遍历r->headers_in.headers链表才能获得。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/129617.html