Nginx 允许引入第三方模块来扩展 Nginx 的功能。官方网站 NGINX 3rd Party Modules 列出了 Nginx 很多的第三方模块。除此之外,很多很有用的模块也能在 github 等网站上找到。
添加模块
接下来通过添加 njs 模块为例来介绍如何添加第三方模块。njs 是 Nginx + JavaScript 的缩写,简单来说,就是 Nginx 里面可以运行 JavaScript,用 JavaScript 来构建动态的 Web 应用。Nginx NJS 包含两个 Nginx 扩展模块:ngx_http_js_module 和 ngx_stream_js_module。
首先克隆 njs 模块的代码:
git clone https://github.com/nginx/njs.git
查看当前已安装的 Nginx 的编译信息:
[root@nginx-plus1 nginx-1.14.2]# /usr/local/nginx/sbin/nginx -V
nginx version: nginx/1.14.2
built by gcc 4.8.5 20150623 (Red Hat 4.8.5-36) (GCC)
built with OpenSSL 1.0.2k-fips 26 Jan 2017
TLS SNI support enabled
configure arguments: --with-debug --with-stream --prefix=/usr/local/nginx --with-http_ssl_module
进入 Nginx 源代码目录,执行 ./configure,在最后通过 --add-module 指令加上 njs 模块。执行完成后会在当前目录生成编译所需的相关文件(例如 Makefile 和 objs 目录等)。
cd /root/nginx-1.14.2
# 复制前面nginx -V看到的参数,在最后--add-module加上njs模块的路径
./configure --with-debug --with-stream --prefix=/usr/local/nginx --with-http_ssl_module --add-module=/root/njs/nginx
执行完上面命令后,objs 目录下的 ngx_modules.c 文件中的 ngx_modules 数组中可以看到新增的 njs 模块:
cat /root/nginx-1.14.2/objs/ngx_modules.c
接下来执行 make 命令开始编译:
make
编译完成后注意不需要执行 make install 命令,只需要将生成的 Nginx 二进制文件覆盖原先的二进制文件即可。
cp /root/nginx-1.14.2/objs/nginx /usr/local/nginx/sbin/nginx -f
现在展示如何使用 njs 模块的功能。在 nginx 的 conf 目录下新建 js 目录,用于存放 javascript 代码。在 js 目录下创建 http.js 文件,内容如下:
function summary(r) {
var a, s, h;
s = "JS summary\n\n";
//打印请求方法,HTTP版本,Host,客户端地址,URI
s += "Method: " + r.method + "\n";
s += "HTTP version: " + r.httpVersion + "\n";
s += "Host: " + r.headersIn.host + "\n";
s += "Remote Address: " + r.remoteAddress + "\n";
s += "URI: " + r.uri + "\n";
//打印请求头
s += "Headers:\n";
for (h in r.headersIn) {
s += " header '" + h + "' is '" + r.headersIn[h] + "'\n";
}
//打印参数内容
s += "Args:\n";
for (a in r.args) {
s += " arg '" + a + "' is '" + r.args[a] + "'\n";
}
return s;
}
function baz(r) {
//设置响应状态码
r.status = 200;
//设置响应头
r.headersOut.foo = 1234;
r.headersOut['Content-Type'] = "text/plain; charset=utf-8";
r.headersOut['Content-Length'] = 15;
r.sendHeader();
//设置响应内容
r.send("nginx");
r.send("java");
r.send("script");
r.finish();
}
//export default用于导出常量、函数、文件、模块等
export default {foo, summary, baz, hello};
编写 Nginx 配置文件 /usr/local/nginx/conf/nginx.conf:
events {}
http {
#导入js文件
js_import js/http.js;
#设置变量,变量值为调用js文件的相应函数的返回值
js_set $foo http.foo;
js_set $summary http.summary;
server {
listen 8000;
location / {
add_header X-Foo $foo; #将变量foo的结果添加到响应头中
js_content http.baz; #执行其中 JS 内容并输出
}
location = /summary {
return 200 $summary; #返回变量summary的结果
}
}
}
启动 Nginx:
/usr/local/nginx/sbin/nginx
客户端请求 Nginx:
[root@nginx-plus1 ~]# curl localhost:8000 -i
HTTP/1.1 200 OK
Server: nginx/1.14.2
Date: Tue, 15 Jun 2021 14:04:39 GMT
Content-Type: text/plain; charset=utf-8
Connection: keep-alive
foo: 1234
Content-Length: 15
X-Foo: foo
nginxjavascript
[root@nginx-plus1 ~]# curl localhost:8000/summary?name=chengzw
JS summary
Method: GET
HTTP version: 1.1
Host: localhost:8000
Remote Address: 127.0.0.1
URI: /summary
Headers:
header 'User-Agent' is 'curl/7.29.0'
header 'Host' is 'localhost:8000'
header 'Accept' is '*/*'
Args:
arg 'name' is 'chengzw'
动态模块
Nginx 在 Nginx 1.9.11(release at 2016-02-09) 版本中新增了动态模块(Dynamic Module) 的支持。动态模块在第一次通过 ./configure --add-dynamic-module 编译后,之后如果要对动态模块进行升级,只需要重新编译动态模块,然后替换在 modules 目录内该动态模块的 .so
文件即可,无需替换 Nginx 二进制文件。
下面还是以 njs 模块的例子来演示如何添加动态模块。使用 --add-dynamic-module 指令以动态模块的方式添加 njs 模块:
./configure --with-debug --with-stream --prefix=/usr/local/nginx --with-http_ssl_module --add-dynamic-module=/root/njs/nginx
# 编译
make
编译完成后注意不需要执行 make install 命令,只需要将生成的 Nginx 二进制文件覆盖原先的二进制文件即可。
cp /root/nginx-1.14.2/objs/nginx /usr/local/nginx/sbin/nginx -f
如果是使用 make install 命令安装,会自动在 Nginx 运行目录下创建 modules 目录,这里我们需要手动创建 modules 目录:
mkdir /usr/local/nginx/modules
查看 modules 目录,可以看到有 http 和 stream 两个 njs 模块对应的文件:
[root@nginx-plus1 nginx]# ll /usr/local/nginx/modules
total 7392
-rwxr-xr-x 1 root root 3825792 Jun 11 08:03 ngx_http_js_module.so
-rwxr-xr-x 1 root root 3738944 Jun 11 08:03 ngx_stream_js_module.so
使用 load_module 指令加载动态模块,注意 load_module 指令必须在所有 block (包括 events、http、stream、mail)之前使用:
# 加载动态模块
load_module modules/ngx_http_js_module.so;
load_module modules/ngx_stream_js_module.so;
events {}
http {
#导入js文件
js_import js/http.js;
#设置变量,变量值为调用js文件的相应函数的返回值
js_set $foo http.foo;
js_set $summary http.summary;
server {
listen 8000;
location / {
add_header X-Foo $foo; #将变量foo的结果添加到响应头中
js_content http.baz; #执行其中 JS 内容并输出
}
location = /summary {
return 200 $summary; #返回变量summary的结果
}
}
}
自定义 HTTP 模块
我们定义一个简单的 HTTP 模块,当客户端发送请求时,返回 Hello World!
。
模块开发有以下步骤:
- 1.编写模块基本结构。包括模块配置项,模块上下文,模块配置信息。
- 2.实现 handler 的挂载函数。根据模块的需求选择正确的挂载方式。
- 3.编写 handler 处理函数。模块的功能主要通过这个函数来完成。
- 4.编写编译的文件 config。
本例源码地址:https://github.com/cr7258/nginx-module/tree/master/helloworld
定义模块配置项
模块配置项 ngx_command_s 结构体定义在 src/core/ngx_conf_file.h 文件中,其结构如下所示:
struct ngx_command_s {
// 配置项的名称,例如gzip
ngx_str_t name;
// 配置项类型,type将指定配置项可以出现的位置。例如出现在location{}或者server{}中,
// 以及它可以携带的参数个数
ngx_uint_t type;
// 出现了name中定义的配置项后,将会调用该方法处理配置项的参数
char *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *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。
ngx_uint_t conf;
//指定该配置项值的精确存放位置,一般指定为某一个结构体变量的字段偏移。因为对于配置信息的存储,一般我们都是定义个结构体来存储的。
// 那么比如我们定义了一个结构体A,该项配置的值需要存储到该结构体的b字段。那么在这里就可以填写为offsetof(A, b)。
// 对于有些配置项,它的值不需要保存或者是需要保存到更为复杂的结构中时,这里可以设置为0。
ngx_uint_t offset;
// 该字段存储一个指针。可以指向任何一个在读取配置过程中需要的数据,以便于进行配置读取的处理。
// 大多数时候,都不需要,所以简单地设为NULL即可。
void *post;
};
我们定义了一个配置项,该配置项可以出现在 location 配置块中,该配置项没有参数。当在某个配置块中出现 mytest 配置时,Nginx 将会调用 ngx_http_mytest 方法。
/*
定义模块配置项
*/
static ngx_command_t ngx_http_mytest_commands[] =
{
{
ngx_string("mytest"),
NGX_HTTP_LOC_CONF | NGX_CONF_NOARGS,
// 当在某个配置块中出现 mytest 配置时,Nginx 将会调用 ngx_http_mytest 方法。
ngx_http_mytest,
NGX_HTTP_LOC_CONF_OFFSET,
0,
NULL
},
ngx_null_command //在ngx_http_hello_commands这个数组定义的最后,都要加一个ngx_null_command作为结尾。
};
定义模块上下文
模块上下文 ngx_http_module_t 结构体定义在 src/http/ngx_http_config.h 文件中。这是一个 ngx_http_module_t 类型的静态变量,这个变量实际上是提供一组回调函数指针,这些函数将被 Nginx 在合适的时间进行调用。其结构如下所示:
typedef struct {
// 解析配置文件前调用
ngx_int_t (*preconfiguration)(ngx_conf_t *cf);
// 完成配置文件的解析后调用
ngx_int_t (*postconfiguration)(ngx_conf_t *cf);
// 当需要创建数据结构用于存储main级别(直属于http{...}块的配置项)的全局配置项时,
// 可以通过create_main_conf回调方法创建存储全局配置项的结构体
void *(*create_main_conf)(ngx_conf_t *cf);
// 常用于初始化main级别配置项
char *(*init_main_conf)(ngx_conf_t *cf, void *conf);
// 当需要创建数据结构用于存储srv级别(直属于server{...}块的配置项)的配置项时,
// 可以通过create_srv_conf回调方法创建存储srv级别配置项的结构体
void *(*create_srv_conf)(ngx_conf_t *cf);
// 主要用于合并main级别和srv级别下同名的配置项
char *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, void *conf);
// 当需要创建数据结构用于存储loc级别(直属于location{...}块的配置项)的配置项时,
// 可以通过create_loc_conf回调方法创建存储loc级别配置项的结构体
void *(*create_loc_conf)(ngx_conf_t *cf);
// 主要用于合并srv级别和loc级别下同名的配置项
char *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf);
} ngx_http_module_t;
HTTP 框架在读取,重载配置文件时定义了由 ngx_http_module_t 接口描述的 8 个阶段,HTTP 框架在启动过程中会在每个阶段中调用 ngx_http_module_t 中相应的方法。当然,如果 ngx_http_module_t 中的某个回调方法设置为 NULL 空指针时,那么 HTTP 框架是不会调用它的。
本例中我们没有实现 HTTP 框架初始化时会调用的 ngx_http_module_t 中的 8 个方法。
/*
定义模块上下文
*/
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 类型的变量来说明这个模块本身的信息,从某种意义上来说,这是这个模块最重要的一个信息,它告诉了 Nginx 这个模块的一些信息,上面定义的配置信息,还有模块上下文信息,都是通过这个结构来告诉 Nginx 系统的,也就是加载模块的上层代码,都需要通过定义的这个结构,来获取这些信息。
#define NGX_MODULE_V1 0, 0, 0, 0, 0, 0, 1
#define NGX_MODULE_V1_PADDING 0, 0, 0, 0, 0, 0, 0, 0
// src/core/ngx_core.h
typedef struct ngx_module_s ngx_module_t;
// src/core/ngx_conf_file.h
struct ngx_module_s {
// 下面的 ctx_index,index,spare0,spare1,spare2,spare3,version 变量不需要在定义时赋值,
// 可以用 Nginx 准备好的宏 NGX_MODULE_V1 来定义,它已经定义好了这7个值
ngx_uint_t ctx_index;
ngx_uint_t index;
ngx_uint_t spare0;
ngx_uint_t spare1;
ngx_uint_t spare2;
ngx_uint_t spare3;
ngx_uint_t version;
// 指向模块的上下文结构体
void *ctx;
// 模块配置项
ngx_command_t *commands;
// 模块的类型,它与ctx指针是紧密相关的。在官方Nginx中,它的取值范围是以下5种:
// NGINX_HTTP_MODULE,NGINX_CORE_MODULE,NGINX_CONF_MODULE,NGX_EVENT_MODULE,NGX_MAIL_MODULE
ngx_uint_t type;
// 在Nginx启动、停止的过程中,以下7个函数指针表示有7个执行点会分别调用这7种方法。
// 如果不需要在Nginx启动或者停止的过程中执行它,就简单设置为NULL即可。
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);
// 以下8个字段为保留字段,目前没有使用,可以使用Nginx提供的NGX_MODULE_V1_PADDING 宏来填充。
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;
};
定义 mytest 模块,这样 mytest 模块在编译时将会被加入到 ngx_modules 全局数组中。Nginx 在启动时,会调用所有模块的初始化回调方法,这个例子中我们没有实现它们。
/*
定义模块
*/
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,当 Nginx 在创建进程线程或者结束进程线程时进行调用。但大多数模块在这些时刻并不需要做什么,所以都简单赋值为 NULL。
定义 handler 挂载函数
在 Nginx 的 ngx_http_phases 中定义了 HTTP 框架对请求进行处理的 11 个阶段,结构体定义在 src/http/ngx_http_core_module.h :
typedef enum {
// 1.在接收到完整的HTTP头部后读取请求内容的阶段
NGX_HTTP_POST_READ_PHASE = 0,
// 2.在将请求的URI与location表达式匹配之前,修改请求的URI的阶段(所谓的重定向)
NGX_HTTP_SERVER_REWRITE_PHASE,
// 3.根据请求的URI寻找匹配的location表达式,
// 这个阶段只能由ngx_http_core_module模块实现,不建议其他HTTP模块重新定义这一阶段的行为
NGX_HTTP_FIND_CONFIG_PHASE,
// 4.寻找到匹配的location之后再次利用rewrite修改请求的URI
NGX_HTTP_REWRITE_PHASE,
// 5.这一阶段是用于在rewrite重写URI后,防止错误的nginx.conf配置导致死循环(递归地修改URI)
// 因此,这一阶段仅由ngx_http_core_module模块处理。目前,控制死循环的方式很简单,首先检查rewrite的次数,
// 如果一个请求超过10次重定向,就认为进入了死循环,这时候就会向用户返回500,表示服务器内部错误
NGX_HTTP_POST_REWRITE_PHASE,
// 6.决定请求访问权限之前,HTTP模块可以介入处理的阶段
NGX_HTTP_PREACCESS_PHASE,
// 7.这个阶段用于让HTTP模块判断是否允许这个请求访问Nginx服务器
NGX_HTTP_ACCESS_PHASE,
// 8.在NGX_HTTP_ACCESS_PHASE阶段中,当HTTP模块的handler处理函数返回不允许访问的错误代码时(实际就是NGX_HTTP_FORBIDDEN或者NGX_HTTP_UNAUTHORIZED),
// 这里将负责向用户发送拒绝服务的错误响应,因此,这个阶段实际上是用于给NGX_HTTP_ACCESS_PHASE阶段收尾的
NGX_HTTP_POST_ACCESS_PHASE,
// 9.这个阶段完全是为了给try_files配置项而设立的,当Nginx请求静态文件资源时,
// try_files配置项可以使这个请求顺序地访问多个静态文件资源,如果某一次访问失败,则继续访问try_files中指定的下一个静态资源。
NGX_HTTP_TRY_FILES_PHASE,
// 10.处理HTTP请求内容的阶段,这是大部分HTTP模块最愿意介入的阶段
NGX_HTTP_CONTENT_PHASE,
// 11.处理完请求后记录日志的阶段
NGX_HTTP_LOG_PHASE
} ngx_http_phases;
一般情况下,我们自定义的模块,大多数是挂载在 NGX_HTTP_CONTENT_PHASE 阶段的,默认也就是属于这个模块。
有几个阶段是特例,它们没有 Hook 挂载点(也就意味着,在这几个阶段不允许挂载任何第三方处理逻辑),它们仅由 HTTP 框架实现:
- NGX_HTTP_FIND_CONFIG_PHASE
- NGX_HTTP_POST_ACCESS_PHASE
- NGX_HTTP_POST_REWRITE_PHASE
- NGX_HTTP_TRY_FILES_PHASE
函数的挂载分为两种方式:一种方式就是按处理阶段挂载,以这种方式挂载的 handler 被称为 content phase handlers;另外一种挂载方式就是按需挂载,以这种方式挂载的 handler 被称为 content handler。
按处理阶段挂载
按处理阶段挂载的动作一般是在模块上下文调用的 postconfiguration 函数中。本例介绍按需挂载,这里不展开说明。
按需挂载
当一个请求进来以后,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。
本例我们使用按需挂载的方式。当 Nginx 接收完 HTTP 请求的头部信息时,就会调用 HTTP 框架处理请求。在 ngx_http_mytest 方法中,我们定义了请求的处理方法为 ngx_http_mytest_handler,
/*
按需挂载,ngx_http_mytest_handler方法是真正处理请求的方法
*/
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;
}
定义 handler 处理函数
当出现 mytest 配置项时,ngx_http_mytest 方法会被调用,这时将 ngx_http_core_loc_conf_t 结构的 handler 成员指定为 ngx_http_mytest_handler,另外,HTTP 框架在接收完 HTTP 请求的头部后,会调用 handler 挂载函数指向的 handler 处理函数。下面看一下 handler 成员的原型 ngx_http_handler_pr:
// src/http/ngx_http_request.h
typedef ngx_int_t (*ngx_http_handler_pt)(ngx_http_request_t *r);
从上面这段代码可以看出,实际处理请求的方法 ngx_http_mytest_handler 在接收一个 ngx_http_request_t 类型的参数 r,返回一个 ngx_int_t 类型的结果。
/*
定义handler处理函数,是真正处理请求的方法
*/
static ngx_int_t ngx_http_mytest_handler(ngx_http_request_t *r)
{
//必须是GET或者HEAD方法,否则返回405 Not Allowed
if (!(r->method & (NGX_HTTP_GET | NGX_HTTP_HEAD)))
{
return NGX_HTTP_NOT_ALLOWED;
}
//丢弃请求中的包体
ngx_int_t rc = ngx_http_discard_request_body(r);
if (rc != NGX_OK)
{
return rc;
}
//设置返回的Content-Type。注意,ngx_str_t有一个很方便的初始化宏
//ngx_string,它可以把ngx_str_t的data和len成员都设置好
ngx_str_t type = ngx_string("text/plain");
//返回的包体内容
ngx_str_t response = ngx_string("Hello World!");
//设置返回状态码
r->headers_out.status = NGX_HTTP_OK;
//响应包是有包体内容的,所以需要设置Content-Length长度
r->headers_out.content_length_n = response.len;
//设置Content-Type
r->headers_out.content_type = type;
//发送http头部
rc = ngx_http_send_header(r);
if (rc == NGX_ERROR || rc > NGX_OK || r->header_only)
{
return rc;
}
//构造ngx_buf_t结构准备发送包体
ngx_buf_t *b;
b = ngx_create_temp_buf(r->pool, response.len);
if (b == NULL)
{
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
//将Hello World拷贝到ngx_buf_t指向的内存中
ngx_memcpy(b->pos, response.data, response.len);
//注意,一定要设置好last指针
b->last = b->pos + response.len;
//声明这是最后一块缓冲区
b->last_buf = 1;
//构造发送时的ngx_chain_t结构体
ngx_chain_t out;
//赋值ngx_buf_t
out.buf = b;
//设置next为NULL
out.next = NULL;
//最后一步发送包体,http框架会调用ngx_http_finalize_request方法
//结束请求
return ngx_http_output_filter(r, &out);
}
定义 config 文件
config 文件其实就是一个可执行的 Shell 脚本。如果只想开发一个 HTTP 模块,那么 config 文件中需要定义以下 3 个变量:
- ngx_addon_name: 仅在configure 执行时使用,一般设置为模块名称。
- HTTP_MODULES:保存所有的 HTTP 模块名称,每个 HTTP 模块间由空格符相连。在重新设置 HTTP_MODULES 变量时,不要直接覆盖它,因为 configure 调用到自定义的 config 脚本前,已经将各个 HTTP 模块设置到 HTTP_MODULES 变量中了。
- NGX_ADDON_SRCS:用于指定新增模块的源代码,多个待编译的源代码间以空格符相连。注意:在设置 NGX_ADDON_SRCS 时可以使用
$ngx_addon_dir
变量,它等价于 configure 时执行 --add-module=PATH 的 PATH 参数。
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_http_mytest_module.c 和 config 文件放在以下目录中:
[root@nginx-plus1 ~]# ll /root/mymodules/mytest/
total 12
-rw-r--r-- 1 root root 164 Jun 16 10:20 config
-rw-r--r-- 1 root root 4567 Jun 16 10:45 ngx_http_mytest_module.c
编译时使用 --add-module 指令添加我们自定义的模块,这里使用 make install 重新安装 Nginx:
cd /root/nginx-1.14.2/
./configure --prefix=/usr/local/nginx --add-module=/root/mymodules/mytest
# 删除原先的Nginx文件
rm -rm /usr/local/nginx
# 编译
make
# 安装
make install
编辑 Nginx 配置文件 /usr/local/nginx/conf/nginx.conf:
worker_processes 1;
events {
worker_connections 1024;
}
http {
server {
listen 80;
location / {
mytest; #使用我们自定义的配置项
}
}
}
启动 Nginx:
/usr/local/nginx/sbin/nginx
客户端请求,可以看到响应了我们自定义的内容:
[root@nginx-plus1 ~]# curl localhost
Hello World!
参考链接
- http://tengine.taobao.org/book/chapter_03.html
- https://tangocc.github.io/2018/07/05/nginx-modul-myself/
- 深入理解 Nginx 模块开发与结构解析