nginx模块开发实战

来源:互联网 发布:北京现代软件学院骗局 编辑:程序博客网 时间:2024/06/01 20:05
想要进行nginx模块开发,首先你需要比较熟悉C语言,其次你需要对HTTP协议有一定的了解。

点击打开链接

从配置开始

Nginx主配置文件中主要包括六块:main,events,http,server,location,upstream 结构如下:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#main
...
events {
    ...
}
http {
    ...
    upstream {
        ...
    }
    server {
        ...
        location {
           ...
        }
    }
}

main块:主要控制Nginx子进程的所属用户/用户组、派生子进程数、错误日志位置/级别、pid位置、子进程优先级、进程对应cpu、进程能够打开的文件描述符数目等。
events块:控制Nginx处理连接的方式。
http块:是Nginx处理http请求的主要配置模块,大多数配置都在这里面进行。
server块:是Nginx中主机的配置块,可以配置多个虚拟主机。
location块:是server中对应的目录级别的控制块,可以有多个。
upstream块:是Nginx做反向代理和负载均衡的配置块,可以有多个。
  其中我们经常关注的模块有main、server、upstream和location。main部分设置的指令将影响其它所有设置;server部分的指令主要用于指定主机和端口;upstream的指令用于设置一系列的后端服务器;location部分用于匹配网页位置(比如,根目录“/”,“/images”,等等)。他们之间的关系式:server继承main,location继承server;upstream既不会继承指令也不会被继承,它有自己的特殊指令,不需要在其他地方应用。 
  

模块概述

  Nginx模块主要有三种角色: 
    handlers :处理http请求并构造输出。
    filters :处理handler产生的输出。
    load-balancers :当有多于一个的后端服务器时,选择一台将http请求发送过去。
  Nginx中许多工作都是由模块来完成的。任何时候,Nginx提供文件或者转发请求到另一个server,都是通过handler来实现的。而当需要对输出在服务端加一些东西的话,filter就派上用场了。如果handler的作用是把请求反向代理到后端服务器,那么就会用到模块的第三种角色load-balancer了。如果handler正常返回,那么filter就会被调用。filter采用了经典的“接力链表”模式:一个filter被调用并处理,接下来调用下一个filter,直到最后一个filter被调用完成,Nginx才真正完成响应流程。
  总结一下,一个典型的请求响应周期: 客户端发送HTTP请求 → Nginx基于location的配置选择一个合适的handler → (如果有) load-balancer选择一个后端服务器 → Handler处理请求并将响应发送给第一个filter → 第一个filter讲输出交给第二个filter → 第二个给第三个→ 以此类推 → 最终响应发送给客户端 。
  之所以说“典型地”是因为Ngingx的模块具有很强的定制性。模块开发者需要花很多精力精确定义模块在何时如何产生作用。模块调用实际上是通过一系列的回调函数做到的。理论上来讲,你的函数可以在以下时候被执行:
  ● server读取配置文件之前
  ● 读取location和server的每一条配置指令
  ● 当Nginx初始化main配置段时
  ● 当Nginx初始化server配置段时(例如:host/port)
  ● 当Nginx合并server配置和main配置时
  ● 当Nginx初始化location配置时
  ● 当Nginx合并location配置和它的父server配置时
  ● 当Nginx的主进程启动时
  ● 当一个新的worker进程启动时
  ● 当一个worker进程退出时
  ● 当主进程退出时
  ● handler一个请求
  ● Filter响应头
  ● Filter响应体
  ● 选择一个后端服务器
  ● 初始化一个将发往后端服务器的请求
  ● 重新初始化一个将发往后端服务器的请求
  ● 处理来自后端服务器的响应
  ● 完成与后端服务器的交互
  

模块的组成

1.模块的存储结构

?
1
2
模块的存储struct有三种,分别是main,server和location。绝大多数模块仅需要一个location配置。名称约定如下:ngx_http_<module name="">_(main|srv|loc)_conf_t. 例如:
</module>
?
1
2
3
4
5
typedef struct
{
    ngx_str_t hello_string;
    ngx_int_t hello_counter;
} ngx_http_hello_loc_conf_t;

