Nginx系列一:概念和配置
Nginx系列二: Nginx 的数据结构
Nginx系列三: Nginx 高级数据结构
Nginx系列四: Nginx的配置指令和handler模块概述
Nginx系列五: handler 模块
文章目录
五 handler 模块
基本上大家开发者最可能开发的就是三种类型的模块,即 handler
, filter
和 load-balancer
。 Handler 模块就是接受来自客户端的请求并产生输出的模块。有些地方说 upstream 模块实际上也是一种 handler 模块,只不过它产生的内容来自于从后端服务器获取的,而非在本机产生的 。配置文件中使用 location 指令可以配置 content handler 模块,当 Nginx系统启动的时候,每个 handler 模块都有一次机会把自己关联到对应的 location 上。如果有多个 handler 模块都关联了同一个 location,那么实际上只有一个 handler 模块真正会起作用。当然大多数情况下,模块开发人员都会避免出现这种情况。 handler 模块处理的结果通常有三种情况: 处理成功,处理失败(处理的时候发生了错误)或者是拒绝去处理。在拒绝处理的情况下,这个 location 的处理就会由默认的 handler 模块来进行处理。例如,当请求一个静态文件的时候,如果关联到这个 location 上的一个 handler模块拒绝处理,就会由默认的 ngx_http_static_module 模块进行处理,该模块是一个典型的handler 模块 。
1. 模块的基本结构
1. 模块的结构
基本上每个模块都会提供一些配置指令,以便于用户可以通过配置来控制该模块的行为。那么这些配置信息怎么存储呢?那就需要定义该模块的配置结构来进行存储。大家都知道Nginx 的配置信息分成了几个作用域(scope,有时也称作上下文),这就是 main, server, 以及location。同样的每个模块提供的配置指令也可以出现在这几个作用域里。那对于这三个作用域的配置信息,每个模块就需要定义三个不同的数据结构去进行存储。当然,不是每个模块都会在这三个作用域都提供配置指令的。那么也就不一定每个模块都需要定义三个数据结构去存储这些配置信息了。视模块的实现而言,需要几个就定义几个。有一点需要特别注意的就是,在模块的开发过程中,我们最好使用 nginx 原有的命名习惯。这样跟原代码的契合度更高,看起来也更舒服。对于模块配置信息的定义,命名习惯是 ngx_http_<module name>_(main|srv|loc)_conf_t
。例如:
typedef struct {
ngx_str_t hello_string;
ngx_int_t hello_counter;
}ngx_http_hello_loc_conf_t;
2. 模块的配置指令
一个模块的配置指令是定义在一个静态数组中的。例如:
static ngx_command_t ngx_http_hello_commands[] = {
{
ngx_string("hello_string"),
NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS|NGX_CONF_TAKE1,
ngx_http_hello_string,
NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_http_hello_loc_conf_t, hello_string),
NULL
},
{
ngx_string("hello_counter"),
NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
ngx_http_hello_counter,
NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_http_hello_loc_conf_t, hello_counter),
NULL
},
ngx_null_command
};
其实看这个定义,就基本能看出来一些信息。例如,我们是定义了两个配置指令,一个是叫hello_string,可以接受一个参数,或者是没有参数。另外一个命令是 hello_counter,接受一个 NGX_CONF_FLAG 类型的参数。除此之外,似乎看起来有点迷惑。没有关系,我们来详细看一下 ngx_command_t
,一旦我们了解这个结构的详细信息,那么我相信上述这个定义所表达的所有信息就不言自明了。 ngx_command_t
的定义,位于 src/core/ngx_conf_file.h
中。
typedef struct ngx_command_s ngx_command_t;
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 提供了很多预定义的属性值(一些宏定义),通过逻辑或运算符可组合在一起,形成对这个配
置指令的详细的说明。下面列出可在这里使用的预定义属性值及说明。
#define NGX_CONF_NOARGS 0x00000001 配置指令不接受任何参数
#define NGX_CONF_TAKE1 0x00000002 配置指令接受 1 个参数
#define NGX_CONF_TAKE2 0x00000004 配置指令接受 2 个参数
#define NGX_CONF_TAKE3 0x00000008 配置指令接受 3 个参数
#define NGX_CONF_TAKE4 0x00000010 配置指令接受 4 个参数
#define NGX_CONF_TAKE5 0x00000020 配置指令接受 5 个参数
#define NGX_CONF_TAKE6 0x00000040 配置指令接受 6 个参数
#define NGX_CONF_TAKE7 0x00000080 配置指令接受 7 个参数
可以组合多个属性,比如一个指令即可以不填参数,也可以接受 1 个或者 2 个参数。那么就是 NGX_CONF_NOARGS|NGX_CONF_TAKE1|NGX_CONF_TAKE2。如果写上面三个属性在一起,你觉得麻烦,那么没有关系, nginx 提供了一些定义,使用起来更简洁。
#define NGX_CONF_TAKE12 (NGX_CONF_TAKE1|NGX_CONF_TAKE2) 配置指令接受 1 个或者 2 个参数
#define NGX_CONF_TAKE13 (NGX_CONF_TAKE1|NGX_CONF_TAKE3) 配置指令接受 1 个或者 3 个参数
#define NGX_CONF_TAKE23 (NGX_CONF_TAKE2|NGX_CONF_TAKE3) 配置指令接受 2 个或者 3 个参数
#define NGX_CONF_TAKE123 (NGX_CONF_TAKE1|NGX_CONF_TAKE2|NGX_CONF_TAKE3) 配置指令接受 1 个或者 2 个或者 3 参数
#define NGX_CONF_TAKE1234 (NGX_CONF_TAKE1|NGX_CONF_TAKE2|NGX_CONF_TAKE3 \ 配置指令接受 1 个或者 2 个或者 3 个或者 4 个参数
|NGX_CONF_TAKE4)
#define NGX_CONF_1MORE 0x00000800 配置指令接受至少一个参数
#define NGX_CONF_2MORE 0x00001000 配置指令接受至少两个参数
#define NGX_CONF_ARGS_NUMBER 0x000000ff 配置指令可以接受多个参数,即个数不定
#define NGX_CONF_BLOCK 0x00000100 配置指令可以接受的值是一个配置信息块。也就是一对大括号括起
来的内容。里面可以再包括很多的配置指令。比如常见的 server 指令就是这个属性的。
#define NGX_CONF_FLAG 0x00000200 配置指令可以接受的值是”on”或者”off”,最终会被转成 bool 值
#define NGX_CONF_ANY 0x00000400 配置指令可以接受的任意的参数值。一个或者多个,或者”on”或者”off”,
或者是配置块
无论如何,nginx的配置指令的参数个数不可以超过NGX_CONF_MAX_ARGS个。目前这个值被定义为8,也就是不能超过8个参数.
set: 这是一个函数指针,当 nginx 在解析配置的时候,如果遇到这个配置指令,将会把读取到的值传递给这个函数进行分解处理。因为具体每个配置指令的值如何处理,只有定义这个配置指令的人是最清楚的。来看一下这个函数指针要求的函数原型。
char *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
先看该函数的返回值,处理成功时,返回 NGX_OK,否则返回 NGX_CONF_ERROR 或者是一个自定义的错误信息的字符串。
cf: 该参数里面保存从配置文件读取到的原始字符串以及相关的一些信息。特别注意的是这个参数的 args 字段是一个 ngx_str_t 类型的数组,该数组的首个元素是这个配置指令本身,第二个元素是指令的第一个参数,第三个元素是第二个参数,依次类推。
cmd: 这个配置指令对应的 ngx_command_t 结构。
conf: 就是定义的存储这个配置值的结构体,比如在上面展示的那个ngx_http_hello_loc_conf_t。当解析这个 hello_string 变量的时候,传入的 conf 就指向一个 ngx_http_hello_loc_conf_t 类型的变量。用户在处理的时候可以使用类型转换,转换成自己知道的类型,再进行字段的赋值。
为了更加方便的实现对配置指令参数的读取, nginx 已经默认提供了对一些标准类型的参数进行读取的函数,可以直接赋值给 set 字段使用。下面来看一下这些已经实现的 set 类型函数。
1. ngx_conf_set_flag_slot: 读取 NGX_CONF_FLAG 类型的参数
2. ngx_conf_set_str_slot:读取字符串类型的参数。
3. ngx_conf_set_str_array_slot: 读取字符串数组类型的参数。
4. ngx_conf_set_keyval_slot: 读取键值对类型的参数。
5. ngx_conf_set_num_slot: 读取整数类型(有符号整数 ngx_int_t)的参数。
6. ngx_conf_set_size_slot:读取 size_t 类型的参数,也就是无符号数。
7. ngx_conf_set_off_slot: 读取 off_t 类型的参数
8. ngx_conf_set_msec_slot: 读取毫秒值类型的参数
9. ngx_conf_set_sec_slot: 读取秒值类型的参数
10. ngx_conf_set_bufs_slot: 读取的参数值是 2 个,一个是 buf 的个数,一个是 buf 的大小。例如: output_buffers 1 128k;
11. ngx_conf_set_enum_slot: 读取枚举类型的参数,将其转换成整数 ngx_uint_t 类型
12. ngx_conf_set_bitmask_slot: 读取参数的值,并将这些参数的值以 bit 位的形式存储。例如: HttpDavModule 模块的 dav_methods 指令。
conf: 该字段被NGX_HTTP_MODULE类型模块所用 (我们编写的基本上都是NGX_HTTP_MOUDLE,只有一些 nginx 核心模块是非 NGX_HTTP_MODULE),该字段指定当前配置项存储的内存位置。实际上是使用哪个内存池的问题。因为 http模块对所有 http 模块所要保存的配置信息,划分了 main, server 和 location 三个地方进行存储,每个地方都有一个内存池用来分配存储这些信息的内存。这里可能的值为NGX_HTTP_MAIN_CONF_OFFSET 、NGX_HTTP_SRV_CONF_OFFSET或NGX_HTTP_LOC_CONF_OFFSET。当然 也可以直接置为0,就是NGX_HTTP_MAIN_CONF_OFFSET。
offset: 指定该配置项值的精确存放位置,一般指定为某一个结构体变量的字段偏移。因为对于配置信息的存储,一般我们都是定义个结构体来存储的。那么比如我们定义了一个结构体 A,该项配置的值需要存储到该结构体的 b 字段。那么在这里就可以填写为 offsetof(A, b)。对于有些配置项,它的值不需要保存或者是需要保存到更为复杂的结构中时,这里可以设置为0。
post: 该字段存储一个指针。可以指向任何一个在读取配置过程中需要的数据,以便于进行配置读取的处理。大多数时候,都不需要,所以简单地设为 0 即可
*/
看到这里,应该就比较清楚了。 ngx_http_hello_commands 这个数组每 5 个元素为一组,用来描述一个配置项的所有情况。那么如果有多个配置项,只要按照需要再增加 5 个对应的元素对新的配置项进行说明。需要注意的是,就是在 ngx_http_hello_commands 这个数组定义的最后,都要加一个ngx_null_command
作为结尾。
3. 模块的上下文结构
这是一个 ngx_http_module_t
类型的静态变量。这个变量实际上是提供一组回调函数指针,这些函数有在创建存储配置信息的对象的函数,也有在创建前和创建后会调用的函数。这些函数都将被 nginx 在合适的时间进行调用。
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);
char *(*init_main_conf)(ngx_conf_t *cf, void *conf);
void *(*create_srv_conf)(ngx_conf_t *cf);
char *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, void *conf);
void *(*create_loc_conf)(ngx_conf_t *cf);
char *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf);
} ngx_http_module_t;
/*
preconfiguration : 在创建和读取该模块的配置信息之前被调用
postconfiguration: 在创建和读取该模块的配置信息之后被调用
create_main_conf: 调用该函数创建本模块位于 http block 的配置信息存储结构。该函数成功的时候,返回创建的配置对象。失败的话,返回 NULL。
init_main_conf: 调用该函数初始化本模块位于 http block 的配置信息存储结构。该函数成功的时候,返回 NGX_CONF_OK。失败的话,返回 NGX_CONF_ERROR 或错误字符串。
create_srv_conf: 调用该函数创建本模块位于 http server block 的配置信息存储结构,每个 server block 会创建一个。该函数成功的时候,返回创建的配置对象。失败的话,返回 NULL。
merge_srv_conf: 因为有些配置指令既可以出现在 http block,也可以出现在 http server block 中。那么遇到这种情况,每个 server都会有自己存储结构来存储该 server 的配置,但是在这种情况下 http block 中的配置与 server block 中的配置信息发生冲突的时候,就需要调用此函数进行合并,该函数并非必须提供,当预计到绝对不会发生需要合并的情况的时候,就无需提供。当然为了安全起见还是建议提供。该函数执行成功的时候,返回 NGX_CONF_OK。失败的话,返回 NGX_CONF_ERROR 或错误字符串。
create_loc_conf : 调用该函数创建本模块位于 location block 的配置信息存储结构。每个在配置中指明的 location 创建一个。该函数执行成功,返回创建的配置对象。失败的话,返回 NULL。
merge_loc_conf: 与 merge_srv_conf 类似,这个也是进行配置值合并的地方。该函数成功的时候,返回 NGX_CONF_OK。失败的话,返回 NGX_CONF_ERROR 或错误字符串。
*/
Nginx 里面的配置信息都是上下一层层的嵌套的,对于具体某个 location 的话,对于同一个配置,如果当前层次没有定义,那么就使用上层的配置,否则使用当
前层次的配置。这些配置信息一般默认都应该设为一个未初始化的值,针对这个需求, Nginx 定义了一系列的宏定义来代表各种配置所对应数据类型的未初始化值,如下:
#define NGX_CONF_UNSET -1
#define NGX_CONF_UNSET_UINT (ngx_uint_t) -1
#define NGX_CONF_UNSET_PTR (void *) -1
#define NGX_CONF_UNSET_SIZE (size_t) -1
#define NGX_CONF_UNSET_MSEC (ngx_msec_t) -1
又因为对于配置项的合并,逻辑都类似,也就是前面已经说过的,如果在本层次已经配置了,也就是配置项的值已经被读取进来了(那么这些配置项的值就不会等于上面已经定义的那些UNSET 的值),就使用本层次的值作为定义合并的结果,否则,使用上层的值,如果上层的值也是这些 UNSET 类的值,那就赋值为默认值,否则就使用上层的值作为合并的结果。对于这样类似的操作, Nginx 定义了一些宏操作来做这些事情,我们来看其中一个的定义 :
#define ngx_conf_merge_uint_value(conf, prev, default) \
if (conf == NGX_CONF_UNSET_UINT) { \
conf = (prev == NGX_CONF_UNSET_UINT) ? default : prev; \
}
显而易见,这个逻辑确实比较简单,所以其它的宏定义也类似。
最后举例:
static ngx_http_module_t ngx_http_hello_module_ctx = {
NULL, /* preconfiguration */
ngx_http_hello_init, /* postconfiguration */
NULL, /* create main configuration*/
NULL, /* init main configuration */
NULL, /* create server configuration*/
NULL, /* merge server configuration*/
ngx_http_hello_create_loc_conf, /* create location configuration */
NULL /* merge location configuration*/
};
注意:这里并没有提供 merge_loc_conf 函数,因为我们这个模块的配置指令已经确定只出现在 NGX_HTTP_LOC_CONF 中这一个层次上,不会发生需要合并的情况。
4. 模块定义
对于开发一个模块来说,我们都需要定义一个 ngx_module_t
类型的变量来说明这个模块本身的信息,从某种意义上来说,这是这个模块最重要的一个信息,它告诉了 nginx 这个模块的一些信息,上面定义的配置信息,还有模块上下文信息,都是通过这个结构来告诉 nginx系统的,也就是加载模块的上层代码,都需要通过定义的这个结构,来获取这些信息。我们先来看下 ngx_module_t
的定义 :
typedef struct ngx_module_s ngx_module_t;
struct ngx_module_s {
ngx_uint_t ctx_index;
ngx_uint_t index;
char *name;
ngx_uint_t spare0;
ngx_uint_t spare1;
ngx_uint_t version;
const char *signature;
void *ctx;
ngx_command_t *commands;
ngx_uint_t type;
ngx_int_t (*init_master)(ngx_log_t *log);
ngx_int_t (*init_module)(ngx_cycle_t *cycle);
ngx_int_t (*init_process)(ngx_cycle_t *cycle);
ngx_int_t (*init_thread)(ngx_cycle_t *cycle);
void (*exit_thread)(ngx_cycle_t *cycle);
void (*exit_process)(ngx_cycle_t *cycle);
void (*exit_master)(ngx_cycle_t *cycle);
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;
};
#define NGX_MODULE_V1 \
NGX_MODULE_UNSET_INDEX, NGX_MODULE_UNSET_INDEX, \
NULL, 0, 0, nginx_version, NGX_MODULE_SIGNATURE
#define NGX_MODULE_V1_PADDING 0, 0, 0, 0, 0, 0, 0, 0
再看下举例:
ngx_module_t ngx_http_hello_module = {
NGX_MODULE_V1,
&ngx_http_hello_module_ctx, /* module context */
ngx_http_hello_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,当 nginx 在创建进程线程或者结束进程线程时进行调用。但大多数模块在这些时刻并不需要做什么,所以都简单赋值为 NULL。
2. handler 模块的基本结构
除了上面说的模块的基本结构外,handler 模块必须提供一个真正的处理函数,这个函数负责对来自客户端请求的真正处理。这个函数的处理,既可以选择自己直接生成内容,也可以选择拒绝处理,由后续的 handler 去进行处理,或者是选择丢给后续的 filter 进行处理。来看一下这个函数的原型申明。
typedef ngx_int_t (*ngx_http_handler_pt)(ngx_http_request_t *r);
r 是 http 请求。里面包含请求所有的信息,这里不详细说明了,可以参考别的章节的介绍。该函数处理成功返回NGX_OK,处理发生错误返回NGX_ERROR,拒绝处理(留给后续的handler进行处理)返回 NGX_DECLINE。 返回 NGX_OK 也就代表给客户端的响应已经生成好了,否则返回 NGX_ERROR 就发生错误了。
3. handle模块的挂载
handler 模块真正的处理函数通过两种方式挂载到处理过程中,一种方式就是按处理阶段挂载;另外一种挂载方式就是按需挂载。
1. 按处理阶段挂载
为了更精细地控制对于客户端请求的处理过程, nginx 把这个处理过程划分成了 11 个阶段。他们从前到后,依次列举如下:
NGX_HTTP_POST_READ_PHASE | 读取请求内容阶段 |
---|---|
NGX_HTTP_SERVER_REWRITE_PHASE | Server 请求地址重写阶段 |
NGX_HTTP_FIND_CONFIG_PHASE | 配置查找阶段 |
NGX_HTTP_REWRITE_PHASE | Location 请求地址重写阶段 |
NGX_HTTP_POST_REWRITE_PHASE | 请求地址重写提交阶段 |
NGX_HTTP_PREACCESS_PHASE | 访问权限检查准备阶段 |
NGX_HTTP_ACCESS_PHASE | 访问权限检查阶段 |
NGX_HTTP_POST_ACCESS_PHASE | 访问权限检查提交阶段 |
NGX_HTTP_TRY_FILES_PHASE | 配置项 try_files 处理阶段 |
NGX_HTTP_CONTENT_PHASE | 内容产生阶段 |
NGX_HTTP_LOG_PHASE | 日志模块处理阶段 |
一般情况下,我们自定义的模块,大多数是挂载在 NGX_HTTP_CONTENT_PHASE
阶段的。挂载的动作一般是在模块上下文调用的 postconfiguration 函数中。 注意:有几个阶段是特例,它不调用挂载地任何的 handler,也就是你就不用挂载到这几个阶段了:
NGX_HTTP_FIND_CONFIG_PHASE
NGX_HTTP_POST_ACCESS_PHASE
NGX_HTTP_POST_REWRITE_PHASE
NGX_HTTP_TRY_FILES_PHASE
所以其实真正是有 7 个 phase 你可以去挂载 handler。
挂载的代码如下 :
static ngx_int_t ngx_http_hello_init(ngx_conf_t *cf)
{
ngx_http_handler_pt *h;
ngx_http_core_main_conf_t *cmcf;
cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers);
if (h == NULL) {
return NGX_ERROR;
}
*h = ngx_http_hello_handler;
return NGX_OK;
}
使用这种方式挂载的 handler 也被称为 content phase handlers。
2. 按需挂载
以这种方式挂载的 handler 也被称为 content handler。当一个请求进来以后, nginx 从 NGX_HTTP_POST_READ_PHASE 阶段开始依次执行每个阶段中
所有 handler。执行到 NGX_HTTP_CONTENT_PHASE 阶段的时候,如果这个 location 有一个对应的 content handler 模块,那么就去执行这个 content handler 模块真正的处理函数。否则继续依次执行 NGX_HTTP_CONTENT_PHASE 阶段中所有 content phase handlers,直到某个函数处理返回 NGX_OK 或者 NGX_ERROR。换句话说,当某个location 处理到 NGX_HTTP_CONTENT_PHASE 阶段时,如果有 content handler模块,那么NGX_HTTP_CONTENT_PHASE挂载的所有content phase handlers都不会被执行了。但是使用这个方法挂载上去的 handler 有一个特点是必须在 NGX_HTTP_CONTENT_PHASE 阶段才能执行到。如果你想自己的 handler 在更早的阶段执行,那就不要使用这种挂载方式。那么在什么情况会使用这种方式来挂载呢?一般情况下,某个模块对某个 location 进行了处理以后,发现符合自己处理的逻辑,而且也没有必要再调用 NGX_HTTP_CONTENT_PHASE 阶段的其它 handler 进行处理的时候,就动态挂载上这个 handler。
下面来看一下使用这种挂载方式的具体例子
static char * ngx_http_circle_gif(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_circle_gif_handler;
return NGX_CONF_OK;
}
4. handler的编写步骤
- 编写模块基本结构。包括模块的定义,模块上下文结构,模块的配置结构等。
- 实现 handler 的挂载函数。根据模块的需求选择正确的挂载方式。
- 编写 handler 处理函数。模块的功能主要通过这个函数来完成。
我们看下完整的hello handler module
模块的代码;在前面已经看到了这个 hello handler module
的部分重要的结构。该模块提供了 2 个配置指令,仅可以出现在 location 指令的作用域中。这两个指令是 hello_string
, 该指令接受一个参数来设置显示的字符串。如果没有跟参数,那么就使用默认的字符串作为响应字符串。另一个指令是 hello_counter
,如果设置为 on,则会在响应的字符串后面追加 Visited Times:的字样,以统计请求的次数。
1.对于 flag 类型的配置指令,当值为 off 的时候,使用 ngx_conf_set_flag_slot 函数,会转化为 0,为 on,则转化为非 0。
2.另外一个是,我提供了 merge_loc_conf
函数,但是却没有设置到模块的上下文定义中。这样有一个缺点,就是如果一个指令没有出现在配置文件中的时候,配置信息中的值,将永远会保持在 create_loc_conf
中的初始化的值。那如果,在类似 create_loc_conf
这样的函数中,对创建出来的配置信息的值,没有设置为合理的值的话,后面用户又没有配置,就会出现问题。
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
typedef struct
{
ngx_str_t hello_string;
ngx_int_t hello_counter;
}ngx_http_hello_loc_conf_t;
static ngx_int_t ngx_http_hello_init(ngx_conf_t *cf);
static void *ngx_http_hello_create_loc_conf(ngx_conf_t *cf);
static char *ngx_http_hello_string(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static char *ngx_http_hello_counter(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static ngx_command_t ngx_http_hello_commands[] = {
{
ngx_string("hello_string"),
NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS|NGX_CONF_TAKE1,
ngx_http_hello_string,
NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_http_hello_loc_conf_t, hello_string),
NULL
},
{
ngx_string("hello_counter"),
NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
ngx_http_hello_counter,
NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_http_hello_loc_conf_t, hello_counter),
NULL
},
ngx_null_command
};
/*
static u_char ngx_hello_default_string[] = "Default String: Hello, world!";
*/
static int ngx_hello_visited_times = 0;
static ngx_http_module_t ngx_http_hello_module_ctx = {
NULL, /* preconfiguration */
ngx_http_hello_init, /* postconfiguration */
NULL, /* create main configuration */
NULL, /* init main configuration*/
NULL, /* create server configuration */
NULL, /* merge server configuration */
ngx_http_hello_create_loc_conf, /* create location configuration */
NULL /* merge location configuration */
};
ngx_module_t ngx_http_hello_module = {
NGX_MODULE_V1,
&ngx_http_hello_module_ctx, /* module context */
ngx_http_hello_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 ngx_int_t ngx_http_hello_handler(ngx_http_request_t *r)
{
ngx_int_t rc;
ngx_buf_t *b;
ngx_chain_t out;
ngx_http_hello_loc_conf_t* my_conf;
u_char ngx_hello_string[1024] = {0};
ngx_uint_t content_length = 0;
ngx_log_error(NGX_LOG_EMERG, r->connection->log, 0, "ngx_http_hello_handler is called!");
my_conf = ngx_http_get_module_loc_conf(r, ngx_http_hello_module);
if (my_conf->hello_string.len == 0 )
{
ngx_log_error(NGX_LOG_EMERG, r->connection->log, 0, "hello_string is empty!");
return NGX_DECLINED;
}
if (my_conf->hello_counter == NGX_CONF_UNSET || my_conf->hello_counter == 0)
{
ngx_sprintf(ngx_hello_string, "%s",
my_conf->hello_string.data);
} else {
ngx_sprintf(ngx_hello_string, "%s Visited Times:%d", my_conf->hello_string.data, ++ngx_hello_visited_times);
}
ngx_log_error(NGX_LOG_EMERG, r->connection->log, 0, "hello_string:%s", ngx_hello_string);
content_length = ngx_strlen(ngx_hello_string);
/* we response to 'GET' and 'HEAD' requests only */
if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))) {
return NGX_HTTP_NOT_ALLOWED;
}
/* discard request body, since we don't need it here */
rc = ngx_http_discard_request_body(r);
if (rc != NGX_OK) {
return rc;
}
/* set the 'Content-type' header */
/*
*r->headers_out.content_type.len = sizeof("text/html") - 1;
*r->headers_out.content_type.data = (u_char *)"text/html";
*/
ngx_str_set(&r->headers_out.content_type, "text/html");
/* send the header only, if the request type is http 'HEAD' */
if (r->method == NGX_HTTP_HEAD) {
r->headers_out.status = NGX_HTTP_OK;
r->headers_out.content_length_n = content_length;
return ngx_http_send_header(r);
}
/* allocate a buffer for your response body */
b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
if (b == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
/* attach this buffer to the buffer chain */
out.buf = b;
out.next = NULL;
/* adjust the pointers of the buffer */
b->pos = ngx_hello_string;
b->last = ngx_hello_string + content_length;
b->memory = 1; /* this buffer is in memory */
b->last_buf = 1; /* this is the last buffer in the
buffer chain */
/* set the status line */
r->headers_out.status = NGX_HTTP_OK;
r->headers_out.content_length_n = content_length;
/* send the headers of your response */
rc = ngx_http_send_header(r);
if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
return rc;
}
/* send the buffer chain of your response */
return ngx_http_output_filter(r, &out);
}
static void *ngx_http_hello_create_loc_conf(ngx_conf_t *cf) {
ngx_http_hello_loc_conf_t* local_conf = NULL;
local_conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_hello_loc_conf_t));
if (local_conf == NULL)
{
return NULL;
}
ngx_str_null(&local_conf->hello_string);
local_conf->hello_counter = NGX_CONF_UNSET;
return local_conf;
}
/*
static char *ngx_http_hello_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
{
ngx_http_hello_loc_conf_t* prev = parent;
ngx_http_hello_loc_conf_t* conf = child;
ngx_conf_merge_str_value(conf->hello_string,
prev->hello_string, ngx_hello_default_string);
ngx_conf_merge_value(conf->hello_counter,
prev->hello_counter, 0);
return NGX_CONF_OK;
}
*/
static char * ngx_http_hello_string(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_http_hello_loc_conf_t* local_conf;
local_conf = conf;
char* rv = ngx_conf_set_str_slot(cf, cmd, conf);
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "hello_string:%s", local_conf->hello_string.data);
return rv;
}
static char *ngx_http_hello_counter(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_http_hello_loc_conf_t* local_conf;
local_conf = conf;
char* rv = NULL;
rv = ngx_conf_set_flag_slot(cf, cmd, conf);
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "hello_counter:%d", local_conf->hello_counter);
return rv;
}
static ngx_int_t ngx_http_hello_init(ngx_conf_t *cf) {
ngx_http_handler_pt *h;
ngx_http_core_main_conf_t *cmcf;
cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers);
if (h == NULL) {
return NGX_ERROR;
}
*h = ngx_http_hello_handler;
return NGX_OK;
}
5. handler 模块的编译和使用
1. config 文件的编写
对于开发一个模块,我们是需要把这个模块的 C 代码组织到一个目录里,同时需要编写一个config 文件。这个 config 文件的内容就是告诉 nginx 的编译脚本,该如何进行编译。我们来看一下 hello handler module
的 config 文件的内容,然后再做解释。
ngx_addon_name=ngx_http_hello_module
HTTP_MODULES="$HTTP_MODULES ngx_http_hello_module"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS
$ngx_addon_dir/ngx_http_hello_module.c"
其实文件很简单,几乎不需要做什么解释。大家一看都懂了。唯一需要说明的是,如果这个模块的实现有多个源文件,那么都在 NGX_ADDON_SRCS 这个变量里,依次写进去就可以。
2.编译
对于模块的编译, nginx 并不像 apache 一样,提供了单独的编译工具,可以在没有 apache 源代码的情况下来单独编译一个模块的代码。 nginx 必须去到 nginx 的源代码目录里,通过 configure 指令的参数,来进行编译。下面看一下 hello module 的 configure 指令:
$ ./configure –prefix=/usr/local/nginx-1.4.1 –add-module= /home/xx/open_source/book_module
我们的这个示例模块的代码和 config 文件都放在/home/xx/open_source/book_module 这 个目录下。
3. 使用
使用一个模块需要根据这个模块定义的配置指令来做。比如我们这个简单的 hello handler module
的使用就很简单。在我的测试服务器的配置文件里,就是在 http 里面的默认的 server 里面加入如下的配置:
location /test {
hello_string helloNginx;
hello_counter on;
}
当我们访问这个地址的时候, http://localhost/test 的时候,就可以看到返回的结果。helloNginx Visited Times:1
当然你访问多次,这个次数是会增加的。