diff --git a/docs/04-nginx-proxy-loadbalancing.md b/docs/04-nginx-proxy-loadbalancing.md
index 9bef752..49fea19 100644
--- a/docs/04-nginx-proxy-loadbalancing.md
+++ b/docs/04-nginx-proxy-loadbalancing.md
@@ -930,4 +930,557 @@ http {
}
}
}
-```
\ No newline at end of file
+```
+
+---
+
+## 18. 主动健康检查详解
+
+### 18.1 被动检查 vs 主动检查
+
+| 特性 | 被动健康检查 (Passive) | 主动健康检查 (Active) |
+|------|----------------------|---------------------|
+| **实现方式** | 基于真实客户端请求响应判断 | 独立的探测请求周期性检测 |
+| **触发时机** | 实际请求失败时 | 按配置间隔主动发起 |
+| **资源占用** | 无额外开销 | 需要额外的连接和请求 |
+| **发现速度** | 慢(依赖真实流量) | 快(独立探测) |
+| **可用性** | 开源 NGINX 内置 | NGINX Plus 商业版 / 第三方模块 |
+| **配置位置** | `server` 指令参数 | `upstream` 块或 `location` 指令 |
+| **典型参数** | `max_fails`, `fail_timeout` | `interval`, `fails`, `passes`, `match` |
+
+**被动检查机制**:
+```nginx
+upstream backend {
+ # 在 fail_timeout(30s) 内连续失败 max_fails(3) 次,标记为不可用
+ server srv1.example.com max_fails=3 fail_timeout=30s;
+}
+```
+
+**主动检查优势**:
+- 不依赖真实客户端流量即可检测后端状态
+- 可以检测特定的健康检查端点(如 `/health`)
+- 支持自定义匹配规则验证响应内容
+- 支持 gRPC、TCP、UDP 等多种协议
+
+### 18.2 HTTP 健康检查指令详解 (NGINX Plus)
+
+**注意**:HTTP 主动健康检查模块 (`ngx_http_upstream_hc_module`) 是 NGINX Plus 商业订阅的一部分。
+
+#### health_check 指令
+
+**语法**:`health_check [parameters];`
+**上下文**:`location`
+**功能**:启用 upstream 服务器组的定期健康检查
+
+**参数说明**:
+
+| 参数 | 语法 | 默认值 | 说明 |
+|------|------|--------|------|
+| `interval` | `interval=time` | `5s` | 检查间隔时间 |
+| `jitter` | `jitter=time` | — | 随机延迟时间,避免多个服务器同时检查 |
+| `fails` | `fails=number` | `1` | 连续失败次数判定为不健康 |
+| `passes` | `passes=number` | `1` | 连续成功次数判定为健康 |
+| `uri` | `uri=uri` | `/` | 健康检查请求的 URI |
+| `port` | `port=number` | 服务器端口 | 健康检查使用的端口 |
+| `match` | `match=name` | — | 引用 `match` 块进行响应验证 |
+| `mandatory` | `mandatory [persistent]` | — | 初始状态为 "checking";`persistent` 在 reload 后保持状态 |
+| `keepalive_time` | `keepalive_time=time` | — | 启用健康检查连接的 keepalive |
+| `type=grpc` | `type=grpc [grpc_service=name] [grpc_status=code]` | — | 启用 gRPC 健康检查 |
+
+#### match 指令
+
+**语法**:`match name { ... }`
+**上下文**:`http`
+**功能**:定义响应验证测试集
+
+**测试项**:
+
+| 测试项 | 语法 | 说明 |
+|--------|------|------|
+| `status` | `status [!] code [code...]` | 状态码匹配,支持范围如 `200-399` |
+| `header` | `header header [operator] value` | 响应头匹配,`=` 精确匹配,`~` 正则匹配 |
+| `body` | `body ~ "regex"` | 响应体正则匹配(只检查前 256KB) |
+| `require` | `require $variable` | 变量非空且不为 "0" |
+
+**header 操作符**:
+- `=` 或 `==`:精确相等
+- `!=`:不相等
+- `~`:正则匹配(区分大小写)
+- `~*`:正则匹配(不区分大小写)
+
+#### 配置示例
+
+**基础健康检查**:
+```nginx
+upstream dynamic {
+ zone upstream_dynamic 64k; # 共享内存区必须
+
+ server backend1.example.com weight=5;
+ server backend2.example.com:8080 fail_timeout=5s slow_start=30s;
+}
+
+server {
+ location / {
+ proxy_pass http://dynamic;
+ health_check; # 使用默认配置
+ }
+}
+```
+
+**高级配置**:
+```nginx
+server {
+ location / {
+ proxy_pass http://backend;
+ health_check interval=10s jitter=2s fails=3 passes=2
+ uri=/health port=8080 match=server_ok
+ keepalive_time=60s;
+ }
+}
+
+match server_ok {
+ status 200; # 状态码必须是 200
+ header Content-Type = application/json; # Content-Type 精确匹配
+ header X-Health-Status ~ ^ok$; # 正则匹配头值
+ body ~ "\"status\":\\s*\"healthy\""; # 响应体包含状态标记
+}
+```
+
+**gRPC 健康检查**(不兼容 `uri` 和 `match`):
+```nginx
+upstream grpc_backend {
+ zone grpc_zone 64k;
+ server grpc1.example.com:50051;
+ server grpc2.example.com:50051;
+}
+
+server {
+ location / {
+ grpc_pass grpc://grpc_backend;
+ health_check mandatory type=grpc grpc_service=myapp.HealthCheck grpc_status=12;
+ }
+}
+```
+
+### 18.3 Stream 健康检查指令详解 (NGINX Plus)
+
+**注意**:Stream 主动健康检查模块 (`ngx_stream_upstream_hc_module`) 是 NGINX Plus 商业订阅的一部分。
+
+#### 指令概览
+
+| 指令 | 上下文 | 默认值 | 说明 |
+|------|--------|--------|------|
+| `health_check` | `server` | — | 启用健康检查 |
+| `health_check_timeout` | `stream`, `server` | `5s` | 健康检查超时 |
+| `match` | `stream` | — | 定义响应验证规则 |
+
+#### health_check 参数(Stream)
+
+| 参数 | 默认值 | 说明 |
+|------|--------|------|
+| `interval` | `5s` | 检查间隔 |
+| `jitter` | — | 随机延迟 |
+| `fails` | `1` | 失败次数阈值 |
+| `passes` | `1` | 成功次数阈值 |
+| `match` | — | 引用 match 块 |
+| `port` | 服务器端口 | 检查端口 |
+| `udp` | — | 使用 UDP 协议 |
+| `mandatory` | — | 初始状态为 "checking" |
+| `persistent` | — | reload 后保持状态 |
+
+#### match 块(Stream)
+
+| 测试项 | 语法 | 说明 |
+|--------|------|------|
+| `send` | `send "string"` | 发送给服务器的字符串(支持 `\x` 十六进制) |
+| `expect` | `expect "string"` / `expect ~ "regex"` | 期望的响应 |
+
+**注意**:只检查服务器返回数据的前 `proxy_buffer_size` 字节。
+
+#### 配置示例
+
+**TCP 基础检查**:
+```nginx
+upstream tcp_backend {
+ zone tcp_zone 64k;
+ server backend1.example.com:12345 weight=5;
+ server backend2.example.com:12345;
+}
+
+server {
+ listen 12346;
+ proxy_pass tcp_backend;
+ health_check interval=5s;
+}
+```
+
+**UDP 健康检查**:
+```nginx
+upstream dns_upstream {
+ zone dns_zone 64k;
+ server dns1.example.com:53;
+}
+
+server {
+ listen 53 udp;
+ proxy_pass dns_upstream;
+ health_check udp interval=3s; # 发送探测并期望无 ICMP 不可达回复
+}
+```
+
+**自定义匹配规则(MySQL 检查)**:
+```nginx
+upstream mysql_backend {
+ zone mysql_zone 10m;
+ server db1.example.com:3306;
+ server db2.example.com:3306;
+}
+
+match mysql_handshake {
+ # 发送 MySQL 握手包(十六进制)
+ send "\x3a\x00\x00\x01\x0a\x35\x2e\x35\x2e\x32\x2d\x6d\x32\x00\x01...";
+ # 期望收到包含版本信息的响应
+ expect ~ "\x4a\x00\x00\x00\x0a";
+}
+
+server {
+ listen 3307;
+ proxy_pass mysql_backend;
+ health_check match=mysql_handshake interval=5s;
+ health_check_timeout 10s;
+}
+```
+
+**HTTP 风格的 TCP 检查**:
+```nginx
+match http_check {
+ send "GET /health HTTP/1.0\r\nHost: localhost\r\n\r\n";
+ expect ~ "200 OK";
+}
+
+server {
+ listen 80;
+ proxy_pass backend;
+ health_check match=http_check interval=5s fails=3 passes=2;
+}
+```
+
+### 18.4 自定义健康检查配置示例
+
+#### 场景一:API 网关健康检查
+
+```nginx
+http {
+ upstream api_backend {
+ zone api_zone 64k;
+
+ server api1.example.com:8080;
+ server api2.example.com:8080;
+ server api3.example.com:8080;
+ }
+
+ # 健康检查匹配规则
+ match api_healthy {
+ status 200;
+ header Content-Type = application/json;
+ body ~ "\"status\":\\s*\"up\"";
+ }
+
+ server {
+ listen 80;
+ server_name api.example.com;
+
+ location / {
+ proxy_pass http://api_backend;
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+
+ # 健康检查配置
+ health_check interval=5s jitter=1s fails=3 passes=2
+ uri=/api/health
+ match=api_healthy;
+ }
+
+ # 健康检查状态页(NGINX Plus)
+ location /upstream_status {
+ upstream_status;
+ access_log off;
+ allow 10.0.0.0/8;
+ deny all;
+ }
+ }
+}
+```
+
+#### 场景二:多协议混合检查
+
+```nginx
+# TCP 服务健康检查
+stream {
+ upstream redis_backend {
+ zone redis_zone 64k;
+ server redis1.example.com:6379;
+ server redis2.example.com:6379;
+ }
+
+ match redis_ping {
+ send "PING\r\n";
+ expect ~ "\+PONG";
+ }
+
+ server {
+ listen 6379;
+ proxy_pass redis_backend;
+ health_check match=redis_ping interval=10s fails=2 passes=2;
+ health_check_timeout 3s;
+ }
+}
+
+# HTTP 服务健康检查
+http {
+ upstream web_backend {
+ zone web_zone 64k;
+ server web1.example.com:80;
+ server web2.example.com:80;
+ }
+
+ server {
+ location / {
+ proxy_pass http://web_backend;
+ health_check interval=5s uri=/nginx_health;
+ }
+ }
+}
+```
+
+#### 场景三:微服务 gRPC 健康检查
+
+```nginx
+upstream grpc_services {
+ zone grpc_zone 64k;
+ server service1.example.com:50051;
+ server service2.example.com:50051;
+}
+
+server {
+ listen 50051 http2;
+
+ location / {
+ grpc_pass grpc://grpc_services;
+ # gRPC 健康检查:使用标准 gRPC Health Checking Protocol
+ # grpc_status=12 (UNIMPLEMENTED) 表示服务未实现健康检查接口
+ # grpc_status=0 (OK) 表示服务健康
+ health_check mandatory type=grpc grpc_service=grpc.health.v1.Health
+ interval=5s fails=3 passes=2;
+ }
+}
+```
+
+### 18.5 健康检查与负载均衡配合
+
+#### 状态流转机制
+
+```
+ 初始状态
+ |
+ v
+ ┌───────────┐ ┌──────────────┐
+ │ checking │────▶│ unhealthy │
+ └─────┬─────┘ └──────────────┘
+ │ │
+ │ passes 次成功 │ fails 次失败
+ v v
+ ┌───────────┐ ┌──────────────┐
+ │ healthy │◀────│ │
+ └───────────┘ └──────────────┘
+```
+
+**关键行为**:
+- `checking` 状态:初始或 reload 后,不接收客户端请求
+- `mandatory` 参数:强制等待首次健康检查完成才标记为健康
+- `persistent` 参数:reload 后如之前是健康状态则保持 healthy
+
+#### 与负载均衡算法结合
+
+```nginx
+upstream backend {
+ zone backend 64k;
+ least_conn; # 最少连接算法
+
+ server srv1.example.com weight=5;
+ server srv2.example.com;
+ server srv3.example.com;
+
+ # 被动检查参数与主动检查并存
+ # 被动检查作为兜底,主动检查提供快速发现
+}
+
+server {
+ location / {
+ proxy_pass http://backend;
+ health_check interval=5s fails=3 passes=2;
+
+ # 故障转移配置
+ proxy_next_upstream error timeout http_502 http_503 http_504;
+ proxy_next_upstream_timeout 5s;
+ proxy_next_upstream_tries 2;
+ }
+}
+```
+
+**监控指标集成**:
+
+```nginx
+log_format health_log '$remote_addr - $remote_user [$time_local] '
+ '"$request" $status '
+ 'upstream=$upstream_addr '
+ 'upstream_status=$upstream_status '
+ 'health_check=$upstream_health_check_status';
+
+server {
+ location / {
+ proxy_pass http://backend;
+ health_check interval=5s;
+ access_log /var/log/nginx/health.log health_log;
+ }
+}
+```
+
+### 18.6 开源 NGINX 的替代方案
+
+由于主动健康检查是 NGINX Plus 商业特性,开源版本需要使用第三方模块。
+
+#### nginx_upstream_check_module (Tengine)
+
+由阿里巴巴 Tengine 团队开发的第三方模块,支持主动健康检查。
+
+**源码地址**:https://github.com/yaoweibin/nginx_upstream_check_module
+
+**安装方法**:
+```bash
+# 下载模块源码
+git clone https://github.com/yaoweibin/nginx_upstream_check_module.git
+
+# 下载 NGINX 源码并解压
+cd /usr/local/src
+tar -xzvf nginx-1.24.0.tar.gz
+cd nginx-1.24.0
+
+# 应用补丁(根据版本选择)
+patch -p1 < /path/to/nginx_upstream_check_module/check.patch
+# 或 patch -p1 < /path/to/nginx_upstream_check_module/check_1.20.1+.patch
+
+# 编译安装
+./configure \
+ --prefix=/etc/nginx \
+ --add-module=/path/to/nginx_upstream_check_module \
+ --with-http_ssl_module \
+ --with-http_v2_module
+
+make && make install
+```
+
+**指令说明**:
+
+| 指令 | 语法 | 默认值 | 说明 |
+|------|------|--------|------|
+| `check` | `check interval=ms [fall=N] [rise=N] [timeout=ms] [default_down=true\|false] [type=tcp\|http\|ssl_hello\|mysql\|ajp\|fastcgi]` | 见右侧 | 启用健康检查
`interval`: 检查间隔(ms)
`fall`: 失败次数
`rise`: 成功次数
`timeout`: 超时(ms)
`default_down`: 默认下线状态
`type`: 检查协议 |
+| `check_keepalive_requests` | `check_keepalive_requests num` | `1` | 长连接检查次数 |
+| `check_http_send` | `check_http_send "packet"` | `GET / HTTP/1.0\r\n\r\n` | HTTP 检查请求包 |
+| `check_http_expect_alive` | `check_http_expect_alive [http_2xx] [http_3xx] [http_4xx] [http_5xx]` | `http_2xx` `http_3xx` | 视为健康的 HTTP 状态码 |
+| `check_fastcgi_param` | `check_fastcgi_param parameter value` | — | FastCGI 检查参数 |
+| `check_status` | `check_status [html\|csv\|json]` | `html` | 状态查看页面格式 |
+
+**配置示例**:
+```nginx
+upstream backend {
+ server 192.168.0.1:80;
+ server 192.168.0.2:80;
+
+ # 每 5 秒检查一次,失败 3 次下线,成功 2 次上线,超时 4 秒
+ check interval=5000 rise=2 fall=3 timeout=4000 type=http;
+
+ # HTTP 健康检查配置
+ check_http_send "GET /health HTTP/1.0\r\n\r\n";
+ check_http_expect_alive http_2xx http_3xx;
+
+ # 启用长连接检查(可选)
+ check_keepalive_requests 100;
+}
+
+server {
+ listen 80;
+
+ location / {
+ proxy_pass http://backend;
+ }
+
+ # 健康检查状态页
+ location /status {
+ check_status json; # 可选 html, csv, json
+ access_log off;
+ allow 10.0.0.0/8;
+ deny all;
+ }
+}
+```
+
+**支持的健康检查类型**:
+- `tcp`:仅建立 TCP 连接
+- `http`:发送 HTTP 请求并验证响应
+- `ssl_hello`:发送 SSL Client Hello
+- `mysql`:发送 MySQL ping 包
+- `ajp`:发送 AJP ping 包
+- `fastcgi`:发送 FastCGI 请求
+
+#### 其他替代方案对比
+
+| 方案 | 类型 | 活跃度 | 特点 |
+|------|------|--------|------|
+| **nginx_upstream_check_module** | 第三方模块 | 中等 | 功能完整,Tengine 使用 |
+| **nginx-upsync-module** | 第三方模块 | 低 | 结合 Consul/etcd 动态发现 |
+| **OpenResty + lua-resty-healthcheck** | Lua 扩展 | 高 | 灵活可编程 |
+| **Traefik** | 替代代理 | 高 | 原生支持主动健康检查 |
+| **Envoy** | 替代代理 | 高 | 云原生,功能强大 |
+
+#### OpenResty + Lua 实现示例
+
+```nginx
+lua_shared_dict healthcheck 1m;
+
+upstream backend {
+ server 127.0.0.1:8081;
+ server 127.0.0.1:8082;
+ server 127.0.0.1:8083;
+}
+
+init_worker_by_lua_block {
+ local hc = require "resty.healthcheck"
+ local checker = hc.new({
+ name = "my-checker",
+ shm_name = "healthcheck",
+ checks = {
+ active = {
+ healthy = {
+ interval = 5,
+ successes = 2,
+ },
+ unhealthy = {
+ interval = 5,
+ http_failures = 3,
+ },
+ },
+ },
+ })
+
+ checker:add_target("127.0.0.1", 8081)
+ checker:add_target("127.0.0.1", 8082)
+ checker:add_target("127.0.0.1", 8083)
+}
+```
+
+**选型建议**:
+- 商业环境且有预算:**NGINX Plus**(完整支持,商业支持)
+- 开源替代且功能优先:**nginx_upstream_check_module**
+- 需要动态配置:**OpenResty + lua-resty-upstream-healthcheck**
+- 新架构选型:**Traefik** 或 **Envoy**(原生支持服务发现)
\ No newline at end of file
diff --git a/docs/33-nginx-mqtt-module.md b/docs/33-nginx-mqtt-module.md
new file mode 100644
index 0000000..caa8097
--- /dev/null
+++ b/docs/33-nginx-mqtt-module.md
@@ -0,0 +1,1087 @@
+# NGINX MQTT 模块指南
+
+## 1. MQTT Preread 模块概述
+
+### 1.1 模块介绍
+
+`ngx_stream_mqtt_preread_module` 模块用于在 preread 阶段从 MQTT CONNECT 消息中提取客户端信息,而无需终止 MQTT 连接。
+
+### 1.2 版本要求
+
+- 自 **1.23.4** 版本起可用
+- 属于 **NGINX Plus 商业订阅**功能
+- 支持 MQTT 协议版本 **3.1.1** 和 **5.0**
+
+### 1.3 核心用途
+
+MQTT Preread 模块主要用于以下场景:
+
+1. **基于 Client ID 的路由** - 根据客户端 ID 将连接路由到不同的 MQTT Broker
+2. **基于用户名的路由** - 根据用户名进行负载均衡或访问控制
+3. **透明代理** - 在不解密 MQTT 连接的情况下获取连接元数据
+4. **连接分析** - 记录和分析 MQTT 客户端连接信息
+
+### 1.4 工作原理
+
+```
+客户端 MQTT 连接
+ |
+ v
+[NGINX Stream 模块]
+ |
+ v
+[Preread 阶段] <--- mqtt_preread 在此阶段读取 CONNECT 消息
+ |
+ v
+[变量提取] <--- $mqtt_preread_clientid, $mqtt_preread_username
+ |
+ v
+[路由决策] <--- 基于变量进行 upstream 选择
+ |
+ v
+[代理到后端 MQTT Broker]
+```
+
+在 preread 阶段,NGINX 读取 MQTT CONNECT 消息的前几个字节(不超过 16KB),解析其中的 Client ID 和 Username,然后基于这些信息进行路由决策。
+
+---
+
+## 2. MQTT Filter 模块概述
+
+### 2.1 模块介绍
+
+`ngx_stream_mqtt_filter_module` 模块提供完整的 MQTT 协议支持,允许修改 CONNECT 消息中的字段。
+
+### 2.2 版本要求
+
+- 自 **1.23.4** 版本起可用
+- 属于 **NGINX Plus 商业订阅**功能
+- 支持 MQTT 协议版本 **3.1.1** 和 **5.0**
+
+### 2.3 核心用途
+
+MQTT Filter 模块主要用于以下场景:
+
+1. **修改 Client ID** - 为客户端分配统一的 Client ID 格式
+2. **修改用户名/密码** - 进行身份认证信息的转换或注入
+3. **代理认证** - 在代理层添加统一的认证信息
+4. **会话管理** - 控制客户端会话标识
+
+### 2.4 与 Preread 模块的区别
+
+| 特性 | Preread 模块 | Filter 模块 |
+|------|--------------|-------------|
+| 主要功能 | 读取 CONNECT 信息 | 修改 CONNECT 字段 |
+| 处理阶段 | Preread 阶段 | 代理阶段 |
+| 是否修改数据 | 否(只读) | 是(读写) |
+| 使用场景 | 路由、分析 | 认证、转换 |
+| 性能开销 | 极低 | 较低 |
+
+---
+
+## 3. 指令详解
+
+### 3.1 MQTT Preread 模块指令
+
+#### mqtt_preread
+
+启用或禁用从 MQTT CONNECT 消息中提取信息。
+
+**语法**:
+```nginx
+mqtt_preread on | off;
+```
+
+**默认值**:`off`
+
+**上下文**:`stream`, `server`
+
+**说明**:
+- 在 preread 阶段启用 MQTT CONNECT 消息解析
+- 启用后,可以通过变量 `$mqtt_preread_clientid` 和 `$mqtt_preread_username` 访问提取的信息
+- 仅解析 CONNECT 消息,不修改数据流
+
+**配置示例**:
+```nginx
+stream {
+ server {
+ listen 1883;
+
+ # 启用 MQTT preread
+ mqtt_preread on;
+
+ # 基于 clientid 路由
+ proxy_pass $mqtt_backend;
+ }
+}
+```
+
+---
+
+### 3.2 MQTT Filter 模块指令
+
+#### mqtt
+
+为给定虚拟服务器启用 MQTT 协议支持。
+
+**语法**:
+```nginx
+mqtt on | off;
+```
+
+**默认值**:`off`
+
+**上下文**:`stream`, `server`
+
+**说明**:
+- 启用后可以使用其他 MQTT 相关指令
+- 必须在 `mqtt_set_connect` 之前启用
+
+**配置示例**:
+```nginx
+stream {
+ server {
+ listen 1883;
+
+ # 启用 MQTT 支持
+ mqtt on;
+
+ proxy_pass backend;
+ }
+}
+```
+
+---
+
+#### mqtt_buffers
+
+设置单个连接处理 MQTT 消息的缓冲区数量和大小。
+
+**语法**:
+```nginx
+mqtt_buffers number size;
+```
+
+**默认值**:`100 1k;`
+
+**上下文**:`stream`, `server`
+
+**版本要求**:1.25.1+
+
+**说明**:
+- 控制用于处理 MQTT 消息的缓冲区配置
+- 较大的缓冲区可以处理更大的 MQTT 消息
+- 根据预期的消息大小和并发连接数调整
+
+**配置示例**:
+```nginx
+stream {
+ server {
+ listen 1883;
+ mqtt on;
+
+ # 设置缓冲区:50 个缓冲区,每个 4KB
+ mqtt_buffers 50 4k;
+
+ proxy_pass backend;
+ }
+}
+```
+
+---
+
+#### mqtt_rewrite_buffer_size
+
+设置用于写入修改后消息的缓冲区大小。
+
+**语法**:
+```nginx
+mqtt_rewrite_buffer_size size;
+```
+
+**默认值**:`4k` 或 `8k`(取决于平台内存页大小)
+
+**上下文**:`server`
+
+**版本要求**:1.25.1+
+
+**废弃状态**:已废弃,建议使用 `mqtt_buffers`
+
+**说明**:
+- 该指令在 1.25.1 版本中已被废弃
+- 请使用 `mqtt_buffers` 替代
+
+---
+
+#### mqtt_set_connect
+
+设置 CONNECT 消息的字段为给定值。
+
+**语法**:
+```nginx
+mqtt_set_connect field value;
+```
+
+**默认值**:无
+
+**上下文**:`server`
+
+**说明**:
+- 支持修改的字段:`clientid`, `username`, `password`
+- 值可以包含文本、变量及其组合
+- 可以在同一级别指定多个指令
+
+**可用字段**:
+
+| 字段 | 说明 |
+|------|------|
+| `clientid` | MQTT 客户端标识符 |
+| `username` | 连接用户名 |
+| `password` | 连接密码 |
+
+**配置示例**:
+```nginx
+stream {
+ server {
+ listen 18883;
+ proxy_pass backend;
+ proxy_buffer_size 16k;
+
+ mqtt on;
+
+ # 设置 Client ID
+ mqtt_set_connect clientid "$client";
+
+ # 设置用户名
+ mqtt_set_connect username "$name";
+
+ # 设置密码(从变量获取)
+ mqtt_set_connect password "$mqtt_password";
+ }
+}
+```
+
+---
+
+## 4. 嵌入变量
+
+### 4.1 MQTT Preread 变量
+
+| 变量 | 说明 |
+|------|------|
+| `$mqtt_preread_clientid` | CONNECT 消息中的 Client ID 值 |
+| `$mqtt_preread_username` | CONNECT 消息中的 Username 值 |
+
+**变量使用示例**:
+```nginx
+stream {
+ # 使用 map 基于 clientid 路由
+ map $mqtt_preread_clientid $backend_pool {
+ ~^device-1 backend_1;
+ ~^device-2 backend_2;
+ ~^sensor-.* sensors_backend;
+ default default_backend;
+ }
+
+ server {
+ listen 1883;
+ mqtt_preread on;
+
+ proxy_pass $backend_pool;
+ }
+}
+```
+
+---
+
+## 5. 配置示例
+
+### 5.1 基础 MQTT 代理
+
+简单的 MQTT 代理配置,将所有连接转发到后端 Broker:
+
+```nginx
+stream {
+ upstream mqtt_backend {
+ server 192.168.1.10:1883;
+ server 192.168.1.11:1883 backup;
+ }
+
+ server {
+ listen 1883;
+ proxy_pass mqtt_backend;
+ proxy_timeout 30s;
+ proxy_connect_timeout 5s;
+ }
+}
+```
+
+### 5.2 基于 Client ID 的路由
+
+根据 MQTT Client ID 将连接路由到不同的后端:
+
+```nginx
+stream {
+ # 设备组 1:工业传感器
+ upstream sensors_backend {
+ server 10.0.1.10:1883 weight=5;
+ server 10.0.1.11:1883;
+ }
+
+ # 设备组 2:智能家居设备
+ upstream home_backend {
+ server 10.0.2.10:1883;
+ server 10.0.2.11:1883;
+ }
+
+ # 设备组 3:车联网
+ upstream vehicle_backend {
+ server 10.0.3.10:1883;
+ server 10.0.3.11:1883;
+ }
+
+ # 默认后端
+ upstream default_backend {
+ server 10.0.0.10:1883;
+ }
+
+ # 基于 Client ID 的路由映射
+ map $mqtt_preread_clientid $target_backend {
+ # 传感器设备(匹配 sensor- 开头的 Client ID)
+ ~^sensor- sensors_backend;
+
+ # 智能家居设备(匹配 home- 开头的 Client ID)
+ ~^home- home_backend;
+
+ # 车载设备(匹配 vehicle- 或 car- 开头的 Client ID)
+ ~^vehicle- vehicle_backend;
+ ~^car- vehicle_backend;
+
+ # 默认后端
+ default default_backend;
+ }
+
+ server {
+ listen 1883;
+
+ # 启用 MQTT preread
+ mqtt_preread on;
+
+ # 基于 Client ID 路由
+ proxy_pass $target_backend;
+
+ # 连接超时配置
+ proxy_timeout 300s;
+ proxy_connect_timeout 10s;
+
+ # 启用 TCP keepalive
+ proxy_socket_keepalive on;
+ }
+}
+```
+
+### 5.3 基于用户名的负载均衡
+
+根据用户名进行路由,适用于多租户场景:
+
+```nginx
+stream {
+ # 租户 A 集群
+ upstream tenant_a_backend {
+ server 10.0.10.10:1883;
+ server 10.0.10.11:1883;
+ }
+
+ # 租户 B 集群
+ upstream tenant_b_backend {
+ server 10.0.20.10:1883;
+ server 10.0.20.11:1883;
+ }
+
+ # 管理员集群
+ upstream admin_backend {
+ server 10.0.0.10:1883;
+ }
+
+ # 基于用户名路由
+ map $mqtt_preread_username $tenant_backend {
+ "tenant-a-user" tenant_a_backend;
+ "tenant-a-admin" tenant_a_backend;
+ "tenant-b-user" tenant_b_backend;
+ "tenant-b-admin" tenant_b_backend;
+ ~^admin-.* admin_backend;
+ default tenant_a_backend;
+ }
+
+ server {
+ listen 1883;
+ mqtt_preread on;
+
+ proxy_pass $tenant_backend;
+ proxy_timeout 60s;
+ }
+}
+```
+
+### 5.4 修改 CONNECT 消息(Filter 模块)
+
+在代理层修改 MQTT CONNECT 消息中的字段:
+
+```nginx
+stream {
+ upstream mqtt_backend {
+ server 192.168.1.10:1883;
+ }
+
+ server {
+ listen 1883;
+ proxy_pass mqtt_backend;
+ proxy_buffer_size 16k;
+
+ # 启用 MQTT Filter
+ mqtt on;
+
+ # 设置固定的 Client ID 前缀(追加原始 ID)
+ mqtt_set_connect clientid "ng-$mqtt_preread_clientid";
+
+ # 注入代理认证用户名
+ mqtt_set_connect username "nginx-proxy";
+
+ # 设置代理密码(从文件或环境变量获取)
+ mqtt_set_connect password "$proxy_mqtt_password";
+ }
+}
+```
+
+### 5.5 综合配置示例
+
+结合 Preread 和 Filter 模块的完整配置:
+
+```nginx
+stream {
+ # 日志格式
+ log_format mqtt_log '$remote_addr [$time_local] '
+ 'clientid:$mqtt_preread_clientid '
+ 'username:$mqtt_preread_username '
+ 'upstream:$upstream_addr '
+ 'status:$status';
+
+ # 设备专用集群
+ upstream device_cluster_a {
+ zone devices 64k;
+ server 10.0.1.10:1883 weight=5;
+ server 10.0.1.11:1883;
+ server 10.0.1.12:1883 backup;
+ }
+
+ upstream device_cluster_b {
+ zone devices 64k;
+ server 10.0.2.10:1883;
+ server 10.0.2.11:1883;
+ }
+
+ # 普通设备集群
+ upstream default_devices {
+ zone devices 64k;
+ least_conn;
+ server 10.0.3.10:1883;
+ server 10.0.3.11:1883;
+ server 10.0.3.12:1883;
+ }
+
+ # Client ID 到后端映射
+ map $mqtt_preread_clientid $backend_pool {
+ ~^dev-a- device_cluster_a;
+ ~^device-a- device_cluster_a;
+ ~^dev-b- device_cluster_b;
+ ~^device-b- device_cluster_b;
+ default default_devices;
+ }
+
+ # 服务器配置
+ server {
+ listen 1883;
+ access_log /var/log/nginx/mqtt-access.log mqtt_log;
+
+ # 启用 MQTT Preread 获取 Client ID 和用户名
+ mqtt_preread on;
+
+ # 启用 MQTT Filter(可选,需要修改 CONNECT 时启用)
+ mqtt on;
+ mqtt_buffers 50 4k;
+
+ # 可选:修改 CONNECT 消息
+ # mqtt_set_connect clientid "proxy-$mqtt_preread_clientid";
+
+ # 基于 Client ID 路由
+ proxy_pass $backend_pool;
+
+ # 超时配置
+ proxy_timeout 300s;
+ proxy_connect_timeout 10s;
+
+ # 启用 TCP keepalive
+ proxy_socket_keepalive on;
+
+ # 连接限制
+ limit_conn mqtt_conn 100;
+ }
+
+ # TLS MQTT 端口
+ server {
+ listen 8883 ssl;
+
+ ssl_certificate /etc/nginx/ssl/mqtt.crt;
+ ssl_certificate_key /etc/nginx/ssl/mqtt.key;
+ ssl_protocols TLSv1.2 TLSv1.3;
+
+ mqtt_preread on;
+ proxy_pass $backend_pool;
+ proxy_timeout 300s;
+ }
+}
+
+# 连接限制共享内存
+limit_conn_zone $binary_remote_addr zone=mqtt_conn:10m;
+```
+
+### 5.6 与 SSL/TLS 结合
+
+MQTT over TLS 的配置:
+
+```nginx
+stream {
+ upstream mqtt_ssl_backend {
+ server 192.168.1.10:8883;
+ server 192.168.1.11:8883;
+ }
+
+ # 终止 TLS 并读取 MQTT 信息
+ server {
+ listen 8883 ssl;
+
+ ssl_certificate /etc/nginx/ssl/server.crt;
+ ssl_certificate_key /etc/nginx/ssl/server.key;
+ ssl_protocols TLSv1.2 TLSv1.3;
+
+ # 在解密后读取 MQTT 信息
+ mqtt_preread on;
+ mqtt on;
+
+ proxy_pass mqtt_ssl_backend;
+
+ # 上游也使用 SSL
+ proxy_ssl on;
+ proxy_ssl_protocols TLSv1.2 TLSv1.3;
+ }
+}
+```
+
+---
+
+## 6. 与 Lolly 项目的关系
+
+### 6.1 Lolly 项目简介
+
+Lolly 是一个使用 Go 语言编写的高性能 HTTP 服务器与反向代理,基于 fasthttp 构建。它提供了 HTTP/3、WebSocket、TCP/UDP Stream 代理等功能。
+
+### 6.2 功能对比
+
+| 特性 | NGINX Plus (MQTT) | Lolly |
+|------|-------------------|-------|
+| MQTT Preread | 支持(商业版) | 未实现 |
+| MQTT Filter | 支持(商业版) | 未实现 |
+| TCP Stream 代理 | 支持 | 支持 |
+| 基于内容路由 | 支持 | 有限支持 |
+| SSL/TLS 终端 | 支持 | 支持 |
+| 负载均衡 | 丰富算法 | 轮询/加权/最少连接/IP哈希 |
+
+### 6.3 Lolly 中的 Stream 代理
+
+Lolly 目前支持基础的 TCP/UDP Stream 代理:
+
+```go
+// Lolly Stream 代理示例配置(YAML)
+stream:
+ - listen: ":1883"
+ protocol: "tcp"
+ upstream:
+ targets:
+ - addr: "mqtt1:1883"
+ weight: 3
+ - addr: "mqtt2:1883"
+ weight: 1
+ load_balance: "round_robin"
+```
+
+当前 Lolly 的 Stream 实现位于 `internal/stream/stream.go`,提供:
+- TCP/UDP 代理
+- 负载均衡(轮询、加权轮询、最少连接、IP 哈希)
+- 健康检查
+- 会话管理(UDP)
+
+### 6.4 在 Lolly 中实现 MQTT 支持的方案
+
+#### 方案 1:独立 MQTT Preread 中间件
+
+在 Lolly 的 Stream 模块中添加 MQTT Preread 功能:
+
+```go
+// internal/stream/mqtt_preread.go
+package stream
+
+import (
+ "bufio"
+ "encoding/binary"
+ "io"
+ "net"
+)
+
+// MQTTPrereadConfig MQTT Preread 配置
+type MQTTPrereadConfig struct {
+ Enabled bool
+ OnClientID func(clientID string) string // 路由回调
+ OnUsername func(username string) string // 认证回调
+}
+
+// MQTTConnectInfo 解析后的 MQTT CONNECT 信息
+type MQTTConnectInfo struct {
+ ClientID string
+ Username string
+ Password []byte
+ Protocol byte
+}
+
+// ParseMQTTConnect 从连接读取并解析 MQTT CONNECT 消息
+func ParseMQTTConnect(conn net.Conn) (*MQTTConnectInfo, error) {
+ // 1. 读取固定头(2-5 字节)
+ // 2. 读取剩余长度
+ // 3. 读取可变头(协议名、协议级别、连接标志)
+ // 4. 读取 Payload(Client ID、Will Topic、Will Message、Username、Password)
+ // 5. 返回解析结果
+}
+```
+
+#### 方案 2:基于配置的路由
+
+在 Lolly 配置中添加 MQTT 路由规则:
+
+```yaml
+stream:
+ - listen: ":1883"
+ protocol: "tcp"
+ mqtt_preread: true
+ routes:
+ - match: "clientid =~ ^sensor-"
+ upstream: sensors
+ - match: "clientid =~ ^home-"
+ upstream: home_devices
+ - match: "username == admin"
+ upstream: admin_cluster
+ upstreams:
+ sensors:
+ targets:
+ - addr: "mqtt-sensors-1:1883"
+ - addr: "mqtt-sensors-2:1883"
+ home_devices:
+ targets:
+ - addr: "mqtt-home-1:1883"
+ admin_cluster:
+ targets:
+ - addr: "mqtt-admin-1:1883"
+```
+
+#### 方案 3:与现有 Stream 模块集成
+
+扩展现有的 `internal/stream/stream.go`:
+
+```go
+// Target MQTT 扩展
+type Target struct {
+ addr string
+ weight int
+ healthy atomic.Bool
+ conns int64
+
+ // MQTT 特定字段
+ mqttMatcher func(*MQTTConnectInfo) bool // 匹配函数
+}
+
+// Upstream 添加 MQTT 选择支持
+type Upstream struct {
+ name string
+ targets []*Target
+ balancer Balancer
+
+ // MQTT 路由表
+ mqttRoutes map[string][]*Target // 标签 -> 目标列表
+}
+
+// SelectByMQTT 基于 MQTT CONNECT 信息选择目标
+func (u *Upstream) SelectByMQTT(info *MQTTConnectInfo) *Target {
+ // 1. 检查 MQTT 路由表
+ // 2. 回退到默认负载均衡
+}
+```
+
+### 6.5 实现建议
+
+#### 短期建议(PoC 验证)
+
+1. **实现基础 MQTT CONNECT 解析器**
+ - 支持 MQTT 3.1.1 和 5.0
+ - 提取 Client ID、Username、Password
+ - 位置:`internal/stream/mqtt.go`
+
+2. **添加基于 Client ID 的路由**
+ - 简单的正则匹配
+ - 配置文件支持
+ - 与现有 upstream 集成
+
+3. **性能测试**
+ - 对比有无 preread 的性能差异
+ - 内存占用分析
+
+#### 中期建议(功能完善)
+
+1. **完整 MQTT Filter 支持**
+ - 支持修改 CONNECT 字段
+ - 支持消息重写
+ - 配置热重载
+
+2. **监控与日志**
+ - MQTT 特定指标(连接数、消息数)
+ - 结构化日志输出
+
+3. **安全增强**
+ - 基于 Client ID 的访问控制
+ - 速率限制
+
+#### 长期建议(企业级功能)
+
+1. **MQTT 5.0 完整支持**
+ - 用户属性处理
+ - 共享订阅支持
+ - 消息过期处理
+
+2. **高级路由**
+ - 基于 Topic 的路由(需要解析 PUBLISH/SUBSCRIBE)
+ - 动态后端发现
+
+3. **与 HTTP 层的集成**
+ - 统一的配置管理
+ - 共享的健康检查
+
+### 6.6 代码示例
+
+以下是在 Lolly 中实现 MQTT Preread 的参考代码:
+
+```go
+// internal/stream/mqtt.go
+package stream
+
+import (
+ "bufio"
+ "encoding/binary"
+ "fmt"
+ "io"
+ "net"
+)
+
+const (
+ // MQTT 控制包类型
+ mqttCONNECT = 1
+
+ // MQTT 协议名
+ mqttProtocol311 = "MQTT" // v3.1.1
+ mqttProtocol31 = "MQIsdp" // v3.1
+)
+
+// MQTTPrereadHandler MQTT Preread 处理器
+type MQTTPrereadHandler struct {
+ config *MQTTPrereadConfig
+}
+
+// NewMQTTPrereadHandler 创建处理器
+func NewMQTTPrereadHandler(config *MQTTPrereadConfig) *MQTTPrereadHandler {
+ return &MQTTPrereadHandler{config: config}
+}
+
+// Handle 处理连接,解析 MQTT CONNECT 并返回信息
+func (h *MQTTPrereadHandler) Handle(conn net.Conn) (*MQTTConnectInfo, net.Conn, error) {
+ // 使用 bufio 预读数据
+ reader := bufio.NewReaderSize(conn, 4096)
+
+ // 读取固定头第一个字节
+ firstByte, err := reader.Peek(1)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ // 检查是否为 CONNECT 包
+ packetType := (firstByte[0] >> 4) & 0x0F
+ if packetType != mqttCONNECT {
+ return nil, nil, fmt.Errorf("expected CONNECT packet, got %d", packetType)
+ }
+
+ // 读取剩余长度(可变长度编码)
+ remainingLen, headerLen, err := decodeRemainingLength(reader)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ // 读取完整的 CONNECT 包
+ totalLen := 1 + headerLen + remainingLen
+ packet, err := reader.Peek(totalLen)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ // 解析 CONNECT 包
+ info, err := parseConnectPacket(packet[1+headerLen:])
+ if err != nil {
+ return nil, nil, err
+ }
+
+ // 创建包装连接,将预读的数据返回给后续处理
+ wrappedConn := &mqttConn{
+ Conn: conn,
+ reader: reader,
+ peeked: totalLen,
+ }
+
+ return info, wrappedConn, nil
+}
+
+// decodeRemainingLength 解码 MQTT 剩余长度字段
+func decodeRemainingLength(r *bufio.Reader) (int, int, error) {
+ var value int
+ var multiplier int = 1
+ var headerLen int
+
+ for {
+ b, err := r.ReadByte()
+ if err != nil {
+ return 0, 0, err
+ }
+ headerLen++
+
+ value += int(b&0x7F) * multiplier
+ multiplier *= 128
+
+ if (b & 0x80) == 0 {
+ break
+ }
+
+ if multiplier > 128*128*128 {
+ return 0, 0, fmt.Errorf("malformed remaining length")
+ }
+ }
+
+ return value, headerLen, nil
+}
+
+// parseConnectPacket 解析 CONNECT 包体
+func parseConnectPacket(data []byte) (*MQTTConnectInfo, error) {
+ info := &MQTTConnectInfo{}
+ offset := 0
+
+ // 读取协议名长度
+ protoLen := binary.BigEndian.Uint16(data[offset:])
+ offset += 2
+
+ // 读取协议名
+ protoName := string(data[offset : offset+int(protoLen)])
+ offset += int(protoLen)
+
+ // 判断协议版本
+ if protoName == mqttProtocol311 {
+ info.Protocol = 4 // MQTT 3.1.1
+ } else if protoName == mqttProtocol31 {
+ info.Protocol = 3 // MQTT 3.1
+ }
+
+ // 读取协议级别
+ protocolLevel := data[offset]
+ offset++
+ _ = protocolLevel
+
+ // 读取连接标志
+ connectFlags := data[offset]
+ offset++
+
+ usernameFlag := (connectFlags >> 7) & 1
+ passwordFlag := (connectFlags >> 6) & 1
+ // willRetain := (connectFlags >> 5) & 1
+ willQoS := (connectFlags >> 3) & 3
+ willFlag := (connectFlags >> 2) & 1
+ // cleanSession := (connectFlags >> 1) & 1
+
+ // 读取保持连接时间(跳过)
+ offset += 2
+
+ // 读取 Client ID
+ clientIDLen := binary.BigEndian.Uint16(data[offset:])
+ offset += 2
+ info.ClientID = string(data[offset : offset+int(clientIDLen)])
+ offset += int(clientIDLen)
+
+ // 读取 Will Topic 和 Will Message(如果有)
+ if willFlag == 1 {
+ // Will Topic
+ willTopicLen := binary.BigEndian.Uint16(data[offset:])
+ offset += 2
+ offset += int(willTopicLen)
+
+ // Will Message
+ willMsgLen := binary.BigEndian.Uint16(data[offset:])
+ offset += 2
+ offset += int(willMsgLen)
+ }
+
+ // 读取 Username(如果有)
+ if usernameFlag == 1 {
+ usernameLen := binary.BigEndian.Uint16(data[offset:])
+ offset += 2
+ info.Username = string(data[offset : offset+int(usernameLen)])
+ offset += int(usernameLen)
+ }
+
+ // 读取 Password(如果有)
+ if passwordFlag == 1 {
+ passwordLen := binary.BigEndian.Uint16(data[offset:])
+ offset += 2
+ info.Password = data[offset : offset+int(passwordLen)]
+ }
+
+ return info, nil
+}
+
+// mqttConn 包装连接,支持预读数据回退
+type mqttConn struct {
+ net.Conn
+ reader *bufio.Reader
+ peeked int
+}
+
+func (c *mqttConn) Read(p []byte) (n int, err error) {
+ return c.reader.Read(p)
+}
+```
+
+### 6.7 测试验证
+
+添加 MQTT Preread 的单元测试:
+
+```go
+// internal/stream/mqtt_test.go
+package stream
+
+import (
+ "bytes"
+ "testing"
+)
+
+func TestParseConnectPacket(t *testing.T) {
+ // 构造一个 MQTT 3.1.1 CONNECT 包
+ // CONNECT + 剩余长度 + 协议名长度(4) + "MQTT" + 协议级别(4) + 连接标志 + 保持连接 + Client ID
+ packet := []byte{
+ // 可变头开始
+ 0x00, 0x04, // 协议名长度 = 4
+ 'M', 'Q', 'T', 'T', // 协议名 "MQTT"
+ 0x04, // 协议级别 4 (3.1.1)
+ 0xC2, // 连接标志: 用户名(1) + 密码(1) + Clean Session(0)
+ 0x00, 0x3C, // 保持连接 = 60 秒
+ // Payload
+ 0x00, 0x0A, // Client ID 长度 = 10
+ 't', 'e', 's', 't', '-', 'c', 'l', 'i', 'e', 'n', 't', // Client ID
+ 0x00, 0x05, // 用户名长度 = 5
+ 'a', 'd', 'm', 'i', 'n', // 用户名
+ 0x00, 0x08, // 密码长度 = 8
+ 'p', 'a', 's', 's', 'w', 'o', 'r', 'd', // 密码
+ }
+
+ info, err := parseConnectPacket(packet)
+ if err != nil {
+ t.Fatalf("parseConnectPacket failed: %v", err)
+ }
+
+ if info.ClientID != "test-client" {
+ t.Errorf("ClientID = %q, want %q", info.ClientID, "test-client")
+ }
+
+ if info.Username != "admin" {
+ t.Errorf("Username = %q, want %q", info.Username, "admin")
+ }
+
+ if info.Protocol != 4 {
+ t.Errorf("Protocol = %d, want %d", info.Protocol, 4)
+ }
+}
+
+func TestDecodeRemainingLength(t *testing.T) {
+ tests := []struct {
+ input []byte
+ expected int
+ }{
+ {[]byte{0x00}, 0},
+ {[]byte{0x7F}, 127},
+ {[]byte{0x80, 0x01}, 128},
+ {[]byte{0xFF, 0x7F}, 16383},
+ {[]byte{0x80, 0x80, 0x01}, 16384},
+ }
+
+ for _, tt := range tests {
+ r := bufio.NewReader(bytes.NewReader(tt.input))
+ value, _, err := decodeRemainingLength(r)
+ if err != nil {
+ t.Errorf("decodeRemainingLength(%v) error: %v", tt.input, err)
+ continue
+ }
+ if value != tt.expected {
+ t.Errorf("decodeRemainingLength(%v) = %d, want %d", tt.input, value, tt.expected)
+ }
+ }
+}
+```
+
+---
+
+## 7. 总结
+
+### 7.1 NGINX MQTT 模块要点
+
+1. **Preread 模块** (`ngx_stream_mqtt_preread_module`)
+ - 只读解析 MQTT CONNECT 消息
+ - 提取 Client ID 和 Username 用于路由
+ - 适用于基于内容的负载均衡
+
+2. **Filter 模块** (`ngx_stream_mqtt_filter_module`)
+ - 支持修改 MQTT CONNECT 字段
+ - 可用于代理认证和会话管理
+ - 与 Preread 模块可以配合使用
+
+3. **商业订阅限制**
+ - 两个模块都需要 NGINX Plus 商业订阅
+ - 自 1.23.4 版本起可用
+
+### 7.2 适用场景
+
+- **物联网平台** - 海量设备接入和路由
+- **多租户 MQTT** - 基于用户名隔离租户
+- **边缘网关** - 设备接入层代理
+- **MQTT 迁移** - 平滑迁移到新 Broker
+
+### 7.3 Lolly 实现建议优先级
+
+1. **P0** - 基础 MQTT CONNECT 解析器
+2. **P1** - 基于 Client ID 的路由
+3. **P2** - MQTT Filter(修改 CONNECT)
+4. **P3** - MQTT 5.0 高级特性
+
+### 7.4 参考资料
+
+- [NGINX MQTT Preread Module](https://nginx.org/en/docs/stream/ngx_stream_mqtt_preread_module.html)
+- [NGINX MQTT Filter Module](https://nginx.org/en/docs/stream/ngx_stream_mqtt_filter_module.html)
+- [MQTT 3.1.1 Specification](http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html)
+- [MQTT 5.0 Specification](https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html)
diff --git a/docs/34-nginx-oidc-module.md b/docs/34-nginx-oidc-module.md
new file mode 100644
index 0000000..50f9abb
--- /dev/null
+++ b/docs/34-nginx-oidc-module.md
@@ -0,0 +1,1501 @@
+# NGINX OpenID Connect 模块指南
+
+## 1. OIDC 模块概述
+
+### 什么是 OpenID Connect
+
+OpenID Connect (OIDC) 是基于 OAuth 2.0 协议的身份认证层,允许客户端应用程序验证用户身份并获取用户基本信息。NGINX Plus 提供原生 OIDC 支持,可作为 **Relying Party (RP)** 与 Identity Provider (IdP) 集成,实现单点登录 (SSO) 和集中式身份认证。
+
+### 模块用途
+
+| 场景 | 说明 |
+|------|------|
+| **单点登录 (SSO)** | 用户一次登录,访问多个受保护应用 |
+| **集中式认证** | 统一认证入口,后端服务无需处理认证逻辑 |
+| **API 保护** | 验证 JWT Token,保护 API 端点 |
+| **身份代理** | 将身份信息传递给后端应用 |
+| **会话管理** | 集中管理用户会话生命周期 |
+
+### 架构图
+
+```
+┌─────────┐ ┌─────────────┐ ┌─────────────────┐
+│ Client │────────▶│ NGINX Plus │────────▶│ IdP Provider │
+│ (浏览器) │ │ (OIDC RP) │ │ (Keycloak/Okta) │
+└─────────┘ └──────┬──────┘ └─────────────────┘
+ │
+ ▼
+ ┌──────────────┐
+ │ Backend │
+ │ Applications │
+ └──────────────┘
+```
+
+**工作流程**:
+1. 用户访问受保护资源,NGINX 检查会话状态
+2. 无有效会话时,重定向到 IdP 登录页面
+3. 用户在 IdP 完成认证,IdP 返回授权码
+4. NGINX 使用授权码交换 ID Token 和 Access Token
+5. 用户携带会话 Cookie 访问资源,NGINX 验证 JWT
+6. 后端应用接收带有用户信息的请求
+
+---
+
+## 2. OIDC 认证流程
+
+### 2.1 授权码流程 (Authorization Code Flow)
+
+NGINX Plus 使用标准的 OAuth 2.0 授权码流程,支持 **PKCE** (Proof Key for Code Exchange) 增强安全性。
+
+**完整流程图**:
+
+```
+用户 NGINX Plus IdP Provider
+ | │ │
+ |── 1. 访问 /app ───────────────▶│ │
+ | │── 2. 检查会话 ─────────────▶│
+ | │ │
+ |◀─ 3. 重定向到 /authorize ─────│ │
+ | │ │
+ |── 4. 登录认证 ───────────────────────────────▶│ │
+ | │ │
+ |◀─ 5. 返回授权码 ───────────────│ │
+ | │ │
+ |── 6. 携带 code 回调 ──────────▶│ │
+ | │── 7. Token 请求 ───────────▶│
+ | │ │
+ | │◀─ 8. ID/Access/Refresh Token─│
+ | │ │
+ |◀─ 9. 设置 Session Cookie ─────│ │
+ | │ │
+ |── 10. 携带 Cookie 访问 ───────▶│ │
+ | │── 11. 验证 JWT ────────────▶│
+ | │ │
+ | │── 12. 代理到后端 ──────────▶│
+```
+
+### 2.2 支持的 IdP 提供商
+
+| 提供商 | Discovery URL 格式 |
+|--------|-------------------|
+| **Keycloak** | `https://keycloak.example.com/realms/{realm}/.well-known/openid-configuration` |
+| **Okta** | `https://{your-domain}.okta.com/.well-known/openid-configuration` |
+| **Auth0** | `https://{your-domain}.auth0.com/.well-known/openid-configuration` |
+| **Azure AD / Entra ID** | `https://login.microsoftonline.com/{tenant}/v2.0/.well-known/openid-configuration` |
+| **Google** | `https://accounts.google.com/.well-known/openid-configuration` |
+| **GitHub** | `https://token.actions.githubusercontent.com/.well-known/openid-configuration` |
+| **自建 IdP** | `https://auth.example.com/.well-known/openid-configuration` |
+
+---
+
+## 3. 指令参考
+
+### 3.1 核心指令
+
+#### `auth_jwt`
+
+启用 JWT 认证。
+
+| 属性 | 说明 |
+|------|------|
+| **语法** | `auth_jwt "realm" [token=$variable] \| off;` |
+| **默认值** | `off` |
+| **上下文** | `http`, `server`, `location` |
+
+```nginx
+# 基本用法
+location /protected/ {
+ auth_jwt "API Access";
+ auth_jwt_key_file /etc/nginx/jwks.json;
+}
+
+# 从 Cookie 读取 Token
+location /api/ {
+ auth_jwt "API Access" token=$cookie_auth_token;
+ auth_jwt_key_request /_jwks_uri;
+}
+
+# 从 Header 读取 Token
+location /api/ {
+ auth_jwt "API Access" token=$http_authorization;
+ auth_jwt_key_request /_jwks_uri;
+}
+```
+
+#### `auth_jwt_key_file`
+
+指定 JWKS (JSON Web Key Set) 文件路径,用于验证 JWT 签名。
+
+| 属性 | 说明 |
+|------|------|
+| **语法** | `auth_jwt_key_file file;` |
+| **默认值** | — |
+| **上下文** | `http`, `server`, `location` |
+
+```nginx
+# 本地 JWKS 文件
+auth_jwt_key_file /etc/nginx/oidc/jwks.json;
+
+# 使用变量(动态配置)
+auth_jwt_key_file $oidc_jwt_keyfile;
+```
+
+#### `auth_jwt_key_request`
+
+从指定 location 动态获取 JWKS。
+
+| 属性 | 说明 |
+|------|------|
+| **语法** | `auth_jwt_key_request uri;` |
+| **默认值** | — |
+| **上下文** | `http`, `server`, `location` |
+
+```nginx
+server {
+ location /protected/ {
+ auth_jwt "API Access" token=$cookie_auth_token;
+ auth_jwt_key_request /_jwks_uri;
+ }
+
+ # 内部 JWKS 端点
+ location = /_jwks_uri {
+ internal; # 仅限内部子请求访问
+ proxy_pass https://idp.example.com/.well-known/jwks.json;
+ proxy_cache jwks_cache;
+ proxy_cache_valid 200 1h;
+ }
+}
+```
+
+#### `auth_jwt_require`
+
+配置 JWT 验证的额外要求。
+
+| 属性 | 说明 |
+|------|------|
+| **语法** | `auth_jwt_require $claim [value] ...;` |
+| **默认值** | — |
+| **上下文** | `http`, `server`, `location` |
+
+```nginx
+# 要求特定的 issuer
+location /protected/ {
+ auth_jwt "API Access" token=$cookie_auth_token;
+ auth_jwt_key_request /_jwks_uri;
+ auth_jwt_require $jwt_claim_iss "https://idp.example.com";
+}
+
+# 要求特定的 audience
+location /api/admin/ {
+ auth_jwt "Admin Access" token=$cookie_auth_token;
+ auth_jwt_key_request /_jwks_uri;
+ auth_jwt_require $jwt_claim_aud "my-api-client";
+}
+
+# 组合条件
+location /api/sensitive/ {
+ auth_jwt "Sensitive Access" token=$cookie_auth_token;
+ auth_jwt_key_request /_jwks_uri;
+ auth_jwt_require $jwt_claim_iss "https://idp.example.com" $jwt_claim_aud "sensitive-api";
+}
+```
+
+### 3.2 会话管理指令
+
+#### `keyval_zone`
+
+定义用于存储 OIDC 会话数据的共享内存区域。
+
+| 属性 | 说明 |
+|------|------|
+| **语法** | `keyval_zone zone=name:size [state=file] [timeout=time];` |
+| **默认值** | — |
+| **上下文** | `http` |
+
+```nginx
+# 基本配置
+keyval_zone zone=oidc_id_tokens:1M timeout=1h;
+keyval_zone zone=oidc_access_tokens:1M timeout=1h;
+keyval_zone zone=oidc_refresh_tokens:1M timeout=8h;
+
+# 带状态持久化
+keyval_zone zone=oidc_id_tokens:1M state=/var/lib/nginx/state/oidc_id_tokens.json timeout=1h;
+keyval_zone zone=oidc_access_tokens:1M state=/var/lib/nginx/state/oidc_access_tokens.json timeout=1h;
+```
+
+**参数说明**:
+- `zone=name:size` - 共享内存区域名称和大小
+- `state=file` - 状态持久化文件路径
+- `timeout=time` - 数据过期时间
+
+#### `keyval`
+
+定义键值对存储。
+
+| 属性 | 说明 |
+|------|------|
+| **语法** | `keyval $variable $variable zone=name;` |
+| **默认值** | — |
+| **上下文** | `http` |
+
+```nginx
+# 定义存储变量
+keyval $cookie_auth_token $session_jwt zone=oidc_id_tokens;
+keyval $cookie_auth_token $access_token zone=oidc_access_tokens;
+keyval $cookie_auth_token $refresh_token zone=oidc_refresh_tokens;
+```
+
+### 3.3 JavaScript 模块指令
+
+#### `js_import`
+
+导入 NGINX JavaScript (njs) 模块。
+
+| 属性 | 说明 |
+|------|------|
+| **语法** | `js_import module [as namespace];` |
+| **默认值** | — |
+| **上下文** | `http` |
+
+```nginx
+# 导入 OIDC 处理脚本
+js_import /etc/nginx/conf.d/openid_connect.js;
+
+# 使用命名空间
+js_import /etc/nginx/conf.d/openid_connect.js as oidc;
+```
+
+#### `js_set`
+
+设置 JavaScript 变量。
+
+| 属性 | 说明 |
+|------|------|
+| **语法** | `js_set $variable function;` |
+| **默认值** | — |
+| **上下文** | `http` |
+
+```nginx
+# 设置 OIDC 认证头
+js_set $oidc_auth_header oidc.authHeader;
+
+# 设置 ID Token
+js_set $id_token oidc.idToken;
+```
+
+#### `js_content`
+
+使用 JavaScript 生成响应内容。
+
+| 属性 | 说明 |
+|------|------|
+| **语法** | `js_content function;` |
+| **默认值** | — |
+| **上下文** | `location` |
+
+```nginx
+location /login {
+ js_content oidc.login;
+}
+
+location /logout {
+ js_content oidc.logout;
+}
+
+location = /redirect_uri {
+ js_content oidc.redirect;
+}
+```
+
+### 3.4 代理配置指令
+
+#### `auth_jwt_set`
+
+将 JWT Claims 提取到变量。
+
+| 属性 | 说明 |
+|------|------|
+| **语法** | `auth_jwt_set $variable claim;` |
+| **默认值** | — |
+| **上下文** | `http`, `server`, `location` |
+
+```nginx
+server {
+ location /api/ {
+ auth_jwt "API Access" token=$cookie_auth_token;
+ auth_jwt_key_request /_jwks_uri;
+
+ # 提取常用 Claims
+ auth_jwt_set $jwt_sub sub;
+ auth_jwt_set $jwt_email email;
+ auth_jwt_set $jwt_name name;
+
+ proxy_pass http://backend;
+ proxy_set_header X-User-ID $jwt_sub;
+ proxy_set_header X-User-Email $jwt_email;
+ proxy_set_header X-User-Name $jwt_name;
+ }
+}
+```
+
+---
+
+## 4. 变量参考
+
+### 4.1 JWT Claims 变量
+
+| 变量 | 说明 |
+|------|------|
+| `$jwt_claim_sub` | 用户唯一标识 (Subject) |
+| `$jwt_claim_iss` | Token 签发者 (Issuer) |
+| `$jwt_claim_aud` | Token 受众 (Audience) |
+| `$jwt_claim_exp` | Token 过期时间 (Expiration) |
+| `$jwt_claim_iat` | Token 签发时间 (Issued At) |
+| `$jwt_claim_nbf` | Token 生效时间 (Not Before) |
+| `$jwt_claim_jti` | Token 唯一标识 (JWT ID) |
+| `$jwt_claim_email` | 用户邮箱 |
+| `$jwt_claim_name` | 用户名称 |
+| `$jwt_claim_preferred_username` | 首选用户名 |
+| `$jwt_claim_groups` | 用户组/角色 |
+| `$jwt_claim_scope` | 授权范围 |
+
+### 4.2 会话变量
+
+| 变量 | 说明 |
+|------|------|
+| `$cookie_auth_token` | 会话 Cookie 值 |
+| `$session_jwt` | 存储的 ID Token |
+| `$access_token` | 存储的 Access Token |
+| `$refresh_token` | 存储的 Refresh Token |
+
+---
+
+## 5. IdP 集成配置示例
+
+### 5.1 Keycloak 集成
+
+**Keycloak 配置要点**:
+- 创建 Client,启用 `Standard Flow` (Authorization Code)
+- 配置 Valid Redirect URIs: `https://app.example.com/redirect_uri`
+- 启用 `Client authentication`,记录 Client Secret
+- 配置 Web Origins: `https://app.example.com`
+
+**NGINX 配置**:
+
+```nginx
+http {
+ # 加载 JavaScript 模块
+ load_module modules/ngx_http_js_module.so;
+
+ # 会话存储
+ keyval_zone zone=oidc_id_tokens:1M state=/var/lib/nginx/state/oidc_id_tokens.json timeout=1h;
+ keyval_zone zone=oidc_access_tokens:1M state=/var/lib/nginx/state/oidc_access_tokens.json timeout=1h;
+ keyval_zone zone=oidc_refresh_tokens:1M state=/var/lib/nginx/state/oidc_refresh_tokens.json timeout=8h;
+
+ keyval $cookie_auth_token $session_jwt zone=oidc_id_tokens;
+ keyval $cookie_auth_token $access_token zone=oidc_access_tokens;
+ keyval $cookie_auth_token $refresh_token zone=oidc_refresh_tokens;
+
+ # IdP 端点配置
+ map $host $oidc_authz_endpoint {
+ default "https://keycloak.example.com/realms/myrealm/protocol/openid-connect/auth";
+ }
+
+ map $host $oidc_token_endpoint {
+ default "https://keycloak.example.com/realms/myrealm/protocol/openid-connect/token";
+ }
+
+ map $host $oidc_jwks_uri {
+ default "https://keycloak.example.com/realms/myrealm/protocol/openid-connect/certs";
+ }
+
+ map $host $oidc_userinfo_endpoint {
+ default "https://keycloak.example.com/realms/myrealm/protocol/openid-connect/userinfo";
+ }
+
+ map $host $oidc_end_session_endpoint {
+ default "https://keycloak.example.com/realms/myrealm/protocol/openid-connect/logout";
+ }
+
+ # Client 配置
+ map $host $oidc_client {
+ default "my-nginx-client";
+ }
+
+ map $host $oidc_client_secret {
+ default "your-client-secret-here";
+ }
+
+ map $host $oidc_scopes {
+ default "openid+profile+email";
+ }
+
+ map $host $oidc_pkce_enable {
+ default "1";
+ }
+
+ # 导入 OIDC 脚本
+ js_import /etc/nginx/conf.d/openid_connect.js;
+
+ # 缓存配置
+ proxy_cache_path /var/cache/nginx/jwks levels=1:2 keys_zone=jwks_cache:1m max_size=10m inactive=60m use_temp_path=off;
+
+ server {
+ listen 443 ssl;
+ server_name app.example.com;
+
+ ssl_certificate /etc/nginx/ssl/app.example.com.crt;
+ ssl_certificate_key /etc/nginx/ssl/app.example.com.key;
+
+ # 会话 Cookie 配置
+ set $session_cookie "auth_token";
+ set $session_cookie_flags "Path=/; Secure; HttpOnly; SameSite=Strict";
+
+ location / {
+ # JWT 认证
+ auth_jwt "Keycloak SSO" token=$cookie_auth_token;
+ auth_jwt_key_request /_jwks_uri;
+
+ # 提取用户信息
+ auth_jwt_set $jwt_sub sub;
+ auth_jwt_set $jwt_email email;
+
+ proxy_pass http://backend;
+ proxy_set_header Host $host;
+ proxy_set_header X-User-ID $jwt_sub;
+ proxy_set_header X-User-Email $jwt_email;
+ proxy_set_header Authorization "Bearer $access_token";
+ }
+
+ # OIDC 端点
+ location /login {
+ js_content openid_connect.login;
+ }
+
+ location /logout {
+ js_content openid_connect.logout;
+ }
+
+ location = /redirect_uri {
+ js_content openid_connect.redirect;
+ }
+
+ # 内部 JWKS 端点
+ location = /_jwks_uri {
+ internal;
+ proxy_pass $oidc_jwks_uri;
+ proxy_cache jwks_cache;
+ proxy_cache_valid 200 1h;
+ proxy_cache_use_stale error timeout updating;
+ proxy_ssl_server_name on;
+ }
+ }
+}
+```
+
+### 5.2 Okta 集成
+
+**Okta 配置要点**:
+- 创建 App Integration,选择 `OIDC - OpenID Connect`
+- Application type: `Web Application`
+- Sign-in redirect URIs: `https://app.example.com/redirect_uri`
+- Sign-out redirect URIs: `https://app.example.com/`
+- Grant type: `Authorization Code` + `Refresh Token`
+
+**NGINX 配置**:
+
+```nginx
+http {
+ load_module modules/ngx_http_js_module.so;
+
+ # 会话存储
+ keyval_zone zone=oidc_id_tokens:1M state=/var/lib/nginx/state/oidc_id_tokens.json timeout=1h;
+ keyval_zone zone=oidc_access_tokens:1M state=/var/lib/nginx/state/oidc_access_tokens.json timeout=1h;
+ keyval_zone zone=oidc_refresh_tokens:1M state=/var/lib/nginx/state/oidc_refresh_tokens.json timeout=8h;
+
+ keyval $cookie_auth_token $session_jwt zone=oidc_id_tokens;
+ keyval $cookie_auth_token $access_token zone=oidc_access_tokens;
+ keyval $cookie_auth_token $refresh_token zone=oidc_refresh_tokens;
+
+ # Okta 端点配置
+ map $host $oidc_authz_endpoint {
+ default "https://myorg.okta.com/oauth2/default/v1/authorize";
+ # 或使用自定义授权服务器
+ # default "https://myorg.okta.com/oauth2/ausxxxxxx/v1/authorize";
+ }
+
+ map $host $oidc_token_endpoint {
+ default "https://myorg.okta.com/oauth2/default/v1/token";
+ }
+
+ map $host $oidc_jwks_uri {
+ default "https://myorg.okta.com/oauth2/default/v1/keys";
+ }
+
+ map $host $oidc_userinfo_endpoint {
+ default "https://myorg.okta.com/oauth2/default/v1/userinfo";
+ }
+
+ map $host $oidc_end_session_endpoint {
+ default "https://myorg.okta.com/oauth2/default/v1/logout";
+ }
+
+ map $host $oidc_client {
+ default "0oaxxxxxxxxxxx";
+ }
+
+ map $host $oidc_client_secret {
+ default "your-client-secret";
+ }
+
+ map $host $oidc_scopes {
+ default "openid+profile+email+groups";
+ }
+
+ map $host $oidc_pkce_enable {
+ default "1";
+ }
+
+ js_import /etc/nginx/conf.d/openid_connect.js;
+
+ proxy_cache_path /var/cache/nginx/jwks levels=1:2 keys_zone=jwks_cache:1m max_size=10m inactive=60m;
+
+ server {
+ listen 443 ssl;
+ server_name app.example.com;
+
+ ssl_certificate /etc/nginx/ssl/app.example.com.crt;
+ ssl_certificate_key /etc/nginx/ssl/app.example.com.key;
+
+ location / {
+ auth_jwt "Okta SSO" token=$cookie_auth_token;
+ auth_jwt_key_request /_jwks_uri;
+
+ auth_jwt_set $jwt_sub sub;
+ auth_jwt_set $jwt_email email;
+ auth_jwt_set $jwt_groups groups;
+
+ proxy_pass http://backend;
+ proxy_set_header X-User-ID $jwt_sub;
+ proxy_set_header X-User-Email $jwt_email;
+ proxy_set_header X-User-Groups $jwt_groups;
+ proxy_set_header Authorization "Bearer $access_token";
+ }
+
+ location /login {
+ js_content openid_connect.login;
+ }
+
+ location /logout {
+ js_content openid_connect.logout;
+ }
+
+ location = /redirect_uri {
+ js_content openid_connect.redirect;
+ }
+
+ location = /_jwks_uri {
+ internal;
+ proxy_pass $oidc_jwks_uri;
+ proxy_cache jwks_cache;
+ proxy_cache_valid 200 1h;
+ proxy_ssl_server_name on;
+ }
+ }
+}
+```
+
+### 5.3 Auth0 集成
+
+**Auth0 配置要点**:
+- 创建 Application,类型选择 `Regular Web Application`
+- Allowed Callback URLs: `https://app.example.com/redirect_uri`
+- Allowed Logout URLs: `https://app.example.com/`
+- Allowed Web Origins: `https://app.example.com`
+- 启用 `Refresh Token` 轮换(可选)
+
+**NGINX 配置**:
+
+```nginx
+http {
+ load_module modules/ngx_http_js_module.so;
+
+ # 会话存储
+ keyval_zone zone=oidc_id_tokens:1M state=/var/lib/nginx/state/oidc_id_tokens.json timeout=1h;
+ keyval_zone zone=oidc_access_tokens:1M state=/var/lib/nginx/state/oidc_access_tokens.json timeout=1h;
+ keyval_zone zone=oidc_refresh_tokens:1M state=/var/lib/nginx/state/oidc_refresh_tokens.json timeout=8h;
+
+ keyval $cookie_auth_token $session_jwt zone=oidc_id_tokens;
+ keyval $cookie_auth_token $access_token zone=oidc_access_tokens;
+ keyval $cookie_auth_token $refresh_token zone=oidc_refresh_tokens;
+
+ # Auth0 端点配置
+ map $host $oidc_authz_endpoint {
+ default "https://myapp.auth0.com/authorize";
+ }
+
+ map $host $oidc_token_endpoint {
+ default "https://myapp.auth0.com/oauth/token";
+ }
+
+ map $host $oidc_jwks_uri {
+ default "https://myapp.auth0.com/.well-known/jwks.json";
+ }
+
+ map $host $oidc_userinfo_endpoint {
+ default "https://myapp.auth0.com/userinfo";
+ }
+
+ map $host $oidc_end_session_endpoint {
+ default "https://myapp.auth0.com/v2/logout";
+ }
+
+ map $host $oidc_client {
+ default "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
+ }
+
+ map $host $oidc_client_secret {
+ default "your-client-secret";
+ }
+
+ map $host $oidc_scopes {
+ default "openid+profile+email";
+ }
+
+ map $host $oidc_pkce_enable {
+ default "1";
+ }
+
+ # Auth0 特定:返回首页参数
+ map $host $oidc_logout_redirect {
+ default "https://app.example.com/";
+ }
+
+ js_import /etc/nginx/conf.d/openid_connect.js;
+
+ proxy_cache_path /var/cache/nginx/jwks levels=1:2 keys_zone=jwks_cache:1m max_size=10m inactive=60m;
+
+ server {
+ listen 443 ssl;
+ server_name app.example.com;
+
+ ssl_certificate /etc/nginx/ssl/app.example.com.crt;
+ ssl_certificate_key /etc/nginx/ssl/app.example.com.key;
+
+ location / {
+ auth_jwt "Auth0 SSO" token=$cookie_auth_token;
+ auth_jwt_key_request /_jwks_uri;
+
+ auth_jwt_set $jwt_sub sub;
+ auth_jwt_set $jwt_email email;
+ auth_jwt_set $jwt_name name;
+ auth_jwt_set $jwt_nickname nickname;
+
+ proxy_pass http://backend;
+ proxy_set_header X-User-ID $jwt_sub;
+ proxy_set_header X-User-Email $jwt_email;
+ proxy_set_header X-User-Name $jwt_name;
+ proxy_set_header Authorization "Bearer $access_token";
+ }
+
+ location /login {
+ js_content openid_connect.login;
+ }
+
+ location /logout {
+ js_content openid_connect.logout;
+ }
+
+ location = /redirect_uri {
+ js_content openid_connect.redirect;
+ }
+
+ location = /_jwks_uri {
+ internal;
+ proxy_pass $oidc_jwks_uri;
+ proxy_cache jwks_cache;
+ proxy_cache_valid 200 1h;
+ proxy_ssl_server_name on;
+ }
+ }
+}
+```
+
+### 5.4 Azure AD / Entra ID 集成
+
+**Azure AD 配置要点**:
+- 在 Azure Portal 注册应用
+- 添加平台配置:Web
+- 重定向 URI: `https://app.example.com/redirect_uri`
+- 启用 `ID tokens` (用于隐式和混合流)
+- 创建 Client Secret
+
+**NGINX 配置**:
+
+```nginx
+http {
+ load_module modules/ngx_http_js_module.so;
+
+ # 会话存储
+ keyval_zone zone=oidc_id_tokens:1M state=/var/lib/nginx/state/oidc_id_tokens.json timeout=1h;
+ keyval_zone zone=oidc_access_tokens:1M state=/var/lib/nginx/state/oidc_access_tokens.json timeout=1h;
+ keyval_zone zone=oidc_refresh_tokens:1M state=/var/lib/nginx/state/oidc_refresh_tokens.json timeout=8h;
+
+ keyval $cookie_auth_token $session_jwt zone=oidc_id_tokens;
+ keyval $cookie_auth_token $access_token zone=oidc_access_tokens;
+ keyval $cookie_auth_token $refresh_token zone=oidc_refresh_tokens;
+
+ # Azure AD 端点配置
+ map $host $oidc_authz_endpoint {
+ default "https://login.microsoftonline.com/{tenant-id}/oauth2/v2.0/authorize";
+ }
+
+ map $host $oidc_token_endpoint {
+ default "https://login.microsoftonline.com/{tenant-id}/oauth2/v2.0/token";
+ }
+
+ map $host $oidc_jwks_uri {
+ default "https://login.microsoftonline.com/{tenant-id}/discovery/v2.0/keys";
+ }
+
+ map $host $oidc_userinfo_endpoint {
+ default "https://graph.microsoft.com/oidc/userinfo";
+ }
+
+ map $host $oidc_end_session_endpoint {
+ default "https://login.microsoftonline.com/{tenant-id}/oauth2/v2.0/logout";
+ }
+
+ map $host $oidc_client {
+ default "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
+ }
+
+ map $host $oidc_client_secret {
+ default "your-client-secret";
+ }
+
+ # Azure AD 使用空格分隔 scopes
+ map $host $oidc_scopes {
+ default "openid+profile+email";
+ }
+
+ map $host $oidc_pkce_enable {
+ default "1";
+ }
+
+ js_import /etc/nginx/conf.d/openid_connect.js;
+
+ proxy_cache_path /var/cache/nginx/jwks levels=1:2 keys_zone=jwks_cache:1m max_size=10m inactive=60m;
+
+ server {
+ listen 443 ssl;
+ server_name app.example.com;
+
+ ssl_certificate /etc/nginx/ssl/app.example.com.crt;
+ ssl_certificate_key /etc/nginx/ssl/app.example.com.key;
+
+ location / {
+ auth_jwt "Azure AD SSO" token=$cookie_auth_token;
+ auth_jwt_key_request /_jwks_uri;
+
+ auth_jwt_set $jwt_sub sub;
+ auth_jwt_set $jwt_email email;
+ auth_jwt_set $jwt_name name;
+ auth_jwt_set $jwt_oid oid; # Azure AD Object ID
+
+ proxy_pass http://backend;
+ proxy_set_header X-User-ID $jwt_sub;
+ proxy_set_header X-User-Email $jwt_email;
+ proxy_set_header X-User-Name $jwt_name;
+ proxy_set_header X-Azure-Object-ID $jwt_oid;
+ proxy_set_header Authorization "Bearer $access_token";
+ }
+
+ location /login {
+ js_content openid_connect.login;
+ }
+
+ location /logout {
+ js_content openid_connect.logout;
+ }
+
+ location = /redirect_uri {
+ js_content openid_connect.redirect;
+ }
+
+ location = /_jwks_uri {
+ internal;
+ proxy_pass $oidc_jwks_uri;
+ proxy_cache jwks_cache;
+ proxy_cache_valid 200 1h;
+ proxy_ssl_server_name on;
+ }
+ }
+}
+```
+
+---
+
+## 6. JWT 验证与 Token 刷新
+
+### 6.1 JWT 验证机制
+
+**验证流程**:
+
+```
+1. 提取 Token ──▶ 2. 解析 Header ──▶ 3. 获取公钥 ──▶ 4. 验证签名
+ │
+ ▼
+5. 验证 Claims ◀── 6. 检查过期 ◀── 4. 验证签名
+```
+
+**关键验证点**:
+- **签名验证**:使用 IdP 的公钥验证 JWT 签名
+- **Issuer 验证**:确认 Token 来自正确的 IdP
+- **Audience 验证**:确认 Token 为此应用签发
+- **过期验证**:检查 `exp` Claim
+- **生效时间**:检查 `nbf` (Not Before) Claim
+
+### 6.2 JWKS 缓存配置
+
+```nginx
+# JWKS 缓存
+proxy_cache_path /var/cache/nginx/jwks levels=1:2 keys_zone=jwks_cache:1m
+ max_size=10m inactive=60m use_temp_path=off;
+
+server {
+ location = /_jwks_uri {
+ internal;
+ proxy_pass $oidc_jwks_uri;
+ proxy_cache jwks_cache;
+ proxy_cache_valid 200 1h;
+ proxy_cache_use_stale error timeout updating;
+ proxy_ssl_server_name on;
+
+ # 连接超时设置
+ proxy_connect_timeout 5s;
+ proxy_send_timeout 5s;
+ proxy_read_timeout 5s;
+ }
+}
+```
+
+### 6.3 Token 刷新机制
+
+**自动刷新流程**:
+
+```javascript
+// openid_connect.js 中的刷新逻辑
+function refreshToken(r) {
+ // 1. 检查 Token 是否即将过期
+ var token = r.variables.session_jwt;
+ var payload = JSON.parse(jwtPayload(token));
+ var exp = payload.exp;
+ var now = Math.floor(Date.now() / 1000);
+
+ // 2. 提前 60 秒刷新
+ if (exp - now < 60) {
+ // 3. 使用 Refresh Token 获取新 Token
+ var refreshToken = r.variables.refresh_token;
+ return exchangeRefreshToken(r, refreshToken);
+ }
+
+ return token;
+}
+```
+
+**配置示例**:
+
+```nginx
+server {
+ location / {
+ # 使用 JavaScript 处理 Token 刷新
+ js_set $validated_token oidc.validateAndRefresh;
+
+ auth_jwt "API" token=$validated_token;
+ auth_jwt_key_request /_jwks_uri;
+
+ proxy_pass http://backend;
+ }
+}
+```
+
+### 6.4 多 IdP 支持
+
+```nginx
+http {
+ # 根据域名选择 IdP
+ map $host $oidc_config {
+ default "keycloak";
+ "app1.example.com" "keycloak";
+ "app2.example.com" "okta";
+ "app3.example.com" "auth0";
+ }
+
+ # Keycloak 配置
+ map $oidc_config $keycloak_authz_endpoint {
+ "keycloak" "https://keycloak.example.com/realms/myrealm/protocol/openid-connect/auth";
+ default "";
+ }
+
+ map $oidc_config $keycloak_token_endpoint {
+ "keycloak" "https://keycloak.example.com/realms/myrealm/protocol/openid-connect/token";
+ default "";
+ }
+
+ # Okta 配置
+ map $oidc_config $okta_authz_endpoint {
+ "okta" "https://myorg.okta.com/oauth2/default/v1/authorize";
+ default "";
+ }
+
+ map $oidc_config $okta_token_endpoint {
+ "okta" "https://myorg.okta.com/oauth2/default/v1/token";
+ default "";
+ }
+
+ # 统一端点变量
+ map $oidc_config $oidc_authz_endpoint {
+ "keycloak" $keycloak_authz_endpoint;
+ "okta" $okta_authz_endpoint;
+ }
+
+ map $oidc_config $oidc_token_endpoint {
+ "keycloak" $keycloak_token_endpoint;
+ "okta" $okta_token_endpoint;
+ }
+}
+```
+
+---
+
+## 7. JavaScript 模块 (njs) 实现
+
+### 7.1 基础 openid_connect.js
+
+```javascript
+// openid_connect.js
+var qs = require('querystring');
+var jwt = require('jwt');
+
+// 生成随机状态码
+function generateState() {
+ return Math.random().toString(36).substring(2, 15) +
+ Math.random().toString(36).substring(2, 15);
+}
+
+// 生成 PKCE code_verifier
+function generateCodeVerifier() {
+ var bytes = [];
+ for (var i = 0; i < 32; i++) {
+ bytes.push(Math.floor(Math.random() * 256));
+ }
+ return bytesToBase64Url(bytes);
+}
+
+// Base64 URL 编码
+function bytesToBase64Url(bytes) {
+ var base64 = '';
+ var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
+ for (var i = 0; i < bytes.length; i += 3) {
+ var b1 = bytes[i];
+ var b2 = bytes[i + 1] || 0;
+ var b3 = bytes[i + 2] || 0;
+ var bitmap = (b1 << 16) | (b2 << 8) | b3;
+ base64 += chars[(bitmap >> 18) & 63];
+ base64 += chars[(bitmap >> 12) & 63];
+ base64 += (i + 1 < bytes.length) ? chars[(bitmap >> 6) & 63] : '=';
+ base64 += (i + 2 < bytes.length) ? chars[bitmap & 63] : '=';
+ }
+ return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
+}
+
+// 登录处理
+function login(r) {
+ var state = generateState();
+ var redirectUri = 'https://' + r.headersIn['Host'] + '/redirect_uri';
+
+ var params = {
+ response_type: 'code',
+ client_id: r.variables.oidc_client,
+ redirect_uri: redirectUri,
+ scope: r.variables.oidc_scopes.replace(/\+/g, ' '),
+ state: state
+ };
+
+ // 启用 PKCE
+ if (r.variables.oidc_pkce_enable === '1') {
+ var codeVerifier = generateCodeVerifier();
+ params.code_challenge = codeVerifier;
+ params.code_challenge_method = 'S256';
+ // 存储 code_verifier 用于后续 token 交换
+ r.variables.code_verifier = codeVerifier;
+ }
+
+ var authUrl = r.variables.oidc_authz_endpoint + '?' + qs.stringify(params);
+ r.return(302, authUrl);
+}
+
+// Token 交换处理
+function redirect(r) {
+ var args = r.args;
+
+ // 检查错误
+ if (args.error) {
+ r.return(500, 'Authentication error: ' + args.error_description);
+ return;
+ }
+
+ var code = args.code;
+ var redirectUri = 'https://' + r.headersIn['Host'] + '/redirect_uri';
+
+ // Token 请求
+ var tokenReq = {
+ method: 'POST',
+ body: qs.stringify({
+ grant_type: 'authorization_code',
+ code: code,
+ redirect_uri: redirectUri,
+ client_id: r.variables.oidc_client,
+ client_secret: r.variables.oidc_client_secret
+ }),
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded'
+ }
+ };
+
+ // 发送 Token 请求
+ r.subrequest('/_token', tokenReq, function(res) {
+ if (res.status !== 200) {
+ r.return(500, 'Token exchange failed');
+ return;
+ }
+
+ var tokenData = JSON.parse(res.responseBody);
+ var sessionId = generateState();
+
+ // 存储 Token
+ r.variables.session_jwt = tokenData.id_token;
+ r.variables.access_token = tokenData.access_token;
+ r.variables.refresh_token = tokenData.refresh_token || '';
+
+ // 设置 Cookie 并重定向
+ r.headersOut['Set-Cookie'] = 'auth_token=' + sessionId + '; ' +
+ r.variables.session_cookie_flags;
+ r.return(302, '/');
+ });
+}
+
+// 登出处理
+function logout(r) {
+ var sessionId = r.variables.cookie_auth_token;
+
+ // 清除存储的 Token
+ if (sessionId) {
+ delete r.variables.session_jwt;
+ delete r.variables.access_token;
+ delete r.variables.refresh_token;
+ }
+
+ // 清除 Cookie
+ r.headersOut['Set-Cookie'] = 'auth_token=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT';
+
+ // 如果配置了 end_session_endpoint,重定向到 IdP 登出
+ if (r.variables.oidc_end_session_endpoint) {
+ var logoutUrl = r.variables.oidc_end_session_endpoint +
+ '?post_logout_redirect_uri=' +
+ encodeURIComponent('https://' + r.headersIn['Host'] + '/');
+ r.return(302, logoutUrl);
+ } else {
+ r.return(302, '/');
+ }
+}
+
+// Token 验证和刷新
+function validateAndRefresh(r) {
+ var token = r.variables.session_jwt;
+ if (!token) {
+ return '';
+ }
+
+ try {
+ var payload = jwt.decode(token).payload;
+ var exp = payload.exp;
+ var now = Math.floor(Date.now() / 1000);
+
+ // Token 即将过期,尝试刷新
+ if (exp - now < 60 && r.variables.refresh_token) {
+ // 异步刷新 Token
+ refreshAccessToken(r);
+ }
+
+ return token;
+ } catch (e) {
+ return '';
+ }
+}
+
+// 导出函数
+export default { login, logout, redirect, validateAndRefresh };
+```
+
+### 7.2 高级功能扩展
+
+```javascript
+// 前端频道登出处理
+function frontChannelLogout(r) {
+ var sid = r.args.sid;
+ if (sid) {
+ // 根据 sid 查找并清除会话
+ clearSessionBySid(r, sid);
+ }
+ r.return(200, 'OK');
+}
+
+// 用户信息服务
+function userInfo(r) {
+ var token = r.variables.access_token;
+ if (!token) {
+ r.return(401, 'Unauthorized');
+ return;
+ }
+
+ r.subrequest('/_userinfo', {
+ method: 'GET',
+ headers: {
+ 'Authorization': 'Bearer ' + token
+ }
+ }, function(res) {
+ r.headersOut['Content-Type'] = 'application/json';
+ r.return(res.status, res.responseBody);
+ });
+}
+
+// 会话检查
+function checkSession(r) {
+ var token = r.variables.session_jwt;
+ if (!token) {
+ r.return(401, JSON.stringify({ active: false }));
+ return;
+ }
+
+ try {
+ var payload = jwt.decode(token).payload;
+ r.return(200, JSON.stringify({
+ active: true,
+ sub: payload.sub,
+ exp: payload.exp
+ }));
+ } catch (e) {
+ r.return(401, JSON.stringify({ active: false }));
+ }
+}
+```
+
+---
+
+## 8. 与 Lolly 项目的关系和建议
+
+### 8.1 Lolly 项目概述
+
+Lolly 是使用 Go 语言编写的高性能 HTTP 服务器与反向代理,与 NGINX 有相似的功能定位:
+
+| 特性 | NGINX Plus | Lolly |
+|------|-----------|-------|
+| **语言** | C | Go |
+| **OIDC 支持** | 内置 (auth_jwt, njs) | 待实现 |
+| **配置** | nginx.conf | YAML |
+| **扩展** | njs / C 模块 | Go 中间件 |
+| **性能** | 极高 (C + 事件驱动) | 高 (Go + fasthttp) |
+| **HTTP/3** | 实验性 | 原生支持 |
+| **热重载** | 支持 | 支持 |
+
+### 8.2 Lolly OIDC 实现建议
+
+基于 NGINX OIDC 模块的设计,建议 Lolly 实现以下功能:
+
+#### 配置结构示例
+
+```yaml
+# Lolly OIDC 配置
+oidc:
+ enabled: true
+ providers:
+ - name: keycloak
+ issuer: "https://keycloak.example.com/realms/myrealm"
+ client_id: "lolly-client"
+ client_secret: "${KEYCLOAK_SECRET}"
+ scopes: ["openid", "profile", "email"]
+ pkce_enabled: true
+ redirect_uri: "https://app.example.com/auth/callback"
+
+ # JWKS 配置
+ jwks:
+ url: "https://keycloak.example.com/realms/myrealm/protocol/openid-connect/certs"
+ cache_ttl: "1h"
+
+ # 会话配置
+ session:
+ cookie_name: "auth_token"
+ cookie_secure: true
+ cookie_http_only: true
+ cookie_same_site: "Strict"
+ ttl: "1h"
+
+ # Token 刷新
+ refresh:
+ enabled: true
+ before_expiry: "5m"
+
+ # 用户信息转发
+ claims_forward:
+ - claim: "sub"
+ header: "X-User-ID"
+ - claim: "email"
+ header: "X-User-Email"
+ - claim: "name"
+ header: "X-User-Name"
+
+server:
+ listen: ":443"
+
+ # 全局 OIDC 保护
+ oidc:
+ provider: keycloak
+
+ routes:
+ # 公开路径(无需认证)
+ - path: "/health"
+ public: true
+ static:
+ response: '{"status":"ok"}'
+
+ # 受保护 API
+ - path: "/api/*"
+ oidc:
+ provider: keycloak
+ require_claims:
+ - claim: "groups"
+ values: ["api-users"]
+ proxy:
+ target: "http://backend:8080"
+
+ # 管理后台(更严格)
+ - path: "/admin/*"
+ oidc:
+ provider: keycloak
+ require_claims:
+ - claim: "groups"
+ values: ["admin"]
+ proxy:
+ target: "http://admin:8080"
+```
+
+#### 建议的实现架构
+
+```
+┌─────────────────────────────────────────────────────────────┐
+│ Lolly Server │
+│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │
+│ │ OIDC Middleware │ │ Session Store │ │ Token Validator │ │
+│ │ │ │ - In-Memory │ │ - JWKS Fetch │ │
+│ │ - Login Handler │ │ - Redis │ │ - JWT Verify │ │
+│ │ - Callback │ │ - Cookie │ │ - Claims Extract│ │
+│ │ - Logout │ │ │ │ │ │
+│ │ - Refresh │ │ │ │ │ │
+│ └──────────────┘ └──────────────┘ └──────────────────┘ │
+└─────────────────────────────────────────────────────────────┘
+```
+
+#### 推荐依赖库
+
+| 功能 | 推荐库 | 说明 |
+|------|--------|------|
+| **OIDC Client** | `coreos/go-oidc` | 官方推荐,功能完整 |
+| **JWT 解析** | `golang-jwt/jwt` | 最流行的 Go JWT 库 |
+| **OAuth2** | `golang.org/x/oauth2` | 标准 OAuth2 实现 |
+| **Session** | `gorilla/sessions` | 成熟的 Session 管理 |
+
+#### 实现优先级建议
+
+1. **Phase 1 - 基础功能**
+ - [ ] JWT 验证中间件
+ - [ ] JWKS 获取和缓存
+ - [ ] 基本 Claims 提取
+
+2. **Phase 2 - 完整认证**
+ - [ ] 授权码流程
+ - [ ] Session Cookie 管理
+ - [ ] 登录/登出端点
+
+3. **Phase 3 - 高级功能**
+ - [ ] Token 自动刷新
+ - [ ] PKCE 支持
+ - [ ] 多 IdP 支持
+
+4. **Phase 4 - 企业特性**
+ - [ ] 前端频道登出
+ - [ ] 用户信息服务
+ - [ ] 细粒度权限控制
+
+### 8.3 迁移建议
+
+从 NGINX Plus OIDC 迁移到 Lolly:
+
+1. **配置转换**:使用工具将 nginx.conf 的 map 转换为 YAML
+2. **IdP 配置复用**:Issuer、Client ID、Secret 保持不变
+3. **后端适配**:验证 Header 名称一致性
+4. **Session 迁移**:逐步迁移,支持双写
+5. **灰度切换**:基于域名或路径逐步切换
+
+---
+
+## 9. 故障排查
+
+### 9.1 常见问题
+
+#### Token 验证失败
+
+```nginx
+# 启用详细日志
+error_log /var/log/nginx/error.log debug;
+
+# 检查 JWKS 是否获取成功
+location = /_jwks_uri {
+ internal;
+ proxy_pass $oidc_jwks_uri;
+
+ # 记录响应
+ add_header X-JWKS-Status $upstream_status always;
+ add_header X-JWKS-Cache $upstream_cache_status always;
+}
+```
+
+#### Cookie 问题
+
+```nginx
+# 确保 Cookie 配置正确
+set $session_cookie "auth_token";
+set $session_cookie_flags "Path=/; Secure; HttpOnly; SameSite=Lax";
+
+# 检查 Cookie 是否设置
+add_header X-Debug-Cookie $cookie_auth_token always;
+```
+
+### 9.2 调试端点
+
+```nginx
+server {
+ # 健康检查端点
+ location /auth/health {
+ auth_jwt off;
+ default_type application/json;
+ return 200 '{"status":"ok","provider":"$oidc_config"}';
+ }
+
+ # Token 信息端点(仅调试)
+ location /auth/debug {
+ auth_jwt "Debug" token=$cookie_auth_token;
+ auth_jwt_key_request /_jwks_uri;
+
+ auth_jwt_set $jwt_sub sub;
+ auth_jwt_set $jwt_exp exp;
+
+ default_type application/json;
+ return 200 '{
+ "sub": "$jwt_sub",
+ "exp": "$jwt_exp",
+ "provider": "$oidc_config"
+ }';
+ }
+}
+```
+
+### 9.3 日志分析
+
+```bash
+# 查看认证相关日志
+grep "auth_jwt\|oidc\|openid" /var/log/nginx/error.log | tail -100
+
+# 监控 Token 刷新
+awk '/token.*refresh/ {print $0}' /var/log/nginx/access.log
+```
+
+---
+
+## 10. 最佳实践
+
+### 10.1 安全建议
+
+1. **始终启用 HTTPS**
+ ```nginx
+ # 拒绝 HTTP 访问
+ server {
+ listen 80;
+ return 301 https://$host$request_uri;
+ }
+ ```
+
+2. **使用 Secure Cookie**
+ ```nginx
+ set $session_cookie_flags "Path=/; Secure; HttpOnly; SameSite=Strict";
+ ```
+
+3. **启用 PKCE**
+ ```nginx
+ map $host $oidc_pkce_enable {
+ default "1";
+ }
+ ```
+
+4. **定期轮换 Client Secret**
+
+### 10.2 性能优化
+
+1. **JWKS 缓存**
+ ```nginx
+ proxy_cache_valid 200 1h;
+ proxy_cache_use_stale error timeout updating;
+ ```
+
+2. **会话存储优化**
+ ```nginx
+ # 共享内存区域大小根据并发用户数调整
+ keyval_zone zone=oidc_id_tokens:10M timeout=1h;
+ ```
+
+3. **连接池**
+ ```nginx
+ upstream idp_backend {
+ server keycloak.example.com:443;
+ keepalive 32;
+ }
+ ```
+
+### 10.3 高可用配置
+
+```nginx
+# JWKS 多源备份
+upstream jwks_upstream {
+ server keycloak-primary.example.com:443;
+ server keycloak-backup.example.com:443 backup;
+}
+
+location = /_jwks_uri {
+ internal;
+ proxy_pass https://jwks_upstream/realms/myrealm/protocol/openid-connect/certs;
+ proxy_cache jwks_cache;
+ proxy_cache_valid 200 1h;
+}
+```
+
+---
+
+## 参考资料
+
+- [NGINX Plus OIDC Reference Implementation](https://github.com/nginxinc/nginx-openid-connect)
+- [OpenID Connect Core 1.0 Specification](https://openid.net/specs/openid-connect-core-1_0.html)
+- [OAuth 2.0 Authorization Framework](https://tools.ietf.org/html/rfc6749)
+- [JSON Web Token (JWT) Specification](https://tools.ietf.org/html/rfc7519)
+- [Proof Key for Code Exchange (PKCE)](https://tools.ietf.org/html/rfc7636)
+- [Lolly 项目文档](./README.md)
diff --git a/docs/35-nginx-keyval-module.md b/docs/35-nginx-keyval-module.md
new file mode 100644
index 0000000..2af1dc8
--- /dev/null
+++ b/docs/35-nginx-keyval-module.md
@@ -0,0 +1,1080 @@
+# NGINX Keyval 模块详解
+
+本文档详细介绍 NGINX Keyval 模块(动态键值存储)的配置与使用方法,包括 HTTP 和 Stream 两个子模块。
+
+---
+
+## 1. Keyval 模块概述
+
+### 什么是 Keyval 模块
+
+Keyval 模块是 NGINX 提供的**动态键值存储系统**,允许在运行时通过 API 动态管理键值对数据,无需重启 NGINX 即可更新配置逻辑。
+
+### 模块组成
+
+| 模块名称 | 上下文 | 商业版本 | 可用版本 |
+|---------|--------|---------|---------|
+| `ngx_http_keyval_module` | http | 需要 | 1.13.3+ |
+| `ngx_stream_keyval_module` | stream | 需要 | 1.13.7+ |
+
+### 核心特性
+
+1. **动态更新**:通过 API 实时增删改查键值对
+2. **内存存储**:使用共享内存,高性能访问
+3. **持久化支持**:支持状态文件持久化,重启后数据不丢失
+4. **过期机制**:支持键值对自动过期(TTL)
+5. **集群同步**:支持多节点数据同步
+6. **灵活匹配**:支持精确匹配、IP 子网匹配、前缀匹配
+
+---
+
+## 2. HTTP 与 Stream Keyval 模块对比
+
+| 特性 | HTTP Keyval | Stream Keyval |
+|------|-------------|---------------|
+| **上下文** | `http` | `stream` |
+| **模块名** | `ngx_http_keyval_module` | `ngx_stream_keyval_module` |
+| **可用版本** | 1.13.3+ | 1.13.7+ |
+| **键变量** | HTTP 变量(`$arg_*`, `$host`, `$uri` 等) | Stream 变量(`$ssl_server_name`, `$remote_addr` 等) |
+| **使用场景** | HTTP 请求路由、限流白名单、动态配置 | TCP/UDP 四层代理路由、SSL 分流 |
+| **API 管理** | `/api/{version}/http/keyvals/` | `/api/{version}/stream/keyvals/` |
+
+### HTTP Keyval 典型应用
+
+```nginx
+http {
+ # 根据请求参数动态返回值
+ keyval $arg_user $user_data zone=user_db;
+
+ server {
+ location / {
+ # $user_data 的值由 API 动态管理
+ proxy_pass http://backend_$user_data;
+ }
+ }
+}
+```
+
+### Stream Keyval 典型应用
+
+```nginx
+stream {
+ # 根据 SSL SNI 名称路由到不同后端
+ keyval $ssl_server_name $backend zone=ssl_routes;
+
+ server {
+ listen 443 ssl;
+ proxy_pass $backend;
+ ssl_certificate ...;
+ ssl_certificate_key ...;
+ }
+}
+```
+
+---
+
+## 3. HTTP Keyval 模块
+
+### 3.1 指令详解
+
+#### keyval_zone
+
+定义存储键值对数据库的共享内存区域。
+
+| 属性 | 说明 |
+|------|------|
+| **语法** | `keyval_zone zone=name:size [state=file] [timeout=time] [type=string\|ip\|prefix] [sync];` |
+| **默认值** | — |
+| **上下文** | http |
+
+**参数说明**:
+
+| 参数 | 说明 |
+|------|------|
+| `zone=name:size` | 共享内存区域名称和大小(如 `one:32k`) |
+| `state=file` | 状态文件路径,JSON 格式持久化 |
+| `timeout=time` | 键值对过期时间(1.15.0+) |
+| `type` | 匹配类型:`string`(精确,默认)、`ip`(IP/CIDR)、`prefix`(前缀,1.17.5+) |
+| `sync` | 启用集群同步(1.15.0+,需配合 `timeout`) |
+
+**配置示例**:
+
+```nginx
+http {
+ # 基本配置
+ keyval_zone zone=one:32k;
+
+ # 带持久化
+ keyval_zone zone=two:64k state=/var/lib/nginx/state/two.keyval;
+
+ # 带过期时间和同步
+ keyval_zone zone=three:1m state=/var/lib/nginx/state/three.keyval timeout=1h sync;
+
+ # IP 类型(用于 IP 黑白名单)
+ keyval_zone zone=ip_list:256k type=ip;
+
+ # 前缀类型(用于路由前缀匹配)
+ keyval_zone zone=prefix_routes:128k type=prefix;
+}
+```
+
+#### keyval
+
+创建变量,其值通过键值数据库查找获得。
+
+| 属性 | 说明 |
+|------|------|
+| **语法** | `keyval key $variable zone=name;` |
+| **默认值** | — |
+| **上下文** | http |
+
+**参数说明**:
+
+| 参数 | 说明 |
+|------|------|
+| `key` | 查找键,可使用 NGINX 变量(如 `$arg_user`, `$uri`) |
+| `$variable` | 创建的变量名,存储查找到的值 |
+| `zone=name` | 指定查询的共享内存区域 |
+
+**配置示例**:
+
+```nginx
+http {
+ keyval_zone zone=users:64k state=/var/lib/nginx/state/users.keyval;
+
+ # 根据 URL 参数查找
+ keyval $arg_user_id $user_role zone=users;
+
+ # 根据 Host 查找
+ keyval $host $backend_pool zone=backend_map;
+
+ # 根据 URI 查找
+ keyval $uri $cache_ttl zone=ttl_config;
+
+ server {
+ listen 80;
+
+ location / {
+ # 使用查找到的值
+ proxy_pass http://$user_role;
+ }
+ }
+}
+```
+
+### 3.2 匹配类型详解
+
+#### string(精确匹配,默认)
+
+```nginx
+http {
+ keyval_zone zone=exact:32k type=string;
+ keyval $arg_key $value zone=exact;
+}
+```
+
+- 查找键必须与存储键完全相同
+- 适用于:用户角色映射、配置项查找
+
+#### ip(IP/CIDR 匹配,1.17.1+)
+
+```nginx
+http {
+ keyval_zone zone=ip_allow:256k type=ip;
+ keyval $remote_addr $allowed zone=ip_allow;
+
+ server {
+ listen 80;
+
+ location /api {
+ if ($allowed = "") {
+ return 403 "Access denied";
+ }
+ proxy_pass http://api_backend;
+ }
+ }
+}
+```
+
+- 存储键可以是 IPv4/IPv6 地址或 CIDR
+- 查找时匹配包含该地址的子网
+- 适用于:IP 黑白名单、地理位置限流
+
+**IP 类型数据示例**:
+
+```json
+{
+ "192.168.1.0/24": "allow",
+ "10.0.0.0/8": "allow",
+ "192.168.1.100": "admin"
+}
+```
+
+#### prefix(前缀匹配,1.17.5+)
+
+```nginx
+http {
+ keyval_zone zone=routes:64k type=prefix;
+ keyval $uri $handler zone=routes;
+
+ server {
+ location / {
+ # /api/v1/users 匹配键 /api/v1
+ proxy_pass http://$handler;
+ }
+ }
+}
+```
+
+- 存储键必须是查找键的前缀
+- 适用于:路由前缀匹配、版本控制
+
+**前缀类型数据示例**:
+
+```json
+{
+ "/api/v1": "backend_v1",
+ "/api/v2": "backend_v2",
+ "/admin": "admin_backend"
+}
+```
+
+---
+
+## 4. Stream Keyval 模块
+
+### 4.1 指令详解
+
+#### keyval_zone(Stream)
+
+| 属性 | 说明 |
+|------|------|
+| **语法** | `keyval_zone zone=name:size [state=file] [timeout=time] [type=string\|ip\|prefix] [sync];` |
+| **默认值** | — |
+| **上下文** | stream |
+
+与 HTTP 版本的语法完全相同,仅在 `stream` 上下文中使用。
+
+#### keyval(Stream)
+
+| 属性 | 说明 |
+|------|------|
+| **语法** | `keyval key $variable zone=name;` |
+| **默认值** | — |
+| **上下文** | stream |
+
+### 4.2 配置示例
+
+#### SSL SNI 路由
+
+```nginx
+stream {
+ # 根据 SSL SNI 路由到不同后端
+ keyval_zone zone=ssl_routes:64k state=/var/lib/nginx/state/ssl_routes.keyval;
+ keyval $ssl_server_name $backend zone=ssl_routes;
+
+ server {
+ listen 443 ssl;
+ proxy_pass $backend;
+
+ ssl_certificate /etc/nginx/certs/default.crt;
+ ssl_certificate_key /etc/nginx/certs/default.key;
+ }
+}
+```
+
+#### 动态 TCP 代理
+
+```nginx
+stream {
+ # 根据目标端口路由
+ keyval_zone zone=port_map:32k type=string;
+ keyval $server_port $upstream zone=port_map;
+
+ server {
+ listen 10000-20000;
+ proxy_pass $upstream;
+ }
+}
+```
+
+#### 客户端 IP 限流
+
+```nginx
+stream {
+ # IP 黑名单
+ keyval_zone zone=blocked_ips:256k type=ip;
+ keyval $remote_addr $is_blocked zone=blocked_ips;
+
+ server {
+ listen 3306;
+
+ # 通过 njs 或 map 实现阻断
+ # 注意:stream 需要配合其他模块实现拒绝逻辑
+ proxy_pass mysql_backend;
+ }
+}
+```
+
+---
+
+## 5. API 管理接口
+
+### 5.1 HTTP Keyvals API
+
+#### 端点概览
+
+| 端点 | 方法 | 功能 |
+|------|------|------|
+| `/api/{version}/http/keyvals/` | GET | 列出所有 HTTP keyval zones |
+| `/api/{version}/http/keyvals/{zone}` | GET | 查询指定 zone 的键值对 |
+| `/api/{version}/http/keyvals/{zone}` | POST | 添加键值对 |
+| `/api/{version}/http/keyvals/{zone}` | PATCH | 修改或删除单个键 |
+| `/api/{version}/http/keyvals/{zone}` | DELETE | 清空整个 zone |
+
+> **注意**:API 版本当前为 `9`
+
+#### 查询键值对
+
+```bash
+# 获取所有键值对
+curl http://localhost:8080/api/9/http/keyvals/one
+
+# 获取特定键
+curl http://localhost:8080/api/9/http/keyvals/one?key=user1
+```
+
+**响应示例**:
+
+```json
+{
+ "user1": "backend_a",
+ "user2": "backend_b",
+ "user3": {
+ "value": "backend_c",
+ "expire": 1699123456789
+ }
+}
+```
+
+#### 添加键值对(POST)
+
+```bash
+# 添加单个键值对
+curl -X POST http://localhost:8080/api/9/http/keyvals/one \
+ -H "Content-Type: application/json" \
+ -d '{"user4": "backend_d"}'
+
+# 添加带过期时间的键值对(毫秒)
+curl -X POST http://localhost:8080/api/9/http/keyvals/one \
+ -H "Content-Type: application/json" \
+ -d '{
+ "user5": {
+ "value": "backend_e",
+ "expire": 3600000
+ }
+ }'
+```
+
+**状态码**:
+- `201` - 创建成功
+- `409` - 键已存在
+- `400` - 格式错误
+- `413` - 请求体过大(超过 `client_body_buffer_size`)
+
+#### 修改/删除键值对(PATCH)
+
+```bash
+# 修改键值
+curl -X PATCH http://localhost:8080/api/9/http/keyvals/one \
+ -H "Content-Type: application/json" \
+ -d '{"user1": {"value": "new_backend", "expire": 7200000}}'
+
+# 删除键(设置为 null)
+curl -X PATCH http://localhost:8080/api/9/http/keyvals/one \
+ -H "Content-Type: application/json" \
+ -d '{"user1": null}'
+```
+
+**注意**:PATCH 一次只能更新一个键
+
+#### 清空 Zone(DELETE)
+
+```bash
+# 删除 zone 中所有键值对
+curl -X DELETE http://localhost:8080/api/9/http/keyvals/one
+```
+
+### 5.2 Stream Keyvals API
+
+Stream keyval 使用相同的 API 格式,只是端点路径不同:
+
+| 端点 | 方法 | 功能 |
+|------|------|------|
+| `/api/{version}/stream/keyvals/` | GET | 列出所有 Stream keyval zones |
+| `/api/{version}/stream/keyvals/{zone}` | GET | 查询指定 zone |
+| `/api/{version}/stream/keyvals/{zone}` | POST | 添加键值对 |
+| `/api/{version}/stream/keyvals/{zone}` | PATCH | 修改或删除键 |
+| `/api/{version}/stream/keyvals/{zone}` | DELETE | 清空 zone |
+
+**使用示例**:
+
+```bash
+# 添加 SSL 路由
+curl -X POST http://localhost:8080/api/9/stream/keyvals/ssl_routes \
+ -H "Content-Type: application/json" \
+ -d '{"api.example.com": "192.168.1.10:8443"}'
+
+# 查询
+curl http://localhost:8080/api/9/stream/keyvals/ssl_routes
+```
+
+### 5.3 完整 API 配置示例
+
+```nginx
+http {
+ # 定义 keyval zones
+ keyval_zone zone=users:64k state=/var/lib/nginx/state/users.keyval timeout=1h;
+ keyval_zone zone=routes:32k state=/var/lib/nginx/state/routes.keyval;
+ keyval_zone zone=ip_blacklist:256k type=ip;
+
+ # 使用 keyval
+ keyval $arg_user $user_role zone=users;
+ keyval $uri $backend zone=routes;
+ keyval $remote_addr $blocked zone=ip_blacklist;
+
+ # API 服务配置
+ server {
+ listen 8080;
+ server_name localhost;
+
+ location /api {
+ # 启用 API 写权限
+ api write=on;
+
+ # 安全限制
+ allow 127.0.0.1;
+ allow 10.0.0.0/8;
+ deny all;
+ }
+ }
+
+ # 业务服务
+ server {
+ listen 80;
+ server_name app.example.com;
+
+ location / {
+ # IP 黑名单检查
+ if ($blocked = "blocked") {
+ return 403 "IP blocked";
+ }
+
+ # 动态路由
+ proxy_pass http://$backend;
+ }
+ }
+}
+
+stream {
+ keyval_zone zone=ssl_routes:64k state=/var/lib/nginx/state/ssl_routes.keyval;
+ keyval $ssl_server_name $backend zone=ssl_routes;
+
+ server {
+ listen 443 ssl;
+ proxy_pass $backend;
+
+ ssl_certificate /etc/nginx/certs/default.crt;
+ ssl_certificate_key /etc/nginx/certs/default.key;
+ }
+}
+```
+
+---
+
+## 6. 使用场景详解
+
+### 6.1 动态 IP 黑名单
+
+```nginx
+http {
+ # IP 类型 zone,支持 CIDR
+ keyval_zone zone=blacklist:1m type=ip state=/var/lib/nginx/state/blacklist.keyval;
+ keyval $remote_addr $blocked zone=blacklist;
+
+ server {
+ listen 80;
+
+ location / {
+ # 检查是否在黑名单
+ if ($blocked = "blocked") {
+ return 403 "Access denied";
+ }
+
+ proxy_pass http://backend;
+ }
+ }
+}
+```
+
+**API 操作**:
+
+```bash
+# 封禁单个 IP
+curl -X POST http://localhost:8080/api/9/http/keyvals/blacklist \
+ -d '{"192.168.1.100": "blocked"}'
+
+# 封禁 IP 段
+curl -X POST http://localhost:8080/api/9/http/keyvals/blacklist \
+ -d '{"10.0.0.0/8": "blocked"}'
+
+# 解封
+curl -X PATCH http://localhost:8080/api/9/http/keyvals/blacklist \
+ -d '{"192.168.1.100": null}'
+```
+
+### 6.2 限流白名单
+
+```nginx
+http {
+ # 限流白名单(这些 IP 不限流)
+ keyval_zone zone=rate_whitelist:256k type=ip;
+ keyval $remote_addr $rate_whitelisted zone=rate_whitelist;
+
+ # 定义限流区域
+ limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
+
+ server {
+ listen 80;
+
+ location /api {
+ # 白名单跳过限流
+ if ($rate_whitelisted = "whitelisted") {
+ proxy_pass http://api_backend;
+ break;
+ }
+
+ limit_req zone=api_limit burst=20 nodelay;
+ proxy_pass http://api_backend;
+ }
+ }
+}
+```
+
+### 6.3 动态路由映射
+
+```nginx
+http {
+ keyval_zone zone=routes:64k type=prefix state=/var/lib/nginx/state/routes.keyval;
+ keyval $uri $backend zone=routes;
+
+ upstream backend_a {
+ server 192.168.1.10:8080;
+ }
+
+ upstream backend_b {
+ server 192.168.1.11:8080;
+ }
+
+ server {
+ listen 80;
+
+ location / {
+ # 默认后端
+ if ($backend = "") {
+ set $backend "backend_a";
+ }
+
+ proxy_pass http://$backend;
+ }
+ }
+}
+```
+
+**API 操作**:
+
+```bash
+# 设置路由规则
+curl -X POST http://localhost:8080/api/9/http/keyvals/routes \
+ -d '{
+ "/api/v1": "backend_a",
+ "/api/v2": "backend_b",
+ "/admin": "backend_a"
+ }'
+```
+
+### 6.4 A/B 测试动态分配
+
+```nginx
+http {
+ keyval_zone zone=ab_test:32k state=/var/lib/nginx/state/ab_test.keyval;
+ keyval $cookie_userid $ab_group zone=ab_test;
+
+ upstream version_a {
+ server 192.168.1.10:8080;
+ }
+
+ upstream version_b {
+ server 192.168.1.11:8080;
+ }
+
+ server {
+ listen 80;
+
+ location / {
+ # 未分配或指定为 a 的用户
+ if ($ab_group = "a") {
+ proxy_pass http://version_a;
+ break;
+ }
+
+ # 指定为 b 的用户
+ if ($ab_group = "b") {
+ proxy_pass http://version_b;
+ break;
+ }
+
+ # 新用户默认走版本 A
+ proxy_pass http://version_a;
+ }
+ }
+}
+```
+
+### 6.5 SSL 证书动态路由(Stream)
+
+```nginx
+stream {
+ # 域名到后端的映射
+ keyval_zone zone=ssl_routes:64k state=/var/lib/nginx/state/ssl_routes.keyval;
+ keyval $ssl_server_name $backend zone=ssl_routes;
+
+ server {
+ listen 443 ssl;
+
+ # 默认证书
+ ssl_certificate /etc/nginx/certs/default.crt;
+ ssl_certificate_key /etc/nginx/certs/default.key;
+
+ proxy_pass $backend;
+ }
+}
+
+http {
+ # API 管理 Stream keyval
+ server {
+ listen 8080;
+
+ location /api {
+ api write=on;
+ }
+ }
+}
+```
+
+---
+
+## 7. 与 Lolly 项目的关系和建议
+
+### 7.1 项目对比
+
+[Lolly](https://github.com/xfy/lolly) 是一个使用 Go 语言编写的高性能 HTTP 服务器与反向代理。与 NGINX Keyval 模块相比:
+
+| 特性 | NGINX Keyval | Lolly |
+|------|--------------|-------|
+| **动态配置** | 通过 API 实时更新 | 通过配置热重载(HUP 信号) |
+| **键值存储** | 共享内存,API 管理 | 配置文件/YAML |
+| **持久化** | state 文件自动持久化 | 配置文件持久化 |
+| **匹配类型** | string、ip、prefix | 需自定义实现 |
+| **商业限制** | NGINX Plus 专有功能 | 开源免费 |
+| **集群同步** | 内置支持 | 需自行实现 |
+
+### 7.2 Lolly 的动态配置建议
+
+虽然 Lolly 当前不支持类似 Keyval 的动态键值存储,但可以通过以下方式实现类似功能:
+
+#### 方案 1:配置热重载
+
+Lolly 已支持 SIGHUP 信号触发配置重载:
+
+```go
+// 在配置中定义映射表
+proxy:
+ - path: "/api"
+ dynamic_routes:
+ - key: "user1"
+ target: "http://backend1:8080"
+ - key: "user2"
+ target: "http://backend2:8080"
+```
+
+修改配置后执行:
+
+```bash
+kill -HUP
+```
+
+#### 方案 2:自定义中间件实现
+
+可以在 Lolly 中实现类似 Keyval 的动态路由中间件:
+
+```go
+// internal/middleware/keyval/keyval.go
+package keyval
+
+import (
+ "sync"
+ "time"
+)
+
+type KeyvalStore struct {
+ mu sync.RWMutex
+ data map[string]Entry
+ file string
+}
+
+type Entry struct {
+ Value string
+ Expires time.Time
+}
+
+func (s *KeyvalStore) Get(key string) (string, bool) {
+ s.mu.RLock()
+ defer s.mu.RUnlock()
+
+ entry, ok := s.data[key]
+ if !ok {
+ return "", false
+ }
+
+ if !entry.Expires.IsZero() && time.Now().After(entry.Expires) {
+ return "", false
+ }
+
+ return entry.Value, true
+}
+
+func (s *KeyvalStore) Set(key, value string, ttl time.Duration) {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+
+ entry := Entry{Value: value}
+ if ttl > 0 {
+ entry.Expires = time.Now().Add(ttl)
+ }
+
+ s.data[key] = entry
+}
+```
+
+#### 方案 3:外部存储集成
+
+建议 Lolly 集成外部键值存储:
+
+- **Redis**:支持过期、持久化、集群
+- **etcd**:支持监听变更、服务发现
+- **Consul**:服务发现与健康检查
+
+```go
+// Redis 集成示例
+import "github.com/redis/go-redis/v9"
+
+type RedisKeyval struct {
+ client *redis.Client
+}
+
+func (r *RedisKeyval) Get(ctx context.Context, key string) (string, error) {
+ return r.client.Get(ctx, key).Result()
+}
+
+func (r *RedisKeyval) Set(ctx context.Context, key, value string, ttl time.Duration) error {
+ return r.client.Set(ctx, key, value, ttl).Err()
+}
+```
+
+### 7.3 Lolly 潜在增强功能
+
+参考 NGINX Keyval 模块,建议 Lolly 可考虑实现:
+
+1. **内置动态键值 API**
+ - RESTful API 管理键值对
+ - 支持内存存储与持久化
+ - TTL 过期机制
+
+2. **多种匹配类型**
+ - 精确匹配
+ - IP/CIDR 匹配(用于黑白名单)
+ - 前缀匹配(用于路由)
+
+3. **集群同步**
+ - 基于 Raft 或 gossip 协议
+ - 配置变更广播
+
+4. **NGINX Plus 兼容 API**
+ - 兼容 `/api/{version}/http/keyvals/` 接口
+ - 便于迁移现有 NGINX Plus 用户
+
+---
+
+## 8. 最佳实践
+
+### 8.1 内存大小规划
+
+```nginx
+# 估算每个键值对大小
+# key: 平均 50 字节
+# value: 平均 100 字节
+# overhead: 约 50 字节
+# 总计: 约 200 字节/键值对
+
+# 10万键值对约需 20MB
+keyval_zone zone=large:20m;
+
+# 设置合理超时,避免内存无限增长
+keyval_zone zone=with_ttl:32k timeout=1h;
+```
+
+### 8.2 持久化策略
+
+```nginx
+# 重要数据启用持久化
+keyval_zone zone=critical:64k state=/var/lib/nginx/state/critical.keyval;
+
+# 临时数据不持久化
+keyval_zone zone=temp:32k timeout=5m;
+```
+
+### 8.3 安全建议
+
+```nginx
+http {
+ # API 严格访问控制
+ server {
+ listen 8080;
+
+ location /api {
+ api write=on;
+
+ # 只允许特定 IP
+ allow 127.0.0.1;
+ allow 10.0.0.0/8;
+ deny all;
+
+ # 可选:添加基础认证
+ auth_basic "API Access";
+ auth_basic_user_file /etc/nginx/api.htpasswd;
+ }
+ }
+}
+```
+
+### 8.4 监控与日志
+
+```nginx
+http {
+ # 记录 keyval 相关请求
+ log_format keyval '$remote_addr - $time_local '
+ 'keyval_zone=$keyval_zone key=$key val=$value';
+
+ server {
+ location / {
+ # 添加自定义头便于调试
+ add_header X-Keyval-Value $user_role;
+
+ proxy_pass http://backend;
+ }
+ }
+}
+```
+
+### 8.5 集群部署注意事项
+
+启用 `sync` 时的限制:
+
+```nginx
+http {
+ # sync 需要 timeout 参数
+ keyval_zone zone=shared:64k timeout=1h sync;
+
+ # 注意:
+ # 1. DELETE 操作只在目标节点立即生效
+ # 2. 其他节点需等待 timeout 过期
+ # 3. PATCH 删除(设为 null)同样受此限制
+}
+```
+
+---
+
+## 9. 完整配置示例
+
+### 综合应用场景
+
+```nginx
+# nginx.conf
+
+user nginx;
+worker_processes auto;
+
+events {
+ worker_connections 4096;
+}
+
+http {
+ # ========== Keyval Zones ==========
+
+ # 用户角色映射(持久化)
+ keyval_zone zone=user_roles:64k state=/var/lib/nginx/state/user_roles.keyval;
+ keyval $arg_user $user_role zone=user_roles;
+
+ # IP 黑名单(IP 类型,支持 CIDR)
+ keyval_zone zone=ip_blacklist:1m type=ip state=/var/lib/nginx/state/blacklist.keyval;
+ keyval $remote_addr $blocked zone=ip_blacklist;
+
+ # 限流白名单
+ keyval_zone zone=rate_whitelist:512k type=ip;
+ keyval $remote_addr $whitelisted zone=rate_whitelist;
+
+ # 路由映射(前缀匹配)
+ keyval_zone zone=routes:128k type=prefix state=/var/lib/nginx/state/routes.keyval;
+ keyval $uri $backend zone=routes;
+
+ # 临时会话(5分钟过期)
+ keyval_zone zone=sessions:32k timeout=5m;
+ keyval $cookie_session $session_data zone=sessions;
+
+ # ========== Rate Limit ==========
+ limit_req_zone $binary_remote_addr zone=general:10m rate=100r/s;
+
+ # ========== Upstreams ==========
+ upstream backend_admin {
+ server 192.168.1.10:8080;
+ }
+
+ upstream backend_api {
+ server 192.168.1.11:8080;
+ server 192.168.1.12:8080;
+ }
+
+ upstream backend_default {
+ server 192.168.1.20:8080;
+ }
+
+ # ========== API Server ==========
+ server {
+ listen 127.0.0.1:8080;
+
+ location /api {
+ api write=on;
+ allow 127.0.0.1;
+ allow 10.0.0.0/8;
+ deny all;
+ }
+ }
+
+ # ========== Main Server ==========
+ server {
+ listen 80;
+ server_name app.example.com;
+
+ # 安全头
+ add_header X-Frame-Options "SAMEORIGIN" always;
+ add_header X-Content-Type-Options "nosniff" always;
+
+ location / {
+ # 1. 检查 IP 黑名单
+ if ($blocked = "blocked") {
+ return 403 "Access denied";
+ }
+
+ # 2. 限流(白名单除外)
+ if ($whitelisted = "") {
+ limit_req zone=general burst=200 nodelay;
+ }
+
+ # 3. 根据路由映射选择后端
+ if ($backend = "") {
+ set $backend "default";
+ }
+
+ # 4. 根据用户角色选择后端
+ if ($user_role = "admin") {
+ set $backend "admin";
+ }
+
+ # 5. 代理到对应后端
+ proxy_pass http://backend_$backend;
+
+ # 传递 keyval 信息到后端
+ proxy_set_header X-User-Role $user_role;
+ proxy_set_header X-Session-Data $session_data;
+ }
+ }
+}
+
+stream {
+ # SSL 证书动态路由
+ keyval_zone zone=ssl_routes:64k state=/var/lib/nginx/state/ssl_routes.keyval;
+ keyval $ssl_server_name $backend zone=ssl_routes;
+
+ server {
+ listen 443 ssl;
+
+ ssl_certificate /etc/nginx/certs/default.crt;
+ ssl_certificate_key /etc/nginx/certs/default.key;
+
+ proxy_pass $backend;
+ proxy_ssl on;
+ }
+}
+```
+
+---
+
+## 10. 常见问题
+
+### Q1: Keyval 数据在重启后会丢失吗?
+
+**A**: 如果配置了 `state` 参数,数据会持久化到 JSON 文件,重启后自动加载。未配置 `state` 的数据会丢失。
+
+### Q2: 如何备份 Keyval 数据?
+
+**A**: 直接备份 state 文件即可:
+
+```bash
+cp /var/lib/nginx/state/*.keyval /backup/
+```
+
+### Q3: 集群同步有什么限制?
+
+**A**: 启用 `sync` 后:
+- 添加操作会同步到所有节点
+- 删除操作只在目标节点立即生效,其他节点需等待 `timeout` 过期
+
+### Q4: 可以手动编辑 state 文件吗?
+
+**A**: 不建议。state 文件由 NGINX 自动管理,手动编辑可能导致数据损坏。
+
+### Q5: 与 map 模块有什么区别?
+
+**A**:
+- `map` 是静态配置,重启生效
+- `keyval` 是动态存储,API 实时更新
+
+### Q6: Stream 模块可以使用哪些变量作为 key?
+
+**A**: Stream 上下文的可用变量包括:
+- `$remote_addr` - 客户端地址
+- `$remote_port` - 客户端端口
+- `$server_addr` - 服务器地址
+- `$server_port` - 服务器端口
+- `$ssl_server_name` - SSL SNI 名称
+- `$ssl_session_id` - SSL 会话 ID
+
+---
+
+## 参考链接
+
+- [NGINX HTTP Keyval 模块官方文档](https://nginx.org/en/docs/http/ngx_http_keyval_module.html)
+- [NGINX Stream Keyval 模块官方文档](https://nginx.org/en/docs/stream/ngx_stream_keyval_module.html)
+- [NGINX HTTP API 模块官方文档](https://nginx.org/en/docs/http/ngx_http_api_module.html)
+- [Lolly 项目 GitHub](https://github.com/xfy/lolly)
diff --git a/docs/36-nginx-streaming-media.md b/docs/36-nginx-streaming-media.md
new file mode 100644
index 0000000..e375b76
--- /dev/null
+++ b/docs/36-nginx-streaming-media.md
@@ -0,0 +1,904 @@
+# NGINX 流媒体模块指南
+
+## 1. 模块概述
+
+NGINX 提供多个模块支持 HTTP 流媒体服务,涵盖直播、点播和伪流媒体场景。
+
+### 1.1 模块对比
+
+| 模块 | 协议/格式 | 用途 | 可用性 |
+|------|-----------|------|--------|
+| `ngx_http_hls_module` | HLS (HTTP Live Streaming) | MP4/MOV 文件的 HLS 直播流 | NGINX Plus 商业版 |
+| `ngx_http_flv_module` | FLV (Flash Video) | FLV 文件伪流媒体 | 开源版 |
+| `ngx_http_mp4_module` | MP4 (H.264/AAC) | MP4 文件伪流媒体 | 开源版 |
+| `ngx_http_f4f_module` | F4F/F4M (Adobe HDS) | Adobe HTTP Dynamic Streaming | NGINX Plus 商业版 |
+
+### 1.2 伪流媒体 vs 直播流
+
+**伪流媒体 (Pseudo-Streaming)**:
+- 客户端通过 `start` 参数请求特定时间点
+- 服务器从该时间点开始发送视频数据
+- 适用于点播场景,支持随机 seek
+
+**直播流 (Live Streaming)**:
+- 实时生成媒体片段 (TS/F4F)
+- 动态更新播放列表 (M3U8/F4M)
+- 支持 HLS、HDS 等自适应码率协议
+
+---
+
+## 2. HLS 模块 (ngx_http_hls_module)
+
+为 MP4 和 MOV 媒体文件提供 HTTP Live Streaming (HLS) 服务器端支持。
+
+### 2.1 编译配置
+
+```bash
+# 商业订阅版本已包含此模块
+# 无需额外编译参数
+```
+
+### 2.2 指令详解
+
+#### hls
+
+**语法**:`hls;`
+
+**默认值**:无
+
+**上下文**:`location`
+
+**说明**:在 surrounding location 中开启 HLS 流媒体服务。
+
+```nginx
+location / {
+ hls;
+}
+```
+
+#### hls_fragment
+
+**语法**:`hls_fragment time;`
+
+**默认值**:`hls_fragment 5s;`
+
+**上下文**:`http`, `server`, `location`
+
+**说明**:为未带 `len` 参数请求的播放列表 URI 定义默认片段长度。
+
+```nginx
+hls_fragment 10s; # 每个 TS 片段 10 秒
+```
+
+#### hls_buffers
+
+**语法**:`hls_buffers number size;`
+
+**默认值**:`hls_buffers 8 2m;`
+
+**上下文**:`http`, `server`, `location`
+
+**说明**:设置用于读写数据帧的最大缓冲区数量和大小。
+
+```nginx
+hls_buffers 10 10m; # 10 个缓冲区,每个 10MB
+```
+
+#### hls_forward_args
+
+**语法**:`hls_forward_args on | off;`
+
+**默认值**:`hls_forward_args off;`
+
+**上下文**:`http`, `server`, `location`
+
+**说明**:将播放列表请求中的参数添加到片段 (fragment) 的 URI 中。
+
+**用途**:
+- 客户端授权
+- 配合 `ngx_http_secure_link_module` 保护 HLS 流
+
+```nginx
+hls_forward_args on;
+```
+
+#### hls_mp4_buffer_size
+
+**语法**:`hls_mp4_buffer_size size;`
+
+**默认值**:`hls_mp4_buffer_size 512k;`
+
+**上下文**:`http`, `server`, `location`
+
+**说明**:设置用于处理 MP4 和 MOV 文件的初始缓冲区大小。
+
+```nginx
+hls_mp4_buffer_size 1m;
+```
+
+#### hls_mp4_max_buffer_size
+
+**语法**:`hls_mp4_max_buffer_size size;`
+
+**默认值**:`hls_mp4_max_buffer_size 10m;`
+
+**上下文**:`http`, `server`, `location`
+
+**说明**:在元数据处理期间,缓冲区最大不能超过此值,否则返回 500 错误。
+
+**错误日志**:`"mp4 moov atom is too large"`
+
+```nginx
+hls_mp4_max_buffer_size 5m;
+```
+
+### 2.3 请求参数
+
+HLS 播放列表支持以下 URI 参数:
+
+| 参数 | 说明 | 示例 |
+|------|------|------|
+| `start` | 起始时间(秒) | `?start=1.000` |
+| `end` | 结束时间(秒) | `?end=2.200` |
+| `offset` | 偏移时间(秒) | `?offset=1.000` |
+| `len` | 片段长度(秒) | `?len=10` |
+
+### 2.4 配置示例
+
+#### 基本 HLS 配置
+
+```nginx
+server {
+ listen 80;
+ server_name hls.example.com;
+
+ location / {
+ hls;
+ hls_fragment 5s;
+ hls_buffers 10 10m;
+ hls_mp4_buffer_size 1m;
+ hls_mp4_max_buffer_size 5m;
+ root /var/video/;
+ }
+}
+```
+
+#### 安全链接配置
+
+配合 `hls_forward_args on` 使用 secure_link:
+
+```nginx
+http {
+ # 提取基础 URI(去掉 .m3u8 和 .ts 后缀)
+ map $uri $hls_uri {
+ ~^(?.*)\.m3u8$ $base_uri;
+ ~^(?.*)\.ts$ $base_uri;
+ default $uri;
+ }
+
+ server {
+ listen 80;
+ server_name secure-hls.example.com;
+
+ location /hls/ {
+ hls;
+ hls_forward_args on;
+ alias /var/videos/;
+
+ # 安全链接验证
+ secure_link $arg_md5,$arg_expires;
+ secure_link_md5 "$secure_link_expires$hls_uri$remote_addr secret";
+
+ if ($secure_link = "") { return 403; }
+ if ($secure_link = "0") { return 410; }
+ }
+ }
+}
+```
+
+### 2.5 请求 URI 示例
+
+对于文件 `/var/video/test.mp4`:
+
+| 类型 | URI 示例 |
+|------|----------|
+| 播放列表 | `http://hls.example.com/test.mp4.m3u8?offset=1.000&start=1.000&end=2.200` |
+| 片段 | `http://hls.example.com/test.mp4.ts?start=1.000&end=2.200` |
+
+---
+
+## 3. FLV 模块 (ngx_http_flv_module)
+
+为 Flash Video (FLV) 文件提供伪流媒体服务器端支持。
+
+### 3.1 编译配置
+
+```bash
+# 此模块默认不构建,需要显式启用
+./configure --with-http_flv_module ...
+```
+
+### 3.2 指令详解
+
+#### flv
+
+**语法**:`flv;`
+
+**默认值**:无
+
+**上下文**:`location`
+
+**说明**:在 surrounding location 中开启 FLV 模块处理。
+
+**行为**:
+- 特殊处理包含 `start` 参数的请求
+- 从请求的字节偏移量发送文件内容
+- 自动前置 FLV 头
+
+```nginx
+location ~ \.flv$ {
+ flv;
+}
+```
+
+### 3.3 请求参数
+
+| 参数 | 说明 | 示例 |
+|------|------|------|
+| `start` | 起始字节偏移量 | `?start=1000` |
+
+### 3.4 配置示例
+
+```nginx
+server {
+ listen 80;
+ server_name video.example.com;
+
+ location /videos/ {
+ root /var/www/;
+ }
+
+ location ~ \.flv$ {
+ flv;
+ root /var/www/videos/;
+ }
+}
+```
+
+---
+
+## 4. MP4 模块 (ngx_http_mp4_module)
+
+为 MP4 文件提供服务器端伪流媒体支持,允许通过 `start` 和 `end` 参数进行随机 seek。
+
+### 4.1 编译配置
+
+```bash
+# 此模块默认不构建,需要显式启用
+./configure --with-http_mp4_module ...
+```
+
+### 4.2 指令详解
+
+#### mp4
+
+**语法**:`mp4;`
+
+**默认值**:无
+
+**上下文**:`location`
+
+**说明**:在 surrounding location 中开启 MP4 模块处理。
+
+```nginx
+location /video/ {
+ mp4;
+}
+```
+
+#### mp4_buffer_size
+
+**语法**:`mp4_buffer_size size;`
+
+**默认值**:`mp4_buffer_size 512K;`
+
+**上下文**:`http`, `server`, `location`
+
+**说明**:设置用于处理 MP4 文件的初始缓冲区大小。
+
+```nginx
+mp4_buffer_size 1m;
+```
+
+#### mp4_max_buffer_size
+
+**语法**:`mp4_max_buffer_size size;`
+
+**默认值**:`mp4_max_buffer_size 10M;`
+
+**上下文**:`http`, `server`, `location`
+
+**说明**:元数据处理期间缓冲区的最大大小。若 moov atom 过大,返回 500 错误。
+
+```nginx
+mp4_max_buffer_size 5m;
+```
+
+#### mp4_limit_rate
+
+**语法**:`mp4_limit_rate on | off | factor;`
+
+**默认值**:`mp4_limit_rate off;`
+
+**上下文**:`http`, `server`, `location`
+
+**说明**:基于文件平均比特率限制响应传输速率。
+
+**参数说明**:
+- `on`:限速因子为 1.1
+- `factor`:自定义限速因子
+
+**注意**:此指令仅适用于商业订阅版本。
+
+```nginx
+mp4_limit_rate on; # 因子 1.1
+mp4_limit_rate 1.5; # 自定义因子 1.5
+```
+
+#### mp4_limit_rate_after
+
+**语法**:`mp4_limit_rate_after time;`
+
+**默认值**:`mp4_limit_rate_after 60s;`
+
+**上下文**:`http`, `server`, `location`
+
+**说明**:设置开始限速前的初始媒体数据播放时长。
+
+**注意**:此指令仅适用于商业订阅版本。
+
+```nginx
+mp4_limit_rate_after 30s;
+```
+
+#### mp4_start_key_frame
+
+**语法**:`mp4_start_key_frame on | off;`
+
+**默认值**:`mp4_start_key_frame off;`
+
+**上下文**:`http`, `server`, `location`
+
+**说明**:强制输出视频从关键帧开始。
+
+**行为**:
+- 若 `start` 指定的位置非关键帧,使用 edit list 隐藏初始帧
+- 需要 NGINX 1.21.4+
+- 主流播放器(Chrome、Safari 等)支持 edit list
+
+```nginx
+mp4_start_key_frame on;
+```
+
+### 4.3 请求参数
+
+| 参数 | 说明 | 示例 |
+|------|------|------|
+| `start` | 起始时间(秒) | `?start=238.88` |
+| `end` | 结束时间(秒) | `?end=555.55` |
+
+**组合示例**:`?start=238.88&end=555.55`
+
+### 4.4 配置示例
+
+#### 基本 MP4 配置
+
+```nginx
+server {
+ listen 80;
+ server_name video.example.com;
+
+ location /video/ {
+ mp4;
+ mp4_buffer_size 1m;
+ mp4_max_buffer_size 5m;
+ root /var/videos/;
+ }
+}
+```
+
+#### 高级配置(商业版功能)
+
+```nginx
+server {
+ listen 80;
+ server_name video.example.com;
+
+ location /video/ {
+ mp4;
+ mp4_buffer_size 1m;
+ mp4_max_buffer_size 5m;
+ mp4_limit_rate on;
+ mp4_limit_rate_after 30s;
+ mp4_start_key_frame on;
+ root /var/videos/;
+ }
+}
+```
+
+### 4.5 性能优化建议
+
+**文件结构优化**:
+- 若 moov atom(元数据)位于文件末尾,NGINX 需要读取整个文件
+- 建议使用工具(如 `qt-faststart`)将 moov atom 移到文件开头:
+
+```bash
+# 使用 qt-faststart 优化 MP4 文件
+qt-faststart input.mp4 output.mp4
+```
+
+---
+
+## 5. F4F 模块 (ngx_http_f4f_module)
+
+提供 Adobe HTTP Dynamic Streaming (HDS) 的服务器端支持。
+
+### 5.1 模块说明
+
+**功能**:
+- 处理 `/videoSeg1-Frag1` 形式的请求
+- 使用 `.f4x` 索引文件从 `.f4f` 文件中提取片段
+
+**可用性**:仅作为 NGINX Plus 商业订阅的一部分提供。
+
+### 5.2 指令详解
+
+#### f4f
+
+**语法**:`f4f;`
+
+**默认值**:无
+
+**上下文**:`location`
+
+**说明**:在 surrounding location 中开启 F4F 模块处理。
+
+```nginx
+location /video/ {
+ f4f;
+}
+```
+
+#### f4f_buffer_size
+
+**语法**:`f4f_buffer_size size;`
+
+**默认值**:`f4f_buffer_size 512k;`
+
+**上下文**:`http`, `server`, `location`
+
+**说明**:设置用于读取 `.f4x` 索引文件的缓冲区大小。
+
+```nginx
+f4f_buffer_size 1m;
+```
+
+### 5.3 配置示例
+
+```nginx
+server {
+ listen 80;
+ server_name hds.example.com;
+
+ location /video/ {
+ f4f;
+ f4f_buffer_size 1m;
+ root /var/f4f/;
+ }
+}
+```
+
+---
+
+## 6. 完整配置示例
+
+### 6.1 综合流媒体服务器
+
+```nginx
+# 负载均衡后端(用于回源)
+upstream media_backend {
+ server 192.168.1.10:8080 weight=5;
+ server 192.168.1.11:8080 weight=5;
+ server 192.168.1.12:8080 backup;
+}
+
+# 限速配置
+limit_rate_after 1m;
+limit_rate 1m;
+
+server {
+ listen 80;
+ server_name media.example.com;
+
+ # 日志格式
+ log_format media '$remote_addr - $remote_user [$time_local] '
+ '"$request" $status $bytes_sent '
+ '"$http_referer" "$http_user_agent" '
+ 'time=$request_time';
+
+ access_log /var/log/nginx/media-access.log media;
+
+ # HLS 流媒体(NGINX Plus)
+ location /hls/ {
+ hls;
+ hls_fragment 5s;
+ hls_buffers 10 10m;
+ hls_mp4_buffer_size 1m;
+ hls_mp4_max_buffer_size 5m;
+ alias /var/videos/hls/;
+
+ # 可选:安全链接
+ # hls_forward_args on;
+ # secure_link ...
+ }
+
+ # FLV 伪流媒体
+ location /flv/ {
+ location ~ \.flv$ {
+ flv;
+ alias /var/videos/flv/;
+ }
+ }
+
+ # MP4 伪流媒体
+ location /mp4/ {
+ location ~ \.mp4$ {
+ mp4;
+ mp4_buffer_size 1m;
+ mp4_max_buffer_size 5m;
+ alias /var/videos/mp4/;
+
+ # NGINX Plus 功能
+ # mp4_limit_rate on;
+ # mp4_limit_rate_after 30s;
+ # mp4_start_key_frame on;
+ }
+ }
+
+ # F4F 流媒体(NGINX Plus)
+ location /hds/ {
+ f4f;
+ f4f_buffer_size 1m;
+ alias /var/videos/hds/;
+ }
+
+ # 视频文件通用缓存配置
+ location ~* \.(mp4|flv|f4f|ts|m3u8)$ {
+ expires 1d;
+ add_header Cache-Control "public, immutable";
+
+ # 跨域支持
+ add_header Access-Control-Allow-Origin "*";
+ add_header Access-Control-Allow-Methods "GET, HEAD, OPTIONS";
+ }
+
+ # 播放列表不缓存(实时更新)
+ location ~ \.m3u8$ {
+ expires -1;
+ add_header Cache-Control "no-cache, no-store, must-revalidate";
+ add_header Pragma "no-cache";
+ }
+
+ # 状态监控
+ location /nginx_status {
+ stub_status on;
+ allow 127.0.0.1;
+ allow 10.0.0.0/8;
+ deny all;
+ }
+}
+
+# HTTPS 配置
+server {
+ listen 443 ssl http2;
+ server_name media.example.com;
+
+ ssl_certificate /etc/ssl/certs/media.crt;
+ ssl_certificate_key /etc/ssl/private/media.key;
+ ssl_protocols TLSv1.2 TLSv1.3;
+
+ # 复用 HTTP 配置
+ include /etc/nginx/conf.d/media-locations.conf;
+}
+```
+
+### 6.2 带转码的流媒体配置
+
+```nginx
+# 使用 ngx_rtmp_module(第三方模块)做 RTMP 转 HLS
+rtmp {
+ server {
+ listen 1935;
+
+ application live {
+ live on;
+
+ # 转 HLS
+ hls on;
+ hls_path /var/videos/hls/;
+ hls_fragment 5s;
+ hls_playlist_length 60s;
+
+ # 多码率
+ hls_variant _low BANDWIDTH=500000;
+ hls_variant _mid BANDWIDTH=1500000;
+ hls_variant _high BANDWIDTH=5000000;
+ }
+ }
+}
+
+http {
+ server {
+ listen 80;
+ server_name live.example.com;
+
+ # 服务 HLS 流
+ location /hls/ {
+ types {
+ application/vnd.apple.mpegurl m3u8;
+ video/mp2t ts;
+ }
+
+ alias /var/videos/hls/;
+ add_header Cache-Control "no-cache";
+ add_header Access-Control-Allow-Origin "*";
+ }
+ }
+}
+```
+
+---
+
+## 7. 与 Lolly 项目的关系和建议
+
+### 7.1 当前状态对比
+
+| 特性 | NGINX 流媒体模块 | Lolly 当前状态 |
+|------|------------------|----------------|
+| HLS 支持 | 完整(商业版) | 暂未实现 |
+| FLV 支持 | 完整(开源版) | 暂未实现 |
+| MP4 点播 | 完整(需编译) | 暂未实现 |
+| F4F/HDS | 完整(商业版) | 暂未实现 |
+| 静态文件 | 完整 | 支持 |
+| 文件缓存 | 完整 | 支持 |
+
+### 7.2 实现建议
+
+对于 Lolly 项目,可以考虑以下实现策略:
+
+#### 1. 伪流媒体实现(MP4/FLV)
+
+```go
+// handler/streaming.go
+package handler
+
+import (
+ "github.com/valyala/fasthttp"
+)
+
+// MP4Handler 处理 MP4 伪流媒体请求
+func MP4Handler(ctx *fasthttp.RequestCtx) {
+ start := ctx.QueryArgs().GetFloat64("start")
+ end := ctx.QueryArgs().GetFloat64("end")
+
+ // 解析 MP4 moov atom,计算偏移量
+ // 从指定时间点开始传输
+ // 处理 end 参数截断
+}
+
+// FLVHandler 处理 FLV 伪流媒体请求
+func FLVHandler(ctx *fasthttp.RequestCtx) {
+ start := ctx.QueryArgs().GetInt("start")
+
+ // 发送 FLV 头
+ // 从指定字节偏移开始传输
+}
+```
+
+#### 2. HLS 服务实现
+
+```go
+// handler/hls.go
+package handler
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+ "strings"
+)
+
+// HLSPlaylistHandler 生成 M3U8 播放列表
+func HLSPlaylistHandler(ctx *fasthttp.RequestCtx) {
+ videoPath := getVideoPath(ctx)
+
+ // 解析 MP4,计算片段
+ segments := generateSegments(videoPath, fragmentDuration)
+
+ // 生成 M3U8 内容
+ playlist := generateM3U8(segments)
+
+ ctx.SetContentType("application/vnd.apple.mpegurl")
+ ctx.WriteString(playlist)
+}
+
+// HLSSegmentHandler 返回 TS 片段
+func HLSSegmentHandler(ctx *fasthttp.RequestCtx) {
+ // 从 MP4 提取指定时间范围的 TS 数据
+ // 或使用预生成的 TS 文件
+}
+```
+
+#### 3. 配置扩展示例
+
+```yaml
+# lolly.yaml 流媒体配置示例
+server:
+ # 静态文件服务(已支持)
+ static:
+ - path: "/"
+ root: "/var/www/html"
+
+ # 流媒体服务(建议新增)
+ streaming:
+ # HLS 配置
+ hls:
+ enabled: true
+ path: "/hls/"
+ root: "/var/videos/"
+ fragment: "5s"
+ buffers: 10
+ buffer_size: "10m"
+ mp4_buffer_size: "1m"
+ mp4_max_buffer_size: "5m"
+
+ # MP4 伪流媒体
+ mp4:
+ enabled: true
+ path: "/mp4/"
+ root: "/var/videos/"
+ buffer_size: "1m"
+ max_buffer_size: "5m"
+
+ # FLV 伪流媒体
+ flv:
+ enabled: true
+ path: "/flv/"
+ root: "/var/videos/"
+
+ # 跨域配置
+ cors:
+ enabled: true
+ origins: ["*"]
+ methods: ["GET", "HEAD", "OPTIONS"]
+```
+
+### 7.3 技术实现要点
+
+#### MP4 文件处理
+
+```go
+// 关键:解析 moov atom,计算时间到字节的映射
+type MP4Parser struct {
+ Moov *MoovBox
+}
+
+type MoovBox struct {
+ Tracks []*Track
+}
+
+type Track struct {
+ Timescale uint32
+ Samples []*Sample
+}
+
+// SeekToTime 返回指定时间对应的文件偏移和样本索引
+func (p *MP4Parser) SeekToTime(seconds float64) (offset int64, sampleIdx int) {
+ // 遍历样本表,找到对应时间点的样本
+ // 计算文件偏移
+}
+```
+
+#### HLS 切片生成
+
+```go
+// SegmentInfo 表示一个 TS 片段
+type SegmentInfo struct {
+ Sequence int
+ Duration float64
+ StartTime float64
+ EndTime float64
+}
+
+// GenerateSegments 将 MP4 切分为片段信息
+func GenerateSegments(videoPath string, fragmentDuration float64) ([]SegmentInfo, error) {
+ // 解析 MP4 结构
+ // 按关键帧边界分割片段
+ // 返回片段信息列表
+}
+```
+
+### 7.4 依赖库建议
+
+| 功能 | 推荐库 |
+|------|--------|
+| MP4 解析 | `github.com/abema/go-mp4` 或 `github.com/deepch/mp4` |
+| HLS 生成 | 自行实现或使用 `github.com/grafov/m3u8` |
+| 视频转码 | `github.com/xfrr/goffmpeg` (FFmpeg 绑定) |
+| FLV 解析 | `github.com/yapingcat/gomedia` |
+
+### 7.5 性能优化建议
+
+1. **文件缓存**:复用现有文件缓存系统缓存解析后的 MP4 元数据
+2. **预生成切片**:对于点播内容,预先生成 TS 片段文件
+3. **零拷贝传输**:大视频文件使用 sendfile
+4. **Goroutine 池**:控制并发转码任务数量
+5. **内存复用**:使用 sync.Pool 复用缓冲区
+
+### 7.6 安全建议
+
+1. **路径遍历防护**:验证请求路径,防止访问非视频目录
+2. **限速控制**:对视频流进行带宽限制
+3. **防盗链**:使用 secure_link 或 JWT token 验证
+4. **CORS 配置**:按需配置跨域访问
+
+---
+
+## 8. 常见问题
+
+### Q1: HLS 播放列表不更新?
+
+**A**: 确保播放列表响应头禁用缓存:
+
+```nginx
+location ~ \.m3u8$ {
+ expires -1;
+ add_header Cache-Control "no-cache, no-store, must-revalidate";
+}
+```
+
+### Q2: MP4 seek 不准确?
+
+**A**: 启用 `mp4_start_key_frame on`(NGINX Plus 1.21.4+),或使用 edit list 隐藏非关键帧。
+
+### Q3: FLV 无法 seek?
+
+**A**: FLV 需要播放器支持,确保播放器发送 `start` 参数。
+
+### Q4: 大文件处理缓慢?
+
+**A**: 使用 `qt-faststart` 将 moov atom 移到文件开头:
+
+```bash
+qt-faststart input.mp4 output.mp4
+```
+
+### Q5: 跨域播放失败?
+
+**A**: 添加 CORS 响应头:
+
+```nginx
+add_header Access-Control-Allow-Origin "*";
+add_header Access-Control-Allow-Methods "GET, HEAD, OPTIONS";
+```
+
+---
+
+## 9. 参考资源
+
+- [NGINX HLS Module](https://nginx.org/en/docs/http/ngx_http_hls_module.html)
+- [NGINX FLV Module](https://nginx.org/en/docs/http/ngx_http_flv_module.html)
+- [NGINX MP4 Module](https://nginx.org/en/docs/http/ngx_http_mp4_module.html)
+- [NGINX F4F Module](https://nginx.org/en/docs/http/ngx_http_f4f_module.html)
+- [Apple HLS Specification](https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis)
+- [Adobe HDS Specification](https://www.adobe.com/devnet/hds.html)
diff --git a/docs/37-nginx-dav-module.md b/docs/37-nginx-dav-module.md
new file mode 100644
index 0000000..9b612c6
--- /dev/null
+++ b/docs/37-nginx-dav-module.md
@@ -0,0 +1,687 @@
+# Nginx WebDAV 模块完整指南
+
+## 1. WebDAV 模块概述
+
+### 1.1 模块简介
+
+`ngx_http_dav_module` 是 Nginx 的文件管理自动化模块,通过 WebDAV (Web Distributed Authoring and Versioning) 协议支持远程文件操作。
+
+### 1.2 主要用途
+
+- 文件上传与管理
+- 远程文件编辑
+- 目录结构创建
+- 文件复制与移动
+- 简单的文件服务器
+
+### 1.3 编译要求
+
+该模块**不会默认构建**,需要在编译时显式启用:
+
+```bash
+./configure --with-http_dav_module
+```
+
+### 1.4 支持的 HTTP 方法
+
+| 方法 | 说明 |
+|------|------|
+| `PUT` | 上传/创建文件 |
+| `DELETE` | 删除文件或目录 |
+| `MKCOL` | 创建目录 (Make Collection) |
+| `COPY` | 复制文件或目录 |
+| `MOVE` | 移动文件或目录 |
+
+> **注意**:Nginx WebDAV 模块仅支持上述 5 种方法。需要其他 WebDAV 方法(如 PROPFIND、PROPPATCH、OPTIONS、LOCK、UNLOCK)的客户端将无法与此模块配合工作。
+
+---
+
+## 2. 指令详解
+
+### 2.1 dav_methods
+
+启用指定的 HTTP 方法。
+
+**语法:**
+```nginx
+dav_methods off | PUT | DELETE | MKCOL | COPY | MOVE ...;
+```
+
+**默认值:** `off`
+
+**上下文:** `http`, `server`, `location`
+
+**说明:**
+- `off` - 禁止此模块的所有方法
+- 可以指定一个或多个方法
+- 未列出的方法将被禁止
+
+**示例:**
+```nginx
+# 仅启用 PUT 和 DELETE
+dav_methods PUT DELETE;
+
+# 启用全部支持的方法
+dav_methods PUT DELETE MKCOL COPY MOVE;
+
+# 禁用所有 WebDAV 方法
+dav_methods off;
+```
+
+---
+
+### 2.2 create_full_put_path
+
+允许创建所有必需的中间目录。
+
+**语法:**
+```nginx
+create_full_put_path on | off;
+```
+
+**默认值:** `off`
+
+**上下文:** `http`, `server`, `location`
+
+**说明:**
+- WebDAV 规范通常要求目标目录必须已存在
+- 设置为 `on` 时,PUT 请求可以自动创建路径中的所有中间目录
+- 对于深度嵌套的文件上传非常有用
+
+**示例:**
+```nginx
+# 允许 PUT /files/a/b/c/file.txt 自动创建 a/b/c/ 目录
+create_full_put_path on;
+```
+
+---
+
+### 2.3 dav_access
+
+设置新创建的文件和目录的访问权限。
+
+**语法:**
+```nginx
+dav_access users:permissions ...;
+```
+
+**默认值:** `user:rw`
+
+**上下文:** `http`, `server`, `location`
+
+**权限格式:**
+```
+user:permissions # 文件所有者权限
+group:permissions # 组权限
+all:permissions # 所有用户权限
+```
+
+**权限值:**
+- `r` - 读
+- `w` - 写
+- `x` - 执行(目录为进入)
+
+**示例:**
+```nginx
+# 默认:用户可读写
+dav_access user:rw;
+
+# 用户读写,组读,其他用户只读
+dav_access user:rw group:r all:r;
+
+# 用户完全权限,组读执行,其他无权限
+dav_access user:rwx group:rx all:;
+
+# 多组权限
+dav_access group:rw all:r;
+```
+
+---
+
+### 2.4 min_delete_depth
+
+设置 DELETE 操作允许删除的最小路径深度。
+
+**语法:**
+```nginx
+min_delete_depth number;
+```
+
+**默认值:** `0`
+
+**上下文:** `http`, `server`, `location`
+
+**说明:**
+- 用于防止意外删除重要目录
+- 路径深度计算以 `/` 分隔的元素数量
+- 请求 URI 的元素数量必须 >= 设定值才能执行 DELETE
+
+**示例:**
+```nginx
+# 至少需要 4 层深度才能删除
+min_delete_depth 4;
+
+# 允许删除:DELETE /users/00/00/name (4 层)
+# 禁止删除:DELETE /users/00/00 (3 层)
+```
+
+---
+
+## 3. 配置示例
+
+### 3.1 基础文件共享服务器
+
+```nginx
+server {
+ listen 80;
+ server_name dav.example.com;
+
+ location / {
+ # 根目录
+ root /data/www;
+
+ # 临时文件目录(与根目录同一文件系统以获得最佳性能)
+ client_body_temp_path /data/client_temp;
+
+ # 启用 WebDAV 方法
+ dav_methods PUT DELETE MKCOL COPY MOVE;
+
+ # 允许创建中间目录
+ create_full_put_path on;
+
+ # 设置文件权限
+ dav_access group:rw all:r;
+
+ # 限制写操作的访问
+ limit_except GET {
+ allow 192.168.1.0/24;
+ deny all;
+ }
+ }
+}
+```
+
+### 3.2 只读文件服务器(带选择性上传)
+
+```nginx
+server {
+ listen 80;
+ server_name files.example.com;
+
+ # 公共只读区域
+ location /public/ {
+ root /data/files;
+ dav_methods off; # 禁止写操作
+ autoindex on; # 启用目录列表
+ }
+
+ # 受限上传区域
+ location /uploads/ {
+ root /data/files;
+ dav_methods PUT DELETE;
+ dav_access user:rw;
+
+ # 仅允许特定 IP 上传
+ limit_except GET {
+ allow 10.0.0.0/8;
+ deny all;
+ }
+ }
+}
+```
+
+### 3.3 支持深度目录的上传服务
+
+```nginx
+server {
+ listen 80;
+ server_name upload.example.com;
+
+ location /storage/ {
+ root /data/storage;
+
+ dav_methods PUT DELETE MKCOL;
+ create_full_put_path on; # 自动创建嵌套目录
+ dav_access user:rw group:r;
+
+ # 防止删除顶层目录
+ min_delete_depth 3;
+
+ # 限制请求体大小
+ client_max_body_size 100M;
+
+ # 限制写操作
+ limit_except GET PUT {
+ allow 192.168.0.0/16;
+ deny all;
+ }
+ }
+}
+```
+
+### 3.4 带认证的 WebDAV 服务
+
+```nginx
+server {
+ listen 80;
+ server_name secure-dav.example.com;
+
+ location / {
+ root /data/secure;
+
+ dav_methods PUT DELETE MKCOL COPY MOVE;
+ create_full_put_path on;
+ dav_access user:rw;
+
+ # HTTP 基本认证
+ auth_basic "WebDAV Access";
+ auth_basic_user_file /etc/nginx/.dav_passwd;
+
+ # 限制客户端大小
+ client_max_body_size 500M;
+ }
+}
+```
+
+生成密码文件:
+```bash
+htpasswd -c /etc/nginx/.dav_passwd username
+```
+
+### 3.5 带 SSL 的 WebDAV 服务器
+
+```nginx
+server {
+ listen 80;
+ server_name dav.example.com;
+ return 301 https://$server_name$request_uri;
+}
+
+server {
+ listen 443 ssl;
+ server_name dav.example.com;
+
+ ssl_certificate /etc/nginx/ssl/dav.crt;
+ ssl_certificate_key /etc/nginx/ssl/dav.key;
+ ssl_protocols TLSv1.2 TLSv1.3;
+
+ location / {
+ root /data/www;
+
+ dav_methods PUT DELETE MKCOL COPY MOVE;
+ create_full_put_path on;
+ dav_access user:rw group:r all:r;
+
+ client_max_body_size 1G;
+ }
+}
+```
+
+---
+
+## 4. 客户端使用指南
+
+### 4.1 使用 curl
+
+```bash
+# 上传文件(PUT)
+curl -T localfile.txt http://dav.example.com/remotefile.txt
+
+# 创建目录(MKCOL)
+curl -X MKCOL http://dav.example.com/newdir/
+
+# 删除文件(DELETE)
+curl -X DELETE http://dav.example.com/file.txt
+
+# 复制文件(COPY)
+curl -X COPY -H "Destination: http://dav.example.com/copy.txt" \
+ http://dav.example.com/original.txt
+
+# 移动文件(MOVE)
+curl -X MOVE -H "Destination: http://dav.example.com/moved.txt" \
+ http://dav.example.com/original.txt
+
+# 带认证上传
+curl -T file.txt -u username:password \
+ http://dav.example.com/file.txt
+
+# 查看文件内容(GET)
+curl http://dav.example.com/file.txt
+
+# 列出目录内容(需要客户端支持 PROPFIND)
+curl -X PROPFIND http://dav.example.com/
+```
+
+### 4.2 使用 cadaver(命令行 WebDAV 客户端)
+
+```bash
+# 安装
+# Debian/Ubuntu
+apt-get install cadaver
+# macOS
+brew install cadaver
+
+# 连接
+cadaver http://dav.example.com/
+
+# cadaver 常用命令
+dav:> ls # 列出目录
+dav:> cd directory # 进入目录
+dav:> put file.txt # 上传文件
+dav:> get file.txt # 下载文件
+dav:> mkdir newdir # 创建目录
+dav:> rm file.txt # 删除文件
+dav:> mv old new # 移动/重命名
+dav:> quit # 退出
+```
+
+### 4.3 使用 GNOME Nautilus(文件管理器)
+
+1. 打开 Nautilus 文件管理器
+2. 按 `Ctrl+L` 或选择"其他位置"
+3. 输入地址:`dav://dav.example.com/`
+4. 输入凭据(如需要)
+5. 可像本地文件夹一样操作
+
+### 4.4 挂载为本地文件系统(Linux)
+
+使用 davfs2:
+
+```bash
+# 安装
+apt-get install davfs2
+
+# 挂载
+mount -t davfs http://dav.example.com/ /mnt/webdav
+
+# 或使用 /etc/fstab 自动挂载
+http://dav.example.com/ /mnt/webdav davfs user,noauto 0 0
+```
+
+### 4.5 Windows 资源管理器
+
+1. 打开"此电脑"
+2. 右键 -> "添加网络位置"
+3. 输入:`http://dav.example.com/`
+4. 完成向导
+
+或使用 `net use`:
+```cmd
+net use * http://dav.example.com/
+```
+
+### 4.6 macOS Finder
+
+1. Finder -> "前往" -> "连接服务器"
+2. 输入:`http://dav.example.com/`
+3. 点击"连接"
+
+---
+
+## 5. 与 Lolly 项目的关系
+
+### 5.1 Lolly 项目概述
+
+Lolly 是一个用 Go 编写的高性能 HTTP 服务器/代理工具,提供:
+- 灵活的路由和重写规则
+- 中间件支持
+- 反向代理功能
+- 性能分析工具(pprof)
+
+### 5.2 与 Nginx WebDAV 的对比
+
+| 特性 | Nginx WebDAV | Lolly |
+|------|--------------|-------|
+| 协议支持 | WebDAV (有限) | 标准 HTTP |
+| 配置方式 | 声明式配置 | Go 代码 |
+| 扩展性 | 需重新编译 | 热插拔中间件 |
+| 性能分析 | 第三方模块 | 内置 pprof |
+| 学习曲线 | 低 | 中(需 Go 基础) |
+
+### 5.3 建议与集成方案
+
+#### 方案一:Nginx + Lolly 组合架构
+
+```
+客户端 -> Nginx (WebDAV/静态文件) -> Lolly (动态请求/代理)
+```
+
+```nginx
+server {
+ listen 80;
+ server_name example.com;
+
+ # WebDAV 文件存储
+ location /dav/ {
+ root /data/storage;
+ dav_methods PUT DELETE MKCOL;
+ create_full_put_path on;
+ dav_access user:rw group:r;
+ }
+
+ # 动态请求代理到 Lolly
+ location /api/ {
+ proxy_pass http://localhost:8080;
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ }
+
+ # 静态文件由 Nginx 直接服务
+ location /static/ {
+ root /data/www;
+ expires 30d;
+ }
+}
+```
+
+#### 方案二:Lolly 实现类似 WebDAV 功能
+
+如需在 Lolly 中实现类似 WebDAV 的文件上传功能,可参考以下思路:
+
+```go
+// 概念示例 - 实际实现请参考 Lolly 代码
+router.PUT("/files/*filepath", func(c *gin.Context) {
+ filepath := c.Param("filepath")
+ fullPath := "/data/storage/" + filepath
+
+ // 创建中间目录(类似 create_full_put_path on)
+ os.MkdirAll(path.Dir(fullPath), 0755)
+
+ // 保存上传的文件
+ c.SaveUploadedFile(file, fullPath)
+
+ // 设置权限(类似 dav_access)
+ os.Chmod(fullPath, 0644)
+
+ c.Status(http.StatusCreated)
+})
+
+router.DELETE("/files/*filepath", func(c *gin.Context) {
+ filepath := c.Param("filepath")
+ fullPath := "/data/storage/" + filepath
+
+ // 检查深度(类似 min_delete_depth)
+ depth := len(strings.Split(filepath, "/"))
+ if depth < 4 {
+ c.JSON(http.StatusForbidden, gin.H{"error": "path too shallow"})
+ return
+ }
+
+ os.Remove(fullPath)
+ c.Status(http.StatusNoContent)
+})
+```
+
+#### 方案三:Lolly 作为 WebDAV 后端代理
+
+```nginx
+location /webdav/ {
+ # 认证和限流在 Nginx 层处理
+ auth_basic "WebDAV";
+ auth_basic_user_file /etc/nginx/.passwd;
+
+ # 限流
+ limit_req zone=webdav burst=10;
+
+ # 代理到 Lolly
+ proxy_pass http://localhost:8080;
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header Destination $http_destination;
+}
+```
+
+### 5.4 选择建议
+
+| 场景 | 推荐方案 |
+|------|----------|
+| 简单文件共享 | Nginx WebDAV |
+| 需要复杂业务逻辑 | Lolly |
+| 高性能静态文件 | Nginx |
+| 需要自定义协议扩展 | Lolly |
+| 快速部署 | Nginx WebDAV |
+| 深度集成应用 | Lolly |
+
+---
+
+## 6. 性能优化建议
+
+### 6.1 文件系统建议
+
+**强烈推荐**:临时目录和数据目录使用**同一文件系统**。
+
+```nginx
+location / {
+ root /data/www; # 数据目录
+ client_body_temp_path /data/client_temp; # 临时目录(同文件系统)
+
+ dav_methods PUT DELETE;
+}
+```
+
+**原因:**
+- 同文件系统:文件上传使用 `rename()` 系统调用(原子操作,极快)
+- 不同文件系统:文件上传需要 `copy + delete`(慢,占用双倍空间)
+
+### 6.2 客户端大小限制
+
+```nginx
+# 限制上传文件大小,防止资源耗尽
+client_max_body_size 100M;
+
+# 调整缓冲区
+client_body_buffer_size 128k;
+```
+
+### 6.3 超时设置
+
+```nginx
+# WebDAV 操作可能需要较长时间
+client_body_timeout 300s;
+send_timeout 300s;
+```
+
+---
+
+## 7. 故障排查
+
+### 7.1 常见问题
+
+| 问题 | 可能原因 | 解决方案 |
+|------|----------|----------|
+| 405 Method Not Allowed | 方法未启用 | 检查 `dav_methods` 配置 |
+| 403 Forbidden | 权限不足或 IP 限制 | 检查 `dav_access` 和 `limit_except` |
+| 409 Conflict | 目录不存在 | 设置 `create_full_put_path on` |
+| 423 Locked | 文件被锁定 | Nginx 不支持锁,换客户端 |
+| 500 Internal Error | 路径过深或循环 | 检查 `min_delete_depth` |
+
+### 7.2 启用调试日志
+
+```nginx
+error_log /var/log/nginx/error.log debug;
+```
+
+查看日志:
+```bash
+tail -f /var/log/nginx/error.log | grep dav
+```
+
+### 7.3 验证配置
+
+```bash
+# 测试配置文件语法
+nginx -t
+
+# 重新加载配置
+nginx -s reload
+```
+
+---
+
+## 8. 完整配置模板
+
+```nginx
+# /etc/nginx/conf.d/webdav.conf
+server {
+ listen 80;
+ server_name dav.example.com;
+
+ # 访问日志
+ access_log /var/log/nginx/webdav_access.log;
+ error_log /var/log/nginx/webdav_error.log;
+
+ # 客户端限制
+ client_max_body_size 500M;
+ client_body_buffer_size 128k;
+ client_body_timeout 300s;
+ send_timeout 300s;
+
+ location / {
+ # 根目录
+ root /data/webdav;
+
+ # 临时目录(与根目录同文件系统)
+ client_body_temp_path /data/webdav_tmp;
+
+ # 启用 WebDAV 方法
+ dav_methods PUT DELETE MKCOL COPY MOVE;
+
+ # 允许自动创建中间目录
+ create_full_put_path on;
+
+ # 文件权限设置
+ dav_access user:rw group:r all:r;
+
+ # 防止误删顶层目录
+ min_delete_depth 2;
+
+ # HTTP 基本认证
+ auth_basic "WebDAV Repository";
+ auth_basic_user_file /etc/nginx/.webdav_passwd;
+
+ # 访问控制
+ limit_except GET {
+ # 允许内网
+ allow 192.168.0.0/16;
+ allow 10.0.0.0/8;
+ allow 127.0.0.1;
+ # 拒绝其他
+ deny all;
+ }
+
+ # 目录列表(可选)
+ autoindex on;
+ autoindex_format html;
+ autoindex_localtime on;
+ }
+}
+```
+
+---
+
+## 9. 参考资源
+
+- [Nginx 官方文档 - ngx_http_dav_module](https://nginx.org/en/docs/http/ngx_http_dav_module.html)
+- [WebDAV RFC 4918](https://tools.ietf.org/html/rfc4918)
+- [cadaver 客户端](http://www.webdav.org/cadaver/)
+- [davfs2 - Linux WebDAV 挂载](http://savannah.nongnu.org/projects/davfs2/)
diff --git a/docs/38-nginx-zone-sync-module.md b/docs/38-nginx-zone-sync-module.md
new file mode 100644
index 0000000..c73bcd2
--- /dev/null
+++ b/docs/38-nginx-zone-sync-module.md
@@ -0,0 +1,1502 @@
+# NGINX Zone Sync 模块详解
+
+本文档详细介绍 NGINX Zone Sync 模块(集群区域同步)的配置与使用方法,包括工作原理、指令语法、配置示例和最佳实践。
+
+---
+
+## 1. Zone Sync 模块概述
+
+### 什么是 Zone Sync 模块
+
+Zone Sync 模块(`ngx_stream_zone_sync_module`)是 NGINX Plus 提供的**集群状态同步机制**,用于在多个 NGINX 节点之间同步共享内存区域的内容。
+
+### 核心功能
+
+| 功能 | 说明 |
+|------|------|
+| **多节点共享内存同步** | 在集群节点间自动同步 keyval、sticky sessions、limit_req 等共享内存数据 |
+| **集群状态共享** | 实现分布式 NGINX 实例的状态一致性 |
+| **动态节点发现** | 支持 DNS 动态发现或静态配置节点 |
+| **SSL 加密传输** | 支持节点间通信加密 |
+| **高性能** | 基于 TCP 流的高效同步协议 |
+
+### 模块信息
+
+| 属性 | 说明 |
+|------|------|
+| **模块名称** | `ngx_stream_zone_sync_module` |
+| **首次版本** | 1.13.8 |
+| **可用性** | NGINX Plus 商业订阅 |
+| **上下文** | `stream`, `server` |
+
+### 支持的同步内容
+
+Zone Sync 模块可以同步以下类型的共享内存区域:
+
+1. **HTTP Sticky Sessions**:`sticky learn` 创建的会话粘性数据
+2. **HTTP Limit Request**:`limit_req` 的超额请求计数
+3. **Keyval 键值对**:HTTP 和 Stream 的 `keyval_zone` 数据
+
+---
+
+## 2. Zone Sync 工作原理
+
+### 2.1 同步架构
+
+```
+┌─────────────────┐ ┌─────────────────┐
+│ NGINX Node A │◄───────►│ NGINX Node B │
+│ 192.168.1.10 │ TCP │ 192.168.1.11 │
+│ :12345 │ Sync │ :12345 │
+└─────────────────┘ └─────────────────┘
+ ▲ ▲
+ │ │
+ └───────────┬───────────────┘
+ │
+ ▼
+ ┌─────────────────┐
+ │ NGINX Node C │
+ │ 192.168.1.12 │
+ │ :12345 │
+ └─────────────────┘
+```
+
+### 2.2 同步协议机制
+
+| 机制 | 说明 |
+|------|------|
+| **传输层** | 基于 TCP 流连接,在 `stream` 块中配置 |
+| **轮询机制** | 按 `zone_sync_interval` 间隔轮询共享内存区域更新 |
+| **推送机制** | 使用缓冲区推送区域内容到对等节点 |
+| **双向同步** | 节点间建立双向连接,互相发送和接收更新 |
+
+### 2.3 节点发现方式
+
+#### 静态配置发现
+
+```nginx
+stream {
+ server {
+ zone_sync;
+ listen 127.0.0.1:12345;
+
+ # 静态指定集群节点
+ zone_sync_server 192.168.1.10:12345;
+ zone_sync_server 192.168.1.11:12345;
+ zone_sync_server 192.168.1.12:12345;
+ }
+}
+```
+
+- 需要手动维护节点列表
+- 添加/删除节点需要重新加载配置
+
+#### 动态 DNS 发现
+
+```nginx
+stream {
+ resolver 192.168.1.1 valid=10s;
+
+ server {
+ zone_sync;
+ listen 127.0.0.1:12345;
+
+ # 通过 DNS 动态解析节点
+ zone_sync_server cluster.example.com:12345 resolve;
+ }
+}
+```
+
+- 使用 `resolve` 参数启用 DNS 监控
+- 自动检测 DNS 记录变化
+- 无需重新加载配置即可扩缩容
+
+### 2.4 状态传递流程
+
+1. **变更检测**:每个节点按 `zone_sync_interval` 轮询本地共享内存
+2. **变更缓冲**:将检测到的变更写入同步缓冲区(`zone_sync_buffers`)
+3. **网络传输**:通过 TCP 连接将变更发送给其他节点
+4. **接收解析**:接收节点解析同步消息并更新本地共享内存
+5. **一致性保证**:所有节点最终达到状态一致
+
+### 2.5 节点生命周期管理
+
+| 操作 | 步骤 |
+|------|------|
+| **添加节点** | 1. 更新 DNS(动态)或配置(静态)
2. 启动新 NGINX 实例
3. 自动发现并开始同步 |
+| **移除节点** | 1. 更新 DNS 或配置
2. 发送 `QUIT` 信号优雅关闭
3. 其他节点检测到连接关闭 |
+| **更换节点 IP** | 1. 更新 DNS 记录
2. 其他节点自动检测到变化
3. 重新建立连接 |
+
+---
+
+## 3. 指令详解
+
+### 3.1 zone_sync
+
+**启用共享内存区域同步**。
+
+| 属性 | 说明 |
+|------|------|
+| **语法** | `zone_sync;` |
+| **默认值** | — |
+| **上下文** | `server` |
+| **版本** | 1.13.8+ |
+
+**配置示例**:
+
+```nginx
+stream {
+ server {
+ # 启用 zone 同步
+ zone_sync;
+
+ listen 127.0.0.1:12345;
+
+ zone_sync_server 192.168.1.10:12345;
+ zone_sync_server 192.168.1.11:12345;
+ }
+}
+```
+
+**注意事项**:
+- 必须在 `stream` 块的 `server` 上下文中使用
+- 需要配合 `zone_sync_server` 指定集群节点
+- 每个节点必须有唯一的监听地址
+
+---
+
+### 3.2 zone_sync_server
+
+**定义集群节点地址**。
+
+| 属性 | 说明 |
+|------|------|
+| **语法** | `zone_sync_server address [resolve];` |
+| **默认值** | — |
+| **上下文** | `server` |
+| **版本** | 1.13.8+ |
+
+**参数说明**:
+
+| 参数 | 说明 |
+|------|------|
+| `address` | 节点地址,支持以下格式:
- `IP:port`(如 `192.168.1.10:12345`)
- `域名:port`(如 `nginx-node1.example.com:12345`)
- `unix:/path/to/socket`(Unix 域套接字) |
+| `resolve` | 启用 DNS 监控,域名解析变化时自动更新(需要 `resolver` 指令) |
+
+**配置示例**:
+
+```nginx
+stream {
+ # 静态配置
+ server {
+ zone_sync;
+ listen 127.0.0.1:12345;
+
+ zone_sync_server 192.168.1.10:12345;
+ zone_sync_server 192.168.1.11:12345;
+ }
+}
+```
+
+```nginx
+stream {
+ resolver 192.168.1.1 valid=30s;
+
+ # 动态 DNS 发现
+ server {
+ zone_sync;
+ listen 127.0.0.1:12345;
+
+ # 解析到多个 A 记录
+ zone_sync_server cluster.example.com:12345 resolve;
+ }
+}
+```
+
+**注意事项**:
+- 每个节点在配置中只能出现一次
+- 集群中所有节点应该使用相同的配置
+- 使用 `resolve` 时必须配置 `resolver`
+
+---
+
+### 3.3 zone_sync_connect_timeout
+
+**设置与集群节点建立连接的超时时间**。
+
+| 属性 | 说明 |
+|------|------|
+| **语法** | `zone_sync_connect_timeout time;` |
+| **默认值** | `5s` |
+| **上下文** | `stream`, `server` |
+| **版本** | 1.13.8+ |
+
+**参数说明**:
+
+| 参数 | 说明 |
+|------|------|
+| `time` | 超时时间,支持单位:`ms`, `s`, `m`, `h` |
+
+**配置示例**:
+
+```nginx
+stream {
+ server {
+ zone_sync;
+ listen 127.0.0.1:12345;
+
+ # 连接超时设置为 10 秒
+ zone_sync_connect_timeout 10s;
+
+ zone_sync_server 192.168.1.10:12345;
+ }
+}
+```
+
+**适用场景**:
+- 网络延迟较高时需要增加超时
+- 快速失败场景可减少超时
+
+---
+
+### 3.4 zone_sync_timeout
+
+**设置连续读写操作之间的超时时间**。
+
+| 属性 | 说明 |
+|------|------|
+| **语法** | `zone_sync_timeout time;` |
+| **默认值** | `5s` |
+| **上下文** | `stream`, `server` |
+| **版本** | 1.13.8+ |
+
+**说明**:
+
+该指令替代了早期版本中可能的 `zone_sync_recv_timeout` 和 `zone_sync_send_timeout`,统一控制读写超时。
+
+| 参数 | 说明 |
+|------|------|
+| `time` | 超时时间,两次连续读取或写入之间的最大间隔 |
+
+**配置示例**:
+
+```nginx
+stream {
+ server {
+ zone_sync;
+ listen 127.0.0.1:12345;
+
+ # 读写超时设置为 30 秒
+ zone_sync_timeout 30s;
+
+ zone_sync_server 192.168.1.10:12345;
+ }
+}
+```
+
+---
+
+### 3.5 zone_sync_buffers
+
+**设置用于推送区域内容的缓冲区数量和大小**。
+
+| 属性 | 说明 |
+|------|------|
+| **语法** | `zone_sync_buffers number size;` |
+| **默认值** | `8 4k` 或 `8 8k`(取决于平台内存页大小) |
+| **上下文** | `stream`, `server` |
+| **版本** | 1.13.8+ |
+
+**参数说明**:
+
+| 参数 | 说明 |
+|------|------|
+| `number` | 缓冲区数量 |
+| `size` | 每个缓冲区大小 |
+
+**重要限制**:
+- 单个缓冲区必须足够大以容纳任何共享内存区域中的**单个条目**
+- 如果条目超过缓冲区大小,同步将失败
+
+**配置示例**:
+
+```nginx
+stream {
+ server {
+ zone_sync;
+ listen 127.0.0.1:12345;
+
+ # 16 个 8k 缓冲区
+ zone_sync_buffers 16 8k;
+
+ zone_sync_server 192.168.1.10:12345;
+ }
+}
+```
+
+**调优建议**:
+- 如果 keyval 条目较大(如 JSON 配置),增加 `size`
+- 如果同步频繁,增加 `number`
+
+---
+
+### 3.6 zone_sync_interval
+
+**设置轮询共享内存区域更新的间隔时间**。
+
+| 属性 | 说明 |
+|------|------|
+| **语法** | `zone_sync_interval time;` |
+| **默认值** | `1s` |
+| **上下文** | `stream`, `server` |
+| **版本** | 1.13.8+ |
+
+**参数说明**:
+
+| 参数 | 说明 |
+|------|------|
+| `time` | 轮询间隔,较短间隔意味着更快的同步但更高的 CPU 使用 |
+
+**配置示例**:
+
+```nginx
+stream {
+ server {
+ zone_sync;
+ listen 127.0.0.1:12345;
+
+ # 每 500ms 轮询一次更新
+ zone_sync_interval 500ms;
+
+ zone_sync_server 192.168.1.10:12345;
+ }
+}
+```
+
+**调优建议**:
+- 默认 `1s` 适用于大多数场景
+- 对实时性要求高的场景可减少到 `100-500ms`
+- 资源受限场景可增加到 `2-5s`
+
+---
+
+### 3.7 zone_sync_recv_buffer_size
+
+**设置每个连接的接收缓冲区大小,用于解析同步消息**。
+
+| 属性 | 说明 |
+|------|------|
+| **语法** | `zone_sync_recv_buffer_size size;` |
+| **默认值** | `4k` 或 `8k`(与 `zone_sync_buffers` 的 `size × number` 相同) |
+| **上下文** | `stream`, `server` |
+| **版本** | 1.13.8+ |
+
+**参数说明**:
+
+| 参数 | 说明 |
+|------|------|
+| `size` | 接收缓冲区大小,必须大于或等于 `zone_sync_buffers` 的单个缓冲区大小 |
+
+**配置示例**:
+
+```nginx
+stream {
+ server {
+ zone_sync;
+ listen 127.0.0.1:12345;
+
+ zone_sync_buffers 16 8k;
+ # 接收缓冲区至少 8k
+ zone_sync_recv_buffer_size 16k;
+
+ zone_sync_server 192.168.1.10:12345;
+ }
+}
+```
+
+---
+
+### 3.8 zone_sync_connect_retry_interval
+
+**设置连接失败后重试的间隔时间**。
+
+| 属性 | 说明 |
+|------|------|
+| **语法** | `zone_sync_connect_retry_interval time;` |
+| **默认值** | `1s` |
+| **上下文** | `stream`, `server` |
+| **版本** | 1.13.8+ |
+
+**参数说明**:
+
+| 参数 | 说明 |
+|------|------|
+| `time` | 重试间隔,较短间隔可以更快恢复但可能增加网络负担 |
+
+**配置示例**:
+
+```nginx
+stream {
+ server {
+ zone_sync;
+ listen 127.0.0.1:12345;
+
+ # 连接失败后每 5 秒重试一次
+ zone_sync_connect_retry_interval 5s;
+
+ zone_sync_server 192.168.1.10:12345;
+ }
+}
+```
+
+---
+
+### 3.9 SSL 相关指令
+
+Zone Sync 支持节点间 SSL 加密传输。
+
+| 指令 | 说明 |
+|------|------|
+| `zone_sync_ssl on\|off` | 启用 SSL(默认 `off`) |
+| `zone_sync_ssl_certificate file` | SSL 证书文件 |
+| `zone_sync_ssl_certificate_key file` | SSL 密钥文件 |
+| `zone_sync_ssl_protocols protocols` | SSL 协议版本(如 `TLSv1.2 TLSv1.3`) |
+| `zone_sync_ssl_ciphers ciphers` | SSL 加密套件 |
+| `zone_sync_ssl_server_name on\|off` | 启用 SNI(默认 `off`) |
+| `zone_sync_ssl_name name` | 覆盖 SSL 验证的服务器名称 |
+| `zone_sync_ssl_verify on\|off` | 启用证书验证(默认 `off`) |
+| `zone_sync_ssl_verify_depth number` | 验证深度(默认 `1`) |
+| `zone_sync_ssl_trusted_certificate file` | 受信任的 CA 证书 |
+| `zone_sync_ssl_crl file` | 证书吊销列表 |
+
+**SSL 配置示例**:
+
+```nginx
+stream {
+ resolver 192.168.1.1 valid=30s;
+
+ server {
+ zone_sync;
+ listen 127.0.0.1:12345;
+
+ zone_sync_server cluster.example.com:12345 resolve;
+
+ # 启用 SSL
+ zone_sync_ssl on;
+ zone_sync_ssl_certificate /etc/nginx/certs/cluster.crt;
+ zone_sync_ssl_certificate_key /etc/nginx/certs/cluster.key;
+ zone_sync_ssl_protocols TLSv1.2 TLSv1.3;
+ zone_sync_ssl_ciphers HIGH:!aNULL:!MD5;
+ }
+}
+```
+
+---
+
+## 4. 集群配置示例
+
+### 4.1 最小化配置
+
+```nginx
+# nginx.conf
+
+worker_processes auto;
+
+events {
+ worker_connections 1024;
+}
+
+http {
+ # 定义需要同步的 zone
+ upstream backend {
+ server backend1.example.com:8080;
+ sticky learn
+ create=$upstream_cookie_session
+ lookup=$cookie_session
+ zone=session_store:1m sync;
+ }
+
+ server {
+ listen 80;
+
+ location / {
+ proxy_pass http://backend;
+ }
+ }
+}
+
+stream {
+ server {
+ # 启用 zone 同步
+ zone_sync;
+ listen 127.0.0.1:12345;
+
+ # 集群节点配置
+ zone_sync_server node1.example.com:12345;
+ zone_sync_server node2.example.com:12345;
+ }
+}
+```
+
+---
+
+### 4.2 三节点集群配置
+
+#### 节点 1(192.168.1.10)
+
+```nginx
+# /etc/nginx/nginx.conf (Node 1)
+
+events {
+ worker_connections 4096;
+}
+
+stream {
+ server {
+ zone_sync;
+ listen 192.168.1.10:12345;
+
+ zone_sync_server 192.168.1.10:12345;
+ zone_sync_server 192.168.1.11:12345;
+ zone_sync_server 192.168.1.12:12345;
+
+ zone_sync_connect_timeout 10s;
+ zone_sync_timeout 30s;
+ zone_sync_interval 500ms;
+ }
+}
+```
+
+#### 节点 2(192.168.1.11)
+
+```nginx
+# /etc/nginx/nginx.conf (Node 2)
+
+events {
+ worker_connections 4096;
+}
+
+stream {
+ server {
+ zone_sync;
+ listen 192.168.1.11:12345;
+
+ zone_sync_server 192.168.1.10:12345;
+ zone_sync_server 192.168.1.11:12345;
+ zone_sync_server 192.168.1.12:12345;
+
+ zone_sync_connect_timeout 10s;
+ zone_sync_timeout 30s;
+ zone_sync_interval 500ms;
+ }
+}
+```
+
+#### 节点 3(192.168.1.12)
+
+```nginx
+# /etc/nginx/nginx.conf (Node 3)
+
+events {
+ worker_connections 4096;
+}
+
+stream {
+ server {
+ zone_sync;
+ listen 192.168.1.12:12345;
+
+ zone_sync_server 192.168.1.10:12345;
+ zone_sync_server 192.168.1.11:12345;
+ zone_sync_server 192.168.1.12:12345;
+
+ zone_sync_connect_timeout 10s;
+ zone_sync_timeout 30s;
+ zone_sync_interval 500ms;
+ }
+}
+```
+
+---
+
+### 4.3 动态 DNS 发现配置
+
+```nginx
+# 所有节点使用相同配置
+
+events {
+ worker_connections 4096;
+}
+
+stream {
+ # 配置 DNS 解析器
+ resolver 192.168.1.1 valid=10s;
+
+ server {
+ zone_sync;
+ listen 0.0.0.0:12345;
+
+ # 通过 SRV 或 A 记录动态发现节点
+ zone_sync_server nginx-cluster.internal:12345 resolve;
+
+ zone_sync_connect_timeout 5s;
+ zone_sync_connect_retry_interval 2s;
+ }
+}
+```
+
+**DNS 记录配置示例**:
+
+```
+# DNS A 记录
+nginx-cluster.internal. IN A 192.168.1.10
+nginx-cluster.internal. IN A 192.168.1.11
+nginx-cluster.internal. IN A 192.168.1.12
+```
+
+---
+
+### 4.4 同步 keyval 状态
+
+```nginx
+events {
+ worker_connections 4096;
+}
+
+http {
+ # 需要同步的 keyval zone(带 timeout 和 sync 参数)
+ keyval_zone zone=user_sessions:64k state=/var/lib/nginx/state/sessions.keyval timeout=1h sync;
+ keyval_zone zone=api_keys:32k state=/var/lib/nginx/state/api_keys.keyval sync;
+
+ keyval $cookie_session $session_data zone=user_sessions;
+ keyval $arg_api_key $api_info zone=api_keys;
+
+ # API 管理接口
+ server {
+ listen 127.0.0.1:8080;
+
+ location /api {
+ api write=on;
+ allow 127.0.0.1;
+ deny all;
+ }
+ }
+
+ server {
+ listen 80;
+
+ location / {
+ if ($api_info = "") {
+ return 403 "API key required";
+ }
+ proxy_pass http://backend;
+ }
+ }
+}
+
+stream {
+ server {
+ zone_sync;
+ listen 0.0.0.0:12345;
+
+ zone_sync_server node1.example.com:12345;
+ zone_sync_server node2.example.com:12345;
+
+ zone_sync_buffers 16 8k;
+ zone_sync_interval 500ms;
+ }
+}
+```
+
+---
+
+### 4.5 同步 limit_conn 状态
+
+```nginx
+events {
+ worker_connections 4096;
+}
+
+http {
+ # 限制连接数 zone(需要同步)
+ limit_conn_zone $binary_remote_addr zone=addr_limit:10m;
+
+ # 注意:limit_conn_zone 不直接支持 sync 参数
+ # 需要通过 keyval 间接实现分布式限流
+
+ server {
+ listen 80;
+
+ location /downloads {
+ limit_conn addr_limit 10;
+ proxy_pass http://backend;
+ }
+ }
+}
+
+stream {
+ server {
+ zone_sync;
+ listen 0.0.0.0:12345;
+
+ zone_sync_server node1.example.com:12345;
+ zone_sync_server node2.example.com:12345;
+ }
+}
+```
+
+**注意**:`limit_conn_zone` 和 `limit_req_zone` 的同步需要 NGINX Plus 特定版本支持。
+
+---
+
+### 4.6 同步 sticky sessions
+
+```nginx
+events {
+ worker_connections 4096;
+}
+
+http {
+ upstream api_backend {
+ server 192.168.1.10:8080;
+ server 192.168.1.11:8080;
+
+ # sticky learn 会话粘性,带 sync 参数
+ sticky learn
+ create=$upstream_cookie_route
+ lookup=$cookie_route
+ zone=sticky_sessions:2m sync;
+ }
+
+ server {
+ listen 80;
+
+ location /api {
+ proxy_pass http://api_backend;
+ }
+ }
+}
+
+stream {
+ server {
+ zone_sync;
+ listen 0.0.0.0:12345;
+
+ zone_sync_server node1.example.com:12345;
+ zone_sync_server node2.example.com:12345;
+
+ zone_sync_interval 200ms; # 更快的会话同步
+ }
+}
+```
+
+---
+
+## 5. 与 Keyval 模块配合使用
+
+### 5.1 Keyval Zone 同步配置
+
+```nginx
+events {
+ worker_connections 4096;
+}
+
+http {
+ # ========== 需要同步的 keyval zones ==========
+
+ # 用户会话(带过期时间)
+ keyval_zone zone=sessions:2m state=/var/lib/nginx/state/sessions.keyval timeout=30m sync;
+ keyval $cookie_session $session_info zone=sessions;
+
+ # 动态路由配置
+ keyval_zone zone=routes:512k state=/var/lib/nginx/state/routes.keyval sync;
+ keyval $uri $backend_pool zone=routes;
+
+ # API 限流黑白名单
+ keyval_zone zone=rate_whitelist:1m type=ip sync;
+ keyval $remote_addr $rate_exempt zone=rate_whitelist;
+
+ # ========== 后端定义 ==========
+ upstream backend_pool_a {
+ server 192.168.1.10:8080;
+ server 192.168.1.11:8080;
+ }
+
+ upstream backend_pool_b {
+ server 192.168.1.20:8080;
+ server 192.168.1.21:8080;
+ }
+
+ # ========== API 管理服务器 ==========
+ server {
+ listen 127.0.0.1:8080;
+
+ location /api {
+ api write=on;
+ allow 127.0.0.1;
+ deny all;
+ }
+ }
+
+ # ========== 主业务服务器 ==========
+ server {
+ listen 80;
+ server_name app.example.com;
+
+ location / {
+ # 白名单跳过限流
+ if ($rate_exempt = "exempt") {
+ proxy_pass http://backend_pool_a;
+ break;
+ }
+
+ # 动态路由
+ if ($backend_pool = "") {
+ set $backend_pool "backend_pool_a";
+ }
+
+ proxy_pass http://$backend_pool;
+ }
+ }
+}
+
+stream {
+ # Zone Sync 配置
+ server {
+ zone_sync;
+ listen 0.0.0.0:12345;
+
+ # 集群节点
+ zone_sync_server 192.168.1.10:12345;
+ zone_sync_server 192.168.1.11:12345;
+
+ # 同步参数调优
+ zone_sync_buffers 32 16k;
+ zone_sync_interval 500ms;
+ zone_sync_timeout 30s;
+ }
+}
+```
+
+---
+
+### 5.2 通过 API 管理同步数据
+
+```bash
+#!/bin/bash
+
+# API 端点
+API_BASE="http://127.0.0.1:8080/api/9"
+
+# 添加会话
+curl -X POST "${API_BASE}/http/keyvals/sessions" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "user123": {
+ "value": "{\"user_id\": 123, \"role\": \"admin\"}",
+ "expire": 1800000
+ }
+ }'
+
+# 添加路由规则
+curl -X POST "${API_BASE}/http/keyvals/routes" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "/api/v1": "backend_pool_a",
+ "/api/v2": "backend_pool_b",
+ "/admin": "backend_pool_a"
+ }'
+
+# 查询会话数据
+curl "${API_BASE}/http/keyvals/sessions?key=user123"
+
+# 查询所有路由
+curl "${API_BASE}/http/keyvals/routes"
+
+# 删除键
+curl -X PATCH "${API_BASE}/http/keyvals/sessions" \
+ -H "Content-Type: application/json" \
+ -d '{"user123": null}'
+
+# 清空 zone
+curl -X DELETE "${API_BASE}/http/keyvals/routes"
+```
+
+---
+
+### 5.3 集群状态监控
+
+```bash
+# 查询 Zone Sync 状态
+curl http://127.0.0.1:8080/api/9/stream/zone_sync/
+
+# 示例响应
+{
+ "connections": {
+ "active": 2,
+ "idle": 0,
+ "closed": 5
+ },
+ "messages": {
+ "sent": 12345,
+ "received": 12300
+ },
+ "bytes": {
+ "sent": 1048576,
+ "received": 1024000
+ },
+ "zones": {
+ "sessions": {
+ "entries": 150,
+ "size": 32768
+ },
+ "routes": {
+ "entries": 25,
+ "size": 8192
+ }
+ }
+}
+```
+
+---
+
+## 6. 性能调优与最佳实践
+
+### 6.1 缓冲区大小调优
+
+```nginx
+stream {
+ server {
+ zone_sync;
+ listen 0.0.0.0:12345;
+
+ # 场景 1:小型键值对(<1KB)
+ zone_sync_buffers 8 4k;
+
+ # 场景 2:中型配置(1-4KB)
+ zone_sync_buffers 16 8k;
+
+ # 场景 3:大型 JSON 配置(>4KB)
+ zone_sync_buffers 32 16k;
+
+ zone_sync_server node1.example.com:12345;
+ }
+}
+```
+
+---
+
+### 6.2 同步间隔调优
+
+```nginx
+# 高实时性场景(会话同步)
+zone_sync_interval 100ms;
+
+# 一般场景(路由配置)
+zone_sync_interval 500ms;
+
+# 低频率场景(配置同步)
+zone_sync_interval 2s;
+```
+
+---
+
+### 6.3 网络调优
+
+```nginx
+stream {
+ server {
+ zone_sync;
+ listen 0.0.0.0:12345;
+
+ # 高延迟网络(跨数据中心)
+ zone_sync_connect_timeout 15s;
+ zone_sync_timeout 60s;
+ zone_sync_connect_retry_interval 5s;
+
+ # 低延迟网络(同数据中心)
+ zone_sync_connect_timeout 3s;
+ zone_sync_timeout 10s;
+ zone_sync_connect_retry_interval 1s;
+
+ zone_sync_server node1.example.com:12345;
+ }
+}
+```
+
+---
+
+### 6.4 内存规划
+
+```nginx
+http {
+ # 估算内存需求
+ # 每个键值对:key(50B) + value(100B) + overhead(50B) ≈ 200B
+ # 10,000 键值对 ≈ 2MB
+
+ keyval_zone zone=large_db:4m sync;
+
+ # 建议预留 50% 余量
+ keyval_zone zone=with_margin:6m sync;
+}
+```
+
+---
+
+### 6.5 安全建议
+
+```nginx
+stream {
+ server {
+ zone_sync;
+ # 只监听内网地址
+ listen 192.168.1.10:12345;
+
+ # 启用 SSL 加密
+ zone_sync_ssl on;
+ zone_sync_ssl_certificate /etc/nginx/certs/cluster.crt;
+ zone_sync_ssl_certificate_key /etc/nginx/certs/cluster.key;
+ zone_sync_ssl_protocols TLSv1.3;
+ zone_sync_ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
+
+ zone_sync_server node1.internal:12345;
+ }
+}
+```
+
+---
+
+## 7. 故障排查
+
+### 7.1 常见问题
+
+| 问题 | 可能原因 | 解决方案 |
+|------|----------|----------|
+| 连接失败 | 防火墙/网络不通 | 检查端口连通性,确认防火墙规则 |
+| 同步延迟 | 缓冲区不足 | 增加 `zone_sync_buffers` 大小 |
+| 节点无法发现 | DNS 解析问题 | 检查 `resolver` 配置,验证 DNS 记录 |
+| 数据不一致 | 网络分区 | 检查网络连接,增加超时时间 |
+| 内存增长 | 无过期时间 | 配置 `timeout` 参数 |
+
+---
+
+### 7.2 调试命令
+
+```bash
+# 测试配置
+nginx -t
+
+# 重载配置
+nginx -s reload
+
+# 检查进程状态
+ps aux | grep nginx
+
+# 查看网络连接
+netstat -tlnp | grep nginx
+ss -tlnp | grep nginx
+
+# 监控日志
+tail -f /var/log/nginx/error.log
+
+# 查看 Zone Sync API 状态
+curl http://localhost:8080/api/9/stream/zone_sync/
+```
+
+---
+
+## 8. 与 Lolly 项目的关系和建议
+
+### 8.1 项目对比
+
+[Lolly](https://github.com/xfy/lolly) 是一个使用 Go 语言编写的高性能 HTTP 服务器与反向代理。与 NGINX Zone Sync 相比:
+
+| 特性 | NGINX Zone Sync | Lolly |
+|------|-----------------|-------|
+| **集群同步** | 内置支持,商业功能 | 需自行实现 |
+| **状态存储** | 共享内存 | 内存/外部存储 |
+| **节点发现** | 静态/DNS | 需自行实现 |
+| **传输协议** | TCP(专有协议) | 可自定义 |
+| **许可证** | 商业订阅 | 开源免费 |
+
+---
+
+### 8.2 Go 实现分布式状态同步建议
+
+#### 方案 1:基于 Gossip 协议
+
+```go
+// internal/cluster/gossip.go
+package cluster
+
+import (
+ "sync"
+ "time"
+
+ "github.com/hashicorp/memberlist"
+)
+
+type StateSync struct {
+ list *memberlist.Memberlist
+ store sync.Map
+ config *Config
+}
+
+type Config struct {
+ NodeName string
+ BindAddr string
+ BindPort int
+ JoinNodes []string
+ SyncInterval time.Duration
+}
+
+func NewStateSync(cfg *Config) (*StateSync, error) {
+ s := &StateSync{config: cfg}
+
+ mlConfig := memberlist.DefaultLANConfig()
+ mlConfig.Name = cfg.NodeName
+ mlConfig.BindAddr = cfg.BindAddr
+ mlConfig.BindPort = cfg.BindPort
+ mlConfig.Delegate = s
+
+ list, err := memberlist.Create(mlConfig)
+ if err != nil {
+ return nil, err
+ }
+
+ s.list = list
+
+ if len(cfg.JoinNodes) > 0 {
+ _, err = list.Join(cfg.JoinNodes)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ return s, nil
+}
+
+// 实现 memberlist.Delegate 接口
+func (s *StateSync) NodeMeta(limit int) []byte {
+ return []byte{}
+}
+
+func (s *StateSync) NotifyMsg(buf []byte) {
+ // 处理接收到的同步消息
+}
+
+func (s *StateSync) GetBroadcasts(overhead, limit int) [][]byte {
+ // 返回待广播的变更
+ return nil
+}
+
+func (s *StateSync) LocalState(join bool) []byte {
+ // 返回本地状态
+ return nil
+}
+
+func (s *StateSync) MergeRemoteState(buf []byte, join bool) {
+ // 合并远程状态
+}
+
+// 状态操作接口
+func (s *StateSync) Set(key, value string) error {
+ s.store.Store(key, value)
+ return s.broadcastUpdate(key, value)
+}
+
+func (s *StateSync) Get(key string) (string, bool) {
+ val, ok := s.store.Load(key)
+ if !ok {
+ return "", false
+ }
+ return val.(string), true
+}
+
+func (s *StateSync) Delete(key string) error {
+ s.store.Delete(key)
+ return s.broadcastDelete(key)
+}
+```
+
+---
+
+#### 方案 2:基于 Redis Pub/Sub
+
+```go
+// internal/cluster/redis_sync.go
+package cluster
+
+import (
+ "context"
+ "encoding/json"
+ "time"
+
+ "github.com/redis/go-redis/v9"
+)
+
+type RedisSync struct {
+ client *redis.Client
+ channel string
+ nodeID string
+ store sync.Map
+}
+
+type SyncMessage struct {
+ Type string `json:"type"` // "set", "delete", "full"
+ Key string `json:"key,omitempty"`
+ Value interface{} `json:"value,omitempty"`
+ NodeID string `json:"node_id"`
+ Timestamp int64 `json:"timestamp"`
+}
+
+func NewRedisSync(addr, channel, nodeID string) (*RedisSync, error) {
+ client := redis.NewClient(&redis.Options{
+ Addr: addr,
+ })
+
+ s := &RedisSync{
+ client: client,
+ channel: channel,
+ nodeID: nodeID,
+ }
+
+ // 启动订阅协程
+ go s.subscribe(context.Background())
+
+ return s, nil
+}
+
+func (s *RedisSync) subscribe(ctx context.Context) {
+ pubsub := s.client.Subscribe(ctx, s.channel)
+ defer pubsub.Close()
+
+ ch := pubsub.Channel()
+ for msg := range ch {
+ if err := s.handleMessage(msg.Payload); err != nil {
+ // 处理错误
+ }
+ }
+}
+
+func (s *RedisSync) handleMessage(payload string) error {
+ var msg SyncMessage
+ if err := json.Unmarshal([]byte(payload), &msg); err != nil {
+ return err
+ }
+
+ // 忽略自己发送的消息
+ if msg.NodeID == s.nodeID {
+ return nil
+ }
+
+ switch msg.Type {
+ case "set":
+ s.store.Store(msg.Key, msg.Value)
+ case "delete":
+ s.store.Delete(msg.Key)
+ }
+
+ return nil
+}
+
+func (s *RedisSync) Set(ctx context.Context, key string, value interface{}) error {
+ msg := SyncMessage{
+ Type: "set",
+ Key: key,
+ Value: value,
+ NodeID: s.nodeID,
+ Timestamp: time.Now().UnixNano(),
+ }
+
+ data, _ := json.Marshal(msg)
+ s.store.Store(key, value)
+
+ return s.client.Publish(ctx, s.channel, data).Err()
+}
+
+func (s *RedisSync) Delete(ctx context.Context, key string) error {
+ msg := SyncMessage{
+ Type: "delete",
+ Key: key,
+ NodeID: s.nodeID,
+ Timestamp: time.Now().UnixNano(),
+ }
+
+ data, _ := json.Marshal(msg)
+ s.store.Delete(key)
+
+ return s.client.Publish(ctx, s.channel, data).Err()
+}
+```
+
+---
+
+#### 方案 3:基于 etcd 的强一致性同步
+
+```go
+// internal/cluster/etcd_sync.go
+package cluster
+
+import (
+ "context"
+ "time"
+
+ clientv3 "go.etcd.io/etcd/client/v3"
+)
+
+type EtcdSync struct {
+ client *clientv3.Client
+ prefix string
+ nodeID string
+ ctx context.Context
+}
+
+func NewEtcdSync(endpoints []string, prefix, nodeID string) (*EtcdSync, error) {
+ client, err := clientv3.New(clientv3.Config{
+ Endpoints: endpoints,
+ DialTimeout: 5 * time.Second,
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ return &EtcdSync{
+ client: client,
+ prefix: prefix,
+ nodeID: nodeID,
+ ctx: context.Background(),
+ }, nil
+}
+
+func (s *EtcdSync) Set(key string, value string, ttl time.Duration) error {
+ ctx, cancel := context.WithTimeout(s.ctx, 5*time.Second)
+ defer cancel()
+
+ if ttl > 0 {
+ lease, err := s.client.Grant(ctx, int64(ttl.Seconds()))
+ if err != nil {
+ return err
+ }
+ _, err = s.client.Put(ctx, s.prefix+key, value,
+ clientv3.WithLease(lease.ID))
+ return err
+ }
+
+ _, err := s.client.Put(ctx, s.prefix+key, value)
+ return err
+}
+
+func (s *EtcdSync) Get(key string) (string, error) {
+ ctx, cancel := context.WithTimeout(s.ctx, 5*time.Second)
+ defer cancel()
+
+ resp, err := s.client.Get(ctx, s.prefix+key)
+ if err != nil {
+ return "", err
+ }
+
+ if len(resp.Kvs) == 0 {
+ return "", nil
+ }
+
+ return string(resp.Kvs[0].Value), nil
+}
+
+func (s *EtcdSync) Watch(ctx context.Context, key string) <-chan string {
+ ch := make(chan string)
+
+ go func() {
+ defer close(ch)
+
+ rch := s.client.Watch(ctx, s.prefix+key)
+ for wresp := range rch {
+ for _, ev := range wresp.Events {
+ ch <- string(ev.Kv.Value)
+ }
+ }
+ }()
+
+ return ch
+}
+```
+
+---
+
+### 8.3 Lolly 实现建议
+
+参考 NGINX Zone Sync 模块,建议 Lolly 可以考虑以下实现:
+
+1. **模块化设计**
+ - 抽象 `StateSync` 接口
+ - 支持多种后端(内存、Redis、etcd、Gossip)
+ - 插件式注册
+
+2. **配置示例**
+
+```yaml
+# config.yaml
+cluster:
+ enabled: true
+ node_id: "node-1"
+ bind_addr: "0.0.0.0"
+ bind_port: 9000
+ join_nodes:
+ - "192.168.1.10:9000"
+ - "192.168.1.11:9000"
+ sync_interval: 500ms
+ backend: "gossip" # 或 "redis", "etcd"
+
+# Redis 后端配置
+redis:
+ addr: "localhost:6379"
+ channel: "lolly:sync"
+
+# etcd 后端配置
+etcd:
+ endpoints:
+ - "localhost:2379"
+ prefix: "/lolly/state/"
+```
+
+3. **API 设计**
+
+```go
+// 定义同步状态接口
+type StateSync interface {
+ Set(key string, value interface{}, ttl time.Duration) error
+ Get(key string) (interface{}, bool)
+ Delete(key string) error
+ Watch(key string) <-chan StateChange
+ Close() error
+}
+
+// 状态变更事件
+type StateChange struct {
+ Key string
+ Value interface{}
+ Deleted bool
+ Timestamp time.Time
+}
+```
+
+4. **与 NGINX Plus API 兼容**
+
+```go
+// 提供与 NGINX Plus 兼容的 REST API
+// POST /api/lolly/keyvals/{zone}
+// GET /api/lolly/keyvals/{zone}
+// PATCH /api/lolly/keyvals/{zone}
+// DELETE /api/lolly/keyvals/{zone}
+```
+
+---
+
+## 9. 参考链接
+
+- [NGINX Zone Sync 模块官方文档](https://nginx.org/en/docs/stream/ngx_stream_zone_sync_module.html)
+- [NGINX Keyval 模块官方文档](https://nginx.org/en/docs/stream/ngx_stream_keyval_module.html)
+- [NGINX Plus API 文档](https://nginx.org/en/docs/http/ngx_http_api_module.html)
+- [Lolly 项目 GitHub](https://github.com/xfy/lolly)
+- [memberlist (HashiCorp)](https://github.com/hashicorp/memberlist)
+- [etcd 官方文档](https://etcd.io/docs/)
diff --git a/docs/39-nginx-tunnel-module.md b/docs/39-nginx-tunnel-module.md
new file mode 100644
index 0000000..2fd5b5d
--- /dev/null
+++ b/docs/39-nginx-tunnel-module.md
@@ -0,0 +1,603 @@
+# NGINX HTTP Tunnel 模块文档
+
+## 1. 模块概述
+
+### 1.1 简介
+
+`ngx_http_tunnel_module` 是 NGINX 的商业模块,用于处理 HTTP CONNECT 请求并建立端到端的虚拟连接隧道。
+
+**版本要求**: NGINX 1.29.3 及以上
+
+**授权**: 仅作为 F5 NGINX 商业订阅的一部分提供,开源版本不包含此模块
+
+### 1.2 核心用途
+
+- **HTTP 代理隧道**: 处理 RFC 9110 定义的 CONNECT 方法,用于建立 HTTPS 代理隧道
+- **TCP 穿透**: 允许客户端通过 HTTP 代理与后端 TCP 服务建立直接连接
+- **动态路由**: 支持变量实现动态目标地址和绑定地址
+
+### 1.3 配置上下文
+
+支持以下配置块:
+- `http`
+- `server`
+- `location`
+
+---
+
+## 2. 指令详解
+
+### 2.1 核心指令
+
+#### tunnel_pass
+
+```nginx
+tunnel_pass [address];
+```
+
+| 属性 | 说明 |
+|------|------|
+| **默认值** | 无(必须显式配置) |
+| **上下文** | http, server, location |
+| **支持变量** | 是 |
+
+**说明**:
+- 启用 CONNECT 请求处理
+- 默认目标地址为 `$host:$request_port`
+- `address` 可以是:域名、IP 地址、端口、UNIX 套接字路径或上游服务器组名称
+- 支持变量实现动态路由
+
+**示例**:
+```nginx
+tunnel_pass $host:$request_port;
+tunnel_pass backend_upstream;
+tunnel_pass 127.0.0.1:8443;
+```
+
+---
+
+#### tunnel_allow_upstream
+
+```nginx
+tunnel_allow_upstream string ...;
+```
+
+| 属性 | 说明 |
+|------|------|
+| **默认值** | 无 |
+| **上下文** | http, server, location |
+| **支持变量** | 是 |
+
+**说明**:
+- 定义允许访问后端服务器的条件
+- 所有参数必须非空且不等于 "0" 才允许连接
+- 每次建立连接前都会评估
+
+**示例**:
+```nginx
+tunnel_allow_upstream $allow_port $allow_host;
+```
+
+---
+
+#### tunnel_bind
+
+```nginx
+tunnel_bind address | off;
+```
+
+| 属性 | 说明 |
+|------|------|
+| **默认值** | 无 |
+| **上下文** | http, server, location |
+| **支持变量** | 是 |
+
+**说明**:
+- 指定出站连接到后端服务器时使用的本地 IP 地址(可选端口)
+- `off` 取消从上级配置继承的效果
+
+**示例**:
+```nginx
+tunnel_bind 192.168.1.100;
+tunnel_bind $local_ip:$local_port;
+```
+
+---
+
+#### tunnel_bind_dynamic
+
+```nginx
+tunnel_bind_dynamic on | off;
+```
+
+| 属性 | 说明 |
+|------|------|
+| **默认值** | `off` |
+| **上下文** | http, server, location |
+
+**说明**:
+- 启用后,每次连接尝试时都会执行 `tunnel_bind` 操作
+- 适用于 `tunnel_bind` 中使用动态变量的场景
+
+---
+
+#### tunnel_socket_keepalive
+
+```nginx
+tunnel_socket_keepalive on | off;
+```
+
+| 属性 | 说明 |
+|------|------|
+| **默认值** | `off` |
+| **上下文** | http, server, location |
+
+**说明**:
+- 配置出站连接的 TCP keepalive 行为
+- `on` 开启 `SO_KEEPALIVE` 选项
+
+---
+
+### 2.2 超时与缓冲指令
+
+#### tunnel_connect_timeout
+
+```nginx
+tunnel_connect_timeout time;
+```
+
+| 属性 | 说明 |
+|------|------|
+| **默认值** | `60s` |
+| **上下文** | http, server, location |
+
+**说明**:
+- 建立后端连接的超时时间
+- 通常不应超过 75 秒
+
+---
+
+#### tunnel_read_timeout
+
+```nginx
+tunnel_read_timeout time;
+```
+
+| 属性 | 说明 |
+|------|------|
+| **默认值** | `60s` |
+| **上下文** | http, server, location |
+
+**说明**:
+- 客户端或后端连接上两次连续读写操作之间的超时
+- 无数据传输时关闭连接
+
+---
+
+#### tunnel_send_timeout
+
+```nginx
+tunnel_send_timeout time;
+```
+
+| 属性 | 说明 |
+|------|------|
+| **默认值** | `60s` |
+| **上下文** | http, server, location |
+
+**说明**:
+- 向后端服务器传输请求的超时时间
+- 仅针对两次连续写操作之间
+
+---
+
+#### tunnel_buffer_size
+
+```nginx
+tunnel_buffer_size size;
+```
+
+| 属性 | 说明 |
+|------|------|
+| **默认值** | `16k` |
+| **上下文** | http, server, location |
+
+**说明**:
+- 设置用于从后端服务器读取数据的缓冲区大小
+- 同时也设置从客户端读取数据的缓冲区大小
+
+---
+
+#### tunnel_send_lowat
+
+```nginx
+tunnel_send_lowat size;
+```
+
+| 属性 | 说明 |
+|------|------|
+| **默认值** | `0` |
+| **上下文** | http, server, location |
+
+**说明**:
+- 非零值时尝试最小化发送操作(使用 `NOTE_LOWAT` 或 `SO_SNDLOWAT`)
+- **注意**: 在 Linux、Solaris 和 Windows 上被忽略
+
+---
+
+### 2.3 上游故障转移指令
+
+#### tunnel_next_upstream
+
+```nginx
+tunnel_next_upstream error | timeout | denied | off ...;
+```
+
+| 属性 | 说明 |
+|------|------|
+| **默认值** | `error timeout` |
+| **上下文** | http, server, location |
+
+**说明**:
+- 指定何时将请求传递给下一台服务器
+- `denied`: 被 `tunnel_allow_upstream` 拒绝
+- `off`: 禁用故障转移
+
+**重要限制**: 若已向客户端发送数据,则无法传递到下一台服务器
+
+---
+
+#### tunnel_next_upstream_timeout
+
+```nginx
+tunnel_next_upstream_timeout time;
+```
+
+| 属性 | 说明 |
+|------|------|
+| **默认值** | `0`(无限制) |
+| **上下文** | http, server, location |
+
+**说明**:
+- 限制请求传递给下一台服务器的总时间
+- `0` 表示关闭限制
+
+---
+
+#### tunnel_next_upstream_tries
+
+```nginx
+tunnel_next_upstream_tries number;
+```
+
+| 属性 | 说明 |
+|------|------|
+| **默认值** | `0`(无限制) |
+| **上下文** | http, server, location |
+
+**说明**:
+- 限制传递给下一台服务器的尝试次数
+- `0` 表示关闭限制
+
+---
+
+## 3. TCP 隧道配置示例
+
+### 3.1 基础 HTTP 代理
+
+```nginx
+http {
+ # 定义允许的端口
+ map $request_port $allow_port {
+ 443 1;
+ default 0;
+ }
+
+ # 定义允许的域名
+ map $host $allow_host {
+ hostnames;
+ example.org 1;
+ *.example.org 1;
+ default 0;
+ }
+
+ server {
+ listen 8000;
+ resolver dns.example.com;
+
+ # 权限检查
+ if ($allow_port != 1) {
+ return 502;
+ }
+
+ if ($allow_host != 1) {
+ return 502;
+ }
+
+ # 启用隧道穿透
+ tunnel_pass;
+ }
+}
+```
+
+**客户端使用**:
+```bash
+curl -x http://nginx:8000 https://example.org
+```
+
+---
+
+### 3.2 带上游服务器的 TCP 隧道
+
+```nginx
+http {
+ upstream backend_pool {
+ server 10.0.0.1:8443;
+ server 10.0.0.2:8443;
+ server 10.0.0.3:8443;
+ }
+
+ server {
+ listen 8000;
+
+ location / {
+ # 所有 CONNECT 请求转发到上游池
+ tunnel_pass backend_pool;
+
+ # 故障转移配置
+ tunnel_next_upstream error timeout;
+ tunnel_next_upstream_tries 3;
+ tunnel_connect_timeout 30s;
+
+ # 本地绑定地址
+ tunnel_bind $server_addr;
+ }
+ }
+}
+```
+
+---
+
+### 3.3 动态目标路由
+
+```nginx
+http {
+ map $http_x_target_host $tunnel_target {
+ default $host:$request_port;
+ api.internal 10.0.0.100:8443;
+ db.internal unix:/var/run/db.sock;
+ }
+
+ server {
+ listen 8000;
+
+ location / {
+ tunnel_pass $tunnel_target;
+ tunnel_connect_timeout 10s;
+ tunnel_read_timeout 300s;
+ }
+ }
+}
+```
+
+---
+
+## 4. WebSocket 隧道配置
+
+**注意**: HTTP Tunnel 模块主要用于 TCP 隧道。WebSocket 支持取决于具体实现。
+
+### 4.1 WebSocket 代理配置
+
+```nginx
+http {
+ map $http_upgrade $connection_upgrade {
+ default upgrade;
+ '' close;
+ }
+
+ upstream websocket_backend {
+ server 127.0.0.1:9000;
+ }
+
+ server {
+ listen 8000;
+
+ location /ws/ {
+ # WebSocket 升级
+ proxy_http_version 1.1;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection $connection_upgrade;
+
+ # 隧道穿透(如后端支持 CONNECT)
+ tunnel_pass websocket_backend;
+
+ # 长连接超时
+ tunnel_read_timeout 3600s;
+ tunnel_send_timeout 3600s;
+ }
+ }
+}
+```
+
+---
+
+## 5. 与 stream 模块的区别
+
+| 特性 | HTTP Tunnel Module | Stream Module |
+|------|-------------------|---------------|
+| **层级** | HTTP 层 (L7) | 传输层 (L4) |
+| **协议** | 处理 HTTP CONNECT 请求 | 原始 TCP/UDP |
+| **授权** | 商业订阅 | 开源免费 |
+| **配置块** | `http`/`server`/`location` | `stream`/`server` |
+| **路由能力** | 基于 HTTP 头、Host 等 L7 信息 | 仅基于 IP/端口 |
+| **变量支持** | 丰富的 HTTP 变量 | 有限的 stream 变量 |
+| **访问控制** | 可基于域名、端口、自定义条件 | 基于 IP 的 allow/deny |
+| **典型用途** | HTTP 代理、HTTPS 穿透 | 数据库代理、TCP 负载均衡 |
+
+### 5.1 stream 模块示例对比
+
+```nginx
+# stream 模块 (L4 层)
+stream {
+ server {
+ listen 8443;
+ proxy_pass backend_pool;
+ }
+}
+
+# tunnel 模块 (L7 层,处理 CONNECT)
+http {
+ server {
+ listen 8000;
+ location / {
+ tunnel_pass backend_pool;
+ }
+ }
+}
+```
+
+---
+
+## 6. 与 Lolly 项目的关系和建议
+
+### 6.1 Lolly 项目概述
+
+Lolly 是一个 Go 语言实现的高性能网络代理/隧道项目,专注于:
+- 轻量级部署
+- 简洁的配置
+- 高性能转发
+
+### 6.2 功能对比
+
+| 特性 | NGINX Tunnel | Lolly |
+|------|-------------|-------|
+| **CONNECT 支持** | 原生支持 | 需确认实现 |
+| **配置复杂度** | 高(多指令组合) | 低(简洁配置) |
+| **动态路由** | 支持变量 | 需确认 |
+| **故障转移** | 完善的上游故障转移 | 需确认 |
+| **性能** | 高(C 语言优化) | 高(Go 并发模型) |
+| **可观测性** | 标准日志 | 可定制 |
+| **扩展性** | 模块扩展 | 代码扩展 |
+
+### 6.3 对 Lolly 的建议
+
+#### 6.3.1 参考设计
+
+1. **CONNECT 方法处理**: 参考 `tunnel_pass` 的默认行为 `$host:$request_port`
+2. **条件访问控制**: 实现类似 `tunnel_allow_upstream` 的灵活条件评估
+3. **超时分层**: 区分连接、读取、发送超时,默认值参考 NGINX
+
+#### 6.3.2 差异化优势
+
+1. **简化配置**: Lolly 可提供更简洁的单行配置实现常见场景
+2. **原生可观测性**: 内置 pprof、指标导出(已支持 pprof 端点)
+3. **动态重载**: Go 的热重载比 NGINX 更友好
+
+#### 6.3.3 建议新增功能
+
+```yaml
+# 建议的 Lolly 配置格式
+tunnel:
+ enable: true
+ default_target: "$host:$port" # 类似 tunnel_pass
+ allowed_ports: [443, 8443] # 类似 map $allow_port
+ allowed_hosts: ["*.example.com"]
+ timeouts:
+ connect: 60s
+ read: 300s
+ send: 60s
+ bind_address: "0.0.0.0" # 类似 tunnel_bind
+ keepalive: true # 类似 tunnel_socket_keepalive
+```
+
+#### 6.3.4 实现优先级
+
+| 优先级 | 功能 | 参考 NGINX 指令 |
+|--------|------|---------------|
+| P0 | 基础 CONNECT 处理 | `tunnel_pass` |
+| P0 | 访问控制(端口/域名) | `tunnel_allow_upstream` |
+| P1 | 连接超时 | `tunnel_connect_timeout` |
+| P1 | 读写超时 | `tunnel_read_timeout` / `tunnel_send_timeout` |
+| P2 | 本地地址绑定 | `tunnel_bind` |
+| P2 | TCP Keepalive | `tunnel_socket_keepalive` |
+| P3 | 上游故障转移 | `tunnel_next_upstream` |
+
+---
+
+## 附录
+
+### A. 完整配置模板
+
+```nginx
+http {
+ # 1. 访问控制映射
+ map $request_port $tunnel_allow_port {
+ 443 1;
+ 8443 1;
+ default 0;
+ }
+
+ map $host $tunnel_allow_host {
+ hostnames;
+ example.com 1;
+ *.example.com 1;
+ default 0;
+ }
+
+ # 2. 代理服务器
+ server {
+ listen 8000;
+ server_name proxy.example.com;
+
+ # DNS 解析
+ resolver 8.8.8.8 8.8.4.4 valid=30s;
+
+ # 访问控制
+ if ($tunnel_allow_port != 1) {
+ return 403;
+ }
+
+ if ($tunnel_allow_host != 1) {
+ return 403;
+ }
+
+ # 隧道配置
+ tunnel_pass $host:$request_port;
+
+ # 超时配置
+ tunnel_connect_timeout 30s;
+ tunnel_read_timeout 300s;
+ tunnel_send_timeout 60s;
+
+ # 缓冲配置
+ tunnel_buffer_size 32k;
+
+ # Keepalive
+ tunnel_socket_keepalive on;
+
+ # 日志
+ access_log /var/log/nginx/tunnel_access.log;
+ error_log /var/log/nginx/tunnel_error.log;
+ }
+}
+```
+
+### B. 调试命令
+
+```bash
+# 测试 CONNECT 请求
+curl -v -x http://nginx:8000 https://example.org
+
+# 查看连接状态
+nginx -T | grep tunnel
+
+# 监控日志
+tail -f /var/log/nginx/tunnel_access.log
+tail -f /var/log/nginx/tunnel_error.log
+```
+
+### C. 参考资料
+
+- [NGINX 官方文档](https://nginx.org/en/docs/http/ngx_http_tunnel_module.html)
+- [RFC 9110 - CONNECT 方法](https://datatracker.ietf.org/doc/html/rfc9110#section-9.3.6)
+- [F5 NGINX 商业订阅](https://www.f5.com/products/nginx)