定义一个自己的http模板

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链表才能获得。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值