这是后面将要讲的hello模块的存储结构的定义。

2.模块指令的定义

  配置指令是用来定义你所开发模块的配置信息,例如指令的名称、参数等信息。它是一个静态的ngx_command_t类型的数组。同样举个例子:
  

?
1
2
3
4
5
6
7
8
9
10
11
staticngx_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_null_command
};

下面我们来分析下ngx_command_t 结构体。

?
1
2
3
4
5
6
7
8
struct ngx_command_t {
    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 这个字段表示我们所开发模块的指令名称。ngx_str_t 就理解成一个String类型就可以了。
type 表示该指令的作用域,即这个指令配置在哪里,以及指令参数的个数。 ngx_uint_t 理解成一个无符号整型。  
  可选的值有如下一些:
  NGX_HTTP_MAIN_CONF:指令出现在main配置部分是合法的
  NGX_HTTP_SRV_CONF:指令在server配置部分出现是合法的 config
  NGX_HTTP_LOC_CONF:指令在location配置部分出现是合法的
  NGX_HTTP_UPS_CONF:指令在upstream配置部分出现是合法的
  NGX_CONF_NOARGS:指令没有参数
  NGX_CONF_TAKE1:指令读入1个参数
  NGX_CONF_TAKE2:指令读入2个参数
  ……
  NGX_CONF_TAKE7:指令读入7个参数
  NGX_CONF_FLAG:指令读入1个布尔型数据 (“on” or “off”)
  NGX_CONF_1MORE:指令至少读入1个参数
  NGX_CONF_2MORE:指令至少读入2个参数
  ……
  NGX_CONF_TAKE12:配置指令接受1个或者2个参数。
  NGX_CONF_TAKE13:配置指令接受1个或者3个参数。
  ……
  NGX_CONF_MULTI: 配置指令可以接受多个参数,即个数不定。
  NGX_CONF_BLOCK:配置指令可以接受的值是一个配置信息块。也就是一对大括号括起来的内容。里面可以再包括很多的配置指令。比如常见的server指令就是这个属性的。
  NGX_CONF_ANY:配置指令可以接受的任意的参数值。一个或者多个,或者’on’或者’off’,或者是配置块。
set 是一个函数指针,它指向的函数用来进行模块配置;它一般用来将配置文件中的参数传递给程序,并保存在配置结构体中。此函数有三个入参:
指向结构体 ngx_conf_t 的指针, 这个结构体里包含需要传递给指令的参数
指向结构体 ngx_command_t 的指针
指向模块自定义配置结构体的指针
这个函数会在遇到指令时执行,Nginx提供了多个函数用来保存特定类型的参数数据,这些函数包含有:
    ngx_conf_set_flag_slot:将 “on” or “off” 转换成 1 or 0
    ngx_conf_set_str_slot:将字符串保存为 ngx_str_t
    ngx_conf_set_num_slot:解析一个数字并保存为int
    ngx_conf_set_size_slot:解析一个数据大小, 并保存为size_t
conf 该字段指定当前配置项存储的内存位置,因为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 指定该配置项值的精确存放位置,一般指定为某一个结构体变量的字段偏移。例如上面提到的offsetof(ngx_http_hello_loc_conf_t, hello_string) 表示存储到ngx_http_hello_loc_conf_t结构体的hello_string结构成员中。
post 该字段存储一个指针。可以指向任何一个在读取配置过程中需要的数据,以便于进行配置读取的处理。大多数时候,都不需要,所以简单地设为0即可。

3.模块上下文

  模块上下文定义了一大坨函数引用,用来创建和合并三个部分的配置(main,server,location),它是一个静态的ngx_http_module_t结构体,命名方式一般是:ngx_http__module_ctx 。这些函数引用依次是:
  ◇ preconfiguration: 在读入配置前调用
  ◇ postconfiguration: 在读入配置后调用
  ◇ create_main_conf: 在创建main配置时调用
  ◇ init_main_conf: 在初始化main配置时调用
  ◇ init_main_conf: 在创建server配置时调用
  ◇ merge_srv_conf: 合并server和main配置时调用
  ◇ create_loc_conf: 创建location配置时调用
  ◇ merge_loc_conf: 合并location和server配置时调用
来看下结构体ngx_http_module_t 的定义:

?
1
2
3
4
5
6
7
8
9
10
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;

4.模块的定义

