第11章:扩展与模块开发
11.1 常用第三方模块
Nginx的模块化设计允许通过添加第三方模块来扩展其功能。这些模块可以提供额外的功能,如缓存、安全、负载均衡等。
11.1.1 实时编译与动态模块
Nginx支持两种模块加载方式:
- 静态编译:模块被编译到Nginx二进制文件中,启动时自动加载
- 动态模块:模块被编译为动态链接库(.so文件),可以在运行时加载和卸载
11.1.1.1 静态编译
编译示例:
# 下载Nginx源码
wget http://nginx.org/download/nginx-1.18.0.tar.gz
tar -zxvf nginx-1.18.0.tar.gz
# 下载第三方模块
git clone https://github.com/openresty/headers-more-nginx-module.git
# 编译Nginx,添加第三方模块
cd nginx-1.18.0
./configure --with-http_ssl_module --add-module=../headers-more-nginx-module
make
sudo make install11.1.1.2 动态模块
编译示例:
# 下载Nginx源码
wget http://nginx.org/download/nginx-1.18.0.tar.gz
tar -zxvf nginx-1.18.0.tar.gz
# 下载第三方模块
git clone https://github.com/openresty/headers-more-nginx-module.git
# 编译动态模块
cd nginx-1.18.0
./configure --with-http_ssl_module --add-dynamic-module=../headers-more-nginx-module
make modules
sudo cp objs/ngx_http_headers_more_filter_module.so /etc/nginx/modules/加载动态模块:
在/etc/nginx/nginx.conf文件中添加:
load_module modules/ngx_http_headers_more_filter_module.so;检查已加载的模块:
nginx -V 2>&1 | grep -E "--add-module|--add-dynamic-module"11.1.2 常用模块介绍
11.1.2.1 headers-more-nginx-module
headers-more-nginx-module用于添加、修改和删除HTTP请求和响应头。
安装:
# 编译动态模块
git clone https://github.com/openresty/headers-more-nginx-module.git
cd nginx-1.18.0
./configure --add-dynamic-module=../headers-more-nginx-module
make modules
sudo cp objs/ngx_http_headers_more_filter_module.so /etc/nginx/modules/配置示例:
# 加载模块
load_module modules/ngx_http_headers_more_filter_module.so;
http {
server {
listen 80;
server_name example.com;
# 添加响应头
more_set_headers "X-Server-Name: nginx-server";
more_set_headers "X-Powered-By: custom-header";
# 删除响应头
more_clear_headers "Server:";
more_clear_headers "X-Powered-By:";
# 修改请求头
more_set_input_headers "X-Real-IP: $remote_addr";
location / {
return 200 "Hello, World!";
}
}
}验证配置:
curl -I http://example.com预期输出:
HTTP/1.1 200 OK
Date: Wed, 15 Mar 2023 10:00:00 GMT
Content-Type: text/plain
Content-Length: 13
Connection: keep-alive
X-Server-Name: nginx-server11.1.2.2 ngx_http_sub_module
ngx_http_sub_module用于替换HTTP响应中的文本内容。
配置示例:
http {
server {
listen 80;
server_name example.com;
# 替换响应内容
sub_filter "Hello" "Hi";
sub_filter_once off;
location / {
return 200 "Hello, World! Hello, Nginx!";
}
}
}验证配置:
curl http://example.com预期输出:
Hi, World! Hi, Nginx!11.1.2.3 ngx_http_geoip_module
ngx_http_geoip_module用于基于IP地址的地理位置进行访问控制和内容定制。
安装:
# Ubuntu/Debian
sudo apt-get install -y libgeoip-dev geoip-database
# CentOS/RHEL
sudo yum install -y geoip-devel geoip-data配置示例:
http {
# 加载GeoIP数据库
geoip_country /usr/share/GeoIP/GeoIP.dat;
# 基于国家的访问控制
map $geoip_country_code $allowed_country {
default no;
CN yes;
US yes;
}
server {
listen 80;
server_name example.com;
# 禁止特定国家访问
if ($allowed_country = no) {
return 403;
}
location / {
return 200 "Hello from $geoip_country_code";
}
}
}11.1.2.4 ngx_http_image_filter_module
ngx_http_image_filter_module用于处理图像,如缩放、裁剪、旋转等。
安装:
# Ubuntu/Debian
sudo apt-get install -y libgd-dev
# CentOS/RHEL
sudo yum install -y gd-devel配置示例:
http {
server {
listen 80;
server_name example.com;
location /images/ {
root /var/www;
}
# 缩放图像
location ~* /images/(.*)\.jpg$ {
root /var/www;
image_filter resize 200 200;
image_filter_jpeg_quality 90;
}
# 裁剪图像
location ~* /images/(.*)_crop\.jpg$ {
root /var/www;
image_filter crop 200 200;
image_filter_jpeg_quality 90;
}
}
}11.2 自定义模块开发基础
11.2.1 Nginx模块架构
Nginx模块分为以下几种类型:
- 核心模块:提供Nginx的基本功能
- 事件模块:处理网络事件
- HTTP模块:处理HTTP请求和响应
- Mail模块:处理邮件协议
- Stream模块:处理TCP/UDP流
HTTP模块处理流程:
- 初始化阶段:模块在Nginx启动时初始化
- 配置解析阶段:解析模块的配置指令
- 请求处理阶段:处理HTTP请求,包括:
ngx_http_postconfiguration:配置完成后调用ngx_http_handler:处理请求,生成响应ngx_http_filter:过滤请求或响应ngx_http_log:记录日志
- 清理阶段:Nginx关闭时清理资源
11.2.2 简单模块开发示例
在这个示例中,我们将开发一个简单的HTTP模块,用于返回"Hello, Nginx Module!"响应。
11.2.2.1 模块结构
创建模块目录和文件:
mkdir -p nginx-hello-module
cd nginx-hello-module创建以下文件:
config:模块配置文件,用于编译ngx_http_hello_module.c:模块源代码
11.2.2.2 编写配置文件
config文件:
cat > config << EOF
ngx_addon_name=ngx_http_hello_module
gpx_http_module=ngx_http_hello_module
gpx_source_files="$ngx_addon_dir/ngx_http_hello_module.c"
EOF11.2.2.3 编写模块源代码
ngx_http_hello_module.c文件:
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
static char *ngx_http_hello(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static ngx_int_t ngx_http_hello_handler(ngx_http_request_t *r);
static ngx_command_t ngx_http_hello_commands[] = {
{
ngx_string("hello"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,
ngx_http_hello,
NGX_HTTP_LOC_CONF_OFFSET,
0,
NULL
},
ngx_null_command
};
static ngx_http_module_t ngx_http_hello_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 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 char *ngx_http_hello(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_hello_handler;
return NGX_CONF_OK;
}
static ngx_int_t ngx_http_hello_handler(ngx_http_request_t *r) {
ngx_buf_t *b;
ngx_chain_t out;
ngx_int_t rc;
// 只处理GET请求
if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))) {
return NGX_HTTP_NOT_ALLOWED;
}
// 丢弃请求体
rc = ngx_http_discard_request_body(r);
if (rc != NGX_OK) {
return rc;
}
// 设置响应状态码
r->headers_out.status = NGX_HTTP_OK;
r->headers_out.content_type.len = sizeof("text/plain") - 1;
r->headers_out.content_type.data = (u_char *) "text/plain";
// 发送响应头
rc = ngx_http_send_header(r);
if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
return rc;
}
// 分配内存缓冲区
b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
if (b == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
// 设置响应内容
b->pos = (u_char *) "Hello, Nginx Module!";
b->last = b->pos + sizeof("Hello, Nginx Module!") - 1;
b->memory = 1;
b->last_buf = 1;
// 设置输出链
out.buf = b;
out.next = NULL;
// 发送响应体
return ngx_http_output_filter(r, &out);
}11.2.2.4 编译模块
静态编译:
# 下载Nginx源码
wget http://nginx.org/download/nginx-1.18.0.tar.gz
tar -zxvf nginx-1.18.0.tar.gz
# 编译Nginx,添加自定义模块
cd nginx-1.18.0
./configure --add-module=../nginx-hello-module
make
sudo make install动态编译:
# 编译动态模块
./configure --add-dynamic-module=../nginx-hello-module
make modules
sudo cp objs/ngx_http_hello_module.so /etc/nginx/modules/11.2.2.5 配置和测试模块
配置示例:
# 静态编译模块不需要此指令
# load_module modules/ngx_http_hello_module.so;
http {
server {
listen 80;
server_name example.com;
location /hello {
hello;
}
}
}测试模块:
# 重启Nginx
sudo systemctl restart nginx
# 测试模块
curl http://example.com/hello预期输出:
Hello, Nginx Module!11.3 实战项目:开发自定义日志模块
在这个实战项目中,我们将开发一个自定义日志模块,用于记录请求的处理时间和客户端信息。
11.3.1 模块设计
功能需求:
- 记录请求的处理时间
- 记录客户端IP地址
- 记录客户端浏览器信息
- 记录请求URL
- 记录响应状态码
模块结构:
config:模块配置文件ngx_http_custom_log_module.c:模块源代码
11.3.2 实现模块
创建模块目录和文件:
mkdir -p nginx-custom-log-module
cd nginx-custom-log-module编写config文件:
cat > config << EOF
ngx_addon_name=ngx_http_custom_log_module
gpx_http_module=ngx_http_custom_log_module
gpx_source_files="$ngx_addon_dir/ngx_http_custom_log_module.c"
EOF编写模块源代码:
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
// 模块配置结构
typedef struct {
ngx_flag_t enable;
} ngx_http_custom_log_loc_conf_t;
static void *ngx_http_custom_log_create_loc_conf(ngx_conf_t *cf);
static char *ngx_http_custom_log_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child);
static char *ngx_http_custom_log_enable(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static ngx_int_t ngx_http_custom_log_handler(ngx_http_request_t *r);
static ngx_int_t ngx_http_custom_log_init(ngx_conf_t *cf);
// 模块指令定义
static ngx_command_t ngx_http_custom_log_commands[] = {
{
ngx_string("custom_log_enable"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
ngx_http_custom_log_enable,
NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_http_custom_log_loc_conf_t, enable),
NULL
},
ngx_null_command
};
// 模块上下文定义
static ngx_http_module_t ngx_http_custom_log_module_ctx = {
NULL, /* preconfiguration */
ngx_http_custom_log_init, /* postconfiguration */
NULL, /* create main configuration */
NULL, /* init main configuration */
NULL, /* create server configuration */
NULL, /* merge server configuration */
ngx_http_custom_log_create_loc_conf, /* create location configuration */
ngx_http_custom_log_merge_loc_conf /* merge location configuration */
};
// 模块定义
ngx_module_t ngx_http_custom_log_module = {
NGX_MODULE_V1,
&ngx_http_custom_log_module_ctx, /* module context */
ngx_http_custom_log_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 void *ngx_http_custom_log_create_loc_conf(ngx_conf_t *cf) {
ngx_http_custom_log_loc_conf_t *conf;
conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_custom_log_loc_conf_t));
if (conf == NULL) {
return NULL;
}
conf->enable = NGX_CONF_UNSET;
return conf;
}
// 合并位置配置
static char *ngx_http_custom_log_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) {
ngx_http_custom_log_loc_conf_t *prev = parent;
ngx_http_custom_log_loc_conf_t *conf = child;
ngx_conf_merge_value(conf->enable, prev->enable, 0);
return NGX_CONF_OK;
}
// 处理custom_log_enable指令
static char *ngx_http_custom_log_enable(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) {
char *rv = ngx_conf_set_flag_slot(cf, cmd, conf);
if (rv != NGX_CONF_OK) {
return rv;
}
return NGX_CONF_OK;
}
// 请求处理函数
static ngx_int_t ngx_http_custom_log_handler(ngx_http_request_t *r) {
ngx_http_custom_log_loc_conf_t *conf;
conf = ngx_http_get_module_loc_conf(r, ngx_http_custom_log_module);
// 如果模块未启用,跳过处理
if (!conf->enable) {
return NGX_DECLINED;
}
// 获取请求处理时间
ngx_msec_t request_time = ngx_current_msec - r->start_sec * 1000 - r->start_msec;
// 构建日志消息
ngx_str_t log_msg;
ngx_uint_t len;
// 计算日志消息长度
len = sizeof("[CUSTOM LOG] ") - 1;
len += ngx_http_log_escape(r->pool, NULL, r->connection->addr_text.data, r->connection->addr_text.len);
len += sizeof(" - [") - 1;
len += ngx_http_log_time(r->pool, NULL, &r->start_sec, r->start_msec);
len += sizeof("] \"GET ") - 1;
len += ngx_http_log_escape(r->pool, NULL, r->uri.data, r->uri.len);
len += sizeof(" HTTP/1.1\" ") - 1;
len += ngx_atoi_len((u_char *) &r->headers_out.status, sizeof(int));
len += sizeof(" \