lolly/internal/proxy/header_modifier.go
xfy 6c538a1a56 feat(server,proxy): integrate Request-ID into middleware chain and proxy forwarding
- 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
2026-06-11 23:41:30 +08:00

186 lines
5.3 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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:portHostClient 连接需要此格式)
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:]
}