- Register requestid.New() as first middleware in buildMiddlewareChain (before AccessLog) so the ID is available for logging - Add SetRequestIDHeader() in proxy/headers.go to propagate X-Request-ID to upstream via proxy forwarding - Call SetRequestIDHeader in header_modifier.go after SetForwardedHeaders - Import requestid package in middleware_builder and proxy/headers
186 lines
5.3 KiB
Go
186 lines
5.3 KiB
Go
// Package proxy 提供反向代理的核心功能,支持请求转发、负载均衡、健康检查等特性。
|
||
//
|
||
// 包含请求头修改器相关的结构体,用于配置请求和响应头修改规则。
|
||
//
|
||
// 作者:xfy
|
||
package proxy
|
||
|
||
import (
|
||
"strings"
|
||
|
||
"github.com/valyala/fasthttp"
|
||
"rua.plus/lolly/internal/loadbalance"
|
||
"rua.plus/lolly/internal/logging"
|
||
"rua.plus/lolly/internal/netutil"
|
||
"rua.plus/lolly/internal/variable"
|
||
)
|
||
|
||
// 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
|
||
|
||
// 设置 Host header 为目标主机
|
||
// 从 target.URL 提取 host:port(HostClient 连接需要此格式)
|
||
targetHost, _ := netutil.ParseTargetURL(target.URL, false)
|
||
if targetHost != "" {
|
||
headers.Set("Host", targetHost)
|
||
}
|
||
|
||
// 提取并设置 X-Forwarded 系列头
|
||
fh := ExtractForwardedHeaders(ctx)
|
||
|
||
// 根据配置决定是否设置 X-Forwarded-Host 和 X-Forwarded-Proto
|
||
setHost := true // 默认值(向后兼容)
|
||
if p.config.Headers.SetForwardedHost != nil {
|
||
setHost = *p.config.Headers.SetForwardedHost
|
||
}
|
||
setProto := true // 默认值(向后兼容)
|
||
if p.config.Headers.SetForwardedProto != nil {
|
||
setProto = *p.config.Headers.SetForwardedProto
|
||
}
|
||
|
||
SetForwardedHeaders(headers, fh, true, setHost, setProto)
|
||
SetRequestIDHeader(headers, ctx)
|
||
|
||
// 从配置设置自定义请求头(支持变量展开)
|
||
if p.config.Headers.SetRequest != nil {
|
||
vc := variable.NewContext(ctx)
|
||
defer variable.ReleaseContext(vc)
|
||
for key, value := range p.config.Headers.SetRequest {
|
||
expanded := vc.Expand(value)
|
||
if containsCRLF(expanded) {
|
||
logging.Warn().Msgf("rejected CRLF in header value: %s", key)
|
||
continue
|
||
}
|
||
headers.Set(key, expanded)
|
||
}
|
||
}
|
||
|
||
// 移除配置的请求头
|
||
if len(p.config.Headers.Remove) > 0 {
|
||
for _, key := range p.config.Headers.Remove {
|
||
headers.Del(key)
|
||
}
|
||
}
|
||
}
|
||
|
||
// modifyResponseHeaders 在发送给客户端之前修改响应头。
|
||
//
|
||
// 应用自定义响应头配置,支持变量展开(如 $upstream_addr、$status 等)。
|
||
//
|
||
// 参数:
|
||
// - ctx: FastHTTP 请求上下文
|
||
func (p *Proxy) modifyResponseHeaders(ctx *fasthttp.RequestCtx) {
|
||
respHeaders := &ctx.Response.Header
|
||
|
||
// 构建 PassResponse 集合(多处使用)
|
||
passSet := make(map[string]bool, len(p.config.Headers.PassResponse))
|
||
for _, h := range p.config.Headers.PassResponse {
|
||
passSet[h] = true
|
||
}
|
||
|
||
// PassResponse 白名单模式:仅传递列出的头部
|
||
if len(passSet) > 0 {
|
||
var toDelete []string
|
||
for key := range respHeaders.All() {
|
||
// 不在白名单中的应该删除
|
||
if !isInWhitelist(key, passSet) {
|
||
toDelete = append(toDelete, b2s(key))
|
||
}
|
||
}
|
||
for _, k := range toDelete {
|
||
respHeaders.Del(k)
|
||
}
|
||
}
|
||
|
||
// HideResponse:移除指定的响应头(PassResponse 优先,跳过已传递的头部)
|
||
for _, key := range p.config.Headers.HideResponse {
|
||
if !passSet[key] {
|
||
respHeaders.Del(key)
|
||
}
|
||
}
|
||
|
||
// IgnoreHeaders:从请求和响应中移除(PassResponse 优先)
|
||
for _, key := range p.config.Headers.IgnoreHeaders {
|
||
ctx.Request.Header.Del(key)
|
||
if !passSet[key] {
|
||
respHeaders.Del(key)
|
||
}
|
||
}
|
||
|
||
// Cookie 域/路径重写
|
||
if p.config.Headers.CookieDomain != "" || p.config.Headers.CookiePath != "" {
|
||
p.rewriteCookies(respHeaders)
|
||
}
|
||
|
||
// 从配置设置自定义响应头(支持变量展开)
|
||
if p.config.Headers.SetResponse != nil {
|
||
vc := variable.NewContext(ctx)
|
||
defer variable.ReleaseContext(vc)
|
||
for key, value := range p.config.Headers.SetResponse {
|
||
expanded := vc.Expand(value)
|
||
if containsCRLF(expanded) {
|
||
logging.Warn().Msgf("rejected CRLF in header value: %s", key)
|
||
continue
|
||
}
|
||
respHeaders.Set(key, expanded)
|
||
}
|
||
}
|
||
}
|
||
|
||
// rewriteCookies 重写响应中 Set-Cookie 头的 domain 和 path。
|
||
func (p *Proxy) rewriteCookies(respHeaders *fasthttp.ResponseHeader) {
|
||
cookieDomain := p.config.Headers.CookieDomain
|
||
cookiePath := p.config.Headers.CookiePath
|
||
if cookieDomain == "" && cookiePath == "" {
|
||
return
|
||
}
|
||
|
||
cookies := make([]string, 0, respHeaders.Len())
|
||
for _, value := range respHeaders.Cookies() {
|
||
cookie := string(value)
|
||
if cookieDomain != "" {
|
||
cookie = rewriteCookieAttr(cookie, "Domain", cookieDomain)
|
||
}
|
||
if cookiePath != "" {
|
||
cookie = rewriteCookieAttr(cookie, "Path", cookiePath)
|
||
}
|
||
cookies = append(cookies, cookie)
|
||
}
|
||
|
||
if len(cookies) > 0 {
|
||
respHeaders.Del("Set-Cookie")
|
||
for _, c := range cookies {
|
||
respHeaders.Add("Set-Cookie", c)
|
||
}
|
||
}
|
||
}
|
||
|
||
// rewriteCookieAttr 替换 Cookie 字符串中指定属性的值(大小写不敏感)。
|
||
func rewriteCookieAttr(cookie, attr, newValue string) string {
|
||
prefix := attr + "="
|
||
lower := strings.ToLower(cookie)
|
||
idx := strings.Index(lower, strings.ToLower(prefix))
|
||
if idx == -1 {
|
||
return cookie
|
||
}
|
||
|
||
start := idx + len(prefix)
|
||
end := start
|
||
for end < len(cookie) && cookie[end] != ';' && cookie[end] != ' ' {
|
||
end++
|
||
}
|
||
|
||
return cookie[:start] + newValue + cookie[end:]
|
||
}
|