Handler 模块
Handler 模块简介
Handler 模块就是接受来自客户端的请求并产生输出的模块。
配置文件中使用 location 指令可以配置 content handler 模块,当 Nginx 系统启动的时候,每个 handler 模块都有一次机会把自己关联到对应的 location上。如果有多个 handler 模块都关联了同一个 location,那么实际上只有一个 handler 模块真正会起作用。
handler 模块处理的结果通常有三种情况: 处理成功,处理失败(处理的时候发生了错误)或者是拒绝处理。在拒绝处理的情况下,这个 location 的处理就会由默认的 handler 模块来进行处理。
模块基本结构
模块配置结构
Nginx 的配置信息分成了几个作用域(scope),这就是 main,server 以及 location。同样的每个模块提供的配置指令也可以出现在这几个作用域里。那对于这三个作用域的配置信息,每个模块就需要定义三个不同的数据结构去进行存储。不过不是每个模块都会在这三个作用域都提供配置指令的,所以不一定每个模块都需要定义三个数据结构去存储这些配置信息了。
对于模块配置信息的定义,命名习惯如下
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;
模块配置指令
一个模块的配置指令定义在一个静态数组中。
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 提供了很多预定义的属性值(一些宏定义),通过逻辑或运算符可组合在一起,形成对这个配置指令的详细的说明。下面列出可在这里使用的预定义属性值及说明。
- 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);
先看该函数的返回值,处理成功时,返回 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 类型函数。
- ngx_conf_set_flag_slot: 读取 NGX_CONF_FLAG 类型的参数。
- ngx_conf_set_str_slot:读取字符串类型的参数。
- ngx_conf_set_str_array_slot:读取字符串数组类型的参数。
- ngx_conf_set_keyval_slot: 读取键值对类型的参数。
- ngx_conf_set_num_slot:读取整数类型(有符号整数 ngx_int_t)的参数。
- ngx_conf_set_size_slot:读取 size_t 类型的参数,也就是无符号数。
- ngx_conf_set_off_slot:读取 off_t 类型的参数。
- ngx_conf_set_msec_slot:读取毫秒值类型的参数。
- ngx_conf_set_sec_slot:读取秒值类型的参数。
- ngx_conf_set_bufs_slot: 读取的参数值是 2 个,一个是 buf 的个数,一个是 buf 的大小。例如: output_buffers 1 128k;
- ngx_conf_set_enum_slot:读取枚举类型的参数,将其转换成整数 ngx_uint_t 类型。
- ngx_conf_set_bitmask_slot:读取参数的值,并将这些参数的值以 bit 位的形式存储。例如:HttpDavModule 模块的 dav_methods 指令。
conf: 该字段被 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作为结尾。
模块上下文结构
他是一个 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); 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 或错误字符串。
handler 模块基本结构
函数的原型如下:
typedef ngx_int_t (*ngx_http_handler_pt)(ngx_http_request_t *r);
r 是 http 请求。该函数处理成功返回 NGX_OK,处理发生错误返回 NGX_ERROR,拒绝处理(留给后续的 handler 进行处理)返回 NGX_DECLINE。
handler 模块的挂载
handler 模块真正的处理函数通过两种方式挂载到处理过程中,一种方式是按处理阶段挂载;另外一种方式是按需挂载。
按处理阶段挂载
为了更精细地控制对于客户端请求的处理过程,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
挂载的代码如下:
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。
按需挂载
使用这种方式挂载的 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。这个方法挂载上去的 handler 有一个特点是必须在 NGX_HTTP_CONTENT_PHASE 阶段才能执行到。
挂载的代码如下:
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; }
hello handler 模块
在前面已经看到了 hello handler module 的部分重要的结构。该模块提供了 2 个配置指令,仅可以出现在 location 指令的作用域中。一个指令是 hello_string, 该指令接受一个参数来设置显示的字符串。如果没有跟参数,那么就使用默认的字符串作为响应字符串。另一个指令是 hello_counter,如果设置为 on,则会在响应的字符串后面追加 Visited Times:的字样,以统计请求的次数。
下面是完整的 ngx_http_hello_module 模块的代码。
Handler 模块的编译和使用
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 这个变量里,依次写进去。
编译
Nginx 必须去到 Nginx 的源代码目录里,通过 configure 指令的参数,来进行编译。
hello module 的 configure 指令如下:
./configure --prefix=/usr/local/nginx-1.3.1 --add-module=/home/kevin/open_source/book_module
示例模块的代码和 config 文件都放在/home/kevin/open_source/book_module
目录下。
使用
使用一个模块需要根据这个模块定义的配置指令来做。
location test { hello_string kevin; hello_counter on; }
当我们访问这个地址的时候, lynx http://127.0.0.1/test
的时候,就可以看到返回的结果。
kevin Visited Times:1