  任何模块,我们都需要定义一个ngx_module_t 类型的变量,来说明这个模块本身的信息,这是这个模块最重要的一个信息,它告诉了nginx这个模块的一些信息,上面定义的配置信息,还有模块上下文信息。加载模块的上层代码,都需要通过定义的这个结构,来获取这些信息。
  

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
typedef struct ngx_module_s  ngx_module_t;
struct ngx_module_s {
    ngx_uint_t            ctx_index;
    ngx_uint_t            index;
    ngx_uint_t            spare0;
    ngx_uint_t            spare1;
    ngx_uint_t            abi_compatibility;
    ngx_uint_t            major_version;
    ngx_uint_t            minor_version;
    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_NUMBER_MAJOR  3
#define NGX_NUMBER_MINOR  1
#define NGX_MODULE_V1 0,0,0,0, NGX_DSO_ABI_COMPATIBILITY, NGX_NUMBER_MAJOR, NGX_NUMBER_MINOR
#define NGX_MODULE_V1_PADDING  0,0,0,0,0,0,0,0

举个hello模块的例子:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
ngx_module_t ngx_http_hello_module = {
        NGX_MODULE_V1,                /* 前7个结构成员*/
        &ngx_http_hello_module_ctx,   /* 模块上下文 */
        ngx_http_hello_commands,      /* 模块指令 */
        NGX_HTTP_MODULE,              /* 模块类型,我们所开发的基本都是NGX_HTTP_MODULE类型 */
        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         /* 后8个结构成员*/
};

Handler模块

  Handler一般做4件事:获取location配置、生成合适的响应、发送响应头、发送响应体。Handler有一个参数,即请求结构体。请求结构体包含很多关于客户请求的有用信息,比如说请求方法,URI,请求头等等。

Handler模块结构

  在开发Handler模块时,除了提供上一节介绍的的基本结构体外,handler还需要提供一个处理函数。这个函数负责对来自客户端请求的真正处理。这个函数的处理,既可以选择自己直接生成内容,也可以选择拒绝处理,由后续的handler去进行处理,或者是选择丢给后续的filter进行处理。来看一下这个函数的原型声明:
  

?
1
2
/*r 是http请求, 里面包含请求所有的信息 */
typedef ngx_int_t (*ngx_http_handler_pt) (ngx_http_request_t * r);

该函数处理成功返回NGX_OK,处理发生错误返回NGX_ERROR,拒绝处理(留给后续的handler进行处理)返回NGX_DECLINE。 返回NGX_OK也就代表给客户端的响应已经生成好了,否则返回NGX_ERROR就发生错误了。

Handler模块的挂载

  当一切该定义的结构都已经定义好之后,你需要指定所编写的模块挂载的具体方式及位置。handler有两种挂载方式:
   1. 按处理阶段挂载
   2. 按需挂载

按处理阶段挂载

  为了更精细地控制对于客户端请求的处理过程,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也被称为 content phase handler。举个hello模块挂载的实例:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
staticngx_int_t ngx_http_hello_init(ngx_conf_t *cf)
{
        ngx_http_handler_pt  *h;
        ngx_http_core_main_conf_t  *cmcf;
        /** http模块主配置 */
        cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
        /** 挂载到NGX_HTTP_CONTENT_PHASE阶段 */
        h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers);
        if(h == NULL) {
            returnNGX_ERROR;
        }
        /** 指定handler处理函数 */
        *h = ngx_http_hello_handler;
        returnNGX_OK;
}

注意:有几个阶段是特例,它不调用任何挂载的handler,所以你在开发过程中不要挂载到这几个阶段:
 ● NGX_HTTP_FIND_CONFIG_PHASE
 ● NGX_HTTP_POST_ACCESS_PHASE
 ● NGX_HTTP_POST_REWRITE_PHASE
 ● NGX_HTTP_TRY_FILES_PHASE
所以其实只有7个phase你可以挂载自己的handler。

按需挂载

