diff --git a/internal/variable/builtin.go b/internal/variable/builtin.go index 80aaf85..ec246a2 100644 --- a/internal/variable/builtin.go +++ b/internal/variable/builtin.go @@ -49,6 +49,10 @@ func init() { Getter: func(ctx *fasthttp.RequestCtx) string { return string(ctx.Host()) }, + GetterBytes: func(ctx *fasthttp.RequestCtx) []byte { + // SAFETY: ctx.Host() returns []byte valid within request scope + return ctx.Host() + }, }) // 2. $remote_addr - 客户端 IP @@ -91,6 +95,10 @@ func init() { Getter: func(ctx *fasthttp.RequestCtx) string { return string(ctx.RequestURI()) }, + GetterBytes: func(ctx *fasthttp.RequestCtx) []byte { + // SAFETY: ctx.RequestURI() returns []byte valid within request scope + return ctx.RequestURI() + }, }) // 5. $uri - 解码后的 URI 路径 @@ -100,6 +108,10 @@ func init() { Getter: func(ctx *fasthttp.RequestCtx) string { return string(ctx.Path()) }, + GetterBytes: func(ctx *fasthttp.RequestCtx) []byte { + // SAFETY: ctx.Path() returns []byte valid within request scope + return ctx.Path() + }, }) // 6. $args - 查询参数字符串 @@ -109,6 +121,10 @@ func init() { Getter: func(ctx *fasthttp.RequestCtx) string { return string(ctx.QueryArgs().QueryString()) }, + GetterBytes: func(ctx *fasthttp.RequestCtx) []byte { + // SAFETY: ctx.QueryArgs().QueryString() returns []byte valid within request scope + return ctx.QueryArgs().QueryString() + }, }) // 7. $request_method - 请求方法 @@ -118,6 +134,10 @@ func init() { Getter: func(ctx *fasthttp.RequestCtx) string { return string(ctx.Method()) }, + GetterBytes: func(ctx *fasthttp.RequestCtx) []byte { + // SAFETY: ctx.Method() returns []byte valid within request scope + return ctx.Method() + }, }) // 8. $scheme - 协议 diff --git a/internal/variable/variable.go b/internal/variable/variable.go index 5bb1807..fc22e06 100644 --- a/internal/variable/variable.go +++ b/internal/variable/variable.go @@ -29,6 +29,7 @@ import ( // BuiltinVariable 内置变量定义 type BuiltinVariable struct { Getter func(ctx *fasthttp.RequestCtx) string + GetterBytes func(ctx *fasthttp.RequestCtx) []byte // 零拷贝 getter,用于 EphemeralGet Name string Description string } @@ -37,7 +38,8 @@ type BuiltinVariable struct { type Context struct { ctx *fasthttp.RequestCtx store map[string]string - cache map[string]string + cache map[string]string // string 缓存(用于 PersistentGet) + bytesCache map[string][]byte // []byte 缓存(用于 EphemeralGet) serverName string upstreamAddr string status int @@ -53,8 +55,9 @@ type Context struct { var pool = sync.Pool{ New: func() interface{} { return &Context{ - store: make(map[string]string), - cache: make(map[string]string), + store: make(map[string]string), + cache: make(map[string]string), + bytesCache: make(map[string][]byte), } }, } @@ -133,6 +136,10 @@ func NewContext(ctx *fasthttp.RequestCtx) *Context { 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) @@ -272,6 +279,155 @@ func (vc *Context) Get(name string) (string, bool) { return "", false } +// EphemeralGet returns []byte view valid only within request scope. +// +// WARNING: The returned []byte becomes INVALID after request completes. +// SAFETY: Use only for immediate consumption (logging, header write). +// For persistent storage, use PersistentGet(). +// +// This method provides zero-copy access to variable values by using +// GetterBytes functions registered in BuiltinVariable. +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 returns string for cross-request storage. +// +// Use this method when you need to store the variable value beyond +// the current request scope (e.g., in a database, cache, or long-lived struct). +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 diff --git a/internal/variable/variable_bench_test.go b/internal/variable/variable_bench_test.go index 1a07ee6..912c497 100644 --- a/internal/variable/variable_bench_test.go +++ b/internal/variable/variable_bench_test.go @@ -36,7 +36,7 @@ func BenchmarkVariableExpandSimple(b *testing.B) { template := "$remote_addr - $request_method" b.ResetTimer() - for i := 0; i < b.N; i++ { + for b.Loop() { vc.Expand(template) } } @@ -54,7 +54,7 @@ func BenchmarkVariableExpandComplex(b *testing.B) { template := "$remote_addr - [$time_local] \"$request_method $uri $args\" $status $body_bytes_sent" b.ResetTimer() - for i := 0; i < b.N; i++ { + for b.Loop() { vc.Expand(template) } } @@ -68,7 +68,7 @@ func BenchmarkVariableExpandMixed(b *testing.B) { template := "${remote_addr} - $request_method ${uri}?${args}" b.ResetTimer() - for i := 0; i < b.N; i++ { + for b.Loop() { vc.Expand(template) } } @@ -82,7 +82,7 @@ func BenchmarkVariableExpandNoVar(b *testing.B) { template := "This is a plain string with no variables" b.ResetTimer() - for i := 0; i < b.N; i++ { + for b.Loop() { vc.Expand(template) } } @@ -92,7 +92,7 @@ func BenchmarkVariableContextPool(b *testing.B) { ctx := setupBenchmarkRequestCtx() b.ResetTimer() - for i := 0; i < b.N; i++ { + for b.Loop() { vc := NewContext(ctx) ReleaseContext(vc) } @@ -122,7 +122,7 @@ func BenchmarkVariableGetCache(b *testing.B) { _, _ = vc.Get("remote_addr") b.ResetTimer() - for i := 0; i < b.N; i++ { + for b.Loop() { vc.Get("remote_addr") } } @@ -134,7 +134,7 @@ func BenchmarkVariableGetNoCache(b *testing.B) { ctx := setupBenchmarkRequestCtx() b.ResetTimer() - for i := 0; i < b.N; i++ { + for b.Loop() { vc := NewContext(ctx) vc.Get("remote_addr") ReleaseContext(vc) @@ -153,7 +153,7 @@ func BenchmarkVariableGetMultiple(b *testing.B) { } b.ResetTimer() - for i := 0; i < b.N; i++ { + for b.Loop() { for _, name := range vars { vc.Get(name) } @@ -167,7 +167,7 @@ func BenchmarkVariableSetAndGet(b *testing.B) { defer ReleaseContext(vc) b.ResetTimer() - for i := 0; i < b.N; i++ { + for b.Loop() { vc.Set("custom_var", "custom_value") vc.Get("custom_var") } @@ -188,7 +188,7 @@ func BenchmarkExpandStringStaticWithLookup(b *testing.B) { } b.ResetTimer() - for i := 0; i < b.N; i++ { + for b.Loop() { ExpandString(template, lookup) } } @@ -206,7 +206,7 @@ func BenchmarkVariableExpandLongTemplate(b *testing.B) { template := "$remote_addr - [$time_local] \"$request_method $uri?$args\" $status $body_bytes_sent \"$http_referer\" \"$http_user_agent\" $request_time $server_name" b.ResetTimer() - for i := 0; i < b.N; i++ { + for b.Loop() { vc.Expand(template) } } diff --git a/internal/variable/variable_test.go b/internal/variable/variable_test.go index e320b3d..e6c8fd1 100644 --- a/internal/variable/variable_test.go +++ b/internal/variable/variable_test.go @@ -353,7 +353,7 @@ func BenchmarkExpandSimple(b *testing.B) { b.ResetTimer() b.ReportAllocs() - for i := 0; i < b.N; i++ { + for b.Loop() { _ = vc.Expand(template) } } @@ -374,7 +374,7 @@ func BenchmarkExpandComplex(b *testing.B) { b.ResetTimer() b.ReportAllocs() - for i := 0; i < b.N; i++ { + for b.Loop() { _ = vc.Expand(template) } } @@ -390,7 +390,7 @@ func BenchmarkExpandNoVariable(b *testing.B) { b.ResetTimer() b.ReportAllocs() - for i := 0; i < b.N; i++ { + for b.Loop() { _ = vc.Expand(template) } } @@ -409,7 +409,7 @@ func BenchmarkExpandBrace(b *testing.B) { b.ResetTimer() b.ReportAllocs() - for i := 0; i < b.N; i++ { + for b.Loop() { _ = vc.Expand(template) } } @@ -421,7 +421,7 @@ func BenchmarkPoolGetPut(b *testing.B) { b.ResetTimer() b.ReportAllocs() - for i := 0; i < b.N; i++ { + for b.Loop() { vc := NewContext(ctx) ReleaseContext(vc) } @@ -445,7 +445,7 @@ func BenchmarkExpandStringStatic(b *testing.B) { b.ResetTimer() b.ReportAllocs() - for i := 0; i < b.N; i++ { + for b.Loop() { _ = ExpandString(template, lookup) } } @@ -1029,7 +1029,7 @@ func BenchmarkUpstreamVariables(b *testing.B) { b.ResetTimer() b.ReportAllocs() - for i := 0; i < b.N; i++ { + for b.Loop() { _, _ = vc.Get(VarUpstreamAddr) _, _ = vc.Get(VarUpstreamStatus) _, _ = vc.Get(VarUpstreamResponseTime) @@ -1133,3 +1133,274 @@ func TestGlobalVariablesConcurrent(_ *testing.T) { <-done } } + +// TestEphemeralGet 测试 EphemeralGet 方法 +func TestEphemeralGet(t *testing.T) { + ctx := mockRequestCtx(t) + vc := NewContext(ctx) + defer ReleaseContext(vc) + + tests := []struct { + name string + varName string + expected []byte + }{ + {"host", VarHost, []byte("example.com")}, + {"uri", VarURI, []byte("/test/path")}, + {"request_uri", VarRequestURI, []byte("/test/path?foo=bar&baz=qux")}, + {"args", VarArgs, []byte("foo=bar&baz=qux")}, + {"request_method", VarRequestMethod, []byte("GET")}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := vc.EphemeralGet(tt.varName) + if string(result) != string(tt.expected) { + t.Errorf("EphemeralGet(%q) = %q, want %q", tt.varName, result, tt.expected) + } + }) + } +} + +// TestEphemeralGetCustomVariable 测试自定义变量的 EphemeralGet +func TestEphemeralGetCustomVariable(t *testing.T) { + ctx := mockRequestCtx(t) + vc := NewContext(ctx) + defer ReleaseContext(vc) + + // 设置自定义变量 + vc.Set("custom_var", "custom_value") + + result := vc.EphemeralGet("custom_var") + if string(result) != "custom_value" { + t.Errorf("EphemeralGet('custom_var') = %q, want 'custom_value'", result) + } +} + +// TestEphemeralGetUndefined 测试未定义变量的 EphemeralGet +func TestEphemeralGetUndefined(t *testing.T) { + ctx := mockRequestCtx(t) + vc := NewContext(ctx) + defer ReleaseContext(vc) + + result := vc.EphemeralGet("undefined_var") + if result != nil { + t.Errorf("EphemeralGet('undefined_var') = %q, want nil", result) + } +} + +// TestEphemeralGetCache 测试 EphemeralGet 的缓存 +func TestEphemeralGetCache(t *testing.T) { + ctx := mockRequestCtx(t) + vc := NewContext(ctx) + defer ReleaseContext(vc) + + // 第一次获取 + result1 := vc.EphemeralGet(VarHost) + // 第二次获取应该命中缓存 + result2 := vc.EphemeralGet(VarHost) + + // 验证缓存返回相同的结果 + if string(result1) != string(result2) { + t.Errorf("EphemeralGet cache: %q != %q", result1, result2) + } +} + +// TestPersistentGet 测试 PersistentGet 方法 +func TestPersistentGet(t *testing.T) { + ctx := mockRequestCtx(t) + vc := NewContext(ctx) + defer ReleaseContext(vc) + + tests := []struct { + name string + varName string + expected string + }{ + {"host", VarHost, "example.com"}, + {"uri", VarURI, "/test/path"}, + {"request_method", VarRequestMethod, "GET"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := vc.PersistentGet(tt.varName) + if result != tt.expected { + t.Errorf("PersistentGet(%q) = %q, want %q", tt.varName, result, tt.expected) + } + }) + } +} + +// TestPersistentGetUndefined 测试未定义变量的 PersistentGet +func TestPersistentGetUndefined(t *testing.T) { + ctx := mockRequestCtx(t) + vc := NewContext(ctx) + defer ReleaseContext(vc) + + result := vc.PersistentGet("undefined_var") + if result != "" { + t.Errorf("PersistentGet('undefined_var') = %q, want empty string", result) + } +} + +// TestGetBackwardCompatibility 测试 Get 方法的向后兼容性 +func TestGetBackwardCompatibility(t *testing.T) { + ctx := mockRequestCtx(t) + vc := NewContext(ctx) + defer ReleaseContext(vc) + + // Get 应该返回与 PersistentGet 相同的结果 + tests := []string{VarHost, VarURI, VarRequestMethod, VarRequestURI} + + for _, varName := range tests { + getResult, ok := vc.Get(varName) + if !ok { + t.Errorf("Get(%q) returned not found", varName) + continue + } + persistentResult := vc.PersistentGet(varName) + if getResult != persistentResult { + t.Errorf("Get(%q) = %q, PersistentGet(%q) = %q, should be equal", + varName, getResult, varName, persistentResult) + } + } +} + +// TestEphemeralGetUpstreamVariables 测试上游变量的 EphemeralGet +func TestEphemeralGetUpstreamVariables(t *testing.T) { + ctx := mockRequestCtx(t) + vc := NewContext(ctx) + defer ReleaseContext(vc) + + // 未设置时应该返回默认值 "-" + defaultTests := []struct { + varName string + expected []byte + }{ + {VarUpstreamAddr, []byte("-")}, + {VarUpstreamStatus, []byte("-")}, + {VarUpstreamResponseTime, []byte("-")}, + } + + for _, tt := range defaultTests { + t.Run(tt.varName+"_default", func(t *testing.T) { + result := vc.EphemeralGet(tt.varName) + if string(result) != string(tt.expected) { + t.Errorf("EphemeralGet(%q) = %q, want %q", tt.varName, result, tt.expected) + } + }) + } + + // 设置上游变量 + vc.SetUpstreamVars("http://backend:8080", 200, 0.123, 0.001, 0.045) + + setTests := []struct { + varName string + expected []byte + }{ + {VarUpstreamAddr, []byte("http://backend:8080")}, + {VarUpstreamStatus, []byte("200")}, + {VarUpstreamResponseTime, []byte("0.123")}, + } + + for _, tt := range setTests { + t.Run(tt.varName+"_set", func(t *testing.T) { + result := vc.EphemeralGet(tt.varName) + if string(result) != string(tt.expected) { + t.Errorf("EphemeralGet(%q) = %q, want %q", tt.varName, result, tt.expected) + } + }) + } +} + +// TestEphemeralGetResponseInfo 测试响应信息的 EphemeralGet +func TestEphemeralGetResponseInfo(t *testing.T) { + ctx := mockRequestCtx(t) + vc := NewContext(ctx) + defer ReleaseContext(vc) + + // 设置响应信息 + vc.SetResponseInfo(200, 1024, 15000000) // 15ms + + tests := []struct { + varName string + expected []byte + }{ + {VarStatus, []byte("200")}, + {VarBodyBytesSent, []byte("1024")}, + {VarRequestTime, []byte("0.015")}, + } + + for _, tt := range tests { + t.Run(tt.varName, func(t *testing.T) { + result := vc.EphemeralGet(tt.varName) + if string(result) != string(tt.expected) { + t.Errorf("EphemeralGet(%q) = %q, want %q", tt.varName, result, tt.expected) + } + }) + } +} + +// BenchmarkEphemeralGet 基准测试:EphemeralGet 零拷贝性能 +func BenchmarkEphemeralGet(b *testing.B) { + ctx := setupBenchmarkRequestCtx() + vc := NewContext(ctx) + defer ReleaseContext(vc) + + b.ResetTimer() + b.ReportAllocs() + + for b.Loop() { + _ = vc.EphemeralGet(VarHost) + } +} + +// BenchmarkEphemeralGetCached 基准测试:EphemeralGet 缓存命中性能 +func BenchmarkEphemeralGetCached(b *testing.B) { + ctx := setupBenchmarkRequestCtx() + vc := NewContext(ctx) + defer ReleaseContext(vc) + + // 预热缓存 + _ = vc.EphemeralGet(VarHost) + + b.ResetTimer() + b.ReportAllocs() + + for b.Loop() { + _ = vc.EphemeralGet(VarHost) + } +} + +// BenchmarkPersistentGet 基准测试:PersistentGet 性能 +func BenchmarkPersistentGet(b *testing.B) { + ctx := setupBenchmarkRequestCtx() + vc := NewContext(ctx) + defer ReleaseContext(vc) + + b.ResetTimer() + b.ReportAllocs() + + for b.Loop() { + _ = vc.PersistentGet(VarHost) + } +} + +// BenchmarkEphemeralGetMultiple 基准测试:获取多个变量的 EphemeralGet +func BenchmarkEphemeralGetMultiple(b *testing.B) { + ctx := setupBenchmarkRequestCtx() + vc := NewContext(ctx) + defer ReleaseContext(vc) + + vars := []string{VarHost, VarURI, VarRequestMethod, VarRequestURI, VarArgs} + + b.ResetTimer() + b.ReportAllocs() + + for b.Loop() { + for _, name := range vars { + _ = vc.EphemeralGet(name) + } + } +}