第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 install

11.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-server

11.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模块处理流程

  1. 初始化阶段:模块在Nginx启动时初始化
  2. 配置解析阶段:解析模块的配置指令
  3. 请求处理阶段:处理HTTP请求,包括:
    • ngx_http_postconfiguration:配置完成后调用
    • ngx_http_handler:处理请求,生成响应
    • ngx_http_filter:过滤请求或响应
    • ngx_http_log:记录日志
  4. 清理阶段:Nginx关闭时清理资源

11.2.2 简单模块开发示例

在这个示例中,我们将开发一个简单的HTTP模块,用于返回"Hello, Nginx Module!"响应。

11.2.2.1 模块结构

创建模块目录和文件:

mkdir -p nginx-hello-module
cd nginx-hello-module

创建以下文件:

  1. config:模块配置文件,用于编译
  2. 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"
EOF

11.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(" \
« 上一篇 故障排查与优化