  这种方式挂载的handler被称为content handler。当一个请求进来以后,nginx从第一个阶段开始依次执行每个阶段中所有handler。执行到内容产生阶段阶段的时候,如果这个location有一个对应的content handler模块,那么就去执行这个content handler模块真正的处理函数。否则继续依次执行内容产生阶段中所有content phase handler,直到某个函数处理返回NGX_OK或者NGX_ERROR。所以当某个location处理到NGX_HTTP_CONTENT_PHASE阶段时,如果有content handler模块,那么NGX_HTTP_CONTENT_PHASE挂载的所有content phase handler都不会被执行了。使用这个方法挂载上去的handler有一个特点是必须在NGX_HTTP_CONTENT_PHASE阶段才能执行到。如果你想自己的handler在更早的阶段执行,那就不要使用这种挂载方式。同样举个例子:
  

?
1
2
3
4
5
6
7
8
9
staticchar * ngx_http_hello_string(ngx_conf_t *cf, ngx_command_t *cmd, void*conf)
{
        ngx_http_core_loc_conf_t  *clcf;
        /** 获取location配置 */
        clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
        /** 指定handler */
        clcf->handler = ngx_http_hello_handler;
        returnNGX_CONF_OK;
}
总结

 编写handler模块大致分为以下几个步骤:
    1. 模块基本结构的编写。包括模块的配置结构,模块指令的定义,模块上下文结构,模块的定义等。
    2. 实现handler的挂载函数。根据模块的需求选择正确的挂载方式。
    3. 编写handler处理函数。模块的功能主要通过这个函数来完成。

handler模块实战

  这一节我们将要实现一个自己的hello模块,它的主要功能很简单,当请求匹配到此模块处理时,返回一个配置的字符串,并且增加一个访问次数的统计。

代码编写

创建一个文件ngx_http_hello_module.c ,输入如下完整代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
#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;
 
staticngx_int_t ngx_http_hello_init(ngx_conf_t *cf);
staticvoid *ngx_http_hello_create_loc_conf(ngx_conf_t *cf);
staticchar *ngx_http_hello_string(ngx_conf_t *cf, ngx_command_t *cmd, void*conf);
staticchar *ngx_http_hello_counter(ngx_conf_t *cf, ngx_command_t *cmd, void*conf);
 
/**
 *该模块的配置指令,这里我们定义了两个指令,分别是hello_string,和hello_counter
 */
staticngx_command_t ngx_http_hello_commands[] = {
   {
    ngx_string("hello_string"),
    /* 指定该指令的作用域是location,无参或者有一个参数*/
    NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS|NGX_CONF_TAKE1,
    /* 回调函数,用于解析参数*/
    ngx_http_hello_string,
    /* 该指令配置信息存储的位置在location区*/
    NGX_HTTP_LOC_CONF_OFFSET,
    /* 存储的具体结构及位置*/
    offsetof(ngx_http_hello_loc_conf_t, hello_string),
    NULL
   },
   {
     ngx_string("hello_counter"),
     /* 指定该指令的作用域是location,有一个布尔类型的参数*/
     NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
     /* 回调函数,用于解析参数*/
     ngx_http_hello_counter,
     /* 该指令配置信息存储的位置在location区*/
     NGX_HTTP_LOC_CONF_OFFSET,
     offsetof(ngx_http_hello_loc_conf_t, hello_counter),
     NULL
   },
    /* 该数组读到此处停止*/
    ngx_null_command
};
 
staticint ngx_hello_visited_times = 0;
 
/**
 * 该指令上下文的定义。
 *    ngx_http_hello_init:这个回调函数负责在读取完配置信息之后挂载此模块
 *    ngx_http_hello_create_loc_conf:此回调函数负责在读取location配置之后,创建模块配置结构的存储空间
 */
staticngx_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
};
/**
 * 该模块真正的处理函数,这里用于生成响应内容
 */
