编写nginx http handler模块以便开发自己模块,本文提供hello编写到编译的详细步骤 , 文章最后提供整个示例代码
示例:
static ngx_command_t ngx_http_mytest_commands[] =
{
{
ngx_string("mytest"),
NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_HTTP_LMT_CONF | NGX_CONF_NOARGS,
ngx_http_mytest,
NGX_HTTP_LOC_CONF_OFFSET,
0,
NULL
},
ngx_null_command
};
在nginx.conf 中编写的配置项 mytest 来说, nginx 首先会遍历所有的模块(modules),而对于每个模块, 会遍历他所对应的ngx_command_t 数组, 试图找到关于我们的配置项mytest 的解析方式。
command中用于处理配置项参数的set 方法,函数名格式写法ngx_http_xxxxx,如下所示:
static char *
ngx_http_mytest(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_http_core_loc_conf_t *clcf;
//首先找到mytest配置项所属的配置块,clcf貌似是location块内的数据
//结构,其实不然,它可以是main、srv或者loc级别配置项,也就是说在每个
//http{}和server{}内也都有一个ngx_http_core_loc_conf_t结构体
clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
//http框架在处理用户请求进行到NGX_HTTP_CONTENT_PHASE阶段时,如果
//请求的主机域名、URI与mytest配置项所在的配置块相匹配,就将调用我们
//实现的ngx_http_mytest_handler方法处理这个请求
clcf->handler = ngx_http_mytest_handler;
return NGX_CONF_OK;
}
关于ngx_http_conf_get_module_loc_conf 的定义可以参考: http://lxr.nginx.org/source/src/http/ngx_http_config.h#0065 本质: 就是设置ngx_http_mytest_handler, 匹配项被选中的时候, 应该如何解析。
ngx_command_t的定义,位于src/core/ngx_conf_file.h中。
struct ngx_command_s {
ngx_str_t name;
ngx_uint_t type;
char *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
ngx_uint_t conf;
ngx_uint_t offset;
void *post;
};
name: 配置指令的名称。
type: 该配置的类型,其实更准确一点说,是该配置指令属性的集合。nginx提供了很多预定义的属性值(一些宏定义),通过逻辑或运算符可组合在一起,形成对这个配置指令的详细的说明。下面列出可在这里使用的预定义属性值及说明。
NGX_CONF_NOARGS:配置指令不接受任何参数。
NGX_CONF_TAKE1:配置指令接受1个参数。
NGX_CONF_TAKE2:配置指令接受2个参数。
NGX_CONF_TAKE3:配置指令接受3个参数。
NGX_CONF_TAKE4:配置指令接受4个参数。
NGX_CONF_TAKE5:配置指令接受5个参数。
NGX_CONF_TAKE6:配置指令接受6个参数。
NGX_CONF_TAKE7:配置指令接受7个参数。
可以组合多个属性,比如一个指令即可以不填参数,也可以接受1个或者2个参数。那么就是NGX_CONF_NOARGS|NGX_CONF_TAKE1|NGX_CONF_TAKE2。如果写上面三个属性在一起,你觉得麻烦,那么没有关系,nginx提供了一些定义,使用起来更简洁。
NGX_CONF_TAKE12:配置指令接受1个或者2个参数。
NGX_CONF_TAKE13:配置指令接受1个或者3个参数。
NGX_CONF_TAKE23:配置指令接受2个或者3个参数。
NGX_CONF_TAKE123:配置指令接受1个或者2个或者3参数。
NGX_CONF_TAKE1234:配置指令接受1个或者2个或者3个或者4个参数。
NGX_CONF_1MORE:配置指令接受至少一个参数。
NGX_CONF_2MORE:配置指令接受至少两个参数。
NGX_CONF_MULTI: 配置指令可以接受多个参数,即个数不定。
NGX_CONF_BLOCK:配置指令可以接受的值是一个配置信息块。也就是一对大括号括起来的内容。里面可以再包括很多的配置指令。比如常见的server指令就是这个属性的。
NGX_CONF_FLAG:配置指令可以接受的值是”on”或者”off”,最终会被转成bool值。
NGX_CONF_ANY:配置指令可以接受的任意的参数值。一个或者多个,或者”on”或者”off”,或者是配置块。 最后要说明的是,无论如何,nginx的配置指令的参数个数不可以超过NGX_CONF_MAX_ARGS个。目前这个值被定义为8,也就是不能超过8个参数值。
下面介绍一组说明配置指令可以出现的位置的属性。
NGX_DIRECT_CONF:可以出现在配置文件中最外层。例如已经提供的配置指令daemon,master_process等。
NGX_MAIN_CONF: http、mail、events、error_log等。
NGX_ANY_CONF: 该配置指令可以出现在任意配置级别上。 对于我们编写的大多数模块而言,都是在处理http相关的事情,也就是所谓的都是NGX_HTTP_MODULE,对于这样类型的模块,其配置可能出现的位置也是分为直接出现在http里面,以及其他位置。
NGX_HTTP_MAIN_CONF: 可以直接出现在http配置指令里。
NGX_HTTP_SRV_CONF: 可以出现在http里面的server配置指令里。
NGX_HTTP_LOC_CONF: 可以出现在http server块里面的location配置指令里。
NGX_HTTP_UPS_CONF: 可以出现在http里面的upstream配置指令里。
NGX_HTTP_SIF_CONF: 可以出现在http里面的server配置指令里的if语句所在的block中。
NGX_HTTP_LMT_CONF: 可以出现在http里面的limit_except指令的block中。
NGX_HTTP_LIF_CONF: 可以出现在http server块里面的location配置指令里的if语句所在的block中。
set: 这是一个函数指针,当nginx在解析配置的时候,如果遇到这个配置指令,将会把读取到的值传递给这个函数进行分解处理。因为具体每个配置指令的值如何处理,只有定义这个配置指令的人是最清楚的。来看一下这个函数指针要求的函数原型。
char *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
这部分的代码, 是用于http框架的, 相当于http框架的回掉函数, 由于这里并不需要框架做任何操作,格式ngx_http_xxxxx_module_ctx。
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 */
};
示例: 格式ngx_http_xxxx_module
只需要简单的设置3个项目: ctx(指向模块的上下文), commands, type(模块类型)
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
};
来参考一下深入理解nginx书中对http模块数据结构的理解:
定义 HTTP 模块方式很简单,例如:
ngx_module_t ngx_http_mytest_module;
其中,ngx_module_t 是一个 Nginx 模块的数据结构(详见 8.2 节)。下面来分析一下
Nginx 模块中所有的成员,如下所示:
typedef struct ngx_module_s ngx_module_t;
struct ngx_module_s {
/* 下面的 ctx_index、index、spare0、spare1、spare2、spare3、version 变量不需要在定义时赋值,
可以用 Nginx 准备好的宏 NGX_MODULE_V1 来定义,它已经定义好了这 7 个值。
#define NGX_MODULE_V1 0, 0, 0, 0, 0, 0, 1
对于一类模块(由下面的 type 成员决定类别)而言,ctx_index 表示当前模块在这类模块中的序号。这
个成员常常是由管理这类模块的一个 Nginx 核心模块设置的,对于所有的 HTTP 模块而言,ctx_index 是由核心模
块 ngx_http_module 设置的。ctx_index 非常重要,Nginx 的模块化设计非常依赖于各个模块的顺序,它们既用
于表达优先级,也用于表明每个模块的位置,借以帮助 Nginx 框架快速获得某个模块的数据(HTTP 框架设置 ctx_
index 的过程参见 10.7 节)*/
ngx_uint_t ctx_index;
/*index 表示当前模块在 ngx_modules 数组中的序号。注意,ctx_index 表示的是当前模块在一类模
块中的序号,而 index 表示当前模块在所有模块中的序号,它同样关键。Nginx 启动时会根据 ngx_modules 数组
设置各模块的 index 值。例如:
ngx_max_module = 0;
for (i = 0; ngx_modules[i]; i++) {
ngx_modules[i]->index = ngx_max_module++;
}
*/
ngx_uint_t index;
//spare 系列的保留变量,暂未使用
ngx_uint_t spare0;
ngx_uint_t spare1;
ngx_uint_t spare2;
ngx_uint_t spare3;
// 模块的版本,便于将来的扩展。目前只有一种,默认为 1
ngx_uint_t version;
/*ctx 用于指向一类模块的上下文结构体,为什么需要 ctx 呢?因为前面说过,Nginx 模块有许多种类,
不同类模块之间的功能差别很大。例如,事件类型的模块主要处理 I/O 事件相关的功能,HTTP 类型的模块主要处理
HTTP 应用层的功能。这样,每个模块都有了自己的特性,而 ctx 将会指向特定类型模块的公共接口。例如,在 HTTP
模块中,ctx 需要指向 ngx_http_module_t 结构体 */
void *ctx;
//commands 将处理 nginx.conf 中的配置项,详见第 4 章
ngx_command_t *commands;
/*type 表示该模块的类型,它与 ctx 指针是紧密相关的。在官方 Nginx 中,它的取值范围是以下 5 种 :
NGX_HTTP_MODULE、NGX_CORE_MODULE、NGX_CONF_MODULE、NGX_EVENT_MODULE、NGX_MAIL_MODULE。这
5 种模块间的关系参考图 8-2。实际上,还可以自定义新的模块类型 */
ngx_uint_t type;
/* 在 Nginx 的启动、停止过程中,以下 7 个函数指针表示有 7 个执行点会分别调用这 7 种方法(参见
8.4 节~ 8.6 节)。对于任一个方法而言,如果不需要 Nginx 在某个时刻执行它,那么简单地把它设为 NULL 空指针
即可 */
/* 虽然从字面上理解应当在 master 进程启动时回调 init_master,但到目前为止,框架代码从来不会
调用它,因此,可将 init_master 设为 NULL */
ngx_int_t (*init_master)(ngx_log_t *log);
/*init_module 回调方法在初始化所有模块时被调用。在 master/worker 模式下,这个阶段将在启动
worker 子进程前完成 */
ngx_int_t (*init_module)(ngx_cycle_t *cycle);
/* init_process 回调方法在正常服务前被调用。在 master/worker 模式下,多个 worker 子进程已经产
生,在每个 worker 进程的初始化过程会调用所有模块的 init_process 函数 */
ngx_int_t (*init_process)(ngx_cycle_t *cycle);
/* 由于 Nginx 暂不支持多线程模式,所以 init_thread 在框架代码中没有被调用过,设为 NULL*/
ngx_int_t (*init_thread)(ngx_cycle_t *cycle);
// 同上,exit_thread 也不支持,设为 NULL
void (*exit_thread)(ngx_cycle_t *cycle);
/* exit_process 回调方法在服务停止前调用。在 master/worker 模式下,worker 进程会在退出前调用它 */
void (*exit_process)(ngx_cycle_t *cycle);
// exit_master 回调方法将在 master 进程退出前被调用
void (*exit_master)(ngx_cycle_t *cycle);
/* 以下 8 个 spare_hook 变量也是保留字段,目前没有使用,但可用 Nginx 提供的 NGX_MODULE_V1_
PADDING 宏来填充。看一下该宏的定义:#define NGX_MODULE_V1_PADDING 0, 0, 0, 0, 0, 0, 0, 0*/
uintptr_t spare_hook0;
uintptr_t spare_hook1;
uintptr_t spare_hook2;
第 3 章 开发一个简单的 HTTP 模块 87
uintptr_t spare_hook3;
uintptr_t spare_hook4;
uintptr_t spare_hook5;
uintptr_t spare_hook6;
uintptr_t spare_hook7;
};
定义一个 HTTP 模块时,务必把 type 字段设为 NGX_HTTP_MODULE。
对于下列回调方法 :init_module、init_process、exit_process、exit_master,调用它们的 是 Nginx 的框架代码。换句话说,这 4 个回调方法与 HTTP 框架无关,即使 nginx.conf 中没 有配置 http {...} 这种开启 HTTP 功能的配置项,这些回调方法仍然会被调用。因此,通常 开发 HTTP 模块时都把它们设为 NULL 空指针。这样,当 Nginx 不作为 Web 服务器使用时, 不会执行 HTTP 模块的任何代码。
定义 HTTP 模块时,最重要的是要设置 ctx 和 commands 这两个成员。对于 HTTP 类型 的模块来说,ngx_module_t 中的 ctx 指针必须指向 ngx_http_module_t 接口(HTTP 框架的要 求)。下面先来分析 ngx_http_module_t 结构体的成员。
HTTP 框架在读取、重载配置文件时定义了由 ngx_http_module_t 接口描述的 8 个阶段, HTTP 框架在启动过程中会在每个阶段中调用 ngx_http_module_t 中相应的方法。当然,如果 ngx_http_module_t 中的某个回调方法设为 NULL 空指针,那么 HTTP 框架是不会调用它的。
config文件和编写c文件放在同一个文件下:比如我放在:
[root@hadoop2 nginx-1.13.8]# pwd
/root/nginx-1.13.8
[root@hadoop2 nginx-1.13.8]# cd mytesthttp/
[root@hadoop2 mytesthttp]# ll
total 16
-rw-r--r-- 1 root root 163 Jan 18 09:56 config
-rw-r--r-- 1 root root 8915 Jan 18 15:52 ngx_http_mytest_module.c
[root@hadoop2 mytesthttp]#
其中config文件内容基本下面的形式:
ngx_addon_name=ngx_http_mytest_module
HTTP_MODULES="$HTTP_MODULES ngx_http_mytest_module"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_mytest_module.c"
$ngx_addon_dir 这个值是执行configure 时候添加的选线--add-module提供的:--add-module=/root/nginx-1.13.8/mytesthttp
执行nginx目录下configure生成makefile,--prefix=/root/nginx-1.13.8/bin提供nginx的安装路径,随便设置成你自己的想安装到的目录都是可以的。
./configure \
--prefix=/root/nginx-1.13.8/bin \
--user=root\
--group=root\
--add-module=/root/nginx-1.13.8/mytesthttp \
--with-http_stub_status_module \
--with-http_flv_module \
--with-http_stub_status_module \
--with-http_gzip_static_module \
--with-http_gunzip_module \
--with-pcre \
--with-debug
添加内容参考如下:
server {
listen 8017;
server_name localhost;
access_log logs/get.log main;
#access_log off;
location /
{
root html;
index index.html index.htm;
}
location /hello
{
mytest;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html
{
root html;
}
}
访问示例:
http://192.168.0.153:8017/hello/mytest
整体code参考:(结合文章理解)
#include
#include
#include
#define PB_SIZE (1024 * 2)
#define CONTENT_TYPE "application/json;charset=GB2312"
static char* ngx_http_mytest(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static ngx_int_t ngx_http_mytest_handler(ngx_http_request_t *r);
static void ngx_http_read_client_request_body_handler(ngx_http_request_t *r);
static ngx_command_t ngx_http_mytest_commands[] =
{
{
ngx_string("mytest"),
NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_HTTP_LMT_CONF | NGX_CONF_NOARGS,
ngx_http_mytest,
NGX_HTTP_LOC_CONF_OFFSET,
0,
NULL
},
ngx_null_command
};
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 */
};
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
};
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;
}
static ngx_int_t ngx_http_mytest_handler(ngx_http_request_t *r)
{
static char uri[PB_SIZE];
static char decode[PB_SIZE];
static char args[PB_SIZE];
char* src;
char* dst;
int status=NGX_HTTP_OK;
//int reply_len=0;
//char *reply=0;
ngx_int_t rc;
ngx_chain_t out;
//post handle
if ((r->method & (NGX_HTTP_POST|NGX_HTTP_HEAD)))
{
//get body
rc = ngx_http_read_client_request_body(r, ngx_http_read_client_request_body_handler);
if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
return rc;
}
return NGX_DONE;
}
//get handle
else if ((r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD)))
{
//get uri
if (r->uri.len>=PB_SIZE)
return NGX_HTTP_NOT_ALLOWED;
ngx_memcpy(uri,r->uri.data,r->uri.len);
uri[r->uri.len]=0;
src = uri;
dst = decode;
ngx_unescape_uri((u_char**)&dst, (u_char**)&src, r->uri.len, 0);
ngx_memcpy(uri,decode,dst - decode);
uri[dst - decode] = '\0';
//get args
if (r->args.len>=PB_SIZE)
return NGX_HTTP_NOT_ALLOWED;
ngx_memcpy(args,r->args.data,r->args.len);
args[r->args.len]=0;
src = args;
dst =decode;
ngx_unescape_uri((u_char**)&dst, (u_char**)&src, r->args.len, 0);
ngx_memcpy(args,decode,dst - decode);
args[dst - decode] = '\0';
//reply=request(uri,args,&status,&reply_len);
ngx_str_t response = ngx_string("Hello World!");
if (status!=NGX_HTTP_OK)
{
return status;
}
ngx_str_t type =ngx_string(CONTENT_TYPE);
r->headers_out.status = NGX_HTTP_OK;
r->headers_out.content_type = type;
//r->headers_out.content_length_n = reply_len;
r->headers_out.content_length_n = response.len;
ngx_buf_t *b = ngx_create_temp_buf(r->pool, response.len);
if(b == NULL)
{
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Failed to allocate response buffer.");
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
ngx_memcpy(b->pos, response.data, response.len);
b->last = b->pos+response.len;
b->last_buf = 1;
out.buf = b;
out.next = NULL;
rc = ngx_http_send_header(r);
if (rc == NGX_ERROR || rc > NGX_OK || r->header_only)
{
return rc;
}
return ngx_http_output_filter(r, &out);
}
else
{
return NGX_HTTP_NOT_ALLOWED;
}
}
static void ngx_http_read_client_request_body_handler(ngx_http_request_t *r)
{
static char uri[PB_SIZE];
static char decode[PB_SIZE];
char* body = NULL;
int body_size = 0;
char* src;
char* dst;
//char *reply=0;
int status=NGX_HTTP_OK;
//int reply_len=0;
ngx_int_t rc;
ngx_chain_t out;
ngx_chain_t* bufs = r->request_body->bufs;
ngx_buf_t* buf = NULL;
uint8_t* data_buf = NULL;
size_t content_length = 0;
size_t body_length = 0;
//get uri
if (r->uri.len>=PB_SIZE)
{
ngx_http_finalize_request(r,NGX_HTTP_INTERNAL_SERVER_ERROR);
return;
}
ngx_memcpy(uri,r->uri.data,r->uri.len);
uri[r->uri.len]=0;
src = uri;
dst = decode;
ngx_unescape_uri((u_char**)&dst, (u_char**)&src, r->uri.len, 0);
ngx_memcpy(uri,decode,dst - decode);
uri[dst - decode] = '\0';
//get body
if (r->request_body == NULL)
{
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "reqeust_body:null");
ngx_http_finalize_request(r,NGX_HTTP_INTERNAL_SERVER_ERROR);
return;
}
if ( r->headers_in.content_length == NULL )
{
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "r->headers_in.content_length == NULL");
ngx_http_finalize_request(r,NGX_HTTP_INTERNAL_SERVER_ERROR);
return;
}
content_length = atoi( (char*)(r->headers_in.content_length->value.data) );
data_buf = ( uint8_t* )ngx_palloc( r->pool , content_length + 1 );
size_t buf_length = 0;
while ( bufs )
{
buf = bufs->buf;
bufs = bufs->next;
buf_length = buf->last - buf->pos ;
if( body_length + buf_length > content_length )
{
memcpy( data_buf + body_length, buf->pos, content_length - body_length);
body_length = content_length ;
break;
}
memcpy( data_buf + body_length, buf->pos, buf->last - buf->pos );
body_length += buf->last - buf->pos;
}
if ( body_length )
{
data_buf[body_length] = 0;
}
body = (char *)data_buf;
body_size = body_length;
//int sequence = getSequence(r);
//reply = mypost(uri, body, body_size,sequence,&status, &reply_len);
//ÕâÀïmypostÆäʵ¾ÍÊÇÀ©Õ¹´¦ÀípostÌá½»±íµ¥Êý¾Ýbody£¬¿ÉÒÔ°Ñ´æµ½dbÖÐÖ®ÀàµÄÆäËûÈÎÒâÒµÎñÂß¼
ngx_str_t response = ngx_string("Hello World!");
if(status != NGX_HTTP_OK)
{
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Post failed.");
ngx_http_finalize_request(r,NGX_HTTP_INTERNAL_SERVER_ERROR);
return;
}
ngx_str_t type =ngx_string(CONTENT_TYPE);
r->headers_out.status = NGX_HTTP_OK;
r->headers_out.content_type = type;
//r->headers_out.content_length_n = reply_len;
r->headers_out.content_length_n = response.len;
ngx_buf_t *b = ngx_create_temp_buf(r->pool, response.len);
if(b == NULL)
{
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Failed to allocate response buffer.");
ngx_http_finalize_request(r,NGX_HTTP_INTERNAL_SERVER_ERROR);
return;
}
ngx_memcpy(b->pos, response.data, response.len);
b->last = b->pos+response.len;
b->last_buf = 1;
out.buf = b;
out.next = NULL;
rc = ngx_http_send_header(r);
if (rc == NGX_ERROR || rc > NGX_OK || r->header_only)
{
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Failed to do ngx_http_send_header.");
ngx_http_finalize_request(r,NGX_HTTP_INTERNAL_SERVER_ERROR);
return ;
}
ngx_http_finalize_request(r,ngx_http_output_filter(r, &out));
return;
}