From 0c71a80b5ac9730cd9eae5840f89311c85a54c7d Mon Sep 17 00:00:00 2001 From: xfy Date: Mon, 20 Apr 2026 10:59:35 +0800 Subject: [PATCH] =?UTF-8?q?docs(proxy):=20=E4=B8=BA=E5=8F=8D=E5=90=91?= =?UTF-8?q?=E4=BB=A3=E7=90=86=E6=A8=A1=E5=9D=97=E6=B7=BB=E5=8A=A0=E6=A0=87?= =?UTF-8?q?=E5=87=86=E5=8C=96=20godoc=20=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 为 proxy 包所有文件添加完整文档注释: - proxy: 反向代理核心(负载均衡、缓存、WebSocket、SSL/TLS) - headers: X-Forwarded 系列请求头设置 - health: 后端健康检查 - proxy_ssl: 上游 SSL/TLS 配置 - redirect_rewrite: 重定向响应改写 - tempfile_cleaner: 临时文件清理 包级注释详细说明支持的负载均衡算法、故障转移机制、 代理缓存策略、重定向改写模式等核心功能。 Co-Authored-By: Claude Opus 4.7 --- internal/proxy/headers.go | 30 ++- internal/proxy/health.go | 2 + internal/proxy/proxy.go | 371 ++++++++++++++++++++++------- internal/proxy/proxy_ssl.go | 23 +- internal/proxy/redirect_rewrite.go | 173 +++++++++++--- internal/proxy/tempfile_cleaner.go | 7 + 6 files changed, 477 insertions(+), 129 deletions(-) diff --git a/internal/proxy/headers.go b/internal/proxy/headers.go index 31b37b2..e71a702 100644 --- a/internal/proxy/headers.go +++ b/internal/proxy/headers.go @@ -1,6 +1,20 @@ // Package proxy 反向代理包,为 Lolly HTTP 服务器提供反向代理功能。 // -// 该文件提供统一的 X-Forwarded 头设置逻辑。 +// 该文件提供统一的 X-Forwarded 系列请求头设置逻辑,包括: +// - X-Forwarded-For: 客户端 IP 地址链 +// - X-Real-IP: 客户端真实 IP 地址 +// - X-Forwarded-Host: 原始请求 Host +// - X-Forwarded-Proto: 原始请求协议(http/https) +// +// 主要用途: +// 用于在代理转发时保留客户端原始请求信息,使后端服务能够获取 +// 客户端的真实 IP、Host 和协议。 +// +// 注意事项: +// - 所有函数均为非并发安全(无状态函数) +// - X-Forwarded-For 支持追加模式和覆盖模式 +// +// 作者:xfy package proxy import ( @@ -10,17 +24,19 @@ import ( "rua.plus/lolly/internal/netutil" ) -// 协议常量 +// 协议常量,用于标识请求使用的传输层协议。 const ( - protoHTTP = "http" - protoHTTPS = "https" + protoHTTP = "http" // 明文 HTTP 协议 + protoHTTPS = "https" // TLS 加密的 HTTPS 协议 ) // ForwardedHeaders 包含 X-Forwarded 系列头信息。 +// +// 用于在代理转发时保留客户端原始请求信息。 type ForwardedHeaders struct { - ClientIP string // 客户端 IP - Host string // 原始 Host - Proto string // 协议 (http/https) + ClientIP string // 客户端 IP 地址,从连接信息或 X-Real-IP 头提取 + Host string // 原始请求 Host 头,表示客户端访问的主机名 + Proto string // 原始请求协议,http 或 https } // ExtractForwardedHeaders 从请求上下文中提取 X-Forwarded 头信息。 diff --git a/internal/proxy/health.go b/internal/proxy/health.go index c598657..df06fa9 100644 --- a/internal/proxy/health.go +++ b/internal/proxy/health.go @@ -24,6 +24,8 @@ import ( "rua.plus/lolly/internal/loadbalance" ) +// healthPath 默认健康检查路径。 +// 当配置中未指定 path 时使用此值。 const healthPath = "/health" // HealthChecker 对后端目标执行健康检查。 diff --git a/internal/proxy/proxy.go b/internal/proxy/proxy.go index cacb6ad..937171a 100644 --- a/internal/proxy/proxy.go +++ b/internal/proxy/proxy.go @@ -1,33 +1,31 @@ // Package proxy 反向代理包,为 Lolly HTTP 服务器提供反向代理功能。 // // 该包使用 fasthttp.HostClient 实现高性能反向代理,支持连接池和自动 keep-alive 管理。 -// 支持负载均衡、WebSocket 转发、自定义请求头/响应头和全面的超时配置。 +// 支持负载均衡、WebSocket 转发、自定义请求头/响应头、上游 SSL/TLS、DNS 动态解析、 +// 代理缓存、重定向改写和全面的超时配置。 // -// 使用示例: +// 主要功能: +// - 多后端负载均衡:支持 round_robin、weighted_round_robin、least_conn、ip_hash、consistent_hash +// - Lua 动态选择:通过 balancer_by_lua 脚本实现自定义负载均衡逻辑 +// - 故障转移:支持 next_upstream 配置,自动重试失败请求到其他健康目标 +// - WebSocket 代理:支持 ws:// 和 wss:// 协议的透明双向转发 +// - 上游 SSL/TLS:支持自定义 CA 证书、客户端证书(mTLS)、SNI 和 TLS 版本控制 +// - DNS 动态解析:支持后端域名自动解析、IP 缓存和定时刷新 +// - 代理缓存:支持响应缓存、缓存锁防击穿、后台刷新过期缓存 +// - 重定向改写:支持 default/custom/off 模式改写 Location 和 Refresh 响应头 +// - 健康检查:支持主动 HTTP 探测和被动失败标记 +// - 临时文件:大响应自动写入临时文件,避免内存溢出 // -// targets := []*loadbalance.Target{ -// {URL: "http://backend1:8080", Weight: 1}, -// {URL: "http://backend2:8080", Weight: 2}, -// } -// targets[0].Healthy.Store(true) -// targets[1].Healthy.Store(true) +// 主要用途: +// 用于将客户端 HTTP 请求代理转发到后端服务器集群,实现负载均衡、缓存加速、 +// 协议转换等功能,适用于 API 网关、反向代理服务器等场景。 // -// proxyConfig := &config.ProxyConfig{ -// Path: "/api", -// LoadBalance: "weighted_round_robin", -// Timeout: config.ProxyTimeout{ -// Connect: 5 * time.Second, -// Read: 30 * time.Second, -// Write: 30 * time.Second, -// }, -// } +// 注意事项: +// - Proxy 实例的公开方法均为并发安全 +// - 使用前需确保 targets 中至少有一个健康目标 +// - Lua 脚本执行有超时保护,默认 100ms // -// p, err := proxy.NewProxy(proxyConfig, targets) -// if err != nil { -// log.Fatal(err) -// } -// -// // 使用 p.ServeHTTP 作为 fasthttp 请求处理器 +// 作者:xfy // //go:generate go test -v ./... package proxy @@ -57,20 +55,22 @@ import ( ) const ( - // upstreamCache 上游缓存标识 - // 用于标记请求可直接使用缓存响应,无需转发到上游 + // upstreamCache 上游缓存标识。 + // 用于标记请求可直接使用缓存响应,无需转发到上游。 upstreamCache = "CACHE" - // 负载均衡算法名称 - lbRoundRobin = "round_robin" - lbWeightedRoundRobin = "weighted_round_robin" - lbLeastConn = "least_conn" - lbIPHash = "ip_hash" - lbConsistentHash = "consistent_hash" + // 负载均衡算法名称,与配置中的 LoadBalance 字段对应。 + lbRoundRobin = "round_robin" // 简单轮询 + lbWeightedRoundRobin = "weighted_round_robin" // 加权轮询 + lbLeastConn = "least_conn" // 最少连接 + lbIPHash = "ip_hash" // IP 哈希 + lbConsistentHash = "consistent_hash" // 一致性哈希 ) // headersPool 复用缓存 headers map,减少分配。 // 预容量 20 覆盖大多数 HTTP 响应头数量。 +// 注意:从 pool 获取的 map 使用后不能 Put 回 pool, +// 因为 cache.Set 存储了 map 引用。 var headersPool = sync.Pool{ New: func() interface{} { return make(map[string]string, 20) @@ -79,25 +79,26 @@ var headersPool = sync.Pool{ // Proxy 表示反向代理实例,负责将 HTTP 请求转发到后端目标。 // -// 它为每个后端目标管理连接池,并提供负载均衡功能。 +// 它为每个后端目标管理连接池(HostClient),并提供负载均衡、 +// 缓存、健康检查、Lua 动态选择等功能。 // // 注意事项: // - 所有公开方法均为并发安全 // - 使用前需确保 targets 中至少有一个健康目标 type Proxy struct { - balancer loadbalance.Balancer - fallbackBalancer loadbalance.Balancer // Lua 失败时的备用均衡器 - resolver resolver.Resolver - clients map[string]*fasthttp.HostClient - config *config.ProxyConfig - cache *cache.ProxyCache - healthChecker *HealthChecker - luaEngine *lua.LuaEngine // Lua 引擎引用 - redirectRewriter *RedirectRewriter // 重定向改写器 - stopCh chan struct{} - targets []*loadbalance.Target - mu sync.RWMutex - started atomic.Bool + balancer loadbalance.Balancer // 主负载均衡器 + fallbackBalancer loadbalance.Balancer // Lua 失败时的备用均衡器 + resolver resolver.Resolver // DNS 解析器 + clients map[string]*fasthttp.HostClient // 后端连接池,key 为 target URL + config *config.ProxyConfig // 代理配置 + cache *cache.ProxyCache // 代理缓存 + healthChecker *HealthChecker // 健康检查器 + luaEngine *lua.LuaEngine // Lua 引擎,用于 balancer_by_lua 功能 + redirectRewriter *RedirectRewriter // 重定向改写器 + stopCh chan struct{} // 停止信号通道 + targets []*loadbalance.Target // 后端目标列表 + mu sync.RWMutex // 保护并发访问的读写锁 + started atomic.Bool // 代理启动标志 } // NewProxy 使用给定的配置和后台目标创建一个新的反向代理实例。 @@ -179,7 +180,22 @@ func NewProxy(cfg *config.ProxyConfig, targets []*loadbalance.Target, transportC return p, nil } -// createBalancerByName 根据算法名称创建负载均衡器 +// createBalancerByName 根据算法名称创建负载均衡器。 +// +// 支持的算法: +// - round_robin: 简单轮询,按顺序选择目标 +// - weighted_round_robin: 加权轮询,按权重比例分配 +// - least_conn: 最少连接,选择当前连接数最少的目标 +// - ip_hash: IP 哈希,同一客户端 IP 固定选择同一目标 +// - consistent_hash: 一致性哈希,支持虚拟节点和自定义 hash_key +// +// 参数: +// - name: 算法名称 +// - cfg: 代理配置,用于获取虚拟节点数和 hash_key +// +// 返回值: +// - loadbalance.Balancer: 创建的负载均衡器实例 +// - error: 不支持的算法时返回错误 func createBalancerByName(name string, cfg *config.ProxyConfig) (loadbalance.Balancer, error) { switch name { case lbRoundRobin, "": @@ -202,17 +218,42 @@ func createBalancerByName(name string, cfg *config.ProxyConfig) (loadbalance.Bal } // SetHealthChecker 设置健康检查器用于被动健康检查。 -// 当代理请求失败时,将调用健康检查器的 MarkUnhealthy 方法。 +// +// 当代理请求失败时,将调用健康检查器的 MarkUnhealthy 方法, +// 将失败的目标标记为不健康,避免后续请求继续路由到该目标。 +// +// 参数: +// - hc: 健康检查器实例,nil 时禁用被动健康检查 func (p *Proxy) SetHealthChecker(hc *HealthChecker) { p.healthChecker = hc } -// createBalancer 根据配置的算法创建负载均衡器。 +// createBalancer 根据配置中指定的算法名称创建负载均衡器。 +// 是对 createBalancerByName 的便捷封装。 +// +// 参数: +// - cfg: 代理配置,从 cfg.LoadBalance 读取算法名称 +// +// 返回值: +// - loadbalance.Balancer: 创建的负载均衡器实例 +// - error: 不支持的算法时返回错误 func createBalancer(cfg *config.ProxyConfig) (loadbalance.Balancer, error) { return createBalancerByName(cfg.LoadBalance, cfg) } -// createHostClient 为后台目标 URL 创建 fasthttp.HostClient。 +// createHostClient 为指定的后端目标 URL 创建 fasthttp.HostClient。 +// +// 从目标 URL 解析地址和 TLS 标志,应用 Transport 连接池配置 +//(空闲连接超时、最大连接数),以及上游 SSL 配置。 +// +// 参数: +// - targetURL: 后端目标 URL(如 http://backend:8080) +// - timeout: 代理超时配置(读写超时、连接超时) +// - transportCfg: 可选的 Transport 连接池配置,nil 时使用默认值 +// - sslCfg: 可选的上游 SSL 配置 +// +// 返回值: +// - *fasthttp.HostClient: 配置完成的 HostClient 实例 func createHostClient(targetURL string, timeout config.ProxyTimeout, transportCfg *config.TransportConfig, sslCfg *config.ProxySSLConfig) *fasthttp.HostClient { // 从目标 URL 解析主机和协议 // addDefaultPort=true 确保 HostClient.Addr 包含端口(host:port 格式) @@ -256,43 +297,54 @@ func createHostClient(targetURL string, timeout config.ProxyTimeout, transportCf return client } -// UpstreamTiming 上游时间记录,用于捕获各种时间戳 +// UpstreamTiming 记录上游请求的各个时间戳。 +// +// 用于捕获连接建立、首字节接收、响应完成等关键时间点, +// 计算连接时间、首字节时间和总响应时间,供日志和监控使用。 type UpstreamTiming struct { - start time.Time - connectStart time.Time - connectEnd time.Time - headerReceived time.Time - responseEnd time.Time + start time.Time // 请求开始时间 + connectStart time.Time // 连接开始时间 + connectEnd time.Time // 连接完成时间 + headerReceived time.Time // 接收到响应头的时间 + responseEnd time.Time // 响应完成时间 } -// NewUpstreamTiming 创建新的上游时间记录器 +// NewUpstreamTiming 创建并初始化上游计时器。 +// 自动记录请求开始时间。 +// +// 返回值: +// - *UpstreamTiming: 初始化的计时器实例 func NewUpstreamTiming() *UpstreamTiming { return &UpstreamTiming{ start: time.Now(), } } -// MarkConnectStart 标记连接开始 +// MarkConnectStart 标记连接开始时间点。 func (t *UpstreamTiming) MarkConnectStart() { t.connectStart = time.Now() } -// MarkConnectEnd 标记连接完成 +// MarkConnectEnd 标记连接完成时间点。 func (t *UpstreamTiming) MarkConnectEnd() { t.connectEnd = time.Now() } -// MarkHeaderReceived 标记接收到响应头 +// MarkHeaderReceived 标记接收到响应头时间点。 func (t *UpstreamTiming) MarkHeaderReceived() { t.headerReceived = time.Now() } -// MarkResponseEnd 标记响应完成 +// MarkResponseEnd 标记响应完成时间点。 func (t *UpstreamTiming) MarkResponseEnd() { t.responseEnd = time.Now() } -// GetConnectTime 获取连接时间(秒) +// GetConnectTime 获取连接建立耗时(秒)。 +// 如果连接开始或结束时间未记录,返回 0。 +// +// 返回值: +// - float64: 连接耗时,单位为秒 func (t *UpstreamTiming) GetConnectTime() float64 { if t.connectStart.IsZero() || t.connectEnd.IsZero() { return 0 @@ -300,7 +352,12 @@ func (t *UpstreamTiming) GetConnectTime() float64 { return t.connectEnd.Sub(t.connectStart).Seconds() } -// GetHeaderTime 获取首字节时间(秒) +// GetHeaderTime 获取首字节响应时间(秒)。 +// 计算从连接完成到接收到响应头的耗时。 +// 如果任一时间点未记录,返回 0。 +// +// 返回值: +// - float64: 首字节耗时,单位为秒 func (t *UpstreamTiming) GetHeaderTime() float64 { if t.connectEnd.IsZero() || t.headerReceived.IsZero() { return 0 @@ -308,7 +365,12 @@ func (t *UpstreamTiming) GetHeaderTime() float64 { return t.headerReceived.Sub(t.connectEnd).Seconds() } -// GetResponseTime 获取响应时间(秒) +// GetResponseTime 获取总响应时间(秒)。 +// 计算从连接完成到响应完成的耗时。 +// 如果任一时间点未记录,返回 0。 +// +// 返回值: +// - float64: 响应耗时,单位为秒 func (t *UpstreamTiming) GetResponseTime() float64 { if t.connectEnd.IsZero() || t.responseEnd.IsZero() { return 0 @@ -316,8 +378,20 @@ func (t *UpstreamTiming) GetResponseTime() float64 { return t.responseEnd.Sub(t.connectEnd).Seconds() } -// FinalizeUpstreamVars 在请求处理结束时设置上游变量到 Context -// 这个函数应该在 ServeHTTP 的 defer 中调用 +// FinalizeUpstreamVars 在请求处理结束时设置上游变量到变量上下文。 +// +// 该函数应在 ServeHTTP 的 defer 中调用,用于计算并设置以下变量: +// - upstream_addr: 上游服务器地址 +// - upstream_status: 上游响应状态码 +// - upstream_response_time: 响应耗时 +// - upstream_connect_time: 连接耗时 +// - upstream_header_time: 首字节耗时 +// +// 参数: +// - vc: 变量上下文,用于存储上游变量 +// - upstreamAddr: 上游服务器地址 +// - upstreamStatus: 上游响应状态码 +// - timing: 时间记录器 func FinalizeUpstreamVars(vc *variable.Context, upstreamAddr string, upstreamStatus int, timing *UpstreamTiming) { if vc == nil { return @@ -647,8 +721,18 @@ func (p *Proxy) ServeHTTP(ctx *fasthttp.RequestCtx) { } } -// selectTarget 使用配置的负载均衡器选择后端目标 -// 如果启用 Lua balancer,先尝试 Lua 脚本选择 +// selectTarget 使用配置的负载均衡器选择后端目标。 +// +// 选择优先级: +// 1. 如果启用了 Lua balancer,先尝试 Lua 脚本选择 +// 2. Lua 选择失败时,使用 fallback 算法 +// 3. 否则使用传统负载均衡算法 +// +// 参数: +// - ctx: FastHTTP 请求上下文,用于提取客户端 IP 等信息 +// +// 返回值: +// - *loadbalance.Target: 选中的后端目标,无可用目标时返回 nil func (p *Proxy) selectTarget(ctx *fasthttp.RequestCtx) *loadbalance.Target { p.mu.RLock() targets := p.targets @@ -678,7 +762,18 @@ func (p *Proxy) selectTarget(ctx *fasthttp.RequestCtx) *loadbalance.Target { return p.selectByBalancer(ctx, targets) } -// selectByLua 使用 Lua 脚本选择目标 +// selectByLua 使用 Lua 脚本选择后端目标。 +// +// 执行配置的 Lua 脚本,脚本可通过 ngx.balancer.set_current_peer() 选择目标。 +// 如果 Lua 脚本执行失败或未调用 set_current_peer,返回 nil 表示需要使用 fallback 算法。 +// +// 参数: +// - ctx: FastHTTP 请求上下文 +// - targets: 候选目标列表 +// +// 返回值: +// - *loadbalance.Target: Lua 脚本选中的目标,nil 表示未选择 +// - error: Lua 执行失败时返回错误 func (p *Proxy) selectByLua(ctx *fasthttp.RequestCtx, targets []*loadbalance.Target) (*loadbalance.Target, error) { clientIP := netutil.ExtractClientIP(ctx) @@ -728,7 +823,17 @@ func (p *Proxy) selectByLua(ctx *fasthttp.RequestCtx, targets []*loadbalance.Tar return bctx.Selected, nil } -// selectByFallback 使用 fallback 算法选择目标 +// selectByFallback 使用 fallback 负载均衡算法选择目标。 +// +// 当 Lua balancer 执行失败或未选择目标时使用。 +// 对于 IPHash 算法,会自动提取客户端 IP 进行哈希选择。 +// +// 参数: +// - ctx: FastHTTP 请求上下文 +// - targets: 候选目标列表 +// +// 返回值: +// - *loadbalance.Target: fallback 算法选中的目标 func (p *Proxy) selectByFallback(ctx *fasthttp.RequestCtx, targets []*loadbalance.Target) *loadbalance.Target { p.mu.RLock() balancer := p.fallbackBalancer @@ -742,7 +847,17 @@ func (p *Proxy) selectByFallback(ctx *fasthttp.RequestCtx, targets []*loadbalanc return balancer.Select(targets) } -// selectByBalancer 使用主负载均衡器选择目标 +// selectByBalancer 使用主负载均衡器选择目标。 +// +// 对于特殊算法(IPHash、ConsistentHash),会从请求上下文中提取 +// 相应的哈希键(客户端 IP、URI、自定义 Header 等)。 +// +// 参数: +// - ctx: FastHTTP 请求上下文 +// - targets: 候选目标列表 +// +// 返回值: +// - *loadbalance.Target: 主负载均衡器选中的目标 func (p *Proxy) selectByBalancer(ctx *fasthttp.RequestCtx, targets []*loadbalance.Target) *loadbalance.Target { p.mu.RLock() balancer := p.balancer @@ -793,7 +908,19 @@ func (p *Proxy) selectTargetExcluding(ctx *fasthttp.RequestCtx, excluded []*load return balancer.SelectExcluding(targets, excluded) } -// extractHashKey 根据配置提取哈希键值。 +// extractHashKey 根据一致性哈希配置提取哈希键值。 +// +// 支持的 hash_key 配置: +// - "ip" 或 "": 使用客户端 IP 地址 +// - "uri": 使用完整请求 URI +// - "header:NAME": 使用指定请求头的值,缺失时回退到客户端 IP +// +// 参数: +// - ctx: FastHTTP 请求上下文 +// - hashKey: 哈希键配置 +// +// 返回值: +// - string: 提取的哈希键值 func (p *Proxy) extractHashKey(ctx *fasthttp.RequestCtx, hashKey string) string { switch { case hashKey == "ip" || hashKey == "": @@ -812,7 +939,14 @@ func (p *Proxy) extractHashKey(ctx *fasthttp.RequestCtx, hashKey string) string } } -// getClient 返回给定目标 URL 对应的 HostClient。 +// getClient 返回指定目标 URL 对应的 HostClient 连接池实例。 +// 如果目标 URL 不存在于连接池中,返回 nil。 +// +// 参数: +// - targetURL: 后端目标 URL +// +// 返回值: +// - *fasthttp.HostClient: 对应的连接池实例 func (p *Proxy) getClient(targetURL string) *fasthttp.HostClient { p.mu.RLock() client := p.clients[targetURL] @@ -820,8 +954,17 @@ func (p *Proxy) getClient(targetURL string) *fasthttp.HostClient { return client } -// modifyRequestHeaders 在转发到后端之前修改请求头。 -// 添加标准代理请求头并应用自定义请求头配置。 +// modifyRequestHeaders 在转发请求到后端之前修改请求头。 +// +// 执行以下操作: +// 1. 设置 Host header 为目标主机地址 +// 2. 提取并设置 X-Forwarded-For、X-Real-IP、X-Forwarded-Host、X-Forwarded-Proto +// 3. 应用自定义请求头配置(支持变量展开) +// 4. 移除配置的请求头 +// +// 参数: +// - ctx: FastHTTP 请求上下文 +// - target: 选中的后端目标 func (p *Proxy) modifyRequestHeaders(ctx *fasthttp.RequestCtx, target *loadbalance.Target) { headers := &ctx.Request.Header @@ -855,6 +998,11 @@ func (p *Proxy) modifyRequestHeaders(ctx *fasthttp.RequestCtx, target *loadbalan } // modifyResponseHeaders 在发送给客户端之前修改响应头。 +// +// 应用自定义响应头配置,支持变量展开(如 $upstream_addr、$status 等)。 +// +// 参数: +// - ctx: FastHTTP 请求上下文 func (p *Proxy) modifyResponseHeaders(ctx *fasthttp.RequestCtx) { // 从配置设置自定义响应头(支持变量展开) if p.config.Headers.SetResponse != nil { @@ -868,6 +1016,16 @@ func (p *Proxy) modifyResponseHeaders(ctx *fasthttp.RequestCtx) { } // isWebSocketRequest 检查请求是否为 WebSocket 升级请求。 +// +// 通过检查 Connection 和 Upgrade 请求头判断: +// - Connection 头需包含 "upgrade"(不区分大小写) +// - Upgrade 头需等于 "websocket"(不区分大小写) +// +// 参数: +// - ctx: FastHTTP 请求上下文 +// +// 返回值: +// - bool: true 表示是 WebSocket 升级请求 func isWebSocketRequest(ctx *fasthttp.RequestCtx) bool { // 检查 Connection 请求头 connection := ctx.Request.Header.Peek("Connection") @@ -883,8 +1041,16 @@ func isWebSocketRequest(ctx *fasthttp.RequestCtx) bool { return strings.EqualFold(string(upgrade), "websocket") } -// UpdateTargets 更新代理目标并重新初始化客户端。 -// 适用于动态配置更新。 +// UpdateTargets 更新代理的后端目标列表并重新初始化连接池。 +// +// 清除旧的 HostClient 连接池,为每个新目标创建新的连接。 +// 适用于动态配置更新场景(如热重载配置)。 +// +// 参数: +// - targets: 新的后端目标列表 +// +// 返回值: +// - error: 目标列表为空时返回错误 func (p *Proxy) UpdateTargets(targets []*loadbalance.Target) error { if len(targets) == 0 { return errors.New("no targets provided") @@ -910,22 +1076,36 @@ func (p *Proxy) UpdateTargets(targets []*loadbalance.Target) error { return nil } -// GetTargets 返回当前的目标列表。 +// GetTargets 返回当前的后端目标列表。 +// +// 返回值: +// - []*loadbalance.Target: 后端目标列表 func (p *Proxy) GetTargets() []*loadbalance.Target { p.mu.RLock() defer p.mu.RUnlock() return p.targets } -// GetConfig 返回代理配置。 +// GetConfig 返回代理的配置。 +// +// 返回值: +// - *config.ProxyConfig: 代理配置 func (p *Proxy) GetConfig() *config.ProxyConfig { p.mu.RLock() defer p.mu.RUnlock() return p.config } -// buildCacheKey 构建缓存键。 -// 保留此函数用于日志记录和调试。 +// buildCacheKey 构建缓存键字符串。 +// +// 使用请求方法和完整请求 URI 作为缓存键。 +// 该函数保留用于日志记录和调试场景。 +// +// 参数: +// - ctx: FastHTTP 请求上下文 +// +// 返回值: +// - string: 缓存键(格式 "METHOD:URI") func (p *Proxy) buildCacheKey(ctx *fasthttp.RequestCtx) string { // 使用请求方法和路径作为缓存键 return string(ctx.Request.Header.Method()) + ":" + string(ctx.Request.URI().RequestURI()) @@ -955,7 +1135,13 @@ func (p *Proxy) buildCacheKeyHashValue(ctx *fasthttp.RequestCtx) uint64 { return h.Sum64() } -// writeCachedResponse 写入缓存的响应。 +// writeCachedResponse 将缓存的响应写入 FastHTTP 响应上下文。 +// +// 设置响应体、状态码、响应头,并添加 X-Cache: HIT 头标记缓存命中。 +// +// 参数: +// - ctx: FastHTTP 请求上下文 +// - entry: 缓存条目,包含响应数据和元数据 func (p *Proxy) writeCachedResponse(ctx *fasthttp.RequestCtx, entry *cache.ProxyCacheEntry) { ctx.Response.SetBody(entry.Data) ctx.Response.SetStatusCode(entry.Status) @@ -965,7 +1151,16 @@ func (p *Proxy) writeCachedResponse(ctx *fasthttp.RequestCtx, entry *cache.Proxy ctx.Response.Header.Set("X-Cache", "HIT") } -// backgroundRefresh 后台刷新缓存。 +// backgroundRefresh 在后台异步刷新缓存条目。 +// +// 向对应的上游目标发送请求,获取最新响应并更新缓存。 +// 该方法在独立 goroutine 中运行,不阻塞主请求流程。 +// +// 参数: +// - ctx: 原始 FastHTTP 请求上下文(仅用于复制请求信息) +// - target: 要刷新的后端目标 +// - hashKey: 缓存哈希键 +// - origKey: 缓存原始键 func (p *Proxy) backgroundRefresh(ctx *fasthttp.RequestCtx, target *loadbalance.Target, hashKey uint64, origKey string) { // 创建新的请求上下文副本 req := fasthttp.AcquireRequest() @@ -1021,8 +1216,16 @@ func (p *Proxy) GetCacheStats() *cache.ProxyCacheStats { return &stats } -// extractHostFromURL 从 URL 中提取 host:port。 -// 用于设置代理请求的 Host header。 +// extractHostFromURL 从 URL 字符串中提取 host:port 部分。 +// +// 移除 http:// 或 https:// 协议前缀,以及路径部分, +// 仅保留主机名和端口(如 "example.com:8080")。 +// +// 参数: +// - urlStr: 完整 URL 字符串 +// +// 返回值: +// - string: host:port 格式的主机地址 func extractHostFromURL(urlStr string) string { // 移除协议前缀 host := urlStr diff --git a/internal/proxy/proxy_ssl.go b/internal/proxy/proxy_ssl.go index 19f0143..3e3c8ea 100644 --- a/internal/proxy/proxy_ssl.go +++ b/internal/proxy/proxy_ssl.go @@ -2,6 +2,23 @@ // // 该文件提供上游 SSL/TLS 配置支持,包括自定义 CA 证书、 // 客户端证书(mTLS)、SNI 和 TLS 版本控制。 +// +// 主要功能: +// - 自定义 CA 证书验证:支持指定受信任的 CA 证书文件 +// - 客户端证书认证(mTLS):支持双向 TLS 认证 +// - SNI 支持:自动从目标 URL 提取或使用配置的主机名 +// - TLS 版本控制:支持 TLSv1.0 到 TLSv1.3 的最小/最大版本限制 +// - 跳过证书验证:仅测试环境使用 +// +// 主要用途: +// 用于代理与上游服务器之间的 TLS 连接配置,支持自签名证书、 +// 内部 CA 签发证书以及双向认证场景。 +// +// 注意事项: +// - InsecureSkipVerify 仅建议在测试环境使用 +// - 证书文件路径为绝对路径或相对于工作目录 +// +// 作者:xfy package proxy import ( @@ -14,8 +31,10 @@ import ( "rua.plus/lolly/internal/config" ) -// TLS 版本字符串到 tls 常量的映射。 -// 支持 TLSv1.0, TLSv1.1, TLSv1.2, TLSv1.3 格式(大小写不敏感) +// tlsVersionMap TLS 版本字符串到 tls 常量的映射表。 +// +// 支持 TLSv1.0、TLSv1.1、TLSv1.2、TLSv1.3 格式(大小写不敏感)。 +// 空字符串表示使用 Go 标准库默认值。 var tlsVersionMap = map[string]uint16{ "TLSV1.0": tls.VersionTLS10, "TLSV1.1": tls.VersionTLS11, diff --git a/internal/proxy/redirect_rewrite.go b/internal/proxy/redirect_rewrite.go index 09ec25b..99e2685 100644 --- a/internal/proxy/redirect_rewrite.go +++ b/internal/proxy/redirect_rewrite.go @@ -1,4 +1,25 @@ // Package proxy 反向代理包,为 Lolly HTTP 服务器提供反向代理功能。 +// +// 该文件实现 Location 和 Refresh 响应头的改写功能。 +// 当上游服务器返回重定向响应时,将 Location 头中的上游地址 +// 改写为代理地址,确保客户端能正确访问。 +// +// 主要功能: +// - default 模式:自动将上游地址替换为客户端原始 Host +// - custom 模式:使用自定义规则(正则/前缀匹配)进行改写 +// - off 模式:禁用改写 +// - Refresh 头改写:支持 "N; url=URL" 格式 +// - 变量展开:自定义规则中支持 Lolly 变量 +// +// 主要用途: +// 用于处理上游服务器返回的 3xx 重定向响应,确保客户端 +// 收到的是代理地址而非内部上游地址。 +// +// 注意事项: +// - 调用位置:必须在 modifyResponseHeaders 之前调用 +// - default 模式使用前缀匹配,防止部分匹配问题 +// +// 作者:xfy package proxy import ( @@ -10,31 +31,46 @@ import ( "rua.plus/lolly/internal/variable" ) -// RedirectRewrite 模式常量 +// redirect_rewrite 模式常量,配置 redirect_rewrite.mode 字段。 const ( - redirectModeDefault = "default" - redirectModeOff = "off" - redirectModeCustom = "custom" + redirectModeDefault = "default" // 默认模式:自动替换上游地址为客户端 Host + redirectModeOff = "off" // 关闭模式:不执行任何改写 + redirectModeCustom = "custom" // 自定义模式:使用预编译规则进行改写 ) -// compiledRule 预编译的改写规则 +// compiledRule 预编译的改写规则。 +// +// 用于 custom 模式,在初始化时编译以避免运行时开销。 type compiledRule struct { - pattern *regexp.Regexp // 正则模式,nil 表示非正则匹配 - replacement string // 替换模板(含变量) - exactMatch string // 精确匹配前缀(用于 prefix 匹配) - caseInsensitive bool // 正则大小写不敏感(~* 前缀) + pattern *regexp.Regexp // 正则表达式模式,nil 表示使用前缀匹配 + replacement string // 替换模板,支持 Lolly 变量展开 + exactMatch string // 前缀匹配字符串(非正则模式下使用) + caseInsensitive bool // 正则匹配时是否忽略大小写(~* 前缀触发) } -// RedirectRewriter Location/Refresh 头改写器 +// RedirectRewriter Location 和 Refresh 响应头改写器。 +// +// 根据配置的模式对上游返回的重定向响应进行改写,确保客户端 +// 收到的是代理地址而非内部上游服务器地址。 type RedirectRewriter struct { - proxyPath string // 用于 default 模式(当前代理路径) - mode string // "default" | "off" | "custom"(空字符串视为 default) - rules []compiledRule // 仅 custom 模式预编译 + proxyPath string // 当前代理路径(如 "/api/"),用于 default 模式 + mode string // 改写模式:"default" | "off" | "custom",空字符串视为 default + rules []compiledRule // 预编译的改写规则,仅 custom 模式下使用 } -// NewRedirectRewriter 创建改写器 -// proxyPath: 当前代理路径(如 "/api/") -// 注意:mode 为空字符串时默认为 "default" +// NewRedirectRewriter 创建重定向改写器实例。 +// +// 根据配置模式初始化改写器: +// - nil 配置或未指定模式:默认启用 default 模式 +// - custom 模式:预编译所有改写规则(正则/前缀匹配) +// +// 参数: +// - cfg: 重定向改写配置,nil 时使用 default 模式 +// - proxyPath: 当前代理路径(如 "/api/") +// +// 返回值: +// - *RedirectRewriter: 改写器实例 +// - error: 正则表达式编译失败时返回错误 func NewRedirectRewriter(cfg *config.RedirectRewriteConfig, proxyPath string) (*RedirectRewriter, error) { if cfg == nil { // 未配置时默认启用 default 模式 @@ -84,7 +120,12 @@ func NewRedirectRewriter(cfg *config.RedirectRewriteConfig, proxyPath string) (* return rw, nil } -// Mode 返回当前模式(处理空字符串默认值) +// Mode 返回当前改写模式。 +// +// 处理空字符串默认值,空字符串视为 "default" 模式。 +// +// 返回值: +// - string: 当前模式("default"、"off" 或 "custom") func (r *RedirectRewriter) Mode() string { if r.mode == "" { return redirectModeDefault @@ -92,13 +133,19 @@ func (r *RedirectRewriter) Mode() string { return r.mode } -// RewriteResponse 改写响应中的 Location 和 Refresh 头 -// targetURL: 实际选中的上游地址(用于 default 模式) -// originalClientHost: 客户端原始 Host(在 modifyRequestHeaders 改写前保存) -// 调用位置:必须在 modifyResponseHeaders 之前 -// 内部逻辑: -// - 检查 resp.StatusCode(),仅 3xx 状态码处理 Location 头 +// RewriteResponse 改写响应中的 Location 和 Refresh 头。 +// +// 处理逻辑: +// - 仅对 3xx 状态码处理 Location 头(重定向响应) // - 所有状态码都处理 Refresh 头 +// +// 调用位置:必须在 modifyResponseHeaders 之前调用。 +// +// 参数: +// - resp: 上游响应 +// - ctx: FastHTTP 请求上下文,用于变量展开 +// - targetURL: 实际选中的上游地址(用于 default 模式) +// - originalClientHost: 客户端原始 Host(在 modifyRequestHeaders 改写前保存) func (r *RedirectRewriter) RewriteResponse(resp *fasthttp.Response, ctx *fasthttp.RequestCtx, targetURL string, originalClientHost string) { statusCode := resp.StatusCode() @@ -123,8 +170,16 @@ func (r *RedirectRewriter) RewriteResponse(resp *fasthttp.Response, ctx *fasthtt } } -// RewriteRefreshOnly 仅改写 Refresh 头(用于缓存响应路径) -// Location 头在缓存响应中不存在(缓存仅存储 2xx),故跳过 +// RewriteRefreshOnly 仅改写 Refresh 头(用于缓存响应路径)。 +// +// 缓存响应中仅存储 2xx 状态码,不存在 Location 头, +// 故跳过 Location 处理,仅处理 Refresh 头。 +// +// 参数: +// - resp: 缓存响应 +// - ctx: FastHTTP 请求上下文 +// - targetURL: 上游地址 +// - originalClientHost: 客户端原始 Host func (r *RedirectRewriter) RewriteRefreshOnly(resp *fasthttp.Response, ctx *fasthttp.RequestCtx, targetURL string, originalClientHost string) { refresh := resp.Header.Peek("Refresh") if len(refresh) > 0 { @@ -135,8 +190,21 @@ func (r *RedirectRewriter) RewriteRefreshOnly(resp *fasthttp.Response, ctx *fast } } -// rewriteURL 改写单个 URL 值(Location 或 Refresh 中的 URL 部分) -// originalClientHost: 客户端原始 Host(用于 default 模式构建 replacement) +// rewriteURL 改写单个 URL 值(Location 或 Refresh 头中的 URL 部分)。 +// +// 根据当前模式选择对应的改写逻辑: +// - off: 原样返回 +// - custom: 使用预编译规则改写 +// - default: 动态替换上游地址为客户端 Host +// +// 参数: +// - headerValue: 头中的 URL 值 +// - ctx: FastHTTP 请求上下文 +// - targetURL: 上游目标地址 +// - originalClientHost: 客户端原始 Host +// +// 返回值: +// - string: 改写后的 URL,未匹配时返回原始值 func (r *RedirectRewriter) rewriteURL(headerValue string, ctx *fasthttp.RequestCtx, targetURL string, originalClientHost string) string { if headerValue == "" { return "" @@ -157,12 +225,23 @@ func (r *RedirectRewriter) rewriteURL(headerValue string, ctx *fasthttp.RequestC } } -// rewriteDefault 动态生成 default 规则(运行时) -// 使用前缀匹配:如果 headerValue 以 targetURL 开头,替换为 replacement + 原路径后缀 -// replacement 使用 originalClientHost 构建:"$scheme://originalClientHost/" -// 例如:targetURL="http://backend:8000", headerValue="http://backend:8000/api/v2/users" +// rewriteDefault 使用 default 模式动态改写 URL。 // -// → 替换为 "$scheme://originalClientHost/api/v2/users" +// 使用前缀匹配:如果 headerValue 以 targetURL 开头,且后面紧跟 +// "/"、"?"、"#" 或字符串结束,则将 targetURL 部分替换为 +// "$scheme://originalClientHost"。 +// +// 防止部分匹配问题:例如 "https://www.google.com" 不会 +// 错误匹配 "https://www.google.com.hk"。 +// +// 参数: +// - headerValue: 原始 URL 值 +// - ctx: FastHTTP 请求上下文(用于判断 TLS 协议) +// - targetURL: 上游目标地址 +// - originalClientHost: 客户端原始 Host +// +// 返回值: +// - string: 改写后的 URL,未匹配时返回原始值 func (r *RedirectRewriter) rewriteDefault(headerValue string, ctx *fasthttp.RequestCtx, targetURL string, originalClientHost string) string { if targetURL == "" { return headerValue @@ -187,8 +266,19 @@ func (r *RedirectRewriter) rewriteDefault(headerValue string, ctx *fasthttp.Requ return headerValue } -// rewriteCustom 使用预编译的 custom 规则改写 URL -// 规则按顺序匹配,第一个成功的生效 +// rewriteCustom 使用预编译的 custom 规则改写 URL。 +// +// 规则按定义顺序匹配,第一个成功的规则生效: +// - 正则匹配(~ 或 ~* 前缀):支持大小写不敏感 +// - 前缀匹配(无特殊前缀):使用 HasPrefix 精确前缀匹配 +// 替换模板支持 Lolly 变量展开。 +// +// 参数: +// - headerValue: 原始 URL 值 +// - ctx: FastHTTP 请求上下文 +// +// 返回值: +// - string: 改写后的 URL,未匹配时返回原始值 func (r *RedirectRewriter) rewriteCustom(headerValue string, ctx *fasthttp.RequestCtx) string { vc := variable.NewContext(ctx) defer variable.ReleaseContext(vc) @@ -226,8 +316,19 @@ func (r *RedirectRewriter) rewriteCustom(headerValue string, ctx *fasthttp.Reque return headerValue } -// rewriteRefresh 改写 Refresh 头 -// 格式:`N; url=URL` 或 `N;url=URL`(无空格)或纯数字 `N` +// rewriteRefresh 改写 Refresh 响应头。 +// +// Refresh 头格式:`N; url=URL` 或 `N;url=URL`(无空格)或纯数字 `N`。 +// 该方法提取 URL 部分进行改写,保持延迟值不变。 +// +// 参数: +// - value: 原始 Refresh 头值 +// - ctx: FastHTTP 请求上下文 +// - targetURL: 上游目标地址 +// - originalClientHost: 客户端原始 Host +// +// 返回值: +// - string: 改写后的 Refresh 头值,URL 未变化时返回原始值 func (r *RedirectRewriter) rewriteRefresh(value string, ctx *fasthttp.RequestCtx, targetURL string, originalClientHost string) string { delay, url, valid := parseRefreshHeader(value) if !valid || url == "" { diff --git a/internal/proxy/tempfile_cleaner.go b/internal/proxy/tempfile_cleaner.go index dc398d3..589c0e1 100644 --- a/internal/proxy/tempfile_cleaner.go +++ b/internal/proxy/tempfile_cleaner.go @@ -247,6 +247,13 @@ func StartGlobalTempFileCleaner(tempPath string) { } // StopGlobalTempFileCleaner 停止全局临时文件清理器。 +// +// 该函数安全地停止并清理全局清理器实例。如果清理器 +// 尚未初始化,则不执行任何操作。 +// +// 注意事项: +// - 该函数是并发安全的 +// - 停止后如需再次使用,需调用 StartGlobalTempFileCleaner 重新启动 func StopGlobalTempFileCleaner() { globalCleanerMu.Lock() defer globalCleanerMu.Unlock()