staticngx_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!");
          returnNGX_DECLINED;
      }
      if(my_conf->hello_counter == NGX_CONF_UNSET || my_conf->hello_counter == 0)
      {
           /* 将拼接后的字符串写入到ngx_hello_string */
           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);
      /* 获取ngx_hello_string的长度*/
      content_length = ngx_strlen(ngx_hello_string);
      /* 只处理get方式的请求 */
      if(!(r->method & (NGX_HTTP_GET))) {
              returnNGX_HTTP_NOT_ALLOWED;
      }
      /* 这里我们不需要处理请求体,所以主动丢弃请求体 */
      rc = ngx_http_discard_request_body(r);
      if(rc != NGX_OK) {
              returnrc;
      }
      /* 设置响应的content type为text/html */
      ngx_str_set(&r->headers_out.content_type,"text/html");
      /* 申请内存buffer b存储响应体 */
      b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
      if(b == NULL) {
         returnNGX_HTTP_INTERNAL_SERVER_ERROR;
      }
      /* 绑定buffer b到buffer链中 */
      out.buf = b;
      out.next = NULL;
      /* buffer指针指向正确的内容 */
      b->pos = ngx_hello_string;
      b->last = ngx_hello_string + content_length;
      b->memory = 1;
      /* buffer b是 buffer chain中最后一个buffer*/
      b->last_buf = 1;
      /* 设置状态行 */
      r->headers_out.status = NGX_HTTP_OK;
      r->headers_out.content_length_n = content_length;
      /* 发送响应头 */
      rc = ngx_http_send_header(r);
      if(rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
              returnrc;
      }
      /* 输出内容 */
      returnngx_http_output_filter(r, &out);
}
 
/** 申请内存空间,用于存储模块配置结构*/
staticvoid * 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)
      {
          returnNULL;
      }
      ngx_str_null(&local_conf->hello_string);
      local_conf->hello_counter = NGX_CONF_UNSET;
      returnlocal_conf;
}
 
/** 解析hello_string指令的参数为String类型*/
staticchar * 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);
    returnrv;
}
 
/** 解析hello_string指令的参数为boolean类型*/
staticchar *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);
    returnrv;
}
 
/** 挂载该模块的逻辑,此处是按处理阶段挂载,挂载到NGX_HTTP_CONTENT_PHASE阶段 */
staticngx_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);
        /**ngx_array_push(ngx_array_t *a)函数表示在数组a上新追加一个元素,并返回指向新元素的指针。*/
        h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers);
        if(h == NULL) {
            returnNGX_ERROR;
        }
        *h = ngx_http_hello_handler;
        returnNGX_OK;
}</ngx_http.h></ngx_core.h></ngx_config.h>

config文件

代码编写完之后,我就需要配置和装载该模块,创建一个config文件放到源码同目录,内容如下:

?
1
2
3
4
5
6
#该模块的名字
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"

编译与使用

  Nginx的模块都不是动态链接的,换句话说,Nginx的模块都是静态编译的。此处我们开发的模块放在路径:/home/chengli/ngx-hello-module ,具体信息如下图:
  
 
 /home/chengli/ngx-install-dir 是我们将要安装的目录。另外两个文件是下载的nginx-1-10.1的源码:
 
 这时cd到nginx-1.10.1目录,执行如下命令进行配置:

?
1
./configure –prefix=/home/chengli/ngx-install-dir –add-module=/home/chengli/ngx-hello-module

配置完毕执行make,make install进行编译安装。成功安装之后目录结构如下图:

修改nginx.conf,为了不影响开发环境正常运行,listen端口改成8088.。并添加如下location定义:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
......
server {
      #监听端口改成8088
      listen      8088;
      server_name  localhost;
      ......
      #添加如下配置
      location /hello {
          #hello模块的hello_string指令
          hello_string"created by chengli";
          #hello_counter指令
          hello_counter on;
      }
     ......
  }
  ......

启动nginx,并访问http://10.18.20.5:8088/hello :

至此,我们的hello模块开发完成。

filter模块

概述

  过滤模块是过滤响应头和响应内容的模块,可以对响应的头和内容进行处理。它的处理时间段是在获取回复内容之后,和向用户发送响应之前。它的处理过程分为两个阶段:过滤响应的头部和过滤响应的主体,在这两个阶段可以分别对头部和主体进行修改。过滤响应头部和过滤响应体的入口函数如下:
  

