- logging: pre-allocate fresh slice for request field to avoid mutating fasthttp internal buffers via append into slices with excess capacity - mimeutil: move defaultMIME fallback before cache insertion so unknown extensions are consistently cached as application/octet-stream - builtin: use -0700 timezone format instead of hardcoded +0800; cache generated request_id in UserValue to prevent different IDs per expansion - variable: initialize maps in fallback Context to prevent nil map panic
556 lines
14 KiB
Go
556 lines
14 KiB
Go
// Package variable 提供高性能的变量系统,支持 nginx 风格的变量展开。
|
||
//
|
||
// 该包实现了统一的变量存储和展开机制,用于:
|
||
// - 访问日志格式模板
|
||
// - 代理请求头设置
|
||
// - URL 重写规则
|
||
//
|
||
// 支持的变量格式:
|
||
// - $var: 简单变量
|
||
// - ${var}: 带花括号的变量(用于变量后有字符的场景)
|
||
//
|
||
// 性能特性:
|
||
// - 使用快速字符串扫描(非正则表达式)
|
||
// - sync.Pool 复用 VariableContext
|
||
// - 内置变量惰性求值并缓存
|
||
//
|
||
// 作者:xfy
|
||
package variable
|
||
|
||
import (
|
||
"maps"
|
||
"strconv"
|
||
"strings"
|
||
"sync"
|
||
|
||
"github.com/valyala/fasthttp"
|
||
)
|
||
|
||
// BuiltinVariable 内置变量定义
|
||
type BuiltinVariable struct {
|
||
Getter func(ctx *fasthttp.RequestCtx) string
|
||
GetterBytes func(ctx *fasthttp.RequestCtx) []byte // 零拷贝 getter,用于 EphemeralGet
|
||
Name string
|
||
Description string
|
||
}
|
||
|
||
// Context 变量上下文,绑定到请求
|
||
type Context struct {
|
||
ctx *fasthttp.RequestCtx
|
||
store map[string]string
|
||
cache map[string]string // string 缓存(用于 PersistentGet)
|
||
bytesCache map[string][]byte // []byte 缓存(用于 EphemeralGet)
|
||
serverName string
|
||
upstreamAddr string
|
||
status int
|
||
bodySize int64
|
||
duration int64
|
||
upstreamStatus int
|
||
upstreamResponseTime float64
|
||
upstreamConnectTime float64
|
||
upstreamHeaderTime float64
|
||
}
|
||
|
||
// pool 用于复用 Context
|
||
var pool = sync.Pool{
|
||
New: func() any {
|
||
return &Context{
|
||
store: make(map[string]string),
|
||
cache: make(map[string]string),
|
||
bytesCache: make(map[string][]byte),
|
||
}
|
||
},
|
||
}
|
||
|
||
// 全局自定义变量存储
|
||
var (
|
||
globalVariables map[string]string
|
||
globalVariablesLock sync.RWMutex
|
||
)
|
||
|
||
// SetGlobalVariables 设置全局自定义变量。
|
||
// 在应用启动或配置重载时调用。
|
||
func SetGlobalVariables(vars map[string]string) {
|
||
globalVariablesLock.Lock()
|
||
defer globalVariablesLock.Unlock()
|
||
globalVariables = make(map[string]string, len(vars))
|
||
maps.Copy(globalVariables, vars)
|
||
}
|
||
|
||
// GetGlobalVariable 获取全局变量值。
|
||
//
|
||
// 线程安全地查询全局自定义变量存储。
|
||
// 使用读锁保护,可在多个 goroutine 中并发调用。
|
||
//
|
||
// 参数:
|
||
// - name: 变量名称
|
||
//
|
||
// 返回值:
|
||
// - string: 变量值,不存在时返回空字符串
|
||
// - bool: 变量是否存在,true 表示存在,false 表示不存在
|
||
func GetGlobalVariable(name string) (string, bool) {
|
||
globalVariablesLock.RLock()
|
||
defer globalVariablesLock.RUnlock()
|
||
if globalVariables == nil {
|
||
return "", false
|
||
}
|
||
v, ok := globalVariables[name]
|
||
return v, ok
|
||
}
|
||
|
||
// builtinVars 内置变量注册表
|
||
var builtinVars = make(map[string]*BuiltinVariable)
|
||
|
||
// RegisterBuiltin 注册内置变量
|
||
func RegisterBuiltin(v *BuiltinVariable) {
|
||
builtinVars[v.Name] = v
|
||
}
|
||
|
||
// GetBuiltin 获取内置变量定义
|
||
func GetBuiltin(name string) *BuiltinVariable {
|
||
return builtinVars[name]
|
||
}
|
||
|
||
// NewContext 从池中获取 Context。
|
||
// 全局变量通过 Get() 惰性加载。
|
||
func NewContext(ctx *fasthttp.RequestCtx) *Context {
|
||
vc, ok := pool.Get().(*Context)
|
||
if !ok {
|
||
// 池中类型不正确时返回新 Context
|
||
return &Context{
|
||
ctx: ctx,
|
||
store: make(map[string]string),
|
||
cache: make(map[string]string),
|
||
bytesCache: make(map[string][]byte),
|
||
}
|
||
}
|
||
vc.ctx = ctx
|
||
vc.status = 0
|
||
vc.bodySize = 0
|
||
vc.duration = 0
|
||
vc.serverName = ""
|
||
vc.upstreamAddr = ""
|
||
vc.upstreamStatus = 0
|
||
vc.upstreamResponseTime = 0
|
||
vc.upstreamConnectTime = 0
|
||
vc.upstreamHeaderTime = 0
|
||
// 清空内置变量缓存
|
||
for k := range vc.cache {
|
||
delete(vc.cache, k)
|
||
}
|
||
// 清空内置变量 bytes 缓存
|
||
for k := range vc.bytesCache {
|
||
delete(vc.bytesCache, k)
|
||
}
|
||
// 清空自定义变量 store
|
||
for k := range vc.store {
|
||
delete(vc.store, k)
|
||
}
|
||
return vc
|
||
}
|
||
|
||
// ReleaseContext 释放 Context 回池中
|
||
func ReleaseContext(vc *Context) {
|
||
if vc == nil {
|
||
return
|
||
}
|
||
vc.ctx = nil
|
||
vc.status = 0
|
||
vc.bodySize = 0
|
||
vc.duration = 0
|
||
vc.serverName = ""
|
||
vc.upstreamAddr = ""
|
||
vc.upstreamStatus = 0
|
||
vc.upstreamResponseTime = 0
|
||
vc.upstreamConnectTime = 0
|
||
vc.upstreamHeaderTime = 0
|
||
pool.Put(vc)
|
||
}
|
||
|
||
// SetResponseInfo 设置响应信息(用于需要 status、body_bytes_sent、request_time 的场景)
|
||
func (vc *Context) SetResponseInfo(status int, bodySize int64, durationNs int64) {
|
||
vc.status = status
|
||
vc.bodySize = bodySize
|
||
vc.duration = durationNs
|
||
}
|
||
|
||
// SetServerName 设置服务器名称
|
||
func (vc *Context) SetServerName(name string) {
|
||
vc.serverName = name
|
||
}
|
||
|
||
// SetUpstreamVars 设置上游变量
|
||
func (vc *Context) SetUpstreamVars(addr string, status int, responseTime, connectTime, headerTime float64) {
|
||
vc.upstreamAddr = addr
|
||
vc.upstreamStatus = status
|
||
vc.upstreamResponseTime = responseTime
|
||
vc.upstreamConnectTime = connectTime
|
||
vc.upstreamHeaderTime = headerTime
|
||
}
|
||
|
||
// Get 获取变量值(优先自定义变量,再查全局变量,最后查内置变量)
|
||
func (vc *Context) Get(name string) (string, bool) {
|
||
// 1. 先查自定义变量
|
||
if v, ok := vc.store[name]; ok {
|
||
return v, true
|
||
}
|
||
|
||
// 2. 惰性加载全局变量(首次访问时查找,避免每请求复制)
|
||
if v, ok := GetGlobalVariable(name); ok {
|
||
return v, true
|
||
}
|
||
|
||
// 3. 检查从 SetResponseInfo/SetServerName 设置的值
|
||
// 优先检查 struct 字段,再检查 ctx.UserValue(兼容 SetResponseInfoInContext)
|
||
switch name {
|
||
case VarStatus:
|
||
if vc.status > 0 {
|
||
return strconv.Itoa(vc.status), true
|
||
}
|
||
if v := vc.ctx.UserValue(VarStatus); v != nil {
|
||
if i, ok := v.(int); ok {
|
||
return strconv.Itoa(i), true
|
||
}
|
||
}
|
||
case VarBodyBytesSent:
|
||
if vc.bodySize > 0 {
|
||
return strconv.FormatInt(vc.bodySize, 10), true
|
||
}
|
||
if v := vc.ctx.UserValue(VarBodyBytesSent); v != nil {
|
||
if i, ok := v.(int64); ok {
|
||
return strconv.FormatInt(i, 10), true
|
||
}
|
||
}
|
||
return "0", true
|
||
case VarRequestTime:
|
||
if vc.duration > 0 {
|
||
// 转换为秒,保留 3 位小数
|
||
seconds := float64(vc.duration) / 1e9
|
||
return strconv.FormatFloat(seconds, 'f', 3, 64), true
|
||
}
|
||
if v := vc.ctx.UserValue(VarRequestTime); v != nil {
|
||
if i, ok := v.(int64); ok {
|
||
seconds := float64(i) / 1e9
|
||
return strconv.FormatFloat(seconds, 'f', 3, 64), true
|
||
}
|
||
}
|
||
return "0.000", true
|
||
case VarServerName:
|
||
if vc.serverName != "" {
|
||
return vc.serverName, true
|
||
}
|
||
// 上游变量
|
||
case VarUpstreamAddr:
|
||
if vc.upstreamAddr != "" {
|
||
return vc.upstreamAddr, true
|
||
}
|
||
return "-", true
|
||
case VarUpstreamStatus:
|
||
if vc.upstreamStatus > 0 {
|
||
return strconv.Itoa(vc.upstreamStatus), true
|
||
}
|
||
return "-", true
|
||
case VarUpstreamResponseTime:
|
||
if vc.upstreamResponseTime > 0 {
|
||
return strconv.FormatFloat(vc.upstreamResponseTime, 'f', 3, 64), true
|
||
}
|
||
return "-", true
|
||
case VarUpstreamConnectTime:
|
||
if vc.upstreamConnectTime > 0 {
|
||
return strconv.FormatFloat(vc.upstreamConnectTime, 'f', 3, 64), true
|
||
}
|
||
return "-", true
|
||
case VarUpstreamHeaderTime:
|
||
if vc.upstreamHeaderTime > 0 {
|
||
return strconv.FormatFloat(vc.upstreamHeaderTime, 'f', 3, 64), true
|
||
}
|
||
return "-", true
|
||
}
|
||
|
||
// 3. 查内置变量缓存
|
||
if v, ok := vc.cache[name]; ok {
|
||
return v, true
|
||
}
|
||
|
||
// 4. 求值内置变量并缓存
|
||
if v, ok := vc.evalBuiltin(name); ok {
|
||
vc.cache[name] = v
|
||
return v, true
|
||
}
|
||
|
||
return "", false
|
||
}
|
||
|
||
// EphemeralGet 获取请求作用域内的变量值(返回 []byte)。
|
||
//
|
||
// 警告:返回的 []byte 在请求结束后失效。
|
||
// 安全用法:仅用于即时消费场景(如写入日志、响应头)。
|
||
// 如需持久化存储,请使用 PersistentGet()。
|
||
//
|
||
// 该方法通过 BuiltinVariable 中注册的 GetterBytes 函数
|
||
// 提供零拷贝访问变量值。
|
||
func (vc *Context) EphemeralGet(name string) []byte {
|
||
// 1. 先查自定义变量(需要转换为 []byte)
|
||
if v, ok := vc.store[name]; ok {
|
||
return []byte(v) // 注意:这里分配了,因为 store 是 string
|
||
}
|
||
|
||
// 2. 查全局变量(需要转换为 []byte)
|
||
if v, ok := GetGlobalVariable(name); ok {
|
||
return []byte(v) // 注意:这里分配了,因为全局变量是 string
|
||
}
|
||
|
||
// 3. 检查 bytesCache 缓存
|
||
if v, ok := vc.bytesCache[name]; ok {
|
||
return v
|
||
}
|
||
|
||
// 4. 检查从 SetResponseInfo/SetServerName 设置的值
|
||
// SAFETY: 这些值来自 struct 字段,在请求期间有效
|
||
switch name {
|
||
case VarStatus:
|
||
if vc.status > 0 {
|
||
b := []byte(strconv.Itoa(vc.status))
|
||
vc.bytesCache[name] = b
|
||
return b
|
||
}
|
||
if v := vc.ctx.UserValue(VarStatus); v != nil {
|
||
if i, ok := v.(int); ok {
|
||
b := []byte(strconv.Itoa(i))
|
||
vc.bytesCache[name] = b
|
||
return b
|
||
}
|
||
}
|
||
case VarBodyBytesSent:
|
||
if vc.bodySize > 0 {
|
||
b := []byte(strconv.FormatInt(vc.bodySize, 10))
|
||
vc.bytesCache[name] = b
|
||
return b
|
||
}
|
||
if v := vc.ctx.UserValue(VarBodyBytesSent); v != nil {
|
||
if i, ok := v.(int64); ok {
|
||
b := []byte(strconv.FormatInt(i, 10))
|
||
vc.bytesCache[name] = b
|
||
return b
|
||
}
|
||
}
|
||
b := []byte("0")
|
||
vc.bytesCache[name] = b
|
||
return b
|
||
case VarRequestTime:
|
||
if vc.duration > 0 {
|
||
seconds := float64(vc.duration) / 1e9
|
||
b := []byte(strconv.FormatFloat(seconds, 'f', 3, 64))
|
||
vc.bytesCache[name] = b
|
||
return b
|
||
}
|
||
if v := vc.ctx.UserValue(VarRequestTime); v != nil {
|
||
if i, ok := v.(int64); ok {
|
||
seconds := float64(i) / 1e9
|
||
b := []byte(strconv.FormatFloat(seconds, 'f', 3, 64))
|
||
vc.bytesCache[name] = b
|
||
return b
|
||
}
|
||
}
|
||
b := []byte("0.000")
|
||
vc.bytesCache[name] = b
|
||
return b
|
||
case VarServerName:
|
||
if vc.serverName != "" {
|
||
return []byte(vc.serverName)
|
||
}
|
||
// 上游变量
|
||
case VarUpstreamAddr:
|
||
if vc.upstreamAddr != "" {
|
||
return []byte(vc.upstreamAddr)
|
||
}
|
||
return []byte("-")
|
||
case VarUpstreamStatus:
|
||
if vc.upstreamStatus > 0 {
|
||
b := []byte(strconv.Itoa(vc.upstreamStatus))
|
||
vc.bytesCache[name] = b
|
||
return b
|
||
}
|
||
return []byte("-")
|
||
case VarUpstreamResponseTime:
|
||
if vc.upstreamResponseTime > 0 {
|
||
b := []byte(strconv.FormatFloat(vc.upstreamResponseTime, 'f', 3, 64))
|
||
vc.bytesCache[name] = b
|
||
return b
|
||
}
|
||
return []byte("-")
|
||
case VarUpstreamConnectTime:
|
||
if vc.upstreamConnectTime > 0 {
|
||
b := []byte(strconv.FormatFloat(vc.upstreamConnectTime, 'f', 3, 64))
|
||
vc.bytesCache[name] = b
|
||
return b
|
||
}
|
||
return []byte("-")
|
||
case VarUpstreamHeaderTime:
|
||
if vc.upstreamHeaderTime > 0 {
|
||
b := []byte(strconv.FormatFloat(vc.upstreamHeaderTime, 'f', 3, 64))
|
||
vc.bytesCache[name] = b
|
||
return b
|
||
}
|
||
return []byte("-")
|
||
}
|
||
|
||
// 5. 使用 GetterBytes 求值内置变量(零拷贝)
|
||
if b, ok := vc.evalBuiltinBytes(name); ok {
|
||
vc.bytesCache[name] = b
|
||
return b
|
||
}
|
||
|
||
// 6. 如果只有 Getter,调用并转换为 []byte
|
||
if v, ok := vc.evalBuiltin(name); ok {
|
||
b := []byte(v)
|
||
vc.bytesCache[name] = b
|
||
return b
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// PersistentGet 获取持久化字符串变量值。
|
||
//
|
||
// 当需要跨请求存储变量值时使用此方法
|
||
// (如保存到数据库、缓存或长期存活的结构体中)。
|
||
func (vc *Context) PersistentGet(name string) string {
|
||
// 直接调用 Get,它返回 string
|
||
v, _ := vc.Get(name)
|
||
return v
|
||
}
|
||
|
||
// evalBuiltinBytes 求值内置变量,返回 []byte(零拷贝)
|
||
func (vc *Context) evalBuiltinBytes(name string) ([]byte, bool) {
|
||
builtin := builtinVars[name]
|
||
if builtin == nil || builtin.GetterBytes == nil {
|
||
return nil, false
|
||
}
|
||
return builtin.GetterBytes(vc.ctx), true
|
||
}
|
||
|
||
// Set 设置自定义变量
|
||
func (vc *Context) Set(name string, value string) {
|
||
vc.store[name] = value
|
||
}
|
||
|
||
// evalBuiltin 求值内置变量
|
||
func (vc *Context) evalBuiltin(name string) (string, bool) {
|
||
builtin := builtinVars[name]
|
||
if builtin == nil || builtin.Getter == nil {
|
||
return "", false
|
||
}
|
||
return builtin.Getter(vc.ctx), true
|
||
}
|
||
|
||
// expandCore 是变量展开的核心实现
|
||
// lookup: 变量查找函数,返回变量值和是否找到
|
||
// keepOriginal: 当变量未找到时,是否保持原样(true=保持原样,false=替换为空字符串)
|
||
func expandCore(template string, lookup func(name string) (value string, found bool), keepOriginal bool) string {
|
||
if template == "" {
|
||
return ""
|
||
}
|
||
|
||
// 快速路径:没有变量
|
||
hasVar := false
|
||
for i := 0; i < len(template); i++ {
|
||
if template[i] == '$' {
|
||
hasVar = true
|
||
break
|
||
}
|
||
}
|
||
if !hasVar {
|
||
return template
|
||
}
|
||
|
||
var result strings.Builder
|
||
result.Grow(len(template) * 2)
|
||
|
||
i := 0
|
||
for i < len(template) {
|
||
if template[i] != '$' {
|
||
result.WriteByte(template[i])
|
||
i++
|
||
continue
|
||
}
|
||
|
||
// 到达末尾,保留 $
|
||
if i+1 >= len(template) {
|
||
result.WriteByte('$')
|
||
i++
|
||
continue
|
||
}
|
||
|
||
// ${var} 格式
|
||
if template[i+1] == '{' {
|
||
// 查找匹配的 }
|
||
end := strings.IndexByte(template[i+2:], '}')
|
||
if end == -1 {
|
||
result.WriteByte('$')
|
||
i++
|
||
continue
|
||
}
|
||
// end 是相对 i+2 的偏移量
|
||
varName := template[i+2 : i+2+end]
|
||
if varName == "" {
|
||
// 空变量名,保持 ${}
|
||
result.WriteString("${}")
|
||
i += 2 + end + 1
|
||
continue
|
||
}
|
||
// 获取变量值
|
||
if v, ok := lookup(varName); ok {
|
||
result.WriteString(v)
|
||
} else if keepOriginal {
|
||
// 未定义变量,保持原样
|
||
result.WriteString("${")
|
||
result.WriteString(varName)
|
||
result.WriteByte('}')
|
||
}
|
||
// i+2 是变量名开始,+end 是 } 的位置,+1 跳过 }
|
||
i += 2 + end + 1
|
||
continue
|
||
}
|
||
|
||
// $var 格式(变量名由字母、数字、下划线组成)
|
||
j := i + 1
|
||
for j < len(template) {
|
||
c := template[j]
|
||
if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_' {
|
||
j++
|
||
} else {
|
||
break
|
||
}
|
||
}
|
||
|
||
if j == i+1 {
|
||
// 变量名长度为0,保留 $
|
||
result.WriteByte('$')
|
||
i++
|
||
continue
|
||
}
|
||
|
||
varName := template[i+1 : j]
|
||
if v, ok := lookup(varName); ok {
|
||
result.WriteString(v)
|
||
} else if keepOriginal {
|
||
// 未定义变量,保持原样
|
||
result.WriteByte('$')
|
||
result.WriteString(varName)
|
||
}
|
||
i = j // 跳过变量名
|
||
}
|
||
|
||
return result.String()
|
||
}
|
||
|
||
// Expand 展开模板字符串中的变量
|
||
// 支持 $var 和 ${var} 两种格式
|
||
// 对于未定义的变量,保持原样不变
|
||
func (vc *Context) Expand(template string) string {
|
||
return expandCore(template, func(name string) (string, bool) {
|
||
return vc.Get(name)
|
||
}, true)
|
||
}
|