主要变更: - WebSocket 代理支持 (internal/proxy/websocket.go) - OCSP stapling 实现 (internal/ssl/ocsp.go) - 监控状态端点 (internal/server/status.go) - 新增 nginx 模块文档 (19-24) - UDP 代理超时配置支持 - 多模块代码注释完善和功能增强 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
23 KiB
NGINX 第三方扩展模块详解
概述
第三方模块极大地扩展了 NGINX 的功能边界,使其能够处理各种复杂场景。本文档详细介绍常用的第三方扩展模块及其配置方法。
1. NJS (nginx JavaScript) 模块
NJS 是 NGINX 官方提供的 JavaScript 解释器,允许在 NGINX 中使用 JavaScript 编写动态逻辑。
核心指令
js_import
导入 JavaScript 文件。
js_import /path/to/script.js; # 导入整个模块
js_import main from /path/to/script.js; # 命名导入
js_set
设置 NGINX 变量为 JavaScript 函数返回值。
js_set $auth_token validate_auth; # 变量值由 validate_auth 函数返回
js_content
使用 JavaScript 生成响应内容。
location /api/custom {
js_content custom_response;
}
js_body_filter
使用 JavaScript 修改响应体。
location / {
js_body_filter modify_body;
proxy_pass http://backend;
}
JavaScript 语法支持
NJS 基于 ECMAScript 5.1,支持部分 ES6 特性:
| 特性 | 支持情况 | 示例 |
|---|---|---|
| 基础类型 | 完整 | string, number, boolean, null |
| 函数 | 完整 | function foo() {} |
| 对象 | 完整 | var obj = { a: 1 }; |
| 数组 | 完整 | var arr = [1, 2, 3]; |
| let/const | 支持 | let x = 1; const y = 2; |
| 箭头函数 | 支持 | (x) => x * 2 |
| 模板字符串 | 支持 | `Hello ${name}` |
| Promise | 部分 | Promise.then() 和 catch() |
| async/await | 部分 | async function |
| 类 (class) | 不支持 | - |
| 解构赋值 | 部分 | const {a, b} = obj |
NGINX JavaScript 对象
// r: 请求对象
function handler(r) {
// 请求信息
r.method; // HTTP 方法
r.uri; // 请求 URI
r.args; // 查询参数对象
r.headersIn; // 请求头对象
r.headersOut; // 响应头对象
r.remoteAddress; // 客户端 IP
// 响应操作
r.return(200, "Hello"); // 直接返回
r.error("message"); // 记录错误日志
r.log("message"); // 记录日志
r.warn("message"); // 记录警告
// 子请求
r.subrequest('/internal', function(reply) {
// 处理子请求响应
r.return(200, reply.body);
});
}
配置示例
动态路由
// router.js
function route(r) {
var version = r.headersIn['X-API-Version'];
if (version === 'v2') {
r.internalRedirect('/api/v2' + r.uri);
} else {
r.internalRedirect('/api/v1' + r.uri);
}
}
export default { route };
js_import main from /etc/nginx/router.js;
location /api/ {
js_content main.route;
}
location /api/v1/ {
proxy_pass http://backend_v1/;
}
location /api/v2/ {
proxy_pass http://backend_v2/;
}
自定义认证
// auth.js
async function auth(r) {
// 从数据库验证 token
var reply = await r.subrequest('/auth/check', {
method: 'POST',
body: JSON.stringify({ token: r.headersIn['Authorization'] })
});
if (reply.status === 200) {
var result = JSON.parse(reply.body);
r.headersOut['X-User-Id'] = result.user_id;
r.internalRedirect('/internal' + r.uri);
} else {
r.return(401, "Unauthorized");
}
}
export default { auth };
js_import auth from /etc/nginx/auth.js;
location /protected/ {
js_content auth.auth;
}
location /internal/ {
internal; # 只允许内部跳转
proxy_pass http://backend/;
}
JWT 验证
// jwt.js
function verify_jwt(r) {
var jwt = r.headersIn['Authorization'];
if (!jwt) {
return null;
}
jwt = jwt.replace('Bearer ', '');
var parts = jwt.split('.');
if (parts.length !== 3) {
return null;
}
try {
var header = JSON.parse(Buffer.from(parts[0], 'base64').toString());
var payload = JSON.parse(Buffer.from(parts[1], 'base64').toString());
// 验证过期时间
if (payload.exp && payload.exp < Math.floor(Date.now() / 1000)) {
return null;
}
return payload;
} catch (e) {
return null;
}
}
function get_user_id(r) {
var payload = verify_jwt(r);
return payload ? payload.sub : '';
}
export default { get_user_id };
js_import jwt from /etc/nginx/jwt.js;
js_set $user_id jwt.get_user_id;
server {
location /api/ {
if ($user_id = "") {
return 401 "Invalid or expired token";
}
proxy_set_header X-User-Id $user_id;
proxy_pass http://backend;
}
}
安装
# 从源码编译
./configure \
--add-dynamic-module=/path/to/njs/nginx \
--with-compat
make modules
# 加载模块
# nginx.conf
load_module modules/ngx_http_js_module.so;
2. ngx_http_lua_module (OpenResty)
ngx_http_lua_module 是 OpenResty 平台的核心组件,允许在 NGINX 中嵌入 Lua 脚本。
OpenResty 平台介绍
OpenResty 是一个基于 NGINX 的全功能 Web 平台,集成了:
- LuaJIT(高性能 Lua 解释器)
- 大量精心设计的 Lua 库
- 完整的协程调度器
- 异步非阻塞 I/O 能力
Lua 指令概述
| 指令 | 上下文 | 说明 |
|---|---|---|
init_by_lua |
http | NGINX 启动时执行 |
init_worker_by_lua |
http | 每个 worker 启动时执行 |
set_by_lua |
server/location/if | 设置变量值 |
content_by_lua |
location/location if | 生成响应内容 |
access_by_lua |
http/server/location | 访问控制阶段执行 |
rewrite_by_lua |
http/server/location | 重写阶段执行 |
header_filter_by_lua |
http/server/location | 处理响应头 |
body_filter_by_lua |
http/server/location | 处理响应体 |
log_by_lua |
http/server/location | 日志阶段执行 |
balancer_by_lua |
upstream | 自定义负载均衡 |
配置示例
http {
# 全局初始化
init_by_lua_block {
require "cjson"
redis = require "resty.redis"
}
server {
location /api {
content_by_lua_block {
local cjson = require "cjson"
local data = {
message = "Hello from Lua",
time = ngx.time()
}
ngx.header['Content-Type'] = 'application/json'
ngx.say(cjson.encode(data))
}
}
location /auth {
access_by_lua_block {
local token = ngx.var.http_authorization
if not token then
ngx.exit(ngx.HTTP_UNAUTHORIZED)
end
-- 验证 token 逻辑
}
}
}
}
应用场景
| 场景 | 说明 |
|---|---|
| API 网关 | 路由、限流、认证、日志 |
| WAF | Web 应用防火墙,自定义安全规则 |
| 动态负载均衡 | 基于实时指标的流量调度 |
| 边缘计算 | 在边缘节点执行业务逻辑 |
| 实时数据处理 | 日志实时分析、指标采集 |
3. ngx_http_brotli_module (Brotli 压缩)
Brotli 是 Google 开发的新一代压缩算法,相比 Gzip 提供更好的压缩比。
核心指令
brotli
启用或禁用 Brotli 压缩。
brotli on; # 启用动态压缩
brotli_static on; # 启用静态 .br 文件支持
brotli_comp_level
设置压缩级别(0-11)。
brotli_comp_level 6; # 默认值,平衡压缩比和速度
级别说明:
| 级别 | 压缩比 | 速度 | 适用场景 |
|---|---|---|---|
| 1 | 低 | 最快 | 高流量低延迟场景 |
| 6 | 中 | 中等 | 通用场景(推荐) |
| 11 | 最高 | 最慢 | 预压缩静态资源 |
brotli_types
指定压缩的 MIME 类型。
brotli_types text/plain text/css application/json application/javascript text/xml application/xml;
完整配置示例
# 动态压缩
location / {
brotli on;
brotli_comp_level 6;
brotli_types text/plain text/css application/json
application/javascript text/xml application/xml
application/rss+xml text/javascript
application/vnd.ms-fontobject application/x-font-ttf
font/opentype image/svg+xml image/x-icon;
proxy_pass http://backend;
}
# 静态预压缩文件
location ~ \.(js|css|html|json)$ {
brotli_static on; # 优先查找 .br 文件
gzip_static on; # 回退到 .gz 文件
try_files $uri $uri/ =404;
}
Brotli vs Gzip 对比
| 特性 | Brotli | Gzip |
|---|---|---|
| 压缩比 | 更高(约 20-30%) | 较低 |
| 压缩速度 | 较慢 | 较快 |
| 解压速度 | 快 | 快 |
| 浏览器支持 | 现代浏览器 | 几乎所有浏览器 |
| 字典支持 | 有(静态字典) | 无 |
| 最佳用途 | 静态资源预压缩 | 动态内容压缩 |
预压缩静态资源
# 使用 brotli 命令行工具压缩
brotli -q 11 -o file.js.br file.js
# 批量压缩
find . -type f \( -name "*.js" -o -name "*.css" \) -exec brotli -q 11 -o {}.br {} \;
4. ngx_cache_purge_module (缓存清除)
该模块允许通过 HTTP 请求清除 NGINX 代理缓存中的特定内容。
核心指令
proxy_cache_purge
配置缓存清除的访问控制。
proxy_cache_purge on; # 启用清除功能
proxy_cache_purge $purge_method; # 基于变量控制
fastcgi_cache_purge
FastCGI 缓存清除。
fastcgi_cache_purge on;
配置示例
http {
proxy_cache_path /var/cache/nginx levels=1:2
keys_zone=my_cache:10m
max_size=1g inactive=60m;
map $request_method $purge_method {
default 0;
PURGE 1;
}
server {
listen 80;
server_name example.com;
location / {
proxy_cache my_cache;
proxy_cache_key "$scheme$host$request_uri";
proxy_cache_purge $purge_method;
proxy_pass http://backend;
# 可选:限制 PURGE 来源
if ($purge_method = 1) {
allow 127.0.0.1;
allow 192.168.1.0/24;
deny all;
}
}
}
}
使用方式
# 清除单个 URL
curl -X PURGE http://example.com/path/to/resource
# 清除通配符(需配置 proxy_cache_purge 支持)
curl -X PURGE http://example.com/*
# 使用 API Key 认证
curl -X PURGE -H "X-Purge-Key: secret123" http://example.com/api/data
安全考虑
# 仅允许特定 IP 执行 PURGE
location / {
proxy_cache_purge $purge_method;
if ($purge_method = 1) {
# 检查来源 IP
set $allowed 0;
if ($remote_addr ~ "^192\.168\.") {
set $allowed 1;
}
# 或检查 API Key
if ($http_x_purge_key != "secret_key") {
set $allowed 0;
}
if ($allowed = 0) {
return 403 "Purge not allowed";
}
}
}
5. ngx_headers_more_module (增强头部)
提供更灵活的 HTTP 头部操作能力。
核心指令
more_set_headers
设置响应头,支持条件表达式。
# 基础用法
more_set_headers "Server: MyServer";
more_set_headers "X-Frame-Options: DENY";
# 状态码限定
more_set_headers "X-Debug: on" always;
more_set_headers -s 200 302 "Cache-Control: public";
# 多行
more_set_headers "X-Powered-By: NGINX
X-Custom: Value";
more_clear_headers
清除响应头。
# 清除单个
more_clear_headers Server;
# 清除多个
more_clear_headers Server X-Powered-By X-AspNet-Version;
# 使用通配符
clear_headers "X-*";
more_set_input_headers
设置请求头(传递给后端)。
more_set_input_headers "X-Real-IP: $remote_addr";
配置示例
隐藏 Server 信息
server {
# 清除默认 Server 头
more_clear_headers Server;
# 可选:设置自定义值
more_set_headers "Server: Secure Server";
location / {
proxy_pass http://backend;
}
}
添加安全头部
server {
# 点击劫持防护
more_set_headers "X-Frame-Options: SAMEORIGIN";
# XSS 防护
more_set_headers "X-XSS-Protection: 1; mode=block";
# MIME 类型嗅探防护
more_set_headers "X-Content-Type-Options: nosniff";
# 内容安全策略
more_set_headers "Content-Security-Policy: default-src 'self'";
# 引用策略
more_set_headers "Referrer-Policy: strict-origin-when-cross-origin";
# HSTS(HTTPS 严格传输安全)
more_set_headers "Strict-Transport-Security: max-age=31536000; includeSubDomains";
location / {
proxy_pass http://backend;
}
}
条件头部设置
# 仅在特定路径添加头部
location /api/ {
more_set_headers "X-API-Version: 2.0";
proxy_pass http://api_backend;
}
# 基于状态码
error_page 500 502 503 504 /50x.html;
location = /50x.html {
more_set_headers "X-Error-Source: upstream";
internal;
}
6. nginx-rtmp-module (流媒体)
为 NGINX 添加 RTMP/HLS/DASH 流媒体服务能力。
核心配置块
rtmp {
server {
listen 1935; # RTMP 默认端口
chunk_size 4096;
# 推流应用
application live {
live on;
# HLS 输出
hls on;
hls_path /var/www/hls;
hls_fragment 3s;
hls_playlist_length 60s;
# DASH 输出
dash on;
dash_path /var/www/dash;
dash_fragment 3s;
}
# 录制应用
application record {
live on;
record all;
record_path /var/recordings;
record_unique on;
record_suffix .flv;
}
}
}
关键指令
| 指令 | 说明 |
|---|---|
live on |
启用直播模式 |
hls on |
启用 HLS 输出 |
hls_path |
HLS 文件存储路径 |
hls_fragment |
HLS 切片时长 |
dash on |
启用 DASH 输出 |
push rtmp://... |
推流到其他服务器 |
pull rtmp://... |
从其他服务器拉流 |
完整直播配置示例
rtmp {
server {
listen 1935;
listen [::]:1935 ipv6only=on;
application live {
live on;
# 推流认证
on_publish http://localhost:8080/auth;
# HLS 配置
hls on;
hls_path /var/www/stream/hls;
hls_fragment 3s;
hls_playlist_length 60s;
hls_nested on; # 为每个流创建子目录
hls_cleanup on; # 自动清理旧切片
# 多码率 HLS
hls_variant _low BANDWIDTH=500000;
hls_variant _mid BANDWIDTH=1500000;
hls_variant _high BANDWIDTH=3000000;
# DASH 配置
dash on;
dash_path /var/www/stream/dash;
dash_fragment 3s;
dash_nested on;
# 录制配置
record all;
record_path /var/recordings;
record_unique on;
record_suffix .flv;
record_max_size 500M;
record_max_frames 2;
# 推流到其他平台
push rtmp://youtube.com/live2/stream_key;
push rtmp://facebook.com/rtmp/stream_key;
}
}
}
http {
server {
listen 80;
server_name stream.example.com;
# HLS 播放
location /hls {
types {
application/vnd.apple.mpegurl m3u8;
video/mp2t ts;
}
root /var/www/stream;
add_header Cache-Control no-cache;
add_header Access-Control-Allow-Origin *;
}
# DASH 播放
location /dash {
types {
application/dash+xml mpd;
}
root /var/www/stream;
add_header Cache-Control no-cache;
add_header Access-Control-Allow-Origin *;
}
# 推流认证接口
location /auth {
proxy_pass http://auth_backend/verify;
proxy_set_header Content-Type application/x-www-form-urlencoded;
}
}
}
推流与播放
# OBS / FFmpeg 推流
ffmpeg -re -i input.mp4 -c copy -f flv rtmp://server/live/stream_name
# 播放地址
# HLS: http://server/hls/stream_name.m3u8
# DASH: http://server/dash/stream_name.mpd
7. ngx_http_fancyindex_module (美化目录)
提供更美观、功能更强大的目录列表页面。
核心指令
fancyindex
启用美化目录。
fancyindex on;
fancyindex_css_href
自定义 CSS 样式表。
fancyindex_css_href /fancyindex.css;
fancyindex_exact_size
显示精确文件大小。
fancyindex_exact_size off; # 显示人性化大小(1K, 234M, 2G)
fancyindex_header / fancyindex_footer
自定义页眉页脚。
fancyindex_header /header.html;
fancyindex_footer /footer.html;
配置示例
server {
listen 80;
server_name files.example.com;
root /var/www/files;
location / {
fancyindex on;
fancyindex_exact_size off;
fancyindex_localtime on; # 使用本地时间
fancyindex_show_path off; # 隐藏路径显示
fancyindex_name_length 50; # 文件名最大显示长度
# 自定义样式
fancyindex_css_href "/style/fancyindex.css";
fancyindex_header "/style/header.html";
fancyindex_footer "/style/footer.html";
# 忽略隐藏文件
fancyindex_ignore ".." ".*";
# 默认排序
fancyindex_default_sort name_desc;
}
# 静态资源不显示目录列表
location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
fancyindex off;
}
}
主题配置
创建自定义主题:
<!-- header.html -->
<!DOCTYPE html>
<html>
<head>
<title>文件列表</title>
<link rel="stylesheet" href="/style/custom.css">
</head>
<body>
<div class="container">
<h1>文件下载中心</h1>
<!-- footer.html -->
</div>
<footer>
<p>© 2024 Example Corp</p>
</footer>
</body>
</html>
8. Sticky Module (会话保持)
基于 Cookie 的会话保持模块,确保同一客户端的请求始终路由到同一后端服务器。
核心指令
sticky
启用基于 Cookie 的会话保持。
upstream backend {
server backend1.example.com;
server backend2.example.com;
server backend3.example.com;
sticky cookie srv_id expires=1h domain=.example.com path=/;
}
参数说明:
| 参数 | 说明 |
|---|---|
cookie name |
Cookie 名称 |
expires=time |
Cookie 过期时间 |
domain=name |
Cookie 域名 |
path=path |
Cookie 路径 |
secure |
仅 HTTPS 传输 |
httponly |
禁止 JavaScript 访问 |
sticky vs ip_hash 对比
| 特性 | sticky (cookie) | ip_hash |
|---|---|---|
| 会话保持机制 | 基于 Cookie | 基于客户端 IP |
| 支持 NAT/代理 | 是(无视 IP 变化) | 否(同一 NAT 下用户共享会话) |
| 移动端支持 | 优秀 | 较差(IP 频繁变化) |
| 首次请求 | 轮询分配 | 哈希分配 |
| Cookie 依赖 | 需要 | 不需要 |
配置示例
upstream backend {
server 10.0.0.1:8080 weight=5;
server 10.0.0.2:8080 weight=5;
server 10.0.0.3:8080 backup;
# 基础配置
sticky cookie srv_id expires=1h domain=.example.com path=/;
# 完整配置
# sticky cookie srv_id
# expires=1h
# domain=.example.com
# path=/
# secure
# httponly;
}
server {
listen 80;
server_name app.example.com;
location / {
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
备用方案(无 Cookie 时)
upstream backend {
zone backend 64k;
server backend1.example.com;
server backend2.example.com;
# 尝试 sticky,失败时使用 ip_hash
sticky cookie srv_id expires=1h;
}
# 或使用 route 指令结合其他逻辑
upstream backend {
server backend1.example.com route=a;
server backend2.example.com route=b;
sticky route $route_cookie;
}
9. 安装方法
动态模块安装
动态模块可以在不重新编译 NGINX 的情况下添加功能。
步骤
# 1. 获取与 NGINX 版本匹配的模块源码
cd /usr/local/src
git clone https://github.com/nginx/njs.git
git clone https://github.com/google/ngx_brotli.git
# 2. 下载相同版本的 NGINX 源码
wget http://nginx.org/download/nginx-1.24.0.tar.gz
tar -xzf nginx-1.24.0.tar.gz
cd nginx-1.24.0
# 3. 配置动态模块
./configure \
--with-compat \
--add-dynamic-module=../njs/nginx \
--add-dynamic-module=../ngx_brotli
# 4. 编译模块
make modules
# 5. 安装模块
mkdir -p /usr/local/nginx/modules
cp objs/ngx_http_js_module.so /usr/local/nginx/modules/
cp objs/ngx_http_brotli_filter_module.so /usr/local/nginx/modules/
cp objs/ngx_http_brotli_static_module.so /usr/local/nginx/modules/
加载模块
# nginx.conf
# 在顶级上下文加载
load_module modules/ngx_http_js_module.so;
load_module modules/ngx_http_brotli_filter_module.so;
load_module modules/ngx_http_brotli_static_module.so;
user nginx;
worker_processes auto;
...
编译安装(静态链接)
将模块静态编译进 NGINX 可执行文件。
# 1. 准备源码
cd /usr/local/src
wget http://nginx.org/download/nginx-1.24.0.tar.gz
tar -xzf nginx-1.24.0.tar.gz
# 2. 下载第三方模块
git clone https://github.com/openresty/headers-more-nginx-module.git
git clone https://github.com/FRiCKLE/ngx_cache_purge.git
# 3. 配置编译
cd nginx-1.24.0
./configure \
--prefix=/usr/local/nginx \
--user=nginx \
--group=nginx \
--with-http_ssl_module \
--with-http_v2_module \
--with-http_realip_module \
--with-http_gzip_static_module \
--with-http_stub_status_module \
--with-pcre \
--with-threads \
--add-module=../headers-more-nginx-module \
--add-module=../ngx_cache_purge
# 4. 编译安装
make
make install
包管理器安装
Ubuntu/Debian
# NJS 模块
apt install nginx-module-njs
# OpenResty(包含 Lua 模块)
apt install openresty
CentOS/RHEL
# 使用官方仓库
yum install nginx-module-njs
yum install nginx-module-image-filter
# 启用模块
echo 'load_module /usr/lib64/nginx/modules/ngx_http_js_module.so;' > /etc/nginx/conf.d/njs.conf
动态模块 vs 编译安装对比
| 特性 | 动态模块 | 静态编译 |
|---|---|---|
| 灵活性 | 高(可随时加载/卸载) | 低(需重新编译) |
| 维护成本 | 低 | 高 |
| 性能 | 略低(模块加载开销) | 略高 |
| 部署便捷性 | 高 | 低 |
| 版本兼容性 | 需版本匹配 | 无兼容性问题 |
| 适用场景 | 生产环境、频繁更新 | 性能敏感、定制化需求 |
最佳实践
- 优先使用官方包:通过包管理器安装的模块通常经过充分测试
- 版本匹配:动态模块必须与 NGINX 版本完全匹配
- 最小化模块:仅加载必要的模块,减少攻击面
- 测试环境先行:新模块先在测试环境验证
- 文档记录:记录所有加载的第三方模块及其版本