想要进行nginx模块开发,首先你需要比较熟悉C语言,其次你需要对HTTP协议有一定的了解。
Nginx主配置文件中主要包括六块:main,events,http,server,location,upstream 结构如下:
#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响应体
● 选择一个后端服务器
● 初始化一个将发往后端服务器的请求
● 重新初始化一个将发往后端服务器的请求
● 处理来自后端服务器的响应
● 完成与后端服务器的交互
模块的存储struct有三种,分别是main,server和location。绝大多数模块仅需要一个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;
这是后面将要讲的hello模块的存储结构的定义。
配置指令是用来定义你所开发模块的配置信息,例如指令的名称、参数等信息。它是一个静态的ngx_command_t类型的数组。同样举个例子:
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_null_command
};
下面我们来分析下ngx_command_t 结构体。
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即可。
模块上下文定义了一大坨函数引用,用来创建和合并三个部分的配置(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 的定义:
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;
任何模块,我们都需要定义一个ngx_module_t 类型的变量,来说明这个模块本身的信息,这是这个模块最重要的一个信息,它告诉了nginx这个模块的一些信息,上面定义的配置信息,还有模块上下文信息。加载模块的上层代码,都需要通过定义的这个结构,来获取这些信息。
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模块的例子:
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一般做4件事:获取location配置、生成合适的响应、发送响应头、发送响应体。Handler有一个参数,即请求结构体。请求结构体包含很多关于客户请求的有用信息,比如说请求方法,URI,请求头等等。
在开发Handler模块时,除了提供上一节介绍的的基本结构体外,handler还需要提供一个处理函数。这个函数负责对来自客户端请求的真正处理。这个函数的处理,既可以选择自己直接生成内容,也可以选择拒绝处理,由后续的handler去进行处理,或者是选择丢给后续的filter进行处理。来看一下这个函数的原型声明:
/*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有两种挂载方式:
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模块挂载的实例:
static ngx_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) {
return NGX_ERROR;
}
/** 指定handler处理函数 */
*h = ngx_http_hello_handler;
return NGX_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在更早的阶段执行,那就不要使用这种挂载方式。同样举个例子:
static char * 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;
return NGX_CONF_OK;
}
编写handler模块大致分为以下几个步骤:
1. 模块基本结构的编写。包括模块的配置结构,模块指令的定义,模块上下文结构,模块的定义等。
2. 实现handler的挂载函数。根据模块的需求选择正确的挂载方式。
3. 编写handler处理函数。模块的功能主要通过这个函数来完成。
这一节我们将要实现一个自己的hello模块,它的主要功能很简单,当请求匹配到此模块处理时,返回一个配置的字符串,并且增加一个访问次数的统计。
创建一个文件ngx_http_hello_module.c ,输入如下完整代码:
#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);
/** *该模块的配置指令,这里我们定义了两个指令,分别是hello_string,和hello_counter */
static ngx_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
};
static int ngx_hello_visited_times = 0;
/** * 该指令上下文的定义。 * ngx_http_hello_init:这个回调函数负责在读取完配置信息之后挂载此模块 * ngx_http_hello_create_loc_conf:此回调函数负责在读取location配置之后,创建模块配置结构的存储空间 */
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_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))) {
return NGX_HTTP_NOT_ALLOWED;
}
/* 这里我们不需要处理请求体,所以主动丢弃请求体 */
rc = ngx_http_discard_request_body(r);
if (rc != NGX_OK) {
return rc;
}
/* 设置响应的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) {
return NGX_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) {
return rc;
}
/* 输出内容 */
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;
}
/** 解析hello_string指令的参数为String类型*/
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;
}
/** 解析hello_string指令的参数为boolean类型*/
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;
}
/** 挂载该模块的逻辑,此处是按处理阶段挂载,挂载到NGX_HTTP_CONTENT_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);
/**ngx_array_push(ngx_array_t *a)函数表示在数组a上新追加一个元素,并返回指向新元素的指针。*/
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;
}
代码编写完之后,我就需要配置和装载该模块,创建一个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"
Nginx的模块都不是动态链接的,换句话说,Nginx的模块都是静态编译的。此处我们开发的模块放在路径:/home/chengli/ngx-hello-module ,具体信息如下图:
/home/chengli/ngx-install-dir 是我们将要安装的目录。另外两个文件是下载的nginx-1-10.1的源码:
这时cd到nginx-1.10.1目录,执行如下命令进行配置:
./configure –prefix=/home/chengli/ngx-install-dir –add-module=/home/chengli/ngx-hello-module
配置完毕执行make,make install进行编译安装。成功安装之后目录结构如下图:
修改nginx.conf,为了不影响开发环境正常运行,listen端口改成8088.。并添加如下location定义:
......
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模块开发完成。
过滤模块是过滤响应头和响应内容的模块,可以对响应的头和内容进行处理。它的处理时间段是在获取回复内容之后,和向用户发送响应之前。它的处理过程分为两个阶段:过滤响应的头部和过滤响应的主体,在这两个阶段可以分别对头部和主体进行修改。过滤响应头部和过滤响应体的入口函数如下:
/** 过滤响应头入口 */
ngx_http_top_header_filter(r);
/** 过滤响应体入口 */
ngx_http_top_body_filter(r, in);
filter模块的执行时有顺序的,这个顺序是在编译时就确定了的。编译完之后可以到objs/ngx_modules.c文件中查看所有模块。以下是编译之后的实例:
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模块挂载的实例:
/** * ngx_http_top_header_filter和ngx_http_top_body_filter是Nginx中两个全局变量, * ngx_http_next_header_filter和ngx_http_next_body_filter是我们模块中定义的局部变量。 */
static ngx_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;
return NGX_OK;
}
这样所有filter模块串起来之后就变成下面这样:
header_filter1→ header_filter2→ header_filter3→ …→ header_filterN→ body_filter1→ body_filter2→ body_filter3→ body_filterN
这里我们要实现一个自己的filter模块,它的主要功能是把上节handler模块输出的内容,加粗显示。
#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;
static ngx_int_t ngx_http_content_bold_init(ngx_conf_t *cf);
static void *ngx_http_content_bold_create_loc_conf(ngx_conf_t *cf);
static char *ngx_http_content_bold_string(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static ngx_http_output_body_filter_pt ngx_http_next_body_filter;
static ngx_http_output_header_filter_pt ngx_http_next_header_filter;
/** 模块指令,这里我们创建了一个配置指令content_bold */
static ngx_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配置之后,创建模块配置结构的存储空间 */
static ngx_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
};
static u_char header_str[11] = "<html><h1>";
static u_char tail_str[13] = "</h1></html>";
/** * 请求体处理函数,过滤请求体功能主要是在这里完成的 * 这里主要是在输入链表的首位分别加上header_str和tail_str已达到加粗的目的 */
static ngx_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) {
return ngx_http_next_body_filter(r, in);
}
/* 如果响应体内容为空,也什么都不做直接调用下一个body_filter */
if (in == NULL) {
return ngx_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) {
return NGX_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;
return ngx_http_next_body_filter(r, out);
}
/** * 头部过滤函数,这里主要是更改content_length */
static ngx_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);
return ngx_http_next_header_filter(r);
}
/** * 为该模块的配置结构申请存储空间 */
static void * 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)
{
return NULL;
}
local_conf->bold_flag = NGX_CONF_UNSET;
return local_conf;
}
/** * 解析指令参数 */
static char *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);
return rv;
}
/** * 挂载header_filter处理函数和body_filter处理函数 */
static ngx_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;
return NGX_OK;
}
#模块的名称,要与模块的定义变量名保持一致
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"
此处我们开发的模块放在路径:/home/chengli/ngx-content-bold-filter-module ,具体信息如下图:
![这里写图片描述](http://img.blog.csdn.net/20161108101529594)
这里我们的filter模块与我们上节的hello模块放到的同一个目录:
切换到nginx-1.10.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指令进行测试:
......
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处理函数中。其他的东西即使不了解按照实例照搬即可。