diff --git a/config.example.yaml b/config.example.yaml index 3378c48..a44e66d 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -175,7 +175,7 @@ logging: format: "text" # 全局日志格式(有效值: text, json),控制启动/停止日志格式 access: path: "" # 日志文件路径(空表示输出到 stdout) - format: "$remote_addr - $remote_user [$time] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent"" # 访问日志格式,近似 nginx combined + format: '$remote_addr - $remote_user [$time] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent"' # 访问日志格式,近似 nginx combined # 支持变量: $remote_addr, $remote_user, $request, $status, $body_bytes_sent, $request_time, $http_referer, $http_user_agent, $time # 特殊值 "json" 输出结构化 JSON error: diff --git a/docs/04-nginx-proxy-loadbalancing.md b/docs/04-nginx-proxy-loadbalancing.md index 9d9d017..f011478 100644 --- a/docs/04-nginx-proxy-loadbalancing.md +++ b/docs/04-nginx-proxy-loadbalancing.md @@ -436,7 +436,207 @@ http { --- -## 12. FastCGI 代理 +## 12. 高级代理指令 + +### proxy_bind + +指定连接后端时使用的源地址,用于多网卡服务器选择出口IP。 + +```nginx +语法: proxy_bind address [transparent]; +默认: — +上下文: http, server, location +``` + +```nginx +proxy_bind $server_addr; # 使用服务器IP +proxy_bind 192.168.1.1 transparent; # 透明代理(需要root权限) +``` + +### proxy_intercept_errors + +拦截后端错误响应,配合 error_page 自定义错误页面。 + +```nginx +语法: proxy_intercept_errors on | off; +默认: off +上下文: http, server, location +``` + +```nginx +proxy_intercept_errors on; +error_page 500 502 503 504 /50x.html; +``` + +### proxy_hide_header / proxy_pass_header + +控制后端响应头的传递行为: + +```nginx +# 隐藏后端返回的特定头 +proxy_hide_header X-Powered-By; +proxy_hide_header X-Runtime; + +# 传递被默认隐藏的头 +proxy_pass_header X-Accel-Redirect; +proxy_pass_header X-Accel-Limit-Rate; +``` + +### proxy_ignore_headers + +忽略后端的特定响应头(如缓存控制),允许NGINX处理这些头: + +```nginx +proxy_ignore_headers Cache-Control Expires X-Accel-Redirect X-Accel-Expires; +``` + +### proxy_cookie_* 系列 + +修改后端返回的 Set-Cookie 头: + +```nginx +# 修改域名 +proxy_cookie_domain localhost example.com; +proxy_cookie_domain off; # 禁用域名修改 + +# 修改路径 +proxy_cookie_path /foo/ /bar/; +proxy_cookie_path off; # 禁用路径修改 + +# 添加安全标志 +proxy_cookie_flags session httponly secure samesite=strict; +proxy_cookie_flags * samesite=lax; # 应用到所有cookie +``` + +### proxy_limit_rate + +限制从后端读取响应的传输速率: + +```nginx +proxy_limit_rate 100k; # 100KB/s +``` + +### proxy_request_buffering + +控制请求是否先完整缓冲再发送到后端: + +```nginx +proxy_request_buffering on; # 默认,完整缓冲 +proxy_request_buffering off; # 流式传输,支持上传进度 +``` + +### proxy_redirect + +修改后端返回的重定向头 Location 和 Refresh: + +```nginx +proxy_redirect default; # 使用默认替换 +proxy_redirect off; # 禁用替换 +proxy_redirect http://localhost:8080/ http://$host/; # 自定义替换 +proxy_redirect ~^http://([^/]+)/(.+)$ http://$host/$2; # 使用正则 +``` + +--- + +## 13. SSL 客户端证书认证 (proxy_ssl_*) + +用于 mTLS 双向认证场景,NGINX 作为客户端向后端提供证书: + +```nginx +location / { + proxy_pass https://backend.example.com; + + # mTLS 双向认证 + proxy_ssl_certificate /path/to/client.crt; + proxy_ssl_certificate_key /path/to/client.key; + + # 验证后端证书 + proxy_ssl_verify on; + proxy_ssl_trusted_certificate /path/to/ca.crt; + proxy_ssl_verify_depth 2; + + # SSL协议和加密套件 + proxy_ssl_protocols TLSv1.2 TLSv1.3; + proxy_ssl_ciphers HIGH:!aNULL; + + # 会话复用 + proxy_ssl_session_reuse on; + + # SNI支持 + proxy_ssl_server_name on; + proxy_ssl_name backend.example.com; +} +``` + +| 指令 | 说明 | 默认值 | +|------|------|--------| +| `proxy_ssl_certificate` | 客户端证书路径 | — | +| `proxy_ssl_certificate_key` | 客户端私钥路径 | — | +| `proxy_ssl_verify` | 验证后端证书 | off | +| `proxy_ssl_trusted_certificate` | 受信任CA证书 | — | +| `proxy_ssl_verify_depth` | 验证深度 | 1 | +| `proxy_ssl_protocols` | 启用的协议 | TLSv1.2 TLSv1.3 | +| `proxy_ssl_ciphers` | 加密套件 | DEFAULT | +| `proxy_ssl_session_reuse` | 会话复用 | on | +| `proxy_ssl_name` | SNI名称 | — | + +--- + +## 14. 高级缓存指令 + +### proxy_cache_methods + +指定可缓存的请求方法: + +```nginx +proxy_cache_methods GET HEAD POST; # 可缓存 POST 请求 +``` + +### proxy_cache_min_uses + +设置最小访问次数才开始缓存,避免缓存低频请求: + +```nginx +proxy_cache_min_uses 3; # 第3次访问才开始缓存 +``` + +### proxy_cache_background_update + +后台异步更新过期缓存(类似 stale-while-revalidate): + +```nginx +proxy_cache_background_update on; +``` + +### proxy_cache_revalidate + +使用 If-Modified-Since 和 If-None-Match 重新验证缓存: + +```nginx +proxy_cache_revalidate on; # 减少数据传输 +``` + +### proxy_cache_convert_head + +自动将 HEAD 请求转为 GET 以获取响应体: + +```nginx +proxy_cache_convert_head on; # 默认启用 +``` + +### proxy_cache_purge + +支持 PURGE 方法清除缓存(需编译时启用): + +```nginx +location ~ /purge(/.*) { + proxy_cache_purge cache_zone $1; +} +``` + +--- + +## 15. FastCGI 代理 ### 基础配置 @@ -449,22 +649,113 @@ location ~ \.php$ { } ``` -### 常用 FastCGI 指令 +### FastCGI 指令完整列表 -| 指令 | 说明 | -|------|------| -| `fastcgi_pass` | FastCGI 服务器地址 | -| `fastcgi_index` | URI 以斜杠结尾时追加的文件名 | -| `fastcgi_param` | 传递参数给 FastCGI 服务器 | -| `fastcgi_split_path_info` | 分离 SCRIPT_NAME 和 PATH_INFO | -| `fastcgi_connect_timeout` | 连接超时 | -| `fastcgi_read_timeout` | 读取超时 | -| `fastcgi_buffer_size` | 响应头缓冲区大小 | -| `fastcgi_buffers` | 响应体缓冲区 | +| 指令 | 语法 | 默认值 | 上下文 | +|------|------|--------|--------| +| `fastcgi_pass` | fastcgi_pass address; | — | location | +| `fastcgi_index` | fastcgi_index name; | — | http, server, location | +| `fastcgi_param` | fastcgi_param parameter value [if_not_empty]; | — | http, server, location | +| `fastcgi_split_path_info` | fastcgi_split_path_info regex; | — | location | +| `fastcgi_buffer_size` | fastcgi_buffer_size size; | 4k/8k | http, server, location | +| `fastcgi_buffers` | fastcgi_buffers number size; | 8 4k/8k | http, server, location | +| `fastcgi_busy_buffers_size` | fastcgi_busy_buffers_size size; | 8k/16k | http, server, location | +| `fastcgi_temp_file_write_size` | fastcgi_temp_file_write_size size; | 8k/16k | http, server, location | +| `fastcgi_temp_path` | fastcgi_temp_path path [level1 [level2 [level3]]]; | — | http, server, location | +| `fastcgi_cache` | fastcgi_cache zone; | — | http, server, location | +| `fastcgi_cache_key` | fastcgi_cache_key string; | — | http, server, location | +| `fastcgi_cache_valid` | fastcgi_cache_valid [code...] time; | — | http, server, location | +| `fastcgi_cache_methods` | fastcgi_cache_methods method...; | GET HEAD | http, server, location | +| `fastcgi_cache_min_uses` | fastcgi_cache_min_uses number; | 1 | http, server, location | +| `fastcgi_cache_bypass` | fastcgi_cache_bypass string...; | — | http, server, location | +| `fastcgi_no_cache` | fastcgi_no_cache string...; | — | http, server, location | +| `fastcgi_cache_use_stale` | fastcgi_cache_use_stale condition...; | — | http, server, location | +| `fastcgi_cache_background_update` | fastcgi_cache_background_update on/off; | off | http, server, location | +| `fastcgi_cache_revalidate` | fastcgi_cache_revalidate on/off; | off | http, server, location | +| `fastcgi_cache_lock` | fastcgi_cache_lock on/off; | off | http, server, location | +| `fastcgi_cache_lock_timeout` | fastcgi_cache_lock_timeout time; | 5s | http, server, location | +| `fastcgi_cache_convert_head` | fastcgi_cache_convert_head on/off; | on | http, server, location | +| `fastcgi_connect_timeout` | fastcgi_connect_timeout time; | 60s | http, server, location | +| `fastcgi_send_timeout` | fastcgi_send_timeout time; | 60s | http, server, location | +| `fastcgi_read_timeout` | fastcgi_read_timeout time; | 60s | http, server, location | +| `fastcgi_send_lowat` | fastcgi_send_lowat size; | 0 | http, server, location | +| `fastcgi_request_buffering` | fastcgi_request_buffering on/off; | on | http, server, location | +| `fastcgi_intercept_errors` | fastcgi_intercept_errors on/off; | off | http, server, location | +| `fastcgi_hide_header` | fastcgi_hide_header field; | — | http, server, location | +| `fastcgi_pass_header` | fastcgi_pass_header field; | — | http, server, location | +| `fastcgi_ignore_headers` | fastcgi_ignore_headers field...; | — | http, server, location | +| `fastcgi_limit_rate` | fastcgi_limit_rate rate; | 0 | http, server, location | + +### FastCGI 缓存完整配置示例 + +```nginx +http { + # 缓存路径定义 + fastcgi_cache_path /var/cache/nginx/php + levels=1:2 + keys_zone=php:10m + max_size=100m + inactive=60m + use_temp_path=off; + + server { + listen 80; + server_name example.com; + + location ~ \.php$ { + # 启用缓存 + fastcgi_cache php; + fastcgi_cache_key "$scheme$request_method$host$request_uri"; + + # 缓存有效期 + fastcgi_cache_valid 200 302 1h; + fastcgi_cache_valid 404 1m; + fastcgi_cache_valid any 5m; + + # 使用过期缓存 + fastcgi_cache_use_stale error timeout updating http_500 http_503; + + # 后台更新 + fastcgi_cache_background_update on; + + # 重新验证 + fastcgi_cache_revalidate on; + + # 缓存锁 + fastcgi_cache_lock on; + fastcgi_cache_lock_timeout 5s; + + # 绕过缓存条件 + fastcgi_cache_bypass $cookie_nocache $arg_nocache; + fastcgi_no_cache $http_pragma $http_authorization; + + # FastCGI 后端 + fastcgi_pass unix:/run/php/php-fpm.sock; + fastcgi_index index.php; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + + # 超时设置 + fastcgi_connect_timeout 5s; + fastcgi_send_timeout 60s; + fastcgi_read_timeout 60s; + + # 缓冲配置 + fastcgi_buffer_size 16k; + fastcgi_buffers 8 16k; + fastcgi_busy_buffers_size 32k; + + # 错误处理 + fastcgi_intercept_errors on; + + include fastcgi_params; + } + } +} +``` --- -## 13. 内置变量 +## 16. 内置变量 | 变量 | 说明 | |------|------| @@ -474,7 +765,7 @@ location ~ \.php$ { --- -## 14. 综合配置示例 +## 17. 综合配置示例 ```nginx http { diff --git a/docs/09-nginx-security.md b/docs/09-nginx-security.md index cf97565..c2c6dc5 100644 --- a/docs/09-nginx-security.md +++ b/docs/09-nginx-security.md @@ -41,7 +41,235 @@ http { --- -## 2. HTTP 基础认证 +## 2. 外部认证 (ngx_http_auth_request_module) + +NGINX 的 `ngx_http_auth_request_module` 模块(从 NGINX 1.5.4+ 开始支持,需编译时启用)提供外部认证功能,允许通过发送子请求到认证服务来决定是否允许访问。 + +### 2.1 指令说明 + +| 指令 | 语法 | 默认值 | 上下文 | +|------|------|--------|--------| +| `auth_request` | `auth_request uri \| off;` | `off` | http, server, location | +| `auth_request_set` | `auth_request_set $variable value;` | — | http, server, location | + +### 2.2 工作原理 + +1. **NGINX 发送子请求**:当收到客户端请求时,NGINX 向指定的认证 URI 发送子请求 +2. **认证服务响应**:认证服务返回 HTTP 状态码(200 表示通过,401/403 表示拒绝) +3. **结果判定**: + - 返回 200:NGINX 继续处理原请求 + - 返回 401/403:NGINX 拒绝访问并返回相应状态码 + - 其他状态码:视为错误,返回 500 + +**流程图**: +``` +Client → NGINX → auth_request (子请求) + ↓ + 认证服务 + ↓ + 200 OK? → 是 → 处理原请求 → 后端服务 + ↓ + 否 + ↓ + 返回 401/403 → Client +``` + +### 2.3 基础配置示例 + +```nginx +server { + listen 80; + server_name api.example.com; + + location /protected/ { + auth_request /auth; + proxy_pass http://backend; + } + + # 认证子请求 location + location = /auth { + internal; # 仅限内部子请求访问 + proxy_pass http://auth-server/verify; + proxy_pass_request_body off; # 不传递请求体到认证服务 + proxy_set_header Content-Length ""; + proxy_set_header X-Original-URI $request_uri; + proxy_set_header X-Client-IP $remote_addr; + } +} +``` + +### 2.4 带变量提取的认证 + +从认证服务响应中提取自定义头部到变量: + +```nginx +server { + location /api/ { + auth_request /auth; + auth_request_set $auth_user $upstream_http_x_user; + auth_request_set $auth_role $upstream_http_x_role; + + proxy_pass http://backend; + proxy_set_header X-User $auth_user; + proxy_set_header X-Role $auth_role; + } + + location = /auth { + internal; + proxy_pass http://auth-service/verify; + proxy_pass_request_body off; + proxy_set_header Authorization $http_authorization; + } +} +``` + +**可用变量**: +- `$upstream_http_*`:从认证服务响应中提取任意 HTTP 头部 +- `$upstream_status`:认证服务返回的状态码 +- `$upstream_response_time`:认证响应时间 + +### 2.5 JWT/OAuth2 集成示例 + +```nginx +server { + listen 443 ssl; + server_name api.example.com; + + location /api/ { + auth_request /auth_jwt; + auth_request_set $jwt_claims $upstream_http_x_jwt_claims; + auth_request_set $jwt_sub $upstream_http_x_jwt_sub; + + proxy_pass http://api_backend; + proxy_set_header X-JWT-Claims $jwt_claims; + proxy_set_header X-User-Id $jwt_sub; + + # 自定义 401 响应 + error_page 401 = @unauthorized; + } + + location = /auth_jwt { + internal; + proxy_pass http://jwt-validator/verify; + proxy_set_header Authorization $http_authorization; + proxy_set_header X-Original-URI $request_uri; + proxy_pass_request_body off; + } + + location @unauthorized { + default_type application/json; + return 401 '{"error":"Unauthorized","code":"INVALID_TOKEN"}'; + } +} +``` + +### 2.6 多级认证组合 + +结合 `auth_request` 与 `auth_basic`: + +```nginx +server { + location /admin/ { + # 先进行基础认证 + auth_basic "Admin Access"; + auth_basic_user_file /etc/nginx/.htpasswd; + + # 再进行外部权限校验 + auth_request /auth_admin; + + proxy_pass http://admin_backend; + } + + location = /auth_admin { + internal; + proxy_pass http://permission-service/check-admin; + proxy_set_header X-User $remote_user; + } +} +``` + +### 2.7 错误处理 + +```nginx +server { + location /api/ { + auth_request /auth; + + # 认证失败重定向到登录页 + error_page 401 = @login; + + proxy_pass http://backend; + } + + location @login { + return 302 https://auth.example.com/login?redirect=$request_uri; + } + + # 认证服务不可用时 + error_page 500 = @auth_error; + + location @auth_error { + return 503 '{"error":"Authentication service unavailable"}'; + } +} +``` + +### 2.8 应用场景 + +| 场景 | 实现方式 | +|------|----------| +| **OAuth2/OIDC 集成** | 认证服务验证 access_token,返回用户信息 | +| **JWT Token 验证** | 验证 JWT 签名和过期时间,提取 claims | +| **统一认证网关** | 集中处理多个服务的认证逻辑 | +| **权限分级验证** | 根据路径或资源进行细粒度权限检查 | +| **多因素认证** | 组合多种认证方式(密码 + 短信/邮件) | +| **API Key 验证** | 验证请求中的 API Key 有效性 | + +### 2.9 最佳实践 + +**1. 认证服务高可用** +```nginx +upstream auth_backend { + server 192.168.1.10:8080; + server 192.168.1.11:8080 backup; + keepalive 32; +} + +location = /auth { + internal; + proxy_pass http://auth_backend/verify; + proxy_connect_timeout 5s; + proxy_send_timeout 5s; + proxy_read_timeout 5s; +} +``` + +**2. 缓存认证结果**(减少重复验证) +```nginx +location /api/ { + auth_request /auth; + + # 启用缓存(需配合 proxy_cache) + proxy_cache auth_cache; + proxy_cache_valid 200 1m; + + proxy_pass http://backend; +} +``` + +**3. 调试认证流程** +```nginx +# 记录认证请求日志 +log_format auth_log '$remote_addr - $time_local ' + 'auth_status=$auth_request_status ' + 'user=$auth_user'; + +access_log /var/log/nginx/auth.log auth_log; +``` + +--- + +## 3. HTTP 基础认证 ### 配置认证 @@ -93,7 +321,7 @@ location /admin/ { --- -## 3. 请求限制 +## 4. 请求限制 ### 请求速率限制 @@ -168,9 +396,34 @@ http { } ``` +### Dry Run 模式(限流测试) + +NGINX 支持限流 dry run 模式,用于测试限流配置而不实际拒绝请求: + +```nginx +server { + location /api/ { + limit_req zone=req_limit burst=20 nodelay; + limit_req_dry_run on; # 请求不被拒绝,但记录日志 + + limit_conn conn_limit 10; + limit_conn_dry_run on; # 连接不被拒绝,但记录日志 + + proxy_pass http://backend; + } +} +``` + +**Dry Run 作用**: +- 限流判断正常执行,但不返回 503/429 错误 +- 记录 `limiting requests/connections, dry run` 到错误日志 +- 用于评估限流阈值是否设置合理 + +> **注意**:详细限流配置请参考 [20-nginx-rate-limiting.md](./20-nginx-rate-limiting.md) + --- -## 4. 安全头部 +## 5. 安全头部 ### 基础安全头部 @@ -212,7 +465,7 @@ server { --- -## 5. 防盗链 +## 6. 防盗链 ### 基础防盗链 @@ -240,7 +493,7 @@ location ~* \.(jpg|jpeg|png|gif|webp|flv|mp4|swf)$ { --- -## 6. SSL/TLS 安全 +## 7. SSL/TLS 安全 ### 协议配置 @@ -278,7 +531,7 @@ openssl dhparam -out /etc/nginx/ssl/dhparam.pem 2048 --- -## 7. 防止常见攻击 +## 8. 防止常见攻击 ### SQL 注入防护 @@ -337,7 +590,7 @@ http { --- -## 8. 限制特定 User-Agent +## 9. 限制特定 User-Agent ```nginx # 阻止恶意爬虫 @@ -365,7 +618,7 @@ if ($block_ua) { --- -## 9. WAF 配置(ModSecurity) +## 10. WAF 配置(ModSecurity) ### 安装 ModSecurity @@ -392,7 +645,7 @@ modsecurity_rules ' --- -## 10. fail2ban 集成 +## 11. fail2ban 集成 ### 创建 filter @@ -419,7 +672,7 @@ maxretry = 10 --- -## 11. 安全配置检查清单 +## 12. 安全配置检查清单 ### 基础安全 @@ -459,7 +712,7 @@ maxretry = 10 --- -## 12. 安全测试工具 +## 13. 安全测试工具 ### 在线测试 diff --git a/docs/10-nginx-stream-tcp-udp.md b/docs/10-nginx-stream-tcp-udp.md index efeb83e..dcced8e 100644 --- a/docs/10-nginx-stream-tcp-udp.md +++ b/docs/10-nginx-stream-tcp-udp.md @@ -104,6 +104,8 @@ stream { | 轮询 | 默认 | 加权轮询 | | 最少连接 | `least_conn;` | 连接数最少优先 | | 哈希 | `hash key [consistent];` | 基于键哈希 | +| 最少时间 | `least_time header \| last_byte \| last_byte inflight;` | 最小响应时间(NGINX Plus) | +| 随机 | `random [two] [least_conn];` | 随机选择 | ### server 参数 @@ -117,6 +119,67 @@ stream { | `down` | 标记不可用 | | `resolve` | 解析域名 IP 变化 | +### 新负载均衡算法 + +#### least_time(最小响应时间) + +**版本要求**:NGINX Plus + +```nginx +upstream mysql_backend { + least_time header; # 或 last_byte, last_byte inflight + server 192.168.1.1:3306; + server 192.168.1.2:3306; + zone mysql 64k; +} +``` + +**参数说明**: + +| 参数 | 说明 | +|------|------| +| `header` | 以接收到上游第一个字节的时间为度量 | +| `last_byte` | 以接收到上游完整响应的时间为度量 | +| `last_byte inflight` | 考虑正在传输的数据 | + +#### random(随机选择) + +```nginx +upstream backend { + random; # 纯随机 + # random two; # 随机选两个,按权重选 + # random two least_conn; # 随机选两个,选连接少的 + server 192.168.1.1:3306; + server 192.168.1.2:3306; +} +``` + +**参数说明**: + +| 参数 | 说明 | +|------|------| +| `two` | 随机选择两个上游服务器,再根据负载均衡策略选择 | +| `least_conn` | 与 `two` 配合,选择连接数较少的 | + +### upstream zone 共享内存 + +`zone` 指令启用 upstream 配置的共享内存,多 worker 进程间共享连接状态: + +```nginx +upstream backend { + zone backend 64k; # 64k 共享内存 + server 192.168.1.1:3306; + server 192.168.1.2:3306; + keepalive 32; +} +``` + +**说明**: +- 共享内存使所有 worker 进程共享 upstream 状态 +- 必需用于 `least_conn`、`least_time`、健康检查等 +- 大小根据上游服务器数量调整 + + --- ## 4. 核心指令 @@ -144,6 +207,7 @@ server { | `proxy_protocol` | 启用 PROXY 协议 | | `backlog=N` | 连接队列长度 | | `so_keepalive` | TCP keepalive | +| `transparent` | 启用透明代理 | ### proxy_pass 指令 @@ -173,6 +237,40 @@ server { } ``` +### 其他核心指令 + +| 指令 | 语法 | 默认值 | 上下文 | 说明 | +|------|------|--------|--------|------| +| `proxy_bind` | `proxy_bind address [transparent];` | — | stream, server | 指定代理连接使用的本地地址 | +| `proxy_half_close` | `proxy_half_close on \| off;` | off | stream, server | 启用 TCP 半关闭支持 | +| `proxy_responses` | `proxy_responses number;` | — | stream, server (UDP) | UDP 每请求期望响应数 | +| `proxy_socket_keepalive` | `proxy_socket_keepalive on \| off;` | off | stream, server | 开启与上游的 TCP keepalive | + +**示例配置**: + +```nginx +# 透明代理 +server { + listen 3306 transparent; + proxy_bind $remote_addr transparent; + proxy_pass backend:3306; +} + +# UDP 配置 +server { + listen 53 udp; + proxy_pass dns_backend:53; + proxy_responses 1; # 每请求期望1个响应 +} + +# 半关闭支持(TCP 流式场景) +server { + listen 9000; + proxy_half_close on; + proxy_pass backend:9000; +} +``` + --- ## 5. SSL/TLS 配置 @@ -220,6 +318,37 @@ server { | `proxy_ssl_verify` | 验证上游证书 | | `proxy_ssl_server_name` | 启用 SNI | +#### 完整上游 SSL 配置示例 + +```nginx +server { + listen 3306; + proxy_pass ssl_backend:3306; + + proxy_ssl on; + proxy_ssl_protocols TLSv1.2 TLSv1.3; + proxy_ssl_ciphers HIGH:!aNULL; + proxy_ssl_certificate /path/to/client.crt; + proxy_ssl_certificate_key /path/to/client.key; + proxy_ssl_verify on; + proxy_ssl_trusted_certificate /path/to/ca.crt; + proxy_ssl_verify_depth 2; + proxy_ssl_name backend.example.com; + proxy_ssl_session_reuse on; +} +``` + +**指令说明**: + +| 指令 | 说明 | +|------|------| +| `proxy_ssl_certificate` | 客户端证书(mTLS) | +| `proxy_ssl_certificate_key` | 客户端证书私钥 | +| `proxy_ssl_ciphers` | 加密套件 | +| `proxy_ssl_verify_depth` | 证书链验证深度 | +| `proxy_ssl_name` | 验证上游证书的域名 | +| `proxy_ssl_session_reuse` | 启用 SSL 会话复用 | + --- ## 6. 基于名称的虚拟服务器(SNI) @@ -254,6 +383,47 @@ stream { } ``` +### SSL Preread SNI 路由(无需终止 SSL) + +**版本要求**:1.25.5+ + +使用 `ssl_preread` 模块在不解密的情况下读取 SNI 信息进行路由: + +```nginx +stream { + ssl_preread on; # 启用 ssl_preread + + map $ssl_preread_server_name $backend { + mysql.example.com mysql_backend; + redis.example.com redis_backend; + default default_backend; + } + + upstream mysql_backend { + server 192.168.1.1:3306; + } + + upstream redis_backend { + server 192.168.1.2:6379; + } + + upstream default_backend { + server 192.168.1.3:8080; + } + + server { + listen 443; + ssl_preread on; # 在 server 上下文启用 + proxy_pass $backend; + } +} +``` + +**说明**: +- `ssl_preread` 读取 ClientHello 中的 SNI 扩展 +- 无需配置 SSL 证书即可实现基于域名的路由 +- 适用于多服务共享端口的场景 + --- ## 7. PROXY 协议 @@ -332,15 +502,27 @@ server { ## 10. 连接保持 +### keepalive 连接池配置 + ```nginx upstream backend { server 192.168.1.1:3306; - keepalive 32; # 保持 32 个空闲连接 - keepalive_timeout 60s; # 空闲连接超时 - keepalive_requests 1000; # 单个连接最大请求数 + server 192.168.1.2:3306; + + keepalive 32; # 空闲连接池大小 + keepalive_requests 100; # 单连接最大请求(HTTP 适用) + keepalive_timeout 60s; # 空闲超时 } ``` +**指令说明**: + +| 指令 | 语法 | 默认值 | 说明 | +|------|------|--------|------| +| `keepalive` | `keepalive connections;` | — | 保持到上游的空闲连接数 | +| `keepalive_requests` | `keepalive_requests number;` | 100 | 单连接最大请求数 | +| `keepalive_timeout` | `keepalive_timeout timeout;` | 60s | 空闲连接超时时间 | + --- ## 11. 内置变量 @@ -358,6 +540,18 @@ upstream backend { | `$status` | 会话状态 | | `$ssl_preread_server_name` | SNI 名称 | +### 其他 stream 指令 + +| 指令 | 语法 | 默认值 | 说明 | +|------|------|--------|------| +| `preread_buffer_size` | `preread_buffer_size size;` | 16k | 预读缓冲区大小 | +| `preread_timeout` | `preread_timeout timeout;` | 30s | 预读超时时间 | +| `resolver` | `resolver address ... [valid=time];` | — | DNS 解析器 | +| `resolver_timeout` | `resolver_timeout time;` | 30s | 解析超时 | +| `tcp_nodelay` | `tcp_nodelay on \| off;` | on | 启用 TCP_NODELAY | +| `variables_hash_max_size` | `variables_hash_max_size size;` | 1024 | 变量哈希表最大大小 | +| `variables_hash_bucket_size` | `variables_hash_bucket_size size;` | 64 | 变量哈希表桶大小 | + --- ## 12. 应用场景示例 diff --git a/docs/20-nginx-rate-limiting.md b/docs/20-nginx-rate-limiting.md index 240a414..fc67193 100644 --- a/docs/20-nginx-rate-limiting.md +++ b/docs/20-nginx-rate-limiting.md @@ -32,6 +32,7 @@ http { | `$remote_addr` | 客户端 IP 地址(文本格式) | | `zone=name:size` | 共享内存区域名称和大小 | | `rate=Nr/s` 或 `rate=Nr/m` | 限流速率(每秒/每分钟请求数) | +| `sync` | 多 worker 间同步限流状态(1.15.2+) | **内存使用估算**: - 1MB 共享内存约可存储 16,000 个 IP 地址状态(使用 `$binary_remote_addr`) @@ -86,7 +87,54 @@ server { **可选级别**:`info`, `notice`, `warn`, `error` -### 1.5 burst 和 nodelay 参数详解 +### 1.5 limit_req_dry_run 试运行模式 + +``` +语法: limit_req_dry_run on | off; +默认: off +上下文: http, server, location +版本: 1.19.1+ +``` + +**功能**: 试运行模式,只记录限流事件但不实际拒绝请求。用于测试限流配置效果。 + +```nginx +http { + limit_req_zone $binary_remote_addr zone=prod:10m rate=100r/s; + + server { + location /api/ { + limit_req zone=prod burst=200 nodelay; + limit_req_dry_run on; # 试运行模式,不影响用户 + + proxy_pass http://backend; + } + } +} +``` + +**应用场景**: +- 生产环境测试新限流策略 +- 验证限流阈值是否合理 +- 收集真实流量数据 + +### 1.6 $limit_req 变量 + +``` +变量: $limit_req +功能: 存储请求因限流被延迟的时间(毫秒) +``` + +**用途**: 在日志中记录限流延迟,用于监控分析。 + +```nginx +log_format limit '$remote_addr - $limit_req ms - $request_uri'; +access_log /var/log/nginx/limit.log limit; +``` + +**说明**: 如果请求未被限流延迟,变量值为空。 + +### 1.7 burst 和 nodelay 参数详解 **令牌桶算法原理**: @@ -132,7 +180,7 @@ limit_req zone=ip_limit burst=20 delay=10; - 第 11-20 个请求延迟处理 - 超过 20 个请求返回 503 -### 1.6 多区域限流配置 +### 1.8 多区域限流配置 **分层限流策略**: @@ -174,6 +222,26 @@ http { - 任一区域超限即触发限流 - 可实现更精细的控制策略 +### 1.9 动态试运行控制 + +```nginx +# 通过请求头控制试运行模式 +map $http_x_dry_run $dry_run_mode { + default off; + test on; +} + +server { + location /api/ { + limit_req zone=api burst=200; + limit_req_dry_run $dry_run_mode; + proxy_pass http://backend; + } +} +``` + +**说明**: `limit_req_dry_run` 支持使用变量动态控制,可根据请求头、Cookie 等条件灵活开启/关闭试运行模式。 + --- ## 2. ngx_http_limit_conn_module (连接限制) @@ -199,6 +267,20 @@ http { - 不需要 `rate` 参数(只计数,不限速) - 统计的是并发连接数,不是请求速率 +**sync 参数**(1.15.2+): + +在多 worker 环境下(worker_processes > 1),默认各 worker 独立计数。sync 参数使所有 worker 共享计数,实现精确的全局限流。 + +```nginx +limit_conn_zone $binary_remote_addr zone=addr:10m sync; +limit_req_zone $binary_remote_addr zone=one:10m rate=10r/s sync; +``` + +**适用场景**: +- 多 worker 部署需要精确统计 +- 分布式限流场景 +- 高并发环境防止计数偏差 + ### 2.2 limit_conn 应用连接限制 ```nginx @@ -231,7 +313,32 @@ server { } ``` -### 2.3 limit_conn_status 设置拒绝状态码 +### 2.3 limit_conn_dry_run 试运行模式 + +``` +语法: limit_conn_dry_run on | off; +默认: off +上下文: http, server, location +版本: 1.19.1+ +``` + +**功能**: 连接限制试运行模式。 + +```nginx +http { + limit_conn_zone $binary_remote_addr zone=addr:10m; + + server { + location /download/ { + limit_conn addr 10; + limit_conn_dry_run on; + proxy_pass http://backend; + } + } +} +``` + +### 2.4 limit_conn_status 设置拒绝状态码 ```nginx server { @@ -242,7 +349,7 @@ server { } ``` -### 2.4 limit_conn_log_level 设置日志级别 +### 2.5 limit_conn_log_level 设置日志级别 ```nginx server { diff --git a/docs/21-nginx-http2-http3.md b/docs/21-nginx-http2-http3.md index decf231..638cff9 100644 --- a/docs/21-nginx-http2-http3.md +++ b/docs/21-nginx-http2-http3.md @@ -411,11 +411,160 @@ quic_retry on; quic_max_udp_payload_size 1200; ``` +### 6.6 http3_stream_buffer_size + +| 语法 | 默认值 | 上下文 | +|------|--------|--------| +| `http3_stream_buffer_size size;` | `64k` | `http`, `server` | + +设置 HTTP/3 流缓冲区大小,用于控制单个流的内存使用。 + +```nginx +# 大文件传输场景 +http3_stream_buffer_size 128k; +``` + +### 6.7 http3_max_concurrent_streams + +| 语法 | 默认值 | 上下文 | +|------|--------|--------| +| `http3_max_concurrent_streams number;` | `128` | `http`, `server` | + +设置单个 HTTP/3 连接中最大并发流数量。 + +```nginx +# 高并发场景 +http3_max_concurrent_streams 256; +``` + +### 6.8 http3_max_field_size + +| 语法 | 默认值 | 上下文 | +|------|--------|--------| +| `http3_max_field_size size;` | `4k` | `http`, `server` | + +设置 QPACK 压缩后的请求头字段最大大小。 + +```nginx +# 支持较大的 Cookie 头部 +http3_max_field_size 16k; +``` + +### 6.9 http3_max_table_size + +| 语法 | 默认值 | 上下文 | +|------|--------|--------| +| `http3_max_table_size size;` | `16k` | `http`, `server` | + +设置 QPACK 动态表最大大小,影响头部压缩效率。 + +```nginx +# 提升压缩率 +http3_max_table_size 32k; +``` + --- -## 7. 0-RTT 连接配置 +## 7. ngx_http_quic_module 指令详解 -### 7.1 ssl_early_data 指令 +### 7.1 quic_bpf + +| 语法 | 默认值 | 上下文 | +|------|--------|--------| +| `quic_bpf on \| off;` | `off` | `http`, `server` | + +启用 eBPF 加速 QUIC 连接路由,提升多 worker 场景性能。 + +**要求**:Linux 5.6+ 内核和 eBPF 支持。 + +```nginx +# 高流量场景启用 eBPF 加速 +quic_bpf on; +``` + +### 7.2 quic_cc_algorithm + +| 语法 | 默认值 | 上下文 | +|------|--------|--------| +| `quic_cc_algorithm algorithm;` | `cubic` | `http`, `server` | + +设置 QUIC 拥塞控制算法。 + +| 算法 | 说明 | +|------|------| +| `cubic` | 默认,适合大多数场景 | +| `reno` | 经典 TCP 拥塞控制 | +| `bbr` | Google BBR,高延迟网络推荐 | + +```nginx +# 高延迟网络优化 +quic_cc_algorithm bbr; +``` + +### 7.3 quic_mtu + +| 语法 | 默认值 | 上下文 | +|------|--------|--------| +| `quic_mtu size;` | `—` | `http`, `server` | + +设置 QUIC MTU 大小,影响数据包分片。 + +```nginx +# 以太网标准 MTU +quic_mtu 1200; + +# 数据中心内部网络 +quic_mtu 1400; +``` + +### 7.4 quic_active_connection_id_limit + +| 语法 | 默认值 | 上下文 | +|------|--------|--------| +| `quic_active_connection_id_limit number;` | `8` | `http`, `server` | + +设置活跃连接 ID 数量限制,影响连接迁移能力。 + +```nginx +# 增强连接迁移能力 +quic_active_connection_id_limit 16; +``` + +### 7.5 quic_stack + +| 语法 | 默认值 | 上下文 | +|------|--------|--------| +| `quic_stack ngx \| boringssl;` | `ngx` | `http`, `server` | + +选择 QUIC 协议栈实现。 + +| 值 | 说明 | +|------|------| +| `ngx` | nginx 原生实现(推荐) | +| `boringssl` | BoringSSL QUIC 实现 | + +```nginx +quic_stack ngx; +``` + +### 7.6 quic_socket_options + +| 语法 | 默认值 | 上下文 | +|------|--------|--------| +| `quic_socket_options option ...;` | `—` | `http`, `server` | + +设置 QUIC socket 选项,用于优化 UDP 性能。 + +```nginx +# 优化 socket 缓冲区 +quic_socket_options receive_buffer=1m send_buffer=1m; +``` + +--- + +## 9. 0-RTT 连接配置 + +### 9.1 ssl_early_data 指令 | 语法 | 默认值 | 上下文 | |------|--------|--------| @@ -438,7 +587,7 @@ server { } ``` -### 7.2 0-RTT 安全注意事项 +### 9.2 0-RTT 安全注意事项 ```nginx # 限制 0-RTT 请求(防止重放攻击) @@ -463,9 +612,9 @@ server { --- -## 8. HTTP/3 完整配置示例 +## 10. HTTP/3 完整配置示例 -### 8.1 基础配置 +### 10.1 基础配置 ```nginx http { @@ -493,7 +642,7 @@ http { } ``` -### 8.2 生产环境配置 +### 10.2 生产环境配置 ```nginx http { @@ -553,9 +702,9 @@ http { --- -## 9. HTTP/1.1 vs HTTP/2 vs HTTP/3 对比 +## 11. HTTP/1.1 vs HTTP/2 vs HTTP/3 对比 -### 9.1 特性对比表 +### 11.1 特性对比表 | 特性 | HTTP/1.1 | HTTP/2 | HTTP/3 | |------|----------|--------|--------| @@ -570,7 +719,7 @@ http { | **握手延迟** | 高 | 中 | 低 | | **NAT 友好** | 是 | 是 | 需特殊处理 | -### 9.2 性能对比 +### 11.2 性能对比 | 场景 | HTTP/1.1 | HTTP/2 | HTTP/3 | |------|----------|--------|--------| @@ -579,7 +728,7 @@ http { | **丢包网络** | 基准 | 轻微下降 | 显著提升 | | **移动网络切换** | 需重连 | 需重连 | 无缝迁移 | -### 9.3 适用场景 +### 11.3 适用场景 | 协议 | 推荐场景 | |------|----------| @@ -589,9 +738,9 @@ http { --- -## 10. 迁移指南和兼容性 +## 12. 迁移指南和兼容性 -### 10.1 渐进式迁移策略 +### 12.1 渐进式迁移策略 ```nginx http { @@ -622,7 +771,7 @@ http { } ``` -### 10.2 Alt-Svc 头部详解 +### 12.2 Alt-Svc 头部详解 ```nginx # 基础声明 @@ -640,7 +789,7 @@ add_header Alt-Svc 'h3=":8443"; ma=3600' always; - `h3-29`:HTTP/3 草案版本(兼容旧客户端) - `ma`:最大有效期(秒) -### 10.3 浏览器兼容性 +### 12.3 浏览器兼容性 | 浏览器 | HTTP/2 | HTTP/3 | |--------|--------|--------| @@ -649,7 +798,7 @@ add_header Alt-Svc 'h3=":8443"; ma=3600' always; | Safari 11+ | 支持 | 14+ 实验性,后续稳定 | | Edge 79+ | 支持 | 87+ 实验性,后续稳定 | -### 10.4 回退策略 +### 12.4 回退策略 ```nginx map $http_user_agent $supports_http3 { @@ -674,9 +823,9 @@ server { --- -## 11. 性能优化建议 +## 13. 性能优化建议 -### 11.1 HTTP/2 优化 +### 13.1 HTTP/2 优化 ```nginx http { @@ -713,7 +862,7 @@ http { } ``` -### 11.2 HTTP/3 优化 +### 13.2 HTTP/3 优化 ```nginx http { @@ -746,7 +895,7 @@ http { } ``` -### 11.3 内核参数优化 +### 13.3 内核参数优化 ```bash # /etc/sysctl.conf @@ -770,7 +919,7 @@ net.netfilter.nf_conntrack_udp_timeout_stream = 120 sysctl -p ``` -### 11.4 监控指标 +### 13.4 监控指标 ```nginx # 在日志中记录协议版本 @@ -784,7 +933,7 @@ server { } ``` -### 11.5 调试检查 +### 13.5 调试检查 ```bash # 检查 HTTP/2 支持 @@ -805,9 +954,9 @@ curl -I https://www.example.com | grep -i alt-svc --- -## 12. 常见问题排查 +## 14. 常见问题排查 -### 12.1 HTTP/2 问题 +### 14.1 HTTP/2 问题 | 问题 | 原因 | 解决 | |------|------|------| @@ -815,7 +964,7 @@ curl -I https://www.example.com | grep -i alt-svc | 大量 STREAM_CLOSED 错误 | 客户端提前关闭 | 正常行为,无需处理 | | 内存占用高 | 流数量过多 | 减少 http2_max_concurrent_streams | -### 12.2 HTTP/3 问题 +### 14.2 HTTP/3 问题 | 问题 | 原因 | 解决 | |------|------|------| @@ -826,7 +975,7 @@ curl -I https://www.example.com | grep -i alt-svc --- -## 13. 参考链接 +## 15. 参考链接 - [NGINX HTTP/2 文档](https://nginx.org/en/docs/http/ngx_http_v2_module.html) - [NGINX HTTP/3 文档](https://nginx.org/en/docs/http/ngx_http_v3_module.html) diff --git a/docs/24-nginx-core-events.md b/docs/24-nginx-core-events.md index 4d359bd..351d001 100644 --- a/docs/24-nginx-core-events.md +++ b/docs/24-nginx-core-events.md @@ -49,7 +49,7 @@ worker_processes 8; worker_cpu_affinity 00000001 00000010 00000100 00001000 00010000 00100000 01000000 10000000; # 使用 auto 模式(自动绑定) -worker_cpu affinity auto; +worker_cpu_affinity auto; ``` **位掩码说明**: @@ -66,6 +66,31 @@ worker_cpu affinity auto; --- +#### worker_priority + +设置 worker 进程的调度优先级(类似 nice 命令)。 + +**语法**:`worker_priority number;` + +**默认值**:`worker_priority 0;` + +**上下文**:main + +```nginx +# 高优先级(关键服务) +worker_priority -5; + +# 低优先级(后台任务) +worker_priority 10; +``` + +**说明**: +- 负数表示更高优先级(-20 为最高) +- 正数表示更低优先级(19 为最低) +- 需要 root 权限才能设置为负值 + +--- + #### worker_rlimit_nofile 设置每个 worker 进程可以打开的最大文件描述符数量。 @@ -338,7 +363,66 @@ include /etc/nginx/conf.d/stream.conf; --- -### 1.7 系统相关指令 +### 1.7 动态模块加载指令 + +#### load_module + +动态加载 NGINX 模块(.so 文件)。 + +**语法**:`load_module file;` + +**默认值**:无 + +**上下文**:main + +**版本**:1.9.11+ + +```nginx +# 加载动态模块 +load_module modules/ngx_http_geoip_module.so; +load_module modules/ngx_stream_module.so; +load_module modules/ngx_http_image_filter_module.so; + +worker_processes auto; +events { ... } +``` + +**编译动态模块**: + +```bash +./configure --with-compat --add-dynamic-module=/path/to/module +make modules +``` + +**说明**: +- `load_module` 指令必须在 `events` 和 `http` 块之前 +- 模块文件路径可以是绝对路径或相对于前缀目录的路径 +- 需要 `--with-compat` 选项确保模块兼容性 + +--- + +### 1.8 系统相关指令 + +#### ssl_engine + +指定硬件 SSL 加速设备。 + +**语法**:`ssl_engine device;` + +**默认值**:无 + +**上下文**:main + +```nginx +ssl_engine /dev/crypto; # 使用硬件加密设备 +``` + +**说明**: +- 用于启用硬件 SSL 加速卡(如 OpenSSL 硬件引擎) +- 需要系统支持相应的加密硬件设备 +- 可以显著提升 SSL/TLS 加密解密性能 + +--- #### timer_resolution @@ -605,9 +689,90 @@ events { --- -## 3. 连接处理方法详解 +### 2.5 哈希表优化指令 -### 3.1 epoll (Linux) +#### types_hash_max_size 与 types_hash_bucket_size + +控制 MIME 类型哈希表的内存分配。 + +**语法**: +- `types_hash_max_size size;` +- `types_hash_bucket_size size;` + +**默认值**: +- `types_hash_max_size 1024;` +- `types_hash_bucket_size 64;` + +**上下文**:http, server, location + +```nginx +# 大量 MIME 类型时调整 +http { + types_hash_max_size 2048; + types_hash_bucket_size 128; +} +``` + +**说明**: +- `types_hash_max_size`:设置 MIME 类型哈希表的最大条目数 +- `types_hash_bucket_size`:设置每个哈希桶的大小(必须是 2 的幂) +- 当配置大量自定义 MIME 类型时需要调整 + +--- + +#### variables_hash_max_size 与 variables_hash_bucket_size + +控制变量哈希表的大小。 + +**语法**: +- `variables_hash_max_size size;` +- `variables_hash_bucket_size size;` + +**默认值**: +- `variables_hash_max_size 1024;` +- `variables_hash_bucket_size 64;` + +**上下文**:http + +```nginx +# 大量自定义变量 +http { + variables_hash_max_size 2048; + variables_hash_bucket_size 128; +} +``` + +**说明**: +- 当使用大量 `map` 指令或自定义变量时需要增大 +- 如果启动时报 "could not build variables_hash" 错误,需要调整这些值 + +--- + +## 3. 事件模型深入对比 + +| 模型 | 平台 | 内核要求 | FD 限制 | 触发模式 | 特性 | +|------|------|----------|---------|----------|------| +| epoll | Linux | 2.6+ | 内存限制 | ET/LT | EPOLLEXCLUSIVE (2.6.39+) | +| kqueue | FreeBSD/macOS | 4.1+ | 内存限制 | ET | EVFILT_TIMER/SIGNAL/PROC | +| eventport | Solaris | 10+ | 内存限制 | — | 多事件源支持 | +| /dev/poll | Solaris | — | 内存限制 | — | 状态持久化 | +| select | POSIX | — | 1024 | — | 不推荐生产使用 | +| poll | POSIX | — | 内存限制 | — | 不推荐生产使用 | + +**epoll vs kqueue 性能对比**: + +- **epoll**:Linux 标准,ET 模式需要循环读取直到 EAGAIN,支持 `EPOLLEXCLUSIVE`(2.6.39+)解决惊群问题 +- **kqueue**:FreeBSD/macOS 标准,API 更优雅统一,支持定时器/信号/进程事件过滤,天然无惊群问题 + +**触发模式说明**: +- **ET(Edge Triggered,边缘触发)**:仅在状态变化时通知,需一次性处理所有数据 +- **LT(Level Triggered,水平触发)**:只要就绪就通知,不需要一次性读完 + +--- + +## 4. 连接处理方法详解 + +### 4.1 epoll (Linux) `epoll` 是 Linux 2.6 内核引入的高效 I/O 多路复用机制。 @@ -659,7 +824,7 @@ NGINX 使用边缘触发模式,要求: --- -### 3.2 kqueue (FreeBSD/macOS) +### 4.2 kqueue (FreeBSD/macOS) `kqueue` 是 FreeBSD 引入的高性能事件通知机制,macOS 也支持。 @@ -706,7 +871,7 @@ NGINX 使用边缘触发模式,要求: --- -### 3.3 /dev/poll (Solaris) +### 4.3 /dev/poll (Solaris) Solaris 特有的 I/O 多路复用机制。 @@ -731,7 +896,7 @@ ioctl(dpfd, DP_POLL, &dvpoll); --- -### 3.4 eventport (Solaris) +### 4.4 eventport (Solaris) Solaris 10+ 引入的高性能事件端口机制。 @@ -756,7 +921,7 @@ port_get(port, &event, NULL); --- -### 3.5 select/poll (通用) +### 4.5 select/poll (通用) 标准的 POSIX I/O 多路复用机制,几乎所有平台都支持。 @@ -793,9 +958,9 @@ poll(fds, nfds, timeout); --- -## 4. 各平台最佳配置 +## 5. 各平台最佳配置 -### 4.1 Linux (2.6.39+) +### 5.1 Linux (2.6.39+) ```nginx # /etc/nginx/nginx.conf @@ -822,7 +987,7 @@ http { } ``` -### 4.2 FreeBSD +### 5.2 FreeBSD ```nginx # /usr/local/etc/nginx/nginx.conf @@ -838,7 +1003,7 @@ events { } ``` -### 4.3 macOS (开发环境) +### 5.3 macOS (开发环境) ```nginx # /usr/local/etc/nginx/nginx.conf @@ -853,7 +1018,7 @@ events { } ``` -### 4.4 Solaris +### 5.4 Solaris ```nginx # /etc/nginx/nginx.conf @@ -870,9 +1035,9 @@ events { --- -## 5. 性能调优建议 +## 6. 性能调优建议 -### 5.1 Worker 进程优化 +### 6.1 Worker 进程优化 ```nginx # 匹配 CPU 核心数 @@ -887,7 +1052,7 @@ worker_rlimit_nofile 65535; # daemon off; # 前台运行(Docker/systemd) ``` -### 5.2 事件处理优化 +### 6.2 事件处理优化 ```nginx events { @@ -907,7 +1072,7 @@ events { } ``` -### 5.3 内核参数优化 +### 6.3 内核参数优化 ```bash # /etc/sysctl.conf @@ -932,7 +1097,7 @@ fs.file-max = 2097152 fs.nr_open = 2097152 ``` -### 5.4 文件描述符限制 +### 6.4 文件描述符限制 ```bash # /etc/security/limits.conf @@ -947,15 +1112,15 @@ LimitNOFILE=65535 --- -## 6. 连接数计算公式 +## 7. 连接数计算公式 -### 6.1 基本公式 +### 7.1 基本公式 ``` 总并发连接数 = worker_processes × worker_connections ``` -### 6.2 详细计算 +### 7.2 详细计算 #### Web 服务器场景 @@ -983,7 +1148,7 @@ LimitNOFILE=65535 # worker_connections = (50000 + 64) / 4 × 2 ≈ 25,032 ``` -### 6.3 系统限制检查 +### 7.3 系统限制检查 ```bash # 检查系统文件描述符限制 @@ -997,7 +1162,7 @@ ss -s cat /proc/$(pgrep -o nginx)/limits | grep "Max open files" ``` -### 6.4 配置示例 +### 7.4 配置示例 ```nginx # 支持 100,000 并发连接的完整配置 @@ -1029,7 +1194,7 @@ http { } ``` -### 6.5 监控指标 +### 7.5 监控指标 ```nginx # 启用 stub_status 监控 @@ -1058,9 +1223,9 @@ Reading: 6 Writing: 128 Waiting: 157 # 读/写/等待状态连接数 --- -## 7. 完整配置示例 +## 8. 完整配置示例 -### 7.1 高性能 Web 服务器 +### 8.1 高性能 Web 服务器 ```nginx user nginx; @@ -1117,7 +1282,7 @@ http { } ``` -### 7.2 高性能反向代理 +### 8.2 高性能反向代理 ```nginx user nginx; @@ -1180,9 +1345,9 @@ http { --- -## 8. 常见问题排查 +## 9. 常见问题排查 -### 8.1 "too many open files" 错误 +### 9.1 "too many open files" 错误 **原因**: - `worker_rlimit_nofile` 设置过低 @@ -1202,7 +1367,7 @@ sysctl -p ulimit -n ``` -### 8.2 "worker_connections are not enough" 错误 +### 9.2 "worker_connections are not enough" 错误 **原因**:并发连接数超过 `worker_connections` 限制 @@ -1213,7 +1378,7 @@ events { } ``` -### 8.3 性能下降排查 +### 9.3 性能下降排查 ```bash # 检查 worker 进程是否均匀分布 @@ -1229,7 +1394,7 @@ top -p $(pgrep -d',' nginx) cat /proc/$(pgrep -o nginx)/limits ``` -### 8.4 热升级失败 +### 9.4 热升级失败 **原因**: - PID 文件路径错误 diff --git a/docs/25-nginx-variables-reference.md b/docs/25-nginx-variables-reference.md new file mode 100644 index 0000000..0d0909b --- /dev/null +++ b/docs/25-nginx-variables-reference.md @@ -0,0 +1,255 @@ +# Nginx 内置变量速查表 + +本文档汇总 nginx 所有模块提供的内置变量,便于快速查阅。 + +--- + +## 1. HTTP 核心模块变量 (ngx_http_core_module) + +### 请求信息 + +| 变量 | 说明 | 示例值 | +|------|------|--------| +| `$arg_name` | 请求参数 name 的值 | foo | +| `$args` | 所有请求参数 | a=1&b=2 | +| `$is_args` | 是否有参数 | ? 或 空 | +| `$query_string` | 同 $args | a=1&b=2 | +| `$content_length` | Content-Length 头 | 1024 | +| `$content_type` | Content-Type 头 | application/json | +| `$cookie_name` | Cookie name 的值 | session_id | + +### 客户端信息 + +| 变量 | 说明 | 示例值 | +|------|------|--------| +| `$remote_addr` | 客户端 IP | 192.168.1.1 | +| `$remote_port` | 客户端端口 | 54321 | +| `$binary_remote_addr` | 二进制 IP(4/16字节) | 用于 limit_conn | +| `$remote_user` | 认证用户名 | admin | + +### URI 相关 + +| 变量 | 说明 | 示例值 | +|------|------|--------| +| `$uri` | 当前请求 URI(解码后) | /path/to/file | +| `$document_uri` | 同 $uri | /path/to/file | +| `$request_uri` | 原始请求 URI(含参数) | /path?a=1 | +| `$host` | 请求主机名(优先 Host 头) | example.com | +| `$hostname` | 服务器主机名 | server01 | +| `$server_name` | server 配置的第一个名字 | example.com | + +### 请求详情 + +| 变量 | 说明 | 示例值 | +|------|------|--------| +| `$request` | 完整请求行 | GET / HTTP/1.1 | +| `$request_method` | 请求方法 | GET/POST | +| `$request_body` | 请求体内容 | {...} | +| `$request_body_file` | 请求体临时文件路径 | /tmp/... | +| `$request_completion` | 请求完成状态 | OK 或 空 | +| `$request_filename` | 映射的文件路径 | /var/www/index.html | +| `$request_id` | 唯一请求 ID(1.11.0+) | abc123... | +| `$request_length` | 请求长度(含头) | 2048 | +| `$request_time` | 请求处理时间(秒) | 0.001 | +| `$document_root` | root 指令值 | /var/www | +| `$realpath_root` | root 的真实路径 | /var/www | + +### 服务器信息 + +| 变量 | 说明 | 示例值 | +|------|------|--------| +| `$server_addr` | 服务器 IP | 10.0.0.1 | +| `$server_port` | 服务器端口 | 80 | +| `$scheme` | 协议 | http/https | +| `$server_protocol` | HTTP 版本 | HTTP/1.1 | +| `$https` | 是否 HTTPS | on 或 空 | + +### 响应信息 + +| 变量 | 说明 | 示例值 | +|------|------|--------| +| `$status` | 响应状态码 | 200/404 | +| `$body_bytes_sent` | 响应体字节数 | 1024 | +| `$bytes_sent` | 总发送字节 | 2048 | + +### 时间相关 + +| 变量 | 说明 | 示例值 | +|------|------|--------| +| `$time_local` | 本地时间 | 03/Apr/2026:14:30:00 | +| `$time_iso8601` | ISO8601 时间 | 2026-04-03T14:30:00 | +| `$msec` | 毫秒时间戳 | 1617456789.123 | + +### 连接信息 + +| 变量 | 说明 | 示例值 | +|------|------|--------| +| `$connection` | 连接序号 | 12345 | +| `$connection_requests` | 连接请求数 | 10 | +| `$pipe` | 是否管道化 | p 或 . | +| `$pid` | worker 进程 PID | 12345 | + +### PROXY 协议 + +| 变量 | 说明 | +|------|------| +| `$proxy_protocol_addr` | 客户端真实 IP | +| `$proxy_protocol_port` | 客户端真实端口 | +| `$proxy_protocol_server_addr` | 服务器 IP | +| `$proxy_protocol_server_port` | 服务器端口 | + +### TCP 信息 + +| 变量 | 说明 | +|------|------| +| `$tcpinfo_rtt` | RTT(微秒) | +| `$tcpinfo_rttvar` | RTT 方差 | +| `$tcpinfo_snd_cwnd` | 发送窗口 | +| `$tcpinfo_rcv_space` | 接收窗口 | + +--- + +## 2. Upstream 模块变量 (ngx_http_upstream_module) + +| 变量 | 说明 | 用途 | +|------|------|------| +| `$upstream_addr` | 后端地址 | 日志 | +| `$upstream_status` | 后端状态码 | 监控 | +| `$upstream_response_time` | 后端响应时间 | 性能分析 | +| `$upstream_response_length` | 后端响应长度 | 日志 | +| `$upstream_connect_time` | 连接耗时 | 性能分析 | +| `$upstream_header_time` | 头部接收耗时 | 性能分析 | +| `$upstream_first_byte_time` | 首字节时间 | 性能分析 | +| `$upstream_bytes_sent` | 发送到后端字节 | 流量统计 | +| `$upstream_bytes_received` | 从后端接收字节 | 流量统计 | +| `$upstream_cache_status` | 缓存状态 | HIT/MISS/BYPASS | +| `$upstream_http_name` | 后端响应头 | 提取认证信息 | +| `$upstream_cookie_name` | 后端 Cookie | 提取 Cookie | + +--- + +## 3. SSL/TLS 模块变量 (ngx_http_ssl_module) + +| 变量 | 说明 | +|------|------| +| `$ssl_cipher` | 加密套件 | +| `$ssl_ciphers` | 支持的加密套件列表 | +| `$ssl_protocol` | SSL 协议版本 | +| `$ssl_server_name` | SNI 服务器名 | +| `$ssl_session_id` | 会话 ID | +| `$ssl_session_reused` | 是否重用会话 | +| `$ssl_client_cert` | 客户端证书 | +| `$ssl_client_raw_cert` | 客户端原始证书 | +| `$ssl_client_escaped_cert` | 转义证书 | +| `$ssl_client_fingerprint` | 证书指纹 | +| `$ssl_client_i_dn` | 签发者 DN | +| `$ssl_client_s_dn` | 主体 DN | +| `$ssl_client_serial` | 证书序列号 | +| `$ssl_client_verify` | 验证结果 | +| `$ssl_client_v_start` | 证书开始时间 | +| `$ssl_client_v_end` | 证书结束时间 | +| `$ssl_client_v_remain` | 证书剩余天数 | +| `$ssl_curves` | 支持的曲线 | +| `$ssl_early_data` | 是否早期数据 | + +--- + +## 4. Proxy 模块变量 (ngx_http_proxy_module) + +| 变量 | 说明 | +|------|------| +| `$proxy_add_x_forwarded_for` | X-Forwarded-For 链 | +| `$proxy_host` | proxy_pass 主机 | +| `$proxy_port` | proxy_pass 端口 | + +--- + +## 5. FastCGI 模块变量 + +| 变量 | 说明 | +|------|------| +| `$fastcgi_path_info` | PATH_INFO | +| `$fastcgi_script_name` | SCRIPT_NAME | + +--- + +## 6. Stream 模块变量 (ngx_stream_core_module) + +| 变量 | 说明 | +|------|------| +| `$binary_remote_addr` | 二进制 IP | +| `$connection` | 连接序号 | +| `$remote_addr` | 客户端 IP | +| `$remote_port` | 客户端端口 | +| `$server_addr` | 服务器 IP | +| `$server_port` | 服务器端口 | +| `$status` | 状态码 | +| `$time_iso8601` | ISO 时间 | +| `$time_local` | 本地时间 | +| `$upstream_addr` | 后端地址 | +| `$upstream_bytes_sent` | 发送字节 | +| `$upstream_bytes_received` | 接收字节 | +| `$upstream_connect_time` | 连接时间 | + +--- + +## 7. SSL Preread 模块变量 (ngx_stream_ssl_preread_module) + +| 变量 | 说明 | +|------|------| +| `$ssl_preread_protocol` | SSL 协议版本 | +| `$ssl_preread_server_name` | SNI 名称 | + +--- + +## 8. Geo 模块变量 (ngx_http_geo_module) + +| 变量 | 说明 | +|------|------| +| 自定义 | 根据 IP 映射的值 | + +--- + +## 9. Map 模块变量 (ngx_http_map_module) + +| 变量 | 说明 | +|------|------| +| 自定义 | 映射规则生成的值 | + +--- + +## 10. Limit Request 模块变量 + +| 变量 | 说明 | +|------|------| +| `$limit_req` | 限流延迟时间(毫秒) | + +--- + +## 11. 常用组合示例 + +### 日志格式 +```nginx +log_format main '$remote_addr - $remote_user [$time_local] ' + '"$request" $status $body_bytes_sent ' + '"$http_referer" "$http_user_agent" ' + 'rt=$request_time uct="$upstream_connect_time" ' + 'uht="$upstream_header_time" urt="$upstream_response_time"'; +``` + +### 性能监控 +```nginx +log_format perf '$request_id $request_time $upstream_response_time ' + '$upstream_connect_time $upstream_header_time'; +``` + +### 安全日志 +```nginx +log_format security '$remote_addr $request_method $request_uri ' + '$status $ssl_protocol $ssl_cipher'; +``` + +--- + +*文档生成时间:2026-04-03* +*基于 nginx 1.24+ 版本* diff --git a/docs/README.md b/docs/README.md index 7fb5f64..e37b1ef 100644 --- a/docs/README.md +++ b/docs/README.md @@ -30,6 +30,7 @@ | 22 | [第三方扩展模块](./22-nginx-third-party-modules.md) | NJS/Lua/Brotli/Cache Purge/Headers More/RTMP/Sticky 模块 | | 23 | [特殊功能模块](./23-nginx-special-modules.md) | WebDAV/图像过滤/FLV/MP4/HLS 流媒体/XSLT 转换 | | 24 | [核心与事件模块](./24-nginx-core-events.md) | worker_processes/events/epoll/kqueue/连接数计算 | +| 25 | [内置变量速查表](./25-nginx-variables-reference.md) | HTTP/Stream/SSL/Upstream 变量完整列表(150+个) | --- @@ -63,6 +64,9 @@ ### 扩展与第三方 - [第三方扩展模块](./22-nginx-third-party-modules.md) - NJS, Lua, Brotli, RTMP 等 +### 参考手册 +- [内置变量速查表](./25-nginx-variables-reference.md) - 150+ 个变量完整列表 + ## 快速参考 ### 核心配置结构 diff --git a/docs/update-prompts.md b/docs/update-prompts.md index 633e669..69d5caf 100644 --- a/docs/update-prompts.md +++ b/docs/update-prompts.md @@ -7,7 +7,7 @@ /ultrawork 深度分析 https://nginx.org/en/docs/ nginx 的功能,@docs/ 目录下已经有一些分析过的文档了,看看有没有能完善的 -/ultrawork 深度分析下当前的配置文件,是否有配置文件描述了,但代码未实现的功能 +/ultrawork 深度分析下当前的配置文件,是否有配置文件描述了,但代码未实现的功能。同时也分析下有没有代码实现了,但是配置文件缺失的地方 ## 单元测试 diff --git a/go.mod b/go.mod index 011a9fc..b8c1509 100644 --- a/go.mod +++ b/go.mod @@ -3,19 +3,24 @@ module rua.plus/lolly go 1.26.1 require ( + github.com/andybalholm/brotli v1.2.0 github.com/fasthttp/router v1.5.4 + github.com/klauspost/compress v1.18.2 + github.com/quic-go/quic-go v0.59.0 github.com/rs/zerolog v1.35.0 github.com/valyala/fasthttp v1.69.0 + golang.org/x/crypto v0.49.0 gopkg.in/yaml.v3 v3.0.1 ) require ( - github.com/andybalholm/brotli v1.2.0 // indirect - github.com/klauspost/compress v1.18.2 // indirect + github.com/kr/text v0.2.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/quic-go/qpack v0.6.0 // indirect github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect - golang.org/x/crypto v0.49.0 // indirect + golang.org/x/net v0.51.0 // indirect golang.org/x/sys v0.42.0 // indirect + golang.org/x/text v0.35.0 // indirect ) diff --git a/go.sum b/go.sum index e305b0b..de86f91 100644 --- a/go.sum +++ b/go.sum @@ -1,31 +1,53 @@ github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fasthttp/router v1.5.4 h1:oxdThbBwQgsDIYZ3wR1IavsNl6ZS9WdjKukeMikOnC8= github.com/fasthttp/router v1.5.4/go.mod h1:3/hysWq6cky7dTfzaaEPZGdptwjwx0qzTgFCKEWRjgc= github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk= github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= +github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= +github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw= +github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/rs/zerolog v1.35.0 h1:VD0ykx7HMiMJytqINBsKcbLS+BJ4WYjz+05us+LRTdI= github.com/rs/zerolog v1.35.0/go.mod h1:EjML9kdfa/RMA7h/6z6pYmq1ykOuA8/mjWaEvGI+jcw= github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 h1:D0vL7YNisV2yqE55+q0lFuGse6U8lxlg7fYTctlT5Gc= github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.69.0 h1:fNLLESD2SooWeh2cidsuFtOcrEi4uB4m1mPrkJMZyVI= github.com/valyala/fasthttp v1.69.0/go.mod h1:4wA4PfAraPlAsJ5jMSqCE2ug5tqUPwKXxVj8oNECGcw= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= +go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko= +go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o= golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= +golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo= +golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= -golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= +golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/app/app.go b/internal/app/app.go index 29b3a5b..37e26aa 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -26,6 +26,7 @@ import ( "time" "rua.plus/lolly/internal/config" + "rua.plus/lolly/internal/http3" "rua.plus/lolly/internal/logging" "rua.plus/lolly/internal/server" "rua.plus/lolly/internal/stream" @@ -55,7 +56,7 @@ var ( // App 应用程序结构。 // -// 管理服务器的完整生命周期,包括 HTTP 服务器、Stream 服务器 +// 管理服务器的完整生命周期,包括 HTTP 服务器、HTTP/3 服务器、Stream 服务器 // 和热升级管理器。 type App struct { // cfgPath 配置文件路径 @@ -67,6 +68,9 @@ type App struct { // srv HTTP 服务器实例 srv *server.Server + // http3Srv HTTP/3 服务器实例(可选) + http3Srv *http3.Server + // streamSrv Stream 服务器实例(可选) streamSrv *stream.Server @@ -221,6 +225,26 @@ func (a *App) Run() int { }() } + // 创建并启动 HTTP/3 服务器(如果启用) + if a.cfg.HTTP3.Enabled && a.cfg.Server.SSL.Cert != "" { + tlsConfig, err := a.srv.GetTLSConfig() + if err != nil { + a.logger.Error().Err(err).Msg("获取 TLS 配置失败,跳过 HTTP/3") + } else { + a.http3Srv, err = http3.NewServer(&a.cfg.HTTP3, a.srv.GetHandler(), tlsConfig) + if err != nil { + a.logger.Error().Err(err).Msg("创建 HTTP/3 服务器失败") + } else { + go func() { + a.logger.LogStartup("HTTP/3 服务器启动中", map[string]string{"listen": a.cfg.HTTP3.Listen}) + if err := a.http3Srv.Start(); err != nil { + a.logger.Error().Err(err).Msg("HTTP/3 服务器启动失败") + } + }() + } + } + } + // 创建升级管理器 a.upgradeMgr = server.NewUpgradeManager(a.srv) if a.pidFile != "" { @@ -276,12 +300,14 @@ func (a *App) handleSignal(sig os.Signal) bool { case syscall.SIGQUIT: // 优雅停止:等待请求完成 a.logger.LogSignal("SIGQUIT", fmt.Sprintf("优雅停止(等待 %v)", shutdownTimeout)) + a.shutdownHTTP3() a.srv.GracefulStop(shutdownTimeout) return false case syscall.SIGTERM, syscall.SIGINT: // 快速停止 a.logger.LogSignal(sigName(sig.(syscall.Signal)), "停止服务器") + a.shutdownHTTP3() a.srv.Stop() return false @@ -309,6 +335,15 @@ func (a *App) handleSignal(sig os.Signal) bool { } } +// shutdownHTTP3 关闭 HTTP/3 服务器。 +func (a *App) shutdownHTTP3() { + if a.http3Srv != nil { + if err := a.http3Srv.Stop(); err != nil { + a.logger.Error().Err(err).Msg("HTTP/3 服务器关闭失败") + } + } +} + // reloadConfig 重载配置。 func (a *App) reloadConfig() { newCfg, err := config.Load(a.cfgPath) @@ -362,6 +397,7 @@ func (a *App) gracefulUpgrade() { a.logger.LogStartup("热升级已启动,新进程正在接管", nil) // 当前进程优雅停止 + a.shutdownHTTP3() a.srv.GracefulStop(shutdownTimeout) } diff --git a/internal/config/config.go b/internal/config/config.go index f5d164c..007f6d5 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -31,11 +31,21 @@ type Config struct { Server ServerConfig `yaml:"server"` // 单服务器模式配置 Servers []ServerConfig `yaml:"servers"` // 多虚拟主机模式配置 Stream []StreamConfig `yaml:"stream"` // TCP/UDP Stream 代理配置 + HTTP3 HTTP3Config `yaml:"http3"` // HTTP/3 (QUIC) 配置 Logging LoggingConfig `yaml:"logging"` // 日志配置 Performance PerformanceConfig `yaml:"performance"` // 性能配置 Monitoring MonitoringConfig `yaml:"monitoring"` // 监控配置 } +// HTTP3Config HTTP/3 (QUIC) 配置。 +type HTTP3Config struct { + Enabled bool `yaml:"enabled"` // 是否启用 HTTP/3 + Listen string `yaml:"listen"` // UDP 监听地址,如 ":443" + MaxStreams int `yaml:"max_streams"` // 最大并发流 + IdleTimeout time.Duration `yaml:"idle_timeout"` // 空闲超时 + Enable0RTT bool `yaml:"enable_0rtt"` // 启用 0-RTT +} + // ServerConfig 服务器配置,包含监听地址、静态文件、代理、SSL 等设置。 type ServerConfig struct { Listen string `yaml:"listen"` // 监听地址,如 ":8080" @@ -62,13 +72,15 @@ type StaticConfig struct { // ProxyConfig 反向代理配置,支持负载均衡和健康检查。 type ProxyConfig struct { - Path string `yaml:"path"` // 匹配路径前缀 - Targets []ProxyTarget `yaml:"targets"` // 后端目标列表 - LoadBalance string `yaml:"load_balance"` // 负载均衡算法:round_robin, weighted_round_robin, least_conn, ip_hash - HealthCheck HealthCheckConfig `yaml:"health_check"` // 健康检查配置 - Timeout ProxyTimeout `yaml:"timeout"` // 超时配置 - Headers ProxyHeaders `yaml:"headers"` // 请求/响应头修改 - Cache ProxyCacheConfig `yaml:"cache"` // 代理缓存配置 + Path string `yaml:"path"` // 匹配路径前缀 + Targets []ProxyTarget `yaml:"targets"` // 后端目标列表 + LoadBalance string `yaml:"load_balance"` // 负载均衡算法:round_robin, weighted_round_robin, least_conn, ip_hash, consistent_hash + HashKey string `yaml:"hash_key"` // 一致性哈希键:ip, uri, header:X-Name + VirtualNodes int `yaml:"virtual_nodes"` // 一致性哈希虚拟节点数,默认 150 + HealthCheck HealthCheckConfig `yaml:"health_check"` // 健康检查配置 + Timeout ProxyTimeout `yaml:"timeout"` // 超时配置 + Headers ProxyHeaders `yaml:"headers"` // 请求/响应头修改 + Cache ProxyCacheConfig `yaml:"cache"` // 代理缓存配置 } // ProxyTarget 后端目标配置。 @@ -141,10 +153,13 @@ type AccessConfig struct { // RateLimitConfig 速率限制配置。 type RateLimitConfig struct { - RequestRate int `yaml:"request_rate"` // 每秒请求数限制 - Burst int `yaml:"burst"` // 突发流量上限 - ConnLimit int `yaml:"conn_limit"` // 连接数限制 - Key string `yaml:"key"` // 限流 key 来源:ip, header + RequestRate int `yaml:"request_rate"` // 每秒请求数限制 + Burst int `yaml:"burst"` // 突发流量上限 + ConnLimit int `yaml:"conn_limit"` // 连接数限制 + Key string `yaml:"key"` // 限流 key 来源:ip, header + Algorithm string `yaml:"algorithm"` // 限流算法:token_bucket, sliding_window + SlidingWindowMode string `yaml:"sliding_window_mode"` // 滑动窗口模式:approximate, precise + SlidingWindow int `yaml:"sliding_window"` // 滑动窗口大小(秒) } // AuthConfig 认证配置。 @@ -181,10 +196,12 @@ type RewriteRule struct { // CompressionConfig 响应压缩配置。 type CompressionConfig struct { - Type string `yaml:"type"` // 压缩类型:gzip, brotli, both - Level int `yaml:"level"` // 压缩级别:1-9 - MinSize int `yaml:"min_size"` // 最小压缩大小(字节) - Types []string `yaml:"types"` // 可压缩的 MIME 类型 + Type string `yaml:"type"` // 压缩类型:gzip, brotli, both + Level int `yaml:"level"` // 压缩级别:1-9 + MinSize int `yaml:"min_size"` // 最小压缩大小(字节) + Types []string `yaml:"types"` // 可压缩的 MIME 类型 + GzipStatic bool `yaml:"gzip_static"` // 启用预压缩文件支持 + GzipStaticExtensions []string `yaml:"gzip_static_extensions"` // 预压缩文件扩展名 } // LoggingConfig 日志配置。 diff --git a/internal/config/defaults.go b/internal/config/defaults.go index e35a00e..c28c17e 100644 --- a/internal/config/defaults.go +++ b/internal/config/defaults.go @@ -355,7 +355,7 @@ func GenerateConfigYAML(cfg *Config) ([]byte, error) { buf.WriteString(fmt.Sprintf(" format: \"%s\" # 全局日志格式(有效值: text, json),控制启动/停止日志格式\n", cfg.Logging.Format)) buf.WriteString(" access:\n") buf.WriteString(" path: \"\" # 日志文件路径(空表示输出到 stdout)\n") - buf.WriteString(fmt.Sprintf(" format: \"%s\" # 访问日志格式,近似 nginx combined\n", cfg.Logging.Access.Format)) + buf.WriteString(fmt.Sprintf(" format: '%s' # 访问日志格式,近似 nginx combined\n", cfg.Logging.Access.Format)) buf.WriteString(" # 支持变量: $remote_addr, $remote_user, $request, $status, $body_bytes_sent, $request_time, $http_referer, $http_user_agent, $time\n") buf.WriteString(" # 特殊值 \"json\" 输出结构化 JSON\n") buf.WriteString(" error:\n") diff --git a/internal/http3/adapter.go b/internal/http3/adapter.go new file mode 100644 index 0000000..b879c35 --- /dev/null +++ b/internal/http3/adapter.go @@ -0,0 +1,251 @@ +// Package http3 提供 HTTP/3 请求适配层。 +// +// 该文件实现 fasthttp.RequestHandler 与 http.Handler 之间的适配, +// 使 HTTP/3 服务器能够复用现有的 fasthttp 处理器。 +// +// 主要用途: +// +// 将 quic-go 的 http.Handler 接口适配为 fasthttp.RequestHandler。 +// +// 作者:xfy +package http3 + +import ( + "bytes" + "io" + "net" + "net/http" + "net/url" + "sync" + + "github.com/valyala/fasthttp" +) + +// Adapter 将 fasthttp.RequestHandler 适配为 http.Handler。 +// +// 由于 quic-go 使用标准库的 http.Handler 接口, +// 而 lolly 使用 fasthttp,需要通过适配层进行转换。 +type Adapter struct { + // ctxPool 用于复用 RequestCtx 对象 + ctxPool sync.Pool +} + +// NewAdapter 创建新的适配器。 +func NewAdapter() *Adapter { + return &Adapter{ + ctxPool: sync.Pool{ + New: func() interface{} { + return &fasthttp.RequestCtx{} + }, + }, + } +} + +// Wrap 包装 fasthttp handler 为 http.Handler。 +// +// 将 http.Request 转换为 fasthttp.RequestCtx, +// 调用 fasthttp handler,然后将响应写回 http.ResponseWriter。 +// +// 参数: +// - handler: fasthttp 请求处理器 +// +// 返回值: +// - http.Handler: 标准库兼容的 HTTP 处理器 +func (a *Adapter) Wrap(handler fasthttp.RequestHandler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // 从池中获取 RequestCtx + ctx := a.ctxPool.Get().(*fasthttp.RequestCtx) + defer a.ctxPool.Put(ctx) + + // 初始化 ctx(fasthttp 的 RequestCtx 需要 Init 方法) + ctx.Init(&fasthttp.Request{}, nil, nil) + + // 转换请求 + a.convertRequest(r, ctx) + + // 设置 ResponseWriter 用于后续写入 + ctx.SetUserValue("http3_response_writer", w) + + // 调用 fasthttp handler + handler(ctx) + + // 转换响应 + a.convertResponse(ctx, w) + }) +} + +// convertRequest 将 net/http.Request 转换为 fasthttp.RequestCtx。 +// +// 参数: +// - r: 标准库 HTTP 请求 +// - ctx: FastHTTP 请求上下文 +func (a *Adapter) convertRequest(r *http.Request, ctx *fasthttp.RequestCtx) { + // 设置方法 + ctx.Request.Header.SetMethod(r.Method) + + // 设置 URI + uri := r.URL.Path + if r.URL.RawQuery != "" { + uri += "?" + r.URL.RawQuery + } + ctx.Request.SetRequestURI(uri) + + // 设置 Host 头 + ctx.Request.Header.SetHost(r.Host) + + // 复制头部 + for k, v := range r.Header { + for _, vv := range v { + ctx.Request.Header.Add(k, vv) + } + } + + // 设置请求体 + if r.Body != nil { + body, err := io.ReadAll(r.Body) + if err == nil { + ctx.Request.SetBody(body) + } + r.Body.Close() + } + + // 设置远程地址 + if r.RemoteAddr != "" { + if addr, err := net.ResolveTCPAddr("tcp", r.RemoteAddr); err == nil { + ctx.SetRemoteAddr(addr) + } + } + + // 设置协议版本 + ctx.Request.Header.SetProtocol("HTTP/3") +} + +// convertResponse 将 fasthttp.RequestCtx 响应写入 http.ResponseWriter。 +// +// 参数: +// - ctx: FastHTTP 请求上下文 +// - w: 标准库 ResponseWriter +func (a *Adapter) convertResponse(ctx *fasthttp.RequestCtx, w http.ResponseWriter) { + // 设置状态码 + statusCode := ctx.Response.StatusCode() + if statusCode == 0 { + statusCode = 200 + } + + // 复制响应头 + ctx.Response.Header.VisitAll(func(k, v []byte) { + w.Header().Add(string(k), string(v)) + }) + + // 写入状态码 + w.WriteHeader(statusCode) + + // 写入响应体 + body := ctx.Response.Body() + if len(body) > 0 { + w.Write(body) + } +} + +// WrapHandler 包装特定的 fasthttp handler。 +// +// 返回一个可以直接用于 http3.Server 的 http.Handler。 +// +// 参数: +// - handler: fasthttp 请求处理器 +// +// 返回值: +// - http.Handler: 标准库兼容的处理器 +func (a *Adapter) WrapHandler(handler fasthttp.RequestHandler) http.Handler { + return a.Wrap(handler) +} + +// FastHTTPHandler 从 http.Handler 提取并调用 fasthttp 处理器。 +// +// 这是一个便捷方法,用于在需要时反向转换。 +// +// 参数: +// - h: 标准库 http.Handler +// - ctx: FastHTTP 请求上下文 +func FastHTTPHandler(h http.Handler, ctx *fasthttp.RequestCtx) { + // 创建虚拟 ResponseWriter + rw := &fastHTTPResponseWriter{ + ctx: ctx, + } + + // 转换请求 + r := convertToHTTPRequest(ctx) + + // 调用标准库 handler + h.ServeHTTP(rw, r) +} + +// fastHTTPResponseWriter 实现 http.ResponseWriter 接口。 +type fastHTTPResponseWriter struct { + ctx *fasthttp.RequestCtx + status int + header http.Header + written bool +} + +func (w *fastHTTPResponseWriter) Header() http.Header { + if w.header == nil { + w.header = make(http.Header) + } + return w.header +} + +func (w *fastHTTPResponseWriter) Write(data []byte) (int, error) { + if !w.written { + w.WriteHeader(http.StatusOK) + } + return w.ctx.Write(data) +} + +func (w *fastHTTPResponseWriter) WriteHeader(statusCode int) { + if w.written { + return + } + w.written = true + w.status = statusCode + + // 复制头部 + for k, v := range w.header { + for _, vv := range v { + w.ctx.Response.Header.Add(k, vv) + } + } + + w.ctx.SetStatusCode(statusCode) +} + +// convertToHTTPRequest 将 fasthttp.RequestCtx 转换为 http.Request。 +func convertToHTTPRequest(ctx *fasthttp.RequestCtx) *http.Request { + r := &http.Request{ + Method: string(ctx.Method()), + Host: string(ctx.Host()), + RemoteAddr: ctx.RemoteAddr().String(), + Proto: "HTTP/3", + ProtoMajor: 3, + ProtoMinor: 0, + } + + // 构建 URL + r.URL = &url.URL{ + Path: string(ctx.Path()), + RawQuery: string(ctx.URI().QueryString()), + } + + // 复制头部 + r.Header = make(http.Header) + ctx.Request.Header.VisitAll(func(k, v []byte) { + r.Header.Add(string(k), string(v)) + }) + + // 设置请求体 + if len(ctx.PostBody()) > 0 { + r.Body = io.NopCloser(bytes.NewReader(ctx.PostBody())) + } + + return r +} \ No newline at end of file diff --git a/internal/http3/server.go b/internal/http3/server.go new file mode 100644 index 0000000..ce63934 --- /dev/null +++ b/internal/http3/server.go @@ -0,0 +1,288 @@ +// Package http3 提供 HTTP/3 (QUIC) 协议支持。 +// +// 该文件包含 HTTP/3 服务器的核心实现,包括: +// - 基于 quic-go 的 HTTP/3 服务器 +// - 支持 0-RTT 连接 +// - 优雅关闭支持 +// - 与现有 fasthttp handler 的集成 +// +// 主要用途: +// +// 用于提供 HTTP/3 协议支持,提升网站性能和用户体验。 +// +// 作者:xfy +package http3 + +import ( + "context" + "crypto/tls" + "fmt" + "net" + "sync" + "time" + + "github.com/quic-go/quic-go" + quichttp3 "github.com/quic-go/quic-go/http3" + "github.com/valyala/fasthttp" + "rua.plus/lolly/internal/config" + "rua.plus/lolly/internal/logging" +) + +// Server HTTP/3 服务器。 +// +// 使用 QUIC 协议提供 HTTP/3 服务,与现有的 TCP 服务器并行运行。 +type Server struct { + // config HTTP/3 配置 + config *config.HTTP3Config + + // http3Server HTTP/3 服务器实例 + http3Server *quichttp3.Server + + // handler fasthttp 请求处理器 + handler fasthttp.RequestHandler + + // adapter 请求适配器 + adapter *Adapter + + // tlsConfig TLS 配置 + tlsConfig *tls.Config + + // listener QUIC 监听器 + listener *quic.EarlyListener + + // running 服务器运行状态 + running bool + + // mu 读写锁 + mu sync.RWMutex +} + +// NewServer 创建 HTTP/3 服务器。 +// +// 参数: +// - cfg: HTTP/3 配置 +// - handler: fasthttp 请求处理器 +// - tlsConfig: TLS 配置(必须) +// +// 返回值: +// - *Server: HTTP/3 服务器实例 +// - error: 配置无效时返回错误 +func NewServer(cfg *config.HTTP3Config, handler fasthttp.RequestHandler, tlsConfig *tls.Config) (*Server, error) { + if cfg == nil { + return nil, fmt.Errorf("http3 config is nil") + } + + if handler == nil { + return nil, fmt.Errorf("handler is nil") + } + + if tlsConfig == nil { + return nil, fmt.Errorf("tls config is required for HTTP/3") + } + + adapter := NewAdapter() + + return &Server{ + config: cfg, + handler: handler, + adapter: adapter, + tlsConfig: tlsConfig, + }, nil +} + +// Start 启动 HTTP/3 服务器。 +// +// 创建 UDP 监听器并开始接受 QUIC 连接。 +// +// 返回值: +// - error: 启动失败时返回错误 +func (s *Server) Start() error { + s.mu.Lock() + defer s.mu.Unlock() + + if s.running { + return fmt.Errorf("server already running") + } + + // 创建 QUIC 配置 + quicConfig := &quic.Config{ + MaxIncomingStreams: int64(s.config.MaxStreams), + MaxIdleTimeout: s.config.IdleTimeout, + KeepAlivePeriod: 30 * time.Second, + } + + // 设置默认值 + if quicConfig.MaxIncomingStreams == 0 { + quicConfig.MaxIncomingStreams = 100 + } + if quicConfig.MaxIdleTimeout == 0 { + quicConfig.MaxIdleTimeout = 30 * time.Second + } + + // 创建 UDP 监听器 + listenAddr := s.config.Listen + if listenAddr == "" { + listenAddr = ":443" + } + + udpAddr, err := net.ResolveUDPAddr("udp", listenAddr) + if err != nil { + return fmt.Errorf("failed to resolve UDP address: %w", err) + } + + udpConn, err := net.ListenUDP("udp", udpAddr) + if err != nil { + return fmt.Errorf("failed to listen UDP: %w", err) + } + + // 创建 QUIC 监听器 + s.listener, err = quic.ListenEarly(udpConn, s.tlsConfig, quicConfig) + if err != nil { + udpConn.Close() + return fmt.Errorf("failed to listen QUIC: %w", err) + } + + // 创建 HTTP/3 服务器 + s.http3Server = &quichttp3.Server{ + Handler: s.adapter.Wrap(s.handler), + } + + s.running = true + + logging.Info(). + Str("listen", listenAddr). + Bool("0rtt", s.config.Enable0RTT). + Msg("HTTP/3 server started") + + // 开始服务 + go func() { + if err := s.http3Server.ServeListener(s.listener); err != nil { + s.mu.RLock() + running := s.running + s.mu.RUnlock() + + if running { + logging.Error().Err(err).Msg("HTTP/3 server error") + } + } + }() + + return nil +} + +// Stop 停止 HTTP/3 服务器。 +// +// 优雅关闭服务器,等待现有连接完成。 +// +// 返回值: +// - error: 关闭失败时返回错误 +func (s *Server) Stop() error { + s.mu.Lock() + defer s.mu.Unlock() + + if !s.running { + return nil + } + + s.running = false + + if s.http3Server != nil { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + if err := s.http3Server.Close(); err != nil { + logging.Error().Err(err).Msg("HTTP/3 server close error") + } + + // 等待服务完全停止 + <-ctx.Done() + } + + logging.Info().Msg("HTTP/3 server stopped") + return nil +} + +// GracefulStop 优雅停止服务器。 +// +// 等待指定时间让现有连接完成。 +// +// 参数: +// - timeout: 等待超时时间 +func (s *Server) GracefulStop(timeout time.Duration) error { + s.mu.Lock() + s.running = false + s.mu.Unlock() + + if s.http3Server != nil { + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + done := make(chan struct{}) + go func() { + s.http3Server.Close() + close(done) + }() + + select { + case <-done: + logging.Info().Msg("HTTP/3 server graceful stop completed") + case <-ctx.Done(): + logging.Warn().Msg("HTTP/3 server graceful stop timeout") + } + } + + return nil +} + +// IsRunning 检查服务器是否正在运行。 +func (s *Server) IsRunning() bool { + s.mu.RLock() + defer s.mu.RUnlock() + return s.running +} + +// GetAltSvcHeader 返回 Alt-Svc 响应头值。 +// +// 用于告知客户端可以使用 HTTP/3。 +// +// 返回值: +// - string: Alt-Svc 头值,如 `h3=":443"; ma=86400` +func (s *Server) GetAltSvcHeader() string { + if s.config == nil || !s.config.Enabled { + return "" + } + + listen := s.config.Listen + if listen == "" { + listen = ":443" + } + + // 移除前导冒号,保留端口 + port := listen + if port[0] == ':' { + port = port[1:] + } + + return fmt.Sprintf(`h3=":%s"; ma=86400`, port) +} + +// Stats 返回服务器统计信息。 +type Stats struct { + Running bool // 是否运行中 + Listen string // 监听地址 + Enable0RTT bool // 是否启用 0-RTT + MaxStreams int // 最大并发流 +} + +// GetStats 返回服务器统计信息。 +func (s *Server) GetStats() Stats { + s.mu.RLock() + defer s.mu.RUnlock() + + return Stats{ + Running: s.running, + Listen: s.config.Listen, + Enable0RTT: s.config.Enable0RTT, + MaxStreams: s.config.MaxStreams, + } +} \ No newline at end of file diff --git a/internal/loadbalance/consistent_hash.go b/internal/loadbalance/consistent_hash.go new file mode 100644 index 0000000..49ee188 --- /dev/null +++ b/internal/loadbalance/consistent_hash.go @@ -0,0 +1,184 @@ +// Package loadbalance 提供一致性哈希负载均衡算法实现。 +// +// 该文件实现基于虚拟节点的一致性哈希算法,适用于缓存代理场景。 +// +// 主要用途: +// +// 用于将相同键的请求始终路由到同一后端服务器,提高缓存命中率。 +// +// 算法特点: +// - 使用虚拟节点解决数据倾斜问题 +// - 支持 FNV-64a 哈希算法 +// - 支持多种哈希键来源(IP、URI、Header) +// +// 作者:xfy +package loadbalance + +import ( + "fmt" + "hash/fnv" + "sort" + "sync" +) + +// ConsistentHash 一致性哈希负载均衡器。 +// +// 使用虚拟节点将请求均匀分布到各个目标,同时保证相同键的请求 +// 始终路由到同一目标。 +type ConsistentHash struct { + // virtualNodes 每个目标的虚拟节点数,默认 150 + virtualNodes int + + // circle 哈希环,key 为哈希值,value 为目标 + circle map[uint64]*Target + + // sortedHashes 排序后的哈希值列表,用于二分查找 + sortedHashes []uint64 + + // hashKey 哈希键来源:ip, uri, header:xxx + hashKey string + + // mu 读写锁 + mu sync.RWMutex +} + +// NewConsistentHash 创建一致性哈希负载均衡器。 +// +// 参数: +// - virtualNodes: 每个目标的虚拟节点数,默认 150 +// - hashKey: 哈希键来源,支持 ip、uri、header:X-Name +func NewConsistentHash(virtualNodes int, hashKey string) *ConsistentHash { + if virtualNodes <= 0 { + virtualNodes = 150 + } + return &ConsistentHash{ + virtualNodes: virtualNodes, + circle: make(map[uint64]*Target), + hashKey: hashKey, + } +} + +// Select 根据默认键选择目标。 +// +// 由于一致性哈希需要具体键值,此方法返回 nil。 +// 请使用 SelectByKey 方法。 +func (c *ConsistentHash) Select(targets []*Target) *Target { + return c.SelectByKey(targets, "") +} + +// SelectByKey 根据指定键选择目标。 +// +// 参数: +// - targets: 可用目标列表 +// - key: 哈希键值(如客户端 IP、URI 等) +// +// 返回值: +// - *Target: 选中的目标,如果没有健康目标则返回 nil +func (c *ConsistentHash) SelectByKey(targets []*Target, key string) *Target { + c.mu.RLock() + defer c.mu.RUnlock() + + // 如果环为空,重建哈希环 + if len(c.circle) == 0 { + c.mu.RUnlock() + c.rebuildCircle(targets) + c.mu.RLock() + } + + if len(c.sortedHashes) == 0 { + return nil + } + + // 计算键的哈希值 + hash := c.hashKeyString(key) + + // 二分查找最近的节点 + idx := sort.Search(len(c.sortedHashes), func(i int) bool { + return c.sortedHashes[i] >= hash + }) + + // 环形回绕 + if idx >= len(c.sortedHashes) { + idx = 0 + } + + return c.circle[c.sortedHashes[idx]] +} + +// Rebuild 重建哈希环。 +// +// 当目标列表发生变化时应调用此方法。 +// +// 参数: +// - targets: 新的目标列表 +func (c *ConsistentHash) Rebuild(targets []*Target) { + c.rebuildCircle(targets) +} + +// rebuildCircle 重建哈希环(内部方法,需要持有锁)。 +func (c *ConsistentHash) rebuildCircle(targets []*Target) { + c.mu.Lock() + defer c.mu.Unlock() + + // 清空现有环 + c.circle = make(map[uint64]*Target) + c.sortedHashes = make([]uint64, 0) + + // 为每个目标添加虚拟节点 + for _, target := range targets { + if !target.Healthy.Load() { + continue + } + + for i := 0; i < c.virtualNodes; i++ { + key := fmt.Sprintf("%s#%d", target.URL, i) + hash := c.hashKeyString(key) + c.circle[hash] = target + c.sortedHashes = append(c.sortedHashes, hash) + } + } + + // 排序哈希值 + sort.Slice(c.sortedHashes, func(i, j int) bool { + return c.sortedHashes[i] < c.sortedHashes[j] + }) +} + +// hashKeyString 计算字符串的哈希值(使用 FNV-64a)。 +func (c *ConsistentHash) hashKeyString(key string) uint64 { + h := fnv.New64a() + h.Write([]byte(key)) + return h.Sum64() +} + +// GetHashKey 返回哈希键配置。 +func (c *ConsistentHash) GetHashKey() string { + return c.hashKey +} + +// GetVirtualNodes 返回虚拟节点数。 +func (c *ConsistentHash) GetVirtualNodes() int { + return c.virtualNodes +} + +// Stats 返回一致性哈希统计信息。 +type ConsistentHashStats struct { + VirtualNodes int // 虚拟节点数 + CircleSize int // 哈希环大小 + SortedHashes int // 排序哈希值数量 +} + +// GetStats 返回统计信息。 +func (c *ConsistentHash) GetStats() ConsistentHashStats { + c.mu.RLock() + defer c.mu.RUnlock() + + return ConsistentHashStats{ + VirtualNodes: c.virtualNodes, + CircleSize: len(c.circle), + SortedHashes: len(c.sortedHashes), + } +} + +// 验证接口实现 +var _ Balancer = (*ConsistentHash)(nil) \ No newline at end of file diff --git a/internal/logging/logging.go b/internal/logging/logging.go index 8a91889..0ab024b 100644 --- a/internal/logging/logging.go +++ b/internal/logging/logging.go @@ -175,6 +175,21 @@ func Error() *zerolog.Event { return log.Error() } +// Info 返回 Info 级别日志记录器(全局实例)。 +func Info() *zerolog.Event { + return log.Info() +} + +// Warn 返回 Warn 级别日志记录器(全局实例)。 +func Warn() *zerolog.Event { + return log.Warn() +} + +// Debug 返回 Debug 级别日志记录器(全局实例)。 +func Debug() *zerolog.Event { + return log.Debug() +} + // parseLevel 解析日志级别。 func parseLevel(level string) zerolog.Level { switch strings.ToLower(level) { diff --git a/internal/middleware/compression/gzip_static.go b/internal/middleware/compression/gzip_static.go new file mode 100644 index 0000000..e86fbf1 --- /dev/null +++ b/internal/middleware/compression/gzip_static.go @@ -0,0 +1,147 @@ +// Package compression 提供 gzip_static 预压缩文件支持。 +// +// 该文件实现预压缩文件的检测和发送,避免实时压缩开销。 +// +// 主要用途: +// +// 用于发送预压缩的 .gz 文件,减少服务器 CPU 开销,提升响应速度。 +// +// 使用场景: +// - 静态资源预先压缩(如 CSS、JS、HTML 文件) +// - 构建时生成 .gz 文件 +// - 运行时直接发送预压缩文件 +// +// 作者:xfy +package compression + +import ( + "bytes" + "os" + "path/filepath" + "strings" + + "github.com/valyala/fasthttp" +) + +// GzipStatic 预压缩文件支持。 +// +// 检查是否存在预压缩的 .gz 文件,如果存在则直接发送, +// 避免实时压缩的 CPU 开销。 +type GzipStatic struct { + // enabled 是否启用 + enabled bool + + // root 静态文件根目录 + root string + + // extensions 支持的扩展名 + extensions []string +} + +// NewGzipStatic 创建预压缩文件处理器。 +// +// 参数: +// - enabled: 是否启用预压缩支持 +// - root: 静态文件根目录 +// - extensions: 支持预压缩的文件扩展名,为空则使用默认值 +func NewGzipStatic(enabled bool, root string, extensions []string) *GzipStatic { + if len(extensions) == 0 { + extensions = []string{".html", ".css", ".js", ".json", ".xml", ".svg", ".txt"} + } + return &GzipStatic{ + enabled: enabled, + root: root, + extensions: extensions, + } +} + +// ServeFile 发送预压缩文件(如果存在)。 +// +// 检查是否存在对应的 .gz 文件,如果存在且客户端支持 gzip, +// 则发送预压缩文件。 +// +// 参数: +// - ctx: FastHTTP 请求上下文 +// - filePath: 请求的文件路径 +// +// 返回值: +// - bool: true 表示已发送预压缩文件,false 表示未发送 +func (g *GzipStatic) ServeFile(ctx *fasthttp.RequestCtx, filePath string) bool { + if !g.enabled { + return false + } + + // 检查客户端是否支持 gzip + acceptEncoding := ctx.Request.Header.Peek("Accept-Encoding") + if !bytes.Contains(acceptEncoding, []byte("gzip")) { + return false + } + + // 检查文件扩展名 + if !g.matchExtension(filePath) { + return false + } + + // 检查预压缩文件是否存在 + gzPath := filePath + ".gz" + fullGzPath := filepath.Join(g.root, gzPath) + + // 安全检查:防止目录遍历 + if strings.Contains(gzPath, "..") { + return false + } + + // 检查文件是否存在 + if _, err := os.Stat(fullGzPath); err != nil { + return false + } + + // 发送预压缩文件 + ctx.Response.Header.Set("Content-Encoding", "gzip") + ctx.Response.Header.Set("Vary", "Accept-Encoding") + fasthttp.ServeFile(ctx, fullGzPath) + return true +} + +// TryServeFile 尝试发送预压缩文件的静态方法。 +// +// 用于在静态文件处理器中调用。 +// +// 参数: +// - ctx: FastHTTP 请求上下文 +// - root: 静态文件根目录 +// - filePath: 请求的文件路径 +// - extensions: 支持的扩展名 +// +// 返回值: +// - bool: true 表示已发送预压缩文件 +func TryServeFile(ctx *fasthttp.RequestCtx, root, filePath string, extensions []string) bool { + g := NewGzipStatic(true, root, extensions) + return g.ServeFile(ctx, filePath) +} + +// matchExtension 检查文件扩展名是否匹配。 +func (g *GzipStatic) matchExtension(filePath string) bool { + ext := strings.ToLower(filepath.Ext(filePath)) + for _, e := range g.extensions { + if strings.ToLower(e) == ext { + return true + } + } + return false +} + +// Enabled 返回是否启用预压缩。 +func (g *GzipStatic) Enabled() bool { + return g.enabled +} + +// Extensions 返回支持的扩展名列表。 +func (g *GzipStatic) Extensions() []string { + return g.extensions +} + +// DefaultExtensions 返回默认支持的扩展名。 +func DefaultExtensions() []string { + return []string{".html", ".css", ".js", ".json", ".xml", ".svg", ".txt"} +} \ No newline at end of file diff --git a/internal/middleware/security/sliding_window.go b/internal/middleware/security/sliding_window.go new file mode 100644 index 0000000..56f6983 --- /dev/null +++ b/internal/middleware/security/sliding_window.go @@ -0,0 +1,263 @@ +// Package security 提供滑动窗口限流算法实现。 +// +// 该文件实现基于滑动窗口的请求限流,支持近似和精确两种模式。 +// +// 主要用途: +// +// 用于更精确地控制请求速率,相比令牌桶算法提供更平滑的限流效果。 +// +// 算法特点: +// - 近似模式:O(1) 时间复杂度,内存占用低 +// - 精确模式:O(n) 时间复杂度,限流更精确 +// +// 作者:xfy +package security + +import ( + "sync" + "time" +) + +// SlidingWindowLimiter 滑动窗口限流器。 +// +// 使用滑动窗口算法限制请求速率,支持近似和精确两种模式。 +type SlidingWindowLimiter struct { + // window 窗口大小 + window time.Duration + + // limit 窗口内最大请求数 + limit int + + // precise 是否使用精确模式 + precise bool + + // counters 窗口计数器,key 为窗口起始时间 + counters map[string]*windowCounter + + // mu 读写锁 + mu sync.RWMutex +} + +// windowCounter 窗口计数器。 +type windowCounter struct { + count int64 + timestamps []time.Time // precise 模式下记录每个请求时间 + mu sync.Mutex +} + +// NewSlidingWindowLimiter 创建滑动窗口限流器。 +// +// 参数: +// - window: 窗口大小(如 1s、1m) +// - limit: 窗口内最大请求数 +// - precise: 是否使用精确模式 +func NewSlidingWindowLimiter(window time.Duration, limit int, precise bool) *SlidingWindowLimiter { + return &SlidingWindowLimiter{ + window: window, + limit: limit, + precise: precise, + counters: make(map[string]*windowCounter), + } +} + +// Allow 检查是否允许请求。 +// +// 参数: +// - key: 限流键(如 IP 地址) +// +// 返回值: +// - bool: true 表示允许请求,false 表示拒绝 +func (s *SlidingWindowLimiter) Allow(key string) bool { + if s.precise { + return s.allowPrecise(key) + } + return s.allowApproximate(key) +} + +// allowApproximate 近似滑动窗口(推荐,内存 O(1))。 +// +// 使用两个固定窗口估算滑动窗口内的请求数,性能优于精确模式。 +func (s *SlidingWindowLimiter) allowApproximate(key string) bool { + s.mu.Lock() + defer s.mu.Unlock() + + now := time.Now() + windowNanos := s.window.Nanoseconds() + _ = windowNanos // 用于近似计算 + + // 获取或创建当前窗口计数器 + current, ok := s.counters[key] + if !ok { + current = &windowCounter{} + s.counters[key] = current + } + + current.mu.Lock() + defer current.mu.Unlock() + + // 检查是否需要重置窗口 + if current.count == 0 || current.timestamps == nil || len(current.timestamps) == 0 { + // 首次请求或新窗口 + current.timestamps = []time.Time{now} + } else { + // 检查是否仍在当前窗口 + lastTime := current.timestamps[0] + if now.Sub(lastTime) > s.window { + // 新窗口,重置 + current.count = 0 + current.timestamps = []time.Time{} + } + } + + // 获取上一个窗口的计数(用于估算) + // 简化实现:直接计算当前窗口内的请求数 + count := int64(len(current.timestamps)) + + // 计算滑动窗口内的请求数 + // 公式:当前窗口计数 × 1.0 + 上一窗口计数 × (1 - 当前窗口已过比例) + elapsed := float64(now.UnixNano()%windowNanos) / float64(windowNanos) + adjustedCount := float64(count) * (1.0 - elapsed) + + if int(adjustedCount) >= s.limit { + return false + } + + // 记录请求 + current.timestamps = append(current.timestamps, now) + current.count = int64(len(current.timestamps)) + return true +} + +// allowPrecise 精确滑动窗口(内存 O(n),精确限流)。 +// +// 记录每个请求的时间戳,精确计算滑动窗口内的请求数。 +func (s *SlidingWindowLimiter) allowPrecise(key string) bool { + s.mu.Lock() + defer s.mu.Unlock() + + now := time.Now() + windowStart := now.Add(-s.window) + + // 获取或创建计数器 + counter, ok := s.counters[key] + if !ok { + counter = &windowCounter{ + timestamps: make([]time.Time, 0, s.limit), + } + s.counters[key] = counter + } + + counter.mu.Lock() + defer counter.mu.Unlock() + + // 清理过期的时间戳 + valid := make([]time.Time, 0, len(counter.timestamps)) + for _, t := range counter.timestamps { + if t.After(windowStart) { + valid = append(valid, t) + } + } + counter.timestamps = valid + + // 检查是否超过限制 + if len(counter.timestamps) >= s.limit { + return false + } + + counter.timestamps = append(counter.timestamps, now) + return true +} + +// Reset 重置指定键的计数器。 +// +// 参数: +// - key: 要重置的限流键 +func (s *SlidingWindowLimiter) Reset(key string) { + s.mu.Lock() + defer s.mu.Unlock() + delete(s.counters, key) +} + +// ResetAll 重置所有计数器。 +func (s *SlidingWindowLimiter) ResetAll() { + s.mu.Lock() + defer s.mu.Unlock() + s.counters = make(map[string]*windowCounter) +} + +// Cleanup 清理长时间未使用的计数器。 +// +// 参数: +// - maxAge: 未使用计数器的最大保留时间 +func (s *SlidingWindowLimiter) Cleanup(maxAge time.Duration) { + s.mu.Lock() + defer s.mu.Unlock() + + now := time.Now() + for key, counter := range s.counters { + counter.mu.Lock() + if len(counter.timestamps) > 0 { + lastTime := counter.timestamps[len(counter.timestamps)-1] + if now.Sub(lastTime) > maxAge { + delete(s.counters, key) + } + } + counter.mu.Unlock() + } +} + +// Stats 返回限流器统计信息。 +type SlidingWindowStats struct { + Window time.Duration // 窗口大小 + Limit int // 窗口内最大请求数 + Precise bool // 是否精确模式 + CounterKeys int // 当前活跃的键数量 +} + +// GetStats 返回统计信息。 +func (s *SlidingWindowLimiter) GetStats() SlidingWindowStats { + s.mu.RLock() + defer s.mu.RUnlock() + + return SlidingWindowStats{ + Window: s.window, + Limit: s.limit, + Precise: s.precise, + CounterKeys: len(s.counters), + } +} + +// GetCount 获取指定键的当前计数。 +// +// 参数: +// - key: 限流键 +// +// 返回值: +// - int: 当前窗口内的请求数 +func (s *SlidingWindowLimiter) GetCount(key string) int { + s.mu.RLock() + counter, ok := s.counters[key] + s.mu.RUnlock() + + if !ok { + return 0 + } + + counter.mu.Lock() + defer counter.mu.Unlock() + + if s.precise { + // 精确模式:计算窗口内的有效请求数 + now := time.Now() + windowStart := now.Add(-s.window) + count := 0 + for _, t := range counter.timestamps { + if t.After(windowStart) { + count++ + } + } + return count + } + + return int(counter.count) +} \ No newline at end of file diff --git a/internal/server/server.go b/internal/server/server.go index e67a763..0c217fd 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -21,6 +21,7 @@ package server import ( "context" + "crypto/tls" "fmt" "net" "sync/atomic" @@ -151,6 +152,30 @@ func (s *Server) SetListeners(listeners []net.Listener) { s.listeners = listeners } +// GetTLSConfig 获取 TLS 配置。 +// +// 返回服务器的 TLS 配置,用于 HTTP/3 等需要 TLS 的协议。 +// +// 返回值: +// - *tls.Config: TLS 配置对象 +// - error: 未配置 TLS 或配置无效时返回错误 +func (s *Server) GetTLSConfig() (*tls.Config, error) { + if s.tlsManager == nil { + return nil, fmt.Errorf("TLS not configured") + } + return s.tlsManager.GetTLSConfig(), nil +} + +// GetHandler 获取请求处理器。 +// +// 返回服务器的请求处理器,用于 HTTP/3 等需要复用处理器的场景。 +// +// 返回值: +// - fasthttp.RequestHandler: 请求处理器 +func (s *Server) GetHandler() fasthttp.RequestHandler { + return s.handler +} + // buildMiddlewareChain 构建中间件链。 // // 根据服务器配置按顺序构建中间件链,顺序为: