lolly/internal/proxy/headers.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

148 lines
4.4 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 反向代理包,为 Lolly HTTP 服务器提供反向代理功能。
//
// 该文件提供统一的 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 (
"strings"
"github.com/valyala/fasthttp"
"rua.plus/lolly/internal/middleware/requestid"
"rua.plus/lolly/internal/netutil"
)
// 协议常量,用于标识请求使用的传输层协议。
const (
protoHTTP = "http" // 明文 HTTP 协议
protoHTTPS = "https" // TLS 加密的 HTTPS 协议
)
// ForwardedHeaders 包含 X-Forwarded 系列头信息。
//
// 用于在代理转发时保留客户端原始请求信息。
type ForwardedHeaders struct {
ClientIP string // 客户端 IP 地址,从连接信息或 X-Real-IP 头提取
Host string // 原始请求 Host 头,表示客户端访问的主机名
Proto string // 原始请求协议http 或 https
}
// ExtractForwardedHeaders 从请求上下文中提取 X-Forwarded 头信息。
//
// 参数:
// - ctx: FastHTTP 请求上下文
//
// 返回值:
// - ForwardedHeaders: 提取的头信息
func ExtractForwardedHeaders(ctx *fasthttp.RequestCtx) ForwardedHeaders {
clientIP := netutil.ExtractClientIP(ctx)
host := string(ctx.Host())
proto := protoHTTP
if ctx.IsTLS() {
proto = protoHTTPS
}
return ForwardedHeaders{
ClientIP: clientIP,
Host: host,
Proto: proto,
}
}
// SetForwardedHeaders 设置 X-Forwarded 系列请求头。
//
// 参数:
// - headers: 目标请求头
// - fh: ForwardedHeaders 结构体
// - appendXFF: 是否追加到已有的 X-Forwarded-For 头
// - setHost: 是否设置 X-Forwarded-Host
// - setProto: 是否设置 X-Forwarded-Proto
func SetForwardedHeaders(headers *fasthttp.RequestHeader, fh ForwardedHeaders, appendXFF, setHost, setProto bool) {
// 设置 X-Real-IP
if fh.ClientIP != "" {
headers.Set("X-Real-IP", fh.ClientIP)
}
// 设置 X-Forwarded-For
if fh.ClientIP != "" {
if appendXFF {
existingXFF := headers.Peek("X-Forwarded-For")
if len(existingXFF) > 0 {
// SAFETY: Ephemeral — xffBuf is written to header immediately and not reused.
var xffBuf []byte
xffBuf = append(xffBuf, existingXFF...)
xffBuf = append(xffBuf, ", "...)
xffBuf = append(xffBuf, fh.ClientIP...)
headers.SetBytesKV([]byte("X-Forwarded-For"), xffBuf)
} else {
headers.SetBytesKV([]byte("X-Forwarded-For"), []byte(fh.ClientIP))
}
} else {
headers.SetBytesKV([]byte("X-Forwarded-For"), []byte(fh.ClientIP))
}
}
// 设置 X-Forwarded-Host仅在 setHost 为 true 时)
if setHost && fh.Host != "" {
headers.Set("X-Forwarded-Host", fh.Host)
}
// 设置 X-Forwarded-Proto仅在 setProto 为 true 时)
if setProto && fh.Proto != "" {
headers.Set("X-Forwarded-Proto", fh.Proto)
}
}
// SetRequestIDHeader propagates X-Request-ID from the request context to upstream headers.
func SetRequestIDHeader(headers *fasthttp.RequestHeader, ctx *fasthttp.RequestCtx) {
if id := requestid.GetRequestID(ctx); id != "" {
headers.Set("X-Request-ID", id)
}
}
// WriteForwardedHeaders 将 X-Forwarded 头写入到 strings.Builder。
// 用于 WebSocket 升级请求构建。
//
// 参数:
// - builder: strings.Builder 实例
// - fh: ForwardedHeaders 结构体
// - setHost: 是否设置 X-Forwarded-Host
// - setProto: 是否设置 X-Forwarded-Proto
func WriteForwardedHeaders(builder *strings.Builder, fh ForwardedHeaders, setHost, setProto bool) {
if fh.ClientIP != "" {
builder.WriteString("X-Forwarded-For: ")
builder.WriteString(fh.ClientIP)
builder.WriteString("\r\n")
builder.WriteString("X-Real-IP: ")
builder.WriteString(fh.ClientIP)
builder.WriteString("\r\n")
}
if setHost && fh.Host != "" {
builder.WriteString("X-Forwarded-Host: ")
builder.WriteString(fh.Host)
builder.WriteString("\r\n")
}
if setProto && fh.Proto != "" {
builder.WriteString("X-Forwarded-Proto: ")
builder.WriteString(fh.Proto)
builder.WriteString("\r\n")
}
}