开发一个HTTP模块
Posted Flytiger1220
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了开发一个HTTP模块相关的知识,希望对你有一定的参考价值。
1. 前言
首先来分析一下HTTP模块是怎样介入nginx的。
当master进程fork出若干个workr子进程后,每一个worker子进程都会在自己的for死循环中不断调用事件模块:
for ( ;; )
....
ngx_process_events_and_timers(cycle); /* 调用事件模块 */
....
事件模块检测是否有TCP连接请求,当收到一个SYN包后,由事件模块建立一条TCP连接。连接建立成功后,交由HTTP框架处理,HTTP框架负责接收HTTP头部,并依据头部信息将HTTP请求分发到不同的HTTP模块。
最常见的分发策略就是依据HTTP头部的URI和配置文件nginx.conf里的location配置项的匹配度来决定怎样分发。如果配置文件里有例如以下配置块:
location /name
test;
并如果server域名为www.nestle.com。那么,当client发来的URL请求为“www.nestle.com/name”时,HTTP框架就会依据配置文件调用test模块,将这个HTTP请求交给test模块处理。
HTTP模块在处理完请求后,会自己主动依次调用HTTP过滤模块对准备返回的响应信息做预处理,比方是否压缩。下面是HTTP模块调用的大致流程图:
2. HTTP模块数据结构
2.1 ngx_module_t结构体
HTTP模块有一个结构体ngx_module_t,它的定义例如以下(省略了一些不常用的参数):
typedef struct ngx_module_s ngx_module_t;
struct ngx_module_s
ngx_uint_t ctx_index; /* 当前模块在同类模块中的序号 */
ngx_max_module = 0;
for (i = 0; ngx_module[i]; i++)
ngx_module[i] -> index = ngx_max_module++;
ngx_uint_t index; /* 当前模块在ngx_modules数组中的序号 */
ngx_uint_t spare0; /* 保留 */
ngx_uint_t spare1; /* 保留 */
ngx_uint_t spare2; /* 保留 */
ngx_uint_t spare3; /* 保留 */
ngx_uint_t version; /* 模块版本号,眼下为1 */
void *ctx; /* ctx用于指向一类模块的上下文结构体,指向特定类型模块的公共
接口,在http模块中,ctx需要指向ngx_http_module_t结构体 */
ngx_command_t *commands; /* 用于处理配置文件nginx.conf中的配置项 */
ngx_uint_t type; /* 当前模块类型,http模块的类型是NGX_HTTP_MODULE */
/* 下面7个函数指针表示7个运行点,这些运行点将在Nginx启动和退出过程中被调用
* 假设不须要则设置为NULL
*/
ngx_int_t (*init_master)(ngx_log_t *log); /* 字面意思上是master启动时调用
init_master,但框架从未调用过init_master,这里设为NULL */
ngx_int_t (*init_module)(ngx_cycle_t *cycle); /* 启动worker子进程前调用,用来初始化
所有模块 */
ngx_int_t (*init_process)(ngx_cycle_t *cycle);/* 启动worker子进程后调用 */
ngx_int_t (*init_thread)(ngx_cycle_t *cycle); /* nginx暂不支持多线程模式,init_thread
也从未被调用,设为NULL */
void (*exit_thread)(ngx_cycle_t *cycle); /* 同init_thread,exit_thread
也从未被调用,设为NULL */
void (*exit_process)(ngx_cycle_t *cycle);/* worker子进程退出前调用 */
void (*exit_master)(ngx_cycle_t *cycle); /* master进程退出前调用 */
/* 下面全为保留字段 */
uintptr_t spare_hook0;
uintptr_t spare_hook1;
uintptr_t spare_hook2;
uintptr_t spare_hook3;
uintptr_t spare_hook4;
uintptr_t spare_hook5;
uintptr_t spare_hook6;
uintptr_t spare_hook7;
;
2.2 ngx_module_t结构体说明
- 对于以下回调方法:init_module、init_process、exit_process、exit_master,调用它们的是nginx框架代码。也就是说,这四个回调方法与HTTP框架无关;即使nginx.conf中没有配置http…这种开启HTTP功能的配置项,这些回调方法仍然会被调用。
- HTTP模块的type字段必须设置为NGX_HTTP_MODULE,以表明此模块为HTTP模块。
- HTTP模块的ctx指针必须指向ngx_http_module_t结构体,它表示HTTP模块所定义的通用接口;
- HTTP模块的commands数组由若干ngx_command_t链接而成。当Nginx在解析配置文件的一个配置项时,会遍历全部模块的commands数组,以找到对该配置项感兴趣的ngx_command_t结构体,从而找到对应的模块。数组结尾用ngx_null_command表示。
2.3 ngx_http_module_t结构体
HTTP框架在读取、重载配置文件时定义了由ngx_http_module_t接口描述的8个阶段,HTTP框架在启动过程中会在每个阶段调用ngx_http_module_t中相应的方法。
ngx_http_module_t定义例如以下:
typedef struct
ngx_int_t (*preconfiguration)(ngx_conf_t *cf); /* 解析配置文件前调用 */
ngx_int_t (*postconfiguration)(ngx_conf_t *cf); /* 解析完配置文件后调用 */
void *(*create_main_conf)(ngx_conf_t *cf); /* 创建存储直属于http的配置项的结构体 */
char *(*init_main_conf)(ngx_conf_t *cf, void *conf); /* 初始化main级别配置项 */
void *(*create_srv_conf)(ngx_conf_t *cf); /* 创建存储直属于srv的配置项的结构体 */
char *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, void *conf); /* 合并main级别和srv
级别的同名配置项 */
void *(*create_loc_conf)(ngx_conf_t *cf); /* 创建存储直属于loc的配置项的结构体 */
char *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf); /* 合并srv级别和loc级
别的同名配置项 */
ngx_http_module_t;
注意:
HTTP框架会在启动过程中调用上述结构体中的各个阶段函数,设为NULL则不会调用它们;
nginx启动过程中,以上八个阶段的调用顺序和定义的顺序是不同的;
2.4 ngx_command_t结构体
ngx_command_t结构体定义例如以下:
typedef struct ngx_command_s ngx_command_t;
struct ngx_command_s
ngx_str_t name; /* 配置项名称 */
ngx_uint_t type; /* 配置项类型,包含该配置项能够出现的位置和能够携带參数的个数;
例如,出现在server或location中,以及它可以携带的参数个数 */
char *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); /* 出现
name配置项后,调用此方法解析配置项參数 */
ngx_uint_t conf; /* 配置文件里的偏移量,确定将该配置项放入哪个存储结构体中 */
ngx_uint_t offset; /* 将该配置项放在存储结构体的哪个字段,与conf结合使用 */
void *post; /* 配置项读取后的处理方法 */
;
ngx_null_command只是一个空的ngx_command_t,如下所示:
#define ngx_null_command ngx_null_string, 0, NULL, 0, 0, NULL
3. 定义自己的HTTP模块
3.1 新建源文件
在/home/nginx/src/test/下,新建一个源文件,ngx_http_tiger_module.c,内容如下:
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
static ngx_int_t ngx_http_tiger_handler(ngx_http_request_t *r);
static char *ngx_http_tiger(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
//定义ngx_module_t结构体中的commands数组
static ngx_command_t ngx_http_tiger_commands[] =
ngx_string("tiger"), //配置项名称
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF|NGX_CONF_NOARGS, //类型 type
ngx_http_tiger, //set 函数,出现tiger配置时,ngx_http_tiger会被调用
NGX_HTTP_LOC_CONF_OFFSET,//偏移
0,
NULL
,
ngx_null_command// 以一个空的ngx_command_t作为结尾
;
//回调函数,出现tiger配置项后调用
static char *ngx_http_tiger(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
ngx_http_core_loc_conf_t *clcf;
//找到tiger所属配置块
clcf=ngx_http_conf_get_module_loc_conf(cf,ngx_http_core_module);
//主机域名、URI与mytest所在配置块匹配时,将调用handler
clcf->handler=ngx_http_tiger_handler;
return NGX_CONF_OK;
//定义ngx_module_t结构体中的ctx接口,模块上下文
//如果没有什么工作是必须在HTTP框架初始化时完成,则不必实现8个回调
static ngx_http_module_t ngx_http_tiger_module_ctx=
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL
;
//定义完上述两个最重要的结构体成员ngx_command_t和ngx_http_module_t之后,如今模块的定义例如以下:
ngx_module_t ngx_http_tiger_module =
NGX_MODULE_V1, // 0,0,0,0,0,0,1
&ngx_http_tiger_module_ctx,
ngx_http_tiger_commands,
NGX_HTTP_MODULE, // 定义模块类型
/* Nginx在启动和退出时会调用以下7个回调方法 */
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NGX_MODULE_V1_PADDING, // 0,0,0,0,0,0,0,0,保留字段
;
//处理实际用户请求
//请求的全部信息都存入ngx_http_request_t结构体中
static ngx_int_t ngx_http_tiger_handler(ngx_http_request_t *r)
//请求方法必须是GET或者HEAD,否则返回405 \\NOT ALLOWED
if(!(r->method &(NGX_HTTP_GET|NGX_HTTP_HEAD)))
return NGX_HTTP_NOT_ALLOWED;
//丢弃请求中的包体
ngx_int_t rc=ngx_http_discard_request_body(r);
if(NGX_OK!=rc)
return rc;
//设置返回的Content-Type ngx_string是一个宏可以初始化data字段和len字段
ngx_str_t type=ngx_string("text/plain");
ngx_str_t response=ngx_string("Hello World");
//响应包体内容和状态码设置
r->headers_out.status=NGX_HTTP_OK;
r->headers_out.content_length_n=response.len;
r->headers_out.content_type=type;
//发送http头部
rc=ngx_http_send_header(r);
if(rc==NGX_ERROR || rc>NGX_OK || r->header_only)
return rc;
//构造ngx_buf_t结构体准备发送报文
ngx_buf_t *b;
b=ngx_create_temp_buf(r->pool,response.len);
if(NULL==b)
return NGX_HTTP_INTERNAL_SERVER_ERROR;
//拷贝响应报文
ngx_memcpy(b->pos,response.data,response.len);
//设置好last指针,指向数据末尾
b->last=b->pos+response.len;
//声明这是最后一块缓冲区
b->last_buf=1;
//构造发送时的ngx_chain_t结构体
ngx_chain_t out;
out.buf=b;
out.next=NULL;
//发送响应,结束后HTTP框架会调用ngx_http_finalize_request方法结束请求
return ngx_http_output_filter(r,&out);
3.2 新建config文件
在和源文件同样文件夹下创建一个名为config的文件,文件内容例如以下:
ngx_addon_name=ngx_http_tiger_module
HTTP_MODULES="$HTTP_MODULES ngx_http_tiger_module"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_tiger_module.c"
ngx_addon_name:模块名称
HTTP_MODULES:保存全部的HTTP模块名称,以空格符分隔
NGX_ADDON_SRCS:新增模块的源码路径,变量ngx_addon_dir稍后说明
3.3 编译HTTP模块到nginx中
1、在configure时加入自己定义模块的路径:
./configure --add-module=/home/nginx/src/test
当中,路径/home/nginx/src/test就相应了config文件里的ngx_addon_dir变量。如今configure程序便知道了我们定义的模块的绝对路径。
2、make&make install
3、在/usr/local/nginx/conf/nginx.conf配置文件里加入须要的配置项:
location /
tiger;
#root html;
#index index.html index.htm;
4、启动nginx,并在浏览器访问nginx所在主机的IP:
4. 遇到的问题
问题描述:
自定义http_module之后,执行了如下编译命令./configure --add-module=/home/nginx/src/test,启动nginx一直报nginx.conf的
tiger;
这一行有语法错误:nginx: [emerg] unknown directive tiger
问题解决思路:
1、执行nginx -V | grep adbcc查看nginx已安装的模块,发现没有自定义的模块;
2、查看nginx安装目录/objs/ngx_modules.c文件,发现里面也没有自定义的模块;
3、尝试修改nginx.conf,发现还是不行;
问题原因:
最终原因是安装完nginx之后,编译安装指定了特定目录: ./configure --prefix=/usr/local/webserver/nginx;
在执行./configure --add-module=/home/nginx/src/test时,将模块安装到nginx的默认安装目录/usr/local/nginx中了,启动/usr/local/webserver/nginx这里的配置,也就无法加载到自定义的http模块;
解决思路:
将以上两个编译命令合并执行:
./configure --prefix=/usr/local/webserver/nginx --add-module=/home/nginx/src/test
重新启动nginx即可
5. 参考
【Nginx】开发一个简单的HTTP模块
nginx查看已安装模块
如何查看Nginx安装了哪些模块
以上是关于开发一个HTTP模块的主要内容,如果未能解决你的问题,请参考以下文章