?
1
2
3
4
5
/** 过滤响应头入口 */
ngx_http_top_header_filter(r);
 
/** 过滤响应体入口 */
ngx_http_top_body_filter(r, in);

执行顺序

?
1
filter模块的执行时有顺序的,这个顺序是在编译时就确定了的。编译完之后可以到objs/ngx_modules.c文件中查看所有模块。以下是编译之后的实例:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
char*ngx_module_names[] = {
    "ngx_core_module",
    "ngx_errlog_module",
    "ngx_conf_module",
    "ngx_regex_module",
    "ngx_events_module",
    "ngx_event_core_module",
    "ngx_epoll_module",
    "ngx_http_module",
    "ngx_http_core_module",
    "ngx_http_log_module",
    "ngx_http_upstream_module",
    "ngx_http_static_module",
    "ngx_http_autoindex_module",
    "ngx_http_index_module",
    "ngx_http_auth_basic_module",
    "ngx_http_access_module",
    "ngx_http_limit_conn_module",
    "ngx_http_limit_req_module",
    "ngx_http_geo_module",
    "ngx_http_map_module",
    "ngx_http_split_clients_module",
    "ngx_http_referer_module",
    "ngx_http_rewrite_module",
    "ngx_http_proxy_module",
    "ngx_http_fastcgi_module",
    "ngx_http_uwsgi_module",
    "ngx_http_scgi_module",
    "ngx_http_memcached_module",
    "ngx_http_empty_gif_module",
    "ngx_http_browser_module",
    "ngx_http_upstream_hash_module",
    "ngx_http_upstream_ip_hash_module",
    "ngx_http_upstream_least_conn_module",
    "ngx_http_upstream_keepalive_module",
    "ngx_http_upstream_zone_module",
    /** 这是我们上文中编写的hello模块 */
    "ngx_http_hello_module",
    /** 以下是所有ftiler模块 */
    "ngx_http_write_filter_module",
    "ngx_http_header_filter_module",
    "ngx_http_chunked_filter_module",
    "ngx_http_range_header_filter_module",
    "ngx_http_gzip_filter_module",
    "ngx_http_postpone_filter_module",
    "ngx_http_ssi_filter_module",
    "ngx_http_charset_filter_module",
    "ngx_http_userid_filter_module",
    "ngx_http_headers_filter_module",
    /** 以下是我们将要介绍的filter模块 */
    "ngx_http_content_bold_filter_module",
    "ngx_http_copy_filter_module",
    "ngx_http_range_body_filter_module",
    "ngx_http_not_modified_filter_module",
    NULL
};

从ngx_http_write_filter_module到ngx_http_not_modified_filter_module是本例中所有的filter模块,他们的执行顺序是反过来的。也就是会先执行ngx_http_not_modified_filter_module,最后执行ngx_http_write_filter_module。实际上所有filter模块头部过滤函数和主体过滤函数共同组成了一个链表结构。ngx_http_top_header_filter和ngx_http_top_body_filter是两个全局变量,也是头部过滤和主体过滤函数的入口。为了方便理解,举一个filter模块挂载的实例:

?
1
2
3
4
5
6
7
8
9
10
11
12
/**
 * ngx_http_top_header_filter和ngx_http_top_body_filter是Nginx中两个全局变量,
 * ngx_http_next_header_filter和ngx_http_next_body_filter是我们模块中定义的局部变量。
 */
staticngx_int_t ngx_http_content_bold_init(ngx_conf_t *cf)
{
    ngx_http_next_header_filter = ngx_http_top_header_filter;
    ngx_http_top_header_filter = ngx_http_content_bold_header_filter;
    ngx_http_next_body_filter = ngx_http_top_body_filter;
    ngx_http_top_body_filter = ngx_http_content_bold_body_filter;
    returnNGX_OK;
}

这样所有filter模块串起来之后就变成下面这样:
header_filter1→ header_filter2→ header_filter3→ …→ header_filterN→ body_filter1→ body_filter2→ body_filter3→ body_filterN

filter模块实战

?
1
这里我们要实现一个自己的filter模块,它的主要功能是把上节handler模块输出的内容,加粗显示。

代码编写

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
 
/** 模块的配置结构 */
typedef struct
{
    ngx_int_t bold_flag;
} ngx_http_bold_filter_loc_conf_t;
 
staticngx_int_t ngx_http_content_bold_init(ngx_conf_t *cf);
staticvoid *ngx_http_content_bold_create_loc_conf(ngx_conf_t *cf);
staticchar *ngx_http_content_bold_string(ngx_conf_t *cf, ngx_command_t *cmd, void*conf);
staticngx_http_output_body_filter_pt    ngx_http_next_body_filter;
staticngx_http_output_header_filter_pt  ngx_http_next_header_filter;
 
/** 模块指令,这里我们创建了一个配置指令content_bold */
staticngx_command_t ngx_http_content_bold_commands[] = {
   {
    ngx_string("content_bold"),
    /* 该指令只能出现在location块中,并且有一个参数 */
    NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
    /* 读取解析参数的回调函数 */
    ngx_http_content_bold_string,
    NGX_HTTP_LOC_CONF_OFFSET,
    offsetof(ngx_http_bold_filter_loc_conf_t, bold_flag),
    NULL
   },
    ngx_null_command
};
 
/**
 * 该模块上下文的定义。
 *    ngx_http_content_bold_init:这个回调函数负责在读取完配置信息之后注册filter函数
 *    ngx_http_content_bold_create_loc_conf:此回调函数负责在读取location配置之后,创建模块配置结构的存储空间
 */
staticngx_http_module_t ngx_http_content_bold_module_ctx = {
        NULL,                        
        ngx_http_content_bold_init,   
        NULL,                       
        NULL,                        
        NULL,                       
        NULL,                        
        ngx_http_content_bold_create_loc_conf,
        NULL                         
};
 
/* 该模块的定义,指定了该模块对应的指令、上下文及模块类型 */
ngx_module_t ngx_http_content_bold_filter_module = {
        NGX_MODULE_V1,
        &ngx_http_content_bold_module_ctx, 
        ngx_http_content_bold_commands,   
        NGX_HTTP_MODULE,            
        NULL,                        
        NULL,                       
        NULL,                      
        NULL,                    
        NULL,                       
        NULL,                     
        NULL,                      
        NGX_MODULE_V1_PADDING
};
staticu_char header_str[11] = "<h1>";
staticu_char tail_str[13] = "</h1>";
 
/**
 * 请求体处理函数,过滤请求体功能主要是在这里完成的
 * 这里主要是在输入链表的首位分别加上header_str和tail_str已达到加粗的目的
 */
staticngx_int_t ngx_http_content_bold_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
    ngx_chain_t *out;
    ngx_chain_t *last_chain_ele;
    ngx_buf_t *b;
    ngx_buf_t *e;
 
    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,"content_bold filter");
    ngx_http_bold_filter_loc_conf_t * my_conf;
    /* 这里是获取该指令的配置信息 */
    my_conf = ngx_http_get_module_loc_conf(r, ngx_http_content_bold_filter_module);
    /* 如果没有配置content_bold或者值为off。则什么都不处理直接调用下一个body_filter */
    if(my_conf->bold_flag == NGX_CONF_UNSET || my_conf->bold_flag == 0) {
        returnngx_http_next_body_filter(r, in);
    }
    /* 如果响应体内容为空,也什么都不做直接调用下一个body_filter */
    if(in == NULL) {
        returnngx_http_next_body_filter(r, in);
    }
    /** 为链表首尾结点申请内存 */
    out = ngx_pcalloc(r->pool, sizeof(ngx_chain_t));
    last_chain_ele = ngx_pcalloc(r->pool, sizeof(ngx_chain_t));
    b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
    e = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
    if(out==NULL || b == NULL||e == NULL||last_chain_ele==NULL) {
        returnNGX_HTTP_INTERNAL_SERVER_ERROR;
    }
    /* 为第一个结点赋值:header_str */
    b->pos = header_str;
    b->last = header_str + ngx_strlen(header_str);
    b->memory = 1;
    b->last_buf = 0;
    out->buf = b;
    out->next = in;
    ngx_chain_t *last_but_one = ngx_pcalloc(r->pool, sizeof(ngx_chain_t));
    while(in) {
       if(in!=NULL){
             in->buf->last_buf = 0;
             last_but_one = in;
       }
       in = in -> next;
    }
    /* 为最好一个结点赋值:tail_str */
    e->pos = tail_str;
    e->last = tail_str + ngx_strlen(tail_str);
    e->memory = 1;
    e->last_buf = 1;
    last_chain_ele->buf = e;
    last_but_one->next = last_chain_ele;
    returnngx_http_next_body_filter(r, out);
}
 
