本文档介绍 Gatel 的高级功能,包括 HTTP/3、WebSocket、FastCGI、流代理、PROXY Protocol、匹配器、模板和缓存。
- HTTP/3 (QUIC)
- WebSocket 代理
- FastCGI
- 流代理 (TCP)
- PROXY Protocol
- 请求匹配器
- 模板渲染
- 响应缓存
- 静态文件服务器
- 管理 API 与可观测性
- 热重载机制
- 优雅关闭
HTTP/3 是基于 QUIC 传输协议的 HTTP 版本,提供更低的连接建立延迟和更好的弱网性能。
HTTP/3 是一个可选功能,需要在编译时启用 http3 feature flag:
cargo build --release --features http3依赖库:
| 库 | 版本 | 用途 |
|---|---|---|
| quinn | 0.11 | QUIC 传输协议实现 |
| h3 | 0.0.8 | HTTP/3 协议实现 |
global {
http3 true
https ":443"
}
tls {
acme {
email "admin@example.com"
ca "letsencrypt"
challenge "http-01"
}
}
site "example.com" {
route "/" {
proxy "127.0.0.1:3000"
}
}- Gatel 在 HTTPS 端口同时监听 TCP(用于 HTTP/1.1 和 HTTP/2)和 UDP(用于 QUIC/HTTP/3)。
- 在 HTTP/1.1 和 HTTP/2 的响应中自动添加
Alt-Svc头,告知客户端可以使用 HTTP/3。 - 支持 HTTP/3 的客户端(如现代浏览器)会自动升级到 HTTP/3。
Gatel 自动在响应中添加:
Alt-Svc: h3=":443"; ma=86400
这告诉浏览器该服务支持 HTTP/3,有效期 86400 秒(24 小时)。
- HTTP/3 需要 TLS(QUIC 内置了 TLS 1.3)。
- 防火墙需要开放 UDP 端口(通常与 HTTPS 相同的端口号)。
- 目前 HTTP/3 仅用于客户端到 Gatel 的连接,Gatel 到上游仍使用 HTTP/1.1 或 HTTP/2。
# 使用 curl(需要 HTTP/3 支持编译的 curl)
curl --http3 https://example.com/
# 使用浏览器开发者工具
# 在 Network 面板中,Protocol 列会显示 "h3"Gatel 自动识别和透明代理 WebSocket 连接。
WebSocket 代理不需要特殊配置,标准的 proxy 指令即可:
site "example.com" {
route "/ws" {
proxy "127.0.0.1:8001"
}
}- 客户端发送带有
Upgrade: websocket头的 HTTP 请求。 - Gatel 检测到 WebSocket 升级请求。
- 将升级请求转发到上游。
- 上游返回
101 Switching Protocols响应。 - Gatel 将响应返回给客户端。
- 连接升级完成后,Gatel 在客户端和上游之间双向转发 WebSocket 帧。
site "chat.example.com" {
// WebSocket 连接
route "/ws" {
proxy "127.0.0.1:8001"
}
// 静态页面
route "/" {
root "/var/www/chat"
file-server
}
}WebSocket 连接在升级后会绕过大部分中间件(如 compress、cache),因为 WebSocket 帧不是标准的 HTTP 请求/响应。以下中间件在升级前仍然生效:
- logging — 记录初始握手请求
- ip-filter — 在连接建立前检查 IP
- rate-limit — 限制握手请求频率
- basic-auth — 验证握手请求的认证信息
Gatel 实现了完整的 FastCGI 协议,可以与 PHP-FPM 等 FastCGI 服务通信。
site "php.example.com" {
route "*.php" {
fastcgi "127.0.0.1:9000" {
root "/var/www/html"
split ".php"
index "index.php"
}
}
// 非 PHP 文件作为静态资源
route "/" {
root "/var/www/html"
file-server
}
}连接地址(第一个参数):
// TCP 连接
fastcgi "127.0.0.1:9000" { }
// 也可以使用主机名
fastcgi "php-fpm:9000" { }| 节点 | 类型 | 必填 | 说明 |
|---|---|---|---|
root |
string | 是 | 文档根目录,对应 PHP 的 DOCUMENT_ROOT |
split |
string | 否 | 路径分割标记,通常为 ".php" |
index |
string | 否 | 默认索引文件,通常为 "index.php" |
env |
key-value | 否 | 自定义环境变量(可多次使用) |
Gatel 自动设置以下 FastCGI 环境变量:
| 变量 | 说明 |
|---|---|
SCRIPT_FILENAME |
完整的脚本文件路径 |
SCRIPT_NAME |
脚本名称(相对路径) |
DOCUMENT_ROOT |
文档根目录 |
QUERY_STRING |
URL 查询字符串 |
REQUEST_METHOD |
HTTP 方法 |
REQUEST_URI |
完整的请求 URI |
SERVER_NAME |
服务器名称 |
SERVER_PORT |
服务器端口 |
REMOTE_ADDR |
客户端 IP |
CONTENT_TYPE |
请求体类型 |
CONTENT_LENGTH |
请求体长度 |
自定义环境变量:
fastcgi "127.0.0.1:9000" {
root "/var/www/html"
split ".php"
index "index.php"
env "DB_HOST" "localhost"
env "DB_PORT" "3306"
env "DB_NAME" "myapp"
env "APP_ENV" "production"
}split 参数用于从 URI 中提取脚本路径和 PATH_INFO:
请求: /blog/index.php/article/123
split: ".php"
结果:
SCRIPT_NAME = /blog/index.php
PATH_INFO = /article/123
PHP-FPM 配置示例(www.conf):
[www]
listen = 127.0.0.1:9000
pm = dynamic
pm.max_children = 50
pm.start_servers = 5
pm.min_spare_servers = 5
pm.max_spare_servers = 35Gatel 配置:
site "php-app.example.com" {
route "*.php" {
fastcgi "127.0.0.1:9000" {
root "/var/www/php-app"
split ".php"
index "index.php"
}
}
route "/" {
root "/var/www/php-app"
file-server
}
}site "wordpress.example.com" {
// PHP 文件
route "*.php" {
fastcgi "127.0.0.1:9000" {
root "/var/www/wordpress"
split ".php"
index "index.php"
}
}
// 静态文件
route "/wp-content/*" {
encode "gzip"
cache max-entries=5000 max-age="3600s"
root "/var/www/wordpress"
file-server
}
// 伪静态(WordPress URL 重写)
route "/" {
rewrite "/" "/index.php"
fastcgi "127.0.0.1:9000" {
root "/var/www/wordpress"
split ".php"
index "index.php"
}
}
}Gatel 支持 L4 层的 TCP 流代理,实现双向数据转发,无需解析应用层协议。
stream {
listen ":3306" {
proxy "mysql-primary:3306"
}
}- Gatel 在指定端口监听 TCP 连接。
- 客户端连接到达后,Gatel 建立到上游的 TCP 连接。
- 在客户端和上游之间双向转发数据,直到任一方关闭连接。
stream {
// MySQL 代理
listen ":3306" {
proxy "mysql-server:3306"
}
// Redis 代理
listen ":6379" {
proxy "redis-server:6379"
}
// PostgreSQL 代理
listen ":5432" {
proxy "postgres-server:5432"
}
// 自定义 TCP 服务
listen ":9999" {
proxy "backend:9999"
}
}| 场景 | 说明 |
|---|---|
| 数据库代理 | MySQL、PostgreSQL、MongoDB 等 |
| 缓存代理 | Redis、Memcached 等 |
| 消息队列 | RabbitMQ、Kafka 等的 TCP 连接 |
| 自定义协议 | 任何基于 TCP 的协议 |
| 特性 | HTTP 代理 | TCP 流代理 |
|---|---|---|
| 协议感知 | 是 | 否 |
| 负载均衡 | 9 种策略 | 无 |
| 健康检查 | 支持 | 不支持 |
| 中间件 | 支持 | 不支持 |
| TLS 终止 | 支持 | 不支持 |
| 配置位置 | site > route |
stream > listen |
PROXY Protocol 允许在代理链中传递真实的客户端连接信息(源 IP、源端口、目的 IP、目的端口)。
global {
proxy-protocol true
trusted-proxy "10.0.0.0/8"
}只有匹配 trusted-proxy 的负载均衡器或反向代理可以提供 PROXY 头。
未配置 trusted-proxy 时,只信任 loopback 对端。
| 版本 | 格式 | 说明 |
|---|---|---|
| v1 | 文本 | PROXY TCP4 192.168.1.1 10.0.0.1 56789 80\r\n |
| v2 | 二进制 | 二进制编码头,支持更多信息 |
Gatel 自动检测并解析两种版本。
- 前端代理(如 HAProxy、AWS ELB)在建立连接后,先发送 PROXY Protocol 头。
- Gatel 解析头部,提取真实客户端信息。
- 后续的请求处理中,
remote_ip等变量使用解析出的真实地址。
客户端 (1.2.3.4) → HAProxy (PROXY Protocol) → Gatel → 后端
HAProxy 配置示例:
backend gatel_backend
server s1 10.0.0.1:443 send-proxy-v2
| 特性 | PROXY Protocol | X-Forwarded-For |
|---|---|---|
| 层级 | L4(传输层) | L7(应用层) |
| 可伪造 | 否 | 是(客户端可自行设置) |
| 性能 | 更好(仅连接建立时解析) | 每请求解析 |
| 适用 | 所有 TCP 协议 | 仅 HTTP |
- 仅在前端确实发送 PROXY Protocol 时才启用此选项。
- 如果启用了 PROXY Protocol 但连接不包含协议头,连接会被拒绝。
- 不要在直接面向客户端的场景下启用(客户端不会发送 PROXY Protocol 头)。
匹配器提供灵活的请求匹配能力,在路径匹配的基础上增加额外条件。
| 匹配器 | 说明 | 示例 |
|---|---|---|
method |
HTTP 方法 | match method="GET,POST" |
header |
请求头 | match header="Accept" pattern="*json*" |
query |
查询参数 | match query="format" value="json" |
remote-ip |
客户端 IP | match remote-ip="10.0.0.0/8" |
protocol |
协议 | match protocol="https" |
expression |
组合表达式 | match expression="{method} == GET" |
not |
否定 | match not { ... } |
匹配指定的 HTTP 方法,多个方法用逗号分隔。
route "/api/*" {
// 仅允许 GET 和 POST
match method="GET,POST"
proxy "127.0.0.1:3000"
}支持的方法:GET、POST、PUT、DELETE、PATCH、HEAD、OPTIONS。
匹配指定请求头的值,支持通配符。
route "/api/*" {
// 匹配 JSON 请求
match header="Content-Type" pattern="application/json*"
proxy "127.0.0.1:3000"
}
route "/api/*" {
// 匹配带有特定头的请求
match header="X-API-Key" pattern="*"
proxy "127.0.0.1:3000"
}匹配 URL 查询参数。
route "/search" {
match query="format" value="json"
// /search?format=json → 匹配
// /search?format=xml → 不匹配
proxy "127.0.0.1:3000"
}基于客户端 IP 地址匹配,使用 CIDR 表示法。
route "/internal/*" {
match remote-ip="10.0.0.0/8"
proxy "127.0.0.1:3000"
}匹配请求协议。
// 仅 HTTPS 请求
route "/secure/*" {
match protocol="https"
proxy "127.0.0.1:3000"
}
// HTTP 请求重定向到 HTTPS
route "/" {
match protocol="http"
redirect "https://{host}{path}" permanent=true
}使用表达式语法组合多种条件。
route "/api/*" {
match expression="{method} == GET && {path} ~ /api/public/*"
proxy "127.0.0.1:3000"
}可用变量:
| 变量 | 说明 |
|---|---|
{method} |
HTTP 方法 |
{path} |
请求路径 |
{host} |
主机名 |
{remote_ip} |
客户端 IP |
{protocol} |
协议(http / https) |
运算符:
| 运算符 | 说明 | 示例 |
|---|---|---|
== |
等于 | {method} == GET |
!= |
不等于 | {method} != DELETE |
~ |
通配符匹配 | {path} ~ /api/* |
&& |
逻辑与 | {method} == GET && {protocol} == https |
|| |
逻辑或 | {method} == GET || {method} == HEAD |
复杂表达式示例:
// API 公开端点:仅 HTTPS + GET
route "/api/public/*" {
match expression="{protocol} == https && {method} == GET"
proxy "127.0.0.1:3000"
}
// 允许多种方法
route "/api/data" {
match expression="{method} == GET || {method} == POST || {method} == PUT"
proxy "127.0.0.1:3000"
}反转内部匹配器的结果。
route "/api/*" {
// 不是来自内网的请求
match not {
match remote-ip="10.0.0.0/8"
}
// 对外部请求进行限流
rate-limit window="1m" max=50
proxy "127.0.0.1:3000"
}同一 route 中的多个 match 指令之间是 AND 关系:
route "/api/admin/*" {
// 同时满足以下所有条件
match method="GET,POST" // AND
match protocol="https" // AND
match remote-ip="10.0.0.0/8" // AND
match header="Authorization" pattern="Bearer *"
proxy "127.0.0.1:3001"
}Gatel 支持服务端模板渲染,可以在静态文件中嵌入动态内容。
site "example.com" {
route "/" {
templates root="/var/www/templates"
root "/var/www/html"
file-server
}
}环境变量读取和文件引入默认禁用;只应在可信模板中显式使用 allow-env=true
或 allow-include=true。
| 属性 | 类型 | 必填 | 说明 |
|---|---|---|---|
root |
string | 是 | 模板文件根目录 |
- 请求到达时,先检查请求的文件是否是模板文件。
- 如果是模板文件,读取并渲染模板。
- 将渲染结果作为响应返回。
Gatel 内置基于 LRU 算法的响应缓存,可以显著减少后端负载。
route "/api/*" {
cache max-entries=1000 max-age="300s"
proxy "127.0.0.1:3000"
}| 属性 | 类型 | 必填 | 说明 |
|---|---|---|---|
max-entries |
int | 是 | 最大缓存条目数 |
max-age |
duration | 是 | 缓存最大存活时间 |
命中条件:
- 请求方法为 GET 或 HEAD。
- 缓存键匹配(基于 URL 和相关请求头)。
- 缓存条目未过期。
不缓存的情况:
- 非 GET/HEAD 请求。
- 响应状态码非 2xx。
- 响应包含
Cache-Control: no-store头。 - 请求包含
Cache-Control: no-cache头。
site "example.com" {
// 公开 API — 短期缓存
route "/api/public/*" {
cache max-entries=5000 max-age="60s"
proxy "127.0.0.1:3000"
}
// 静态资源 — 长期缓存
route "/assets/*" {
cache max-entries=10000 max-age="86400s"
root "/var/www/static"
file-server
}
// 私有 API — 不缓存
route "/api/private/*" {
proxy "127.0.0.1:3000"
}
}- 缓存使用 dashmap(并发哈希表)存储,支持高并发读写。
- LRU 淘汰策略确保在内存有限时优先保留热点数据。
- 缓存是进程内的,重启后清空。
Gatel 内置高性能的静态文件服务器。
route "/" {
root "/var/www/html"
file-server
}route "/" {
root "/var/www/html"
file-server browse=true
}| 功能 | 说明 |
|---|---|
| MIME 检测 | 根据文件扩展名自动设置 Content-Type |
| ETag | 基于文件内容生成 ETag,支持条件请求 |
| Last-Modified | 基于文件修改时间,支持条件请求 |
| Range 请求 | 支持断点续传和部分内容请求 |
| 目录浏览 | 可选功能,显示目录内容列表 |
文件服务器自动处理条件请求:
If-None-Match— 与 ETag 比较,匹配则返回 304。If-Modified-Since— 与修改时间比较,未变化则返回 304。
route "/assets/*" {
encode "gzip" "brotli"
cache max-entries=10000 max-age="3600s"
root "/var/www/assets"
file-server
}global {
admin "127.0.0.1:2019"
}| 端点 | 方法 | 说明 |
|---|---|---|
/config |
GET | 返回当前运行的完整配置 |
/config/reload |
POST | 触发配置热重载 |
/health |
GET | 服务健康状态 |
/upstreams |
GET | 所有上游后端的健康状态和连接数 |
/metrics |
GET | Prometheus 格式的指标数据 |
/metrics 端点输出 Prometheus 格式的指标:
curl http://localhost:2019/metrics输出示例:
# HELP gatel_requests_total Total number of requests
# TYPE gatel_requests_total counter
gatel_requests_total{site="api.example.com",status="200"} 12345
gatel_requests_total{site="api.example.com",status="404"} 23
# HELP gatel_request_duration_seconds Request duration in seconds
# TYPE gatel_request_duration_seconds histogram
gatel_request_duration_seconds_bucket{site="api.example.com",le="0.01"} 10000
gatel_request_duration_seconds_bucket{site="api.example.com",le="0.1"} 12000
gatel_request_duration_seconds_bucket{site="api.example.com",le="1"} 12300
# HELP gatel_upstream_health Upstream health status
# TYPE gatel_upstream_health gauge
gatel_upstream_health{upstream="10.0.1.1:3000"} 1
gatel_upstream_health{upstream="10.0.1.2:3000"} 0
Prometheus 配置:
scrape_configs:
- job_name: 'gatel'
scrape_interval: 15s
static_configs:
- targets: ['localhost:2019']
metrics_path: '/metrics'Gatel 使用 tracing 框架生成结构化日志:
global {
log level="info" format="json"
}JSON 格式的日志便于与日志收集系统(ELK、Loki 等)集成:
{"timestamp":"2024-01-15T10:30:00.123Z","level":"INFO","target":"gatel::proxy","message":"request completed","method":"GET","path":"/api/users","status":200,"duration_ms":12,"upstream":"10.0.1.1:3000"}Gatel 支持无中断的配置热重载。
# 方式一:发送 SIGHUP 信号
kill -SIGHUP $(pidof gatel)
# 方式二:使用 CLI
gatel reload
# 方式三:管理 API
curl -X POST http://localhost:2019/config/reloadGatel 使用 ArcSwap 实现无锁原子配置切换:
- 接收到重载信号。
- 重新读取并解析主配置文件,并再次解析其中的
import "..."指令,因此被 import 的文件的改动在每次重载时都会被加载(参见使用import拆分配置)。 - 验证新配置的正确性。
- 如果验证通过,通过
ArcSwap::store()原子替换配置。 - 新请求使用新配置处理。
- 旧配置的引用计数归零后自动释放。
热重载会更新以下内容:
- 站点配置(route、中间件、处理器)
- 上游列表和负载均衡策略
- 健康检查配置
- 手动 TLS 证书(重新加载证书文件)
以下内容不受热重载影响(需要重启):
- 监听地址变更(
http、https端口) - HTTP/3 开关
- 管理 API 地址
如果新配置无效:
- 保持当前配置不变。
- 在日志中记录详细错误信息。
- 管理 API 返回错误响应。
当 Gatel 接收到关闭信号时,执行优雅关闭流程。
SIGTERM— 标准关闭信号(如kill命令、systemctl stop)SIGINT— 中断信号(如 Ctrl+C)
- 停止接受新连接 — 立即停止 TCP 监听。
- 等待活跃连接 — 等待正在处理的请求完成。
- 宽限期超时 — 如果超过宽限期仍有未完成的连接,强制关闭。
- 清理资源 — 释放所有资源,进程退出。
global {
grace-period "30s"
}宽限期的选择建议:
| 场景 | 建议值 | 说明 |
|---|---|---|
| 短连接为主的 API | 10s - 30s |
大部分请求会快速完成 |
| 长连接(WebSocket) | 60s - 120s |
给长连接更多时间关闭 |
| 流式下载服务 | 120s - 300s |
大文件下载可能需要更长时间 |
[Unit]
Description=Gatel Reverse Proxy
After=network.target
[Service]
Type=simple
ExecStart=/usr/local/bin/gatel run --config /etc/gatel/gatel.kdl
ExecReload=/bin/kill -SIGHUP $MAINPID
Restart=on-failure
RestartSec=5
TimeoutStopSec=60
[Install]
WantedBy=multi-user.target注意 TimeoutStopSec 应大于 Gatel 的 grace-period,以确保 systemd 不会在宽限期结束前强制终止进程。