/**
 * 头部过滤函数,这里主要是更改content_length
 */
staticngx_int_t ngx_http_content_bold_header_filter(ngx_http_request_t *r)
{
  r->headers_out.content_length_n = r->headers_out.content_length_n+ ngx_strlen(header_str) + ngx_strlen(tail_str);
  returnngx_http_next_header_filter(r);
}
 
/**
 * 为该模块的配置结构申请存储空间
 */
staticvoid * ngx_http_content_bold_create_loc_conf(ngx_conf_t *cf)
{
    ngx_http_bold_filter_loc_conf_t* local_conf = NULL;
    local_conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_bold_filter_loc_conf_t));
    if(local_conf == NULL)
    {
        returnNULL;
    }
    local_conf->bold_flag = NGX_CONF_UNSET;
    returnlocal_conf;
}
 
/**
 * 解析指令参数
 */
staticchar *ngx_http_content_bold_string(ngx_conf_t *cf, ngx_command_t *cmd, void*conf)
{
    ngx_http_bold_filter_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,"bold_flag:%d", local_conf->bold_flag);
    returnrv;
}
 
/**
 * 挂载header_filter处理函数和body_filter处理函数
 */
staticngx_int_t ngx_http_content_bold_init(ngx_conf_t *cf)
{
    ngx_http_next_header_filter = ngx_http_top_header_filter;
    ngx_http_top_header_filter = ngx_http_content_bold_header_filter;
    ngx_http_next_body_filter = ngx_http_top_body_filter;
    ngx_http_top_body_filter = ngx_http_content_bold_body_filter;
 
    returnNGX_OK;
}</ngx_http.h></ngx_core.h></ngx_config.h>

config文件

?
1
2
3
4
5
6
#模块的名称,要与模块的定义变量名保持一致
ngx_addon_name=ngx_http_content_bold_filter_module
#模块的类型为filter
HTTP_AUX_FILTER_MODULES="$HTTP_AUX_FILTER_MODULES ngx_http_content_bold_filter_module"
#模块源码路径
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_content_bold_filter_module.c"

编译与使用

?
1
2
3
此处我们开发的模块放在路径:/home/chengli/ngx-content-bold-filter-module ,具体信息如下图:
![这里写图片描述](http://img.blog.csdn.net/20161108101529594)
这里我们的filter模块与我们上节的hello模块放到的同一个目录:


切换到nginx-1.10.1目录,执行如下命令进行配置:

?
1
./configure --prefix=/home/chengli/ngx-install-dir --add-module=/home/chengli/ngx-hello-module --add-module=/home/chengli/ngx-content-bold-filter-module

同样configure完之后执行make,make install进行编译安装。
配置nginx.conf,添加content_bold指令进行测试:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
......
server {
      #监听端口改成8088
      listen      8088;
      server_name  localhost;
      ......
      #添加如下配置
      location /hello {
          #hello模块的hello_string指令
          hello_string"created by chengli";
          #hello_counter指令
          hello_counter on;
          #content_bold指令
          content_bold on;
      }
     ......
  }
  ......

重新启动nginx,并访问http://10.18.20.5:8088/hello :

成功实现字体加粗功能。

总结

  想要进行Nginx模块开发,首先需要简单了解下Nginx模块运作流程,其次要知道模块的组成部分及挂载方式。此外还需要了解一些Nginx自定义的结构体(最好的方式就是看源码)。知道上述内容之后就可以进行开发了,个人觉得主要关注的逻辑还是在handler和filter处理函数中。其他的东西即使不了解按照实例照搬即可。

原创粉丝点击