635 Commits

Author SHA1 Message Date
xfy
a73da4e14a fix(sticky): recreate stopCh on Start to support restart 2026-06-08 17:52:20 +08:00
xfy
0a5443f6cf fix(sticky): guard against double Stop, nil fallback, and multiple Start calls
- Add sync.Once to prevent double close of stopCh in Stop()
- Add nil fallback guard in NewStickySession (defaults to RoundRobin)
- Add atomic.Bool to make Start() idempotent
- Add tests for double Stop() and nil fallback scenarios
2026-06-08 17:47:37 +08:00
xfy
360fd0da9d fix(sticky): check cookie expiration in Select method
- Fix Select to check if cookie is expired before routing
- Add TestStickySession_ExpiredCookie test
- Expired cookies now trigger fallback + new cookie set
2026-06-08 17:40:54 +08:00
xfy
66752a47f0 fix(sticky): fix cookie format, shard keying, and tests
- Encode cookie as base64(target_url + | + timestamp) per spec
- Use cookie value (not targetURL) for shard key and session map keys
- Add missing sticky.Start() calls in tests
- Fix time precision in cookie encode/decode tests
2026-06-08 17:36:41 +08:00
xfy
f69a11ea05 feat(loadbalance): implement Session Sticky balancer
- Add 256-shard lock map for concurrent session routing
- Cookie-based session persistence with base64 encoding
- TTL expiration with background cleanup goroutine
- Support Secure, HttpOnly, SameSite cookie attributes
- Fallback to configured balancer when session target unavailable
2026-06-08 17:30:06 +08:00
xfy
fa95b2a76e feat(loadbalance): implement Least Time balancer
- Add atomic EWMA Stats field to Target
- Implement LeastTime balancer with header_time and last_byte metrics
- Support Select and SelectExcluding with zero-lock design
- Add ResponseTimeRecorder interface for proxy integration
2026-06-08 17:21:20 +08:00
xfy
c6bb75cffe feat(loadbalance): add atomic EWMA statistics core
- Zero-lock atomic EWMA implementation using fixed-point arithmetic
- Supports header_time and last_byte_time tracking
- Concurrent-safe with CAS retry loop
2026-06-08 17:13:08 +08:00
xfy
a04dadbe16 feat(examples): add FreeBSD deployment examples 2026-06-05 17:21:26 +08:00
xfy
c847f6036d chore: release v0.3.0 v0.3.0 2026-06-05 14:24:39 +08:00
xfy
85ae7747b8 fix(integration): remove calls to removed proxy.Start/Stop methods 2026-06-05 14:24:34 +08:00
xfy
989a572467 docs(skills): add release workflow skill 2026-06-05 14:02:20 +08:00
xfy
93c0c151d0 fix(lua): wait for SchedulerLoop exit before closing LState; lock cleanupResources 2026-06-05 13:48:04 +08:00
xfy
4789265ca8 fix: add synchronization for concurrent access in server/app/http3/stream 2026-06-05 12:31:41 +08:00
xfy
5e3196c37e fix: resolve race conditions in handler sendfile and lua cosocket tests 2026-06-05 12:31:39 +08:00
xfy
f73a761632 fix(server): protect accessLogMiddleware and accessControl from concurrent writes 2026-06-05 11:49:19 +08:00
xfy
76257a7859 fix(lua): add schedulerMu to protect scheduler LState and callback queue 2026-06-05 11:38:52 +08:00
xfy
2be04f3fb9 fix(lua): add mutex protection for TCPSocket.currentOp in async methods 2026-06-05 11:35:20 +08:00
xfy
170e0f1942 feat(Makefile): add freebsd and openbsd build targets 2026-06-05 10:17:58 +08:00
xfy
0db14c239c chore(Makefile): optimize targets and fix inconsistencies
- Auto-detect VERSION from git tags with fallback
- Extract mkdir as order-only prerequisite to eliminate duplication
- Add PERF_GCFLAGS/PERF_ASMFLAGS to cross-platform builds and install
- Merge bench-regression into bench-check, unify file naming
- Fix bench scope and sampling consistency (internal/ only, -run=^$)
- Fix test-cover scope to avoid un-tagged integration/e2e code
- Fix deprecated go get -u ./... to go get -u
- Add clean-mod target, clean benchmark artifacts in clean
- Remove phantom build-prod/build-perf from help
- Split docker long line for readability
- Add .PHONY declarations for all targets
2026-06-05 10:15:21 +08:00
xfy
d82afa3233 Update readme 2026-06-04 13:29:45 +08:00
xfy
8757f0d5cb chore: add docs/plans/ to .gitignore 2026-06-04 11:31:44 +08:00
xfy
31faf77fcc style: add doc comments for exported hash and utils functions
Fix revive lint warnings for FNV64a, FNV64aBytes, BytesContainsFold.
2026-06-04 11:17:08 +08:00
xfy
2be6b67d0b fix(server): release MatchResult back to pool after use
Add matcher.ReleaseMatchResult(result) in the base handler to prevent
sync.Pool object leak. Every Match() call acquires from pool but the
caller never returned objects, causing unbounded pool growth.
2026-06-04 11:14:32 +08:00
xfy
10f16bfda9 test(ssl): update extractPEMBlock tests for DER output
Verify returned bytes are parseable by x509.ParseCertificate instead
of checking raw PEM text markers.
2026-06-04 11:14:23 +08:00
xfy
434ac0b114 fix(ssl): use encoding/pem for DER extraction in extractPEMBlock
Replace manual PEM text scanning with pem.Decode(). Returns proper
DER-encoded bytes instead of raw PEM text, fixing potential TLS
handshake failures with certificate chains.

Remove unused findMarker and matchMarker helpers.
2026-06-04 11:14:13 +08:00
xfy
197d0d2344 perf(security): reduce GeoIP lookups and deduplicate trusted proxy check
- Check(): single GeoIP LookupCountry call, result reused for both
  deny and allow checks. Removed goto label for structured flow.
- getClientIP(): single trusted proxy CIDR scan gates both
  X-Forwarded-For and X-Real-IP processing.
2026-06-04 11:09:29 +08:00
xfy
e535b9062c perf(gzip_static): pre-build extension set, use BytesContainsFold
- Pre-build extSet map for O(1) extension lookup instead of linear scan
- Replace bytes.ToLower allocation in supportsEncoding with
  utils.BytesContainsFold for case-insensitive encoding detection
2026-06-04 11:09:20 +08:00
xfy
e5fa9fe9de perf(compression): pre-compute MIME type byte slices for isCompressible
Add typesBytes and typesWildcardPrefix fields to Middleware, built once
at construction. isCompressible now uses pre-converted byte slices
instead of allocating []byte(t) per comparison per request.
2026-06-04 11:09:08 +08:00
xfy
bd97c05d0d test(matcher): update test callers for []byte Match/FindLongestPrefix 2026-06-04 11:06:09 +08:00
xfy
1eeab88c98 perf(server): pass ctx.Path() directly to Match, eliminate string alloc
Removes the string(ctx.Path()) conversion that caused one heap
allocation per request in the routing hot path.
2026-06-04 11:06:00 +08:00
xfy
aef0d8357b perf(matcher): change Match/FindLongestPrefix to accept []byte
Accept []byte directly instead of string, allowing callers to pass
fasthttp's ctx.Path() without string conversion. Internally uses
bytes.HasPrefix instead of strings.HasPrefix in radix tree search.
2026-06-04 11:05:49 +08:00
xfy
0a53622351 perf(proxy): pre-build cacheIgnoreSet, single-pass cache key, pool UpstreamTiming
Three optimizations in the proxy cache hot path:

- Pre-build cacheIgnoreSet map once at Proxy creation instead of
  per-response. Eliminates map allocation + linear scan per cached
  response.

- Compute cache key once per request via computeCacheKey() closure.
  Previously buildCacheKeyHash was called up to 5 times per request;
  now computed on first access and reused.

- Pool UpstreamTiming objects with sync.Pool. Eliminates one heap
  allocation per proxied request.
2026-06-04 10:57:38 +08:00
xfy
02775de641 perf(proxy): eliminate string allocations in isWebSocketRequest
Replace string(connection)/strings.EqualFold/strings.ToLower with
bytes.EqualFold and utils.BytesContainsFold. Removes 2-4 heap
allocations per proxied request.
2026-06-04 10:48:57 +08:00
xfy
613c5f8ff0 perf(utils): add BytesContainsFold for zero-allocation case-insensitive search
Reports whether a byte slice contains a subslice, case-insensitively,
without allocating (unlike bytes.Contains(bytes.ToLower(b), sub)).
2026-06-04 10:48:51 +08:00
xfy
83d4e5e860 perf(proxy): inline FNV-1a in buildCacheKeyHash/buildCacheKeyHashValue
Replaces fnv.New64a() with direct inline hash computation over
fasthttp's []byte slices, eliminating 1 allocation per cache key
computation and 1 []byte(":") allocation.
2026-06-04 10:45:55 +08:00
xfy
f7997ab5c4 perf(security): eliminate fnv.New64a() allocation in SlidingWindowLimiter.getBucket
Same inline FNV-1a optimization as RateLimiter.
2026-06-04 10:45:41 +08:00
xfy
bc6bfb5ac3 perf(security): eliminate fnv.New64a() allocation in RateLimiter.getShard
Replaces hash/fnv.New64a() + Write() + Sum64() with inline FNV-1a.
Removes 2 allocations per rate-limited request.
2026-06-04 10:45:25 +08:00
xfy
71bfb895e5 perf(loadbalance): delegate fnvHash64a to internal/hash
Eliminates code duplication, uses shared inline FNV-1a implementation.
2026-06-04 10:45:11 +08:00
xfy
c59d387451 perf(hash): add inline FNV-1a hash functions to internal/hash package
Zero-allocation alternative to hash/fnv.New64a(). Computes FNV-1a
inline without heap-allocating a hash.Hash64 object per call.
2026-06-04 10:45:04 +08:00
xfy
7f08b1387d test(security): 添加安全中间件覆盖测试(覆盖率 75.9% → 88.5%)
新建 internal/middleware/security/coverage_test.go,覆盖:

headers.go 全部函数(原 0% → 100%):
- applySecurityHeaders: 安全头部应用
- 应各种安全头部配置

Argon2id 密码哈希测试(原 0% → 100%):
- authenticateArgon2id: Argon2id 认证
- parseArgon2idHash: 哈希解析(有效/无效格式)
- parseUint32/parseUint8: 参数解析

GeoIPLookup.Close 测试(原 0% → 80%):
- 关闭已初始化的 GeoIP 查找器
- 重复关闭安全性

注:GeoIP 数据库加载和网络认证等函数需要外部资源,
由 integration 测试覆盖。
2026-06-04 08:33:49 +08:00
xfy
9ae7a2b8ef test(server): 添加服务器模块覆盖测试(覆盖率 78.6% → 83.3%)
新建 internal/server/coverage_test.go,覆盖:

GetTLSConfig 测试(原 66.7% → 100%):
- 完整 TLS 配置生成
- HSTS 头部设置
- 自动 HTTP→HTTPS 重定向

registerLuaRoutesWithLocationEngine 测试(原 12.5% → 87.5%):
- Lua 路由注册到 location engine
- 多路由注册
- 无 Lua 路由时的处理

注:start* 系列函数(startSingleMode、startMultiServerMode、startServer)
由于涉及真实网络监听,更适合由 integration/e2e 测试覆盖。
2026-06-04 08:33:39 +08:00
xfy
164589a9cc test(proxy): 添加代理模块低覆盖率函数测试(覆盖率 71.1% → 预计 >80%)
新建 internal/proxy/proxy_low_coverage_test.go,覆盖:

proxyDebugLog 测试(原 0%):
- 字符串/整数/布尔/错误/nil 值的调试日志
- 空键值对处理

ServeHTTP 测试(原 47.3%):
- GET/POST/PUT 真实后端转发
- 连接拒绝、超时、故障转移
- X-Accel-Redirect 内部重定向
- 可疑路径拦截
- 缓存存储/命中/stale
- 重定向重写、空 URL、查询参数

selectTarget 测试(原 46.7%):
- random 算法选择
- Lua 选择成功/回退

selectByLua 测试(原 39.1%):
- 有效脚本执行、未选择、无 ngx 表

backgroundRefresh 测试(原 41.9%):
- 缓存条目重新验证、请求错误

WebSocket 测试(原 15.4%):
- Hijack 失败、读取响应、升级拒绝
- 拨号目标成功/超时

DNS 解析测试(原 0%):
- Start 幂等性、解析器启动失败
- 刷新成功/错误、TTL 获取
- 默认端口处理

WebSocket 辅助函数测试:
- 头部配置、升级响应错误、连接关闭错误
2026-06-04 08:33:29 +08:00
xfy
294ff73a7a test(variable): 添加变量系统覆盖测试(覆盖率 74.5% → 预计 >85%)
新建 internal/variable/builtin_coverage_test.go,覆盖低覆盖率函数:

formatRequestTime 测试(原 0%):
- 8 个子测试覆盖零值、毫秒、秒、大值等各种时间格式

SetGlobalVariables 测试(原 0%):
- 正常设置、空配置、覆盖、变量展开

EphemeralGet 测试(原 49.3%):
- 全局变量、回退、server_name、upstream_connect_time
- upstream_header_time、body_bytes_sent、request_time
- UserValue 响应信息、并发安全

GetSSLClientVerify 测试(原 40%):
- TLS UserValue 设置、对端证书存在、无效类型

init 注册验证测试(原 49.3%):
- 验证 16 个内置变量注册
- 验证 5 个 upstream 变量
- 验证 9 个 SSL 变量
- 验证 host/uri/request_uri/args/method 的 GetterBytes 注册
2026-06-04 08:21:48 +08:00
xfy
b0e795bc9a test(stream): 添加 Stream 服务器覆盖测试(覆盖率 57% → 预计 >75%)
新建 internal/stream/server_coverage_test.go,覆盖之前 0% 的函数:

TCP 监听测试:
- TestListenTCP_Success: 成功监听随机端口
- TestListenTCP_InvalidAddress: 无效地址返回错误

服务器启动测试:
- TestStart_NoListeners: 无监听器时启动
- TestStart_WithTCPListeners: 有 TCP 监听器时启动
- TestStart_AcceptConnections: 实际接受 TCP 连接

UDP 服务器测试:
- TestNewUDPServer_DefaultTimeout: 默认 60 秒超时
- TestNewUDPServer_CustomTimeout: 自定义超时

会话管理测试:
- TestSessionKey: 会话键生成正确性
- TestGetSession_NotExist/Existing: 会话查找
- TestRemoveSession/NotExist: 会话移除
- TestCleanupExpiredSessions_RemovesExpired/AllExpired: 过期清理

会话创建测试:
- TestGetOrCreateSession_NoHealthyTargets: 无健康目标
- TestGetOrCreateSession_ExistingSession: 复用现有会话
- TestGetOrCreateSession_NewSession: 创建新会话

响应处理测试:
- TestHandleBackendResponse_Timeout: 后端超时处理
- TestServe_ReceivesAndForwards: UDP 数据转发
- TestStartCleanupTicker_StopsOnSignal: 定时清理停止
2026-06-04 08:21:41 +08:00
xfy
8bb88e8898 test(http3): 完善 HTTP/3 服务器测试(覆盖率 46% → 93.1%)
补充 server_test.go 中未实现的测试用例:

新增测试:
- TestNewServer_TableDriven: 表驱动验证所有 NewServer 错误/成功路径
- TestNewServer_VerifyInternalFields: 验证服务器内部字段初始化
- TestStart_AlreadyRunning: 重复启动返回 "server already running"
- TestStart_InvalidListenAddress: 无效监听地址返回错误
- TestStart_Success: 绑定随机端口并验证运行状态
- TestStart_EmptyListenAddress: 空地址回退到 :443(无权限时 skip)
- TestStart_QUICConfigDefaults: 零值/自定义 MaxStreams、IdleTimeout、0RTT
- TestStart_MultipleStartsAndStops: start → stop → start 生命周期循环
- TestStop_NotRunning: 空闲服务器 stop 为空操作
- TestStop_Running: stop 正确设置 running = false
- TestStop_CalledMultipleTimes: 重复 stop 安全
- TestStartStop_Lifecycle: 完整生命周期状态断言
2026-06-04 08:21:32 +08:00
xfy
f26a4a7949 test(sslutil): 为 tlsconfig.go 添加全面单元测试(覆盖率 29.2% → 预计 >85%)
添加 internal/sslutil/tlsconfig_test.go,覆盖所有 TLS 配置函数:

- TestParseTLSVersions (16 子测试): 空/nil、仅 TLS1.2、仅 TLS1.3、
  混合、大小写不敏感、TLS1.0/1.1 拒绝、未知版本错误、重复项
- TestParseMinTLSVersion (9 子测试): TLS1.2/1.3、默认值、
  大小写、首次匹配优先、未知版本回退
- TestParseCipherSuites (16 子测试): OpenSSL 名称、Go 标准名称、
  TLS1.3 密码套件、未知/不安全错误、混合有效无效
- TestParseCipherSuitesLenient (10 子测试): 有效/跳过未知/跳过不安全/
  全无效返回 nil/混合
- TestIsInsecureCipher (4 子测试): 8 个不安全 ID 全部识别、
  安全 ID 正确排除
- TestDefaultCipherSuites (4 子测试): 非空、全部安全、
  包含预期套件、每次返回新切片
- TestTLSVersionMap (3 子测试): 键/值/条目数验证
- TestCipherNameToID_Consistency: OpenSSL↔Go 名称映射一致性
2026-06-04 08:13:42 +08:00
xfy
a836152836 test(utils): 为 utils 包添加全面单元测试(覆盖率 4.6% → 预计 >70%)
添加三个新测试文件:

bytes_test.go - 字节/字符串零拷贝转换测试:
- TestB2s: nil 切片、空切片、ASCII、UTF-8、特殊字符
- TestB2s_ZeroAlloc: 验证内存共享(指针比较)
- TestS2b: 空字符串返回 nil、正常转换
- TestS2b_ZeroAlloc: 验证内存共享
- TestB2s_S2b_RoundTrip: 往返转换正确性,包括二进制数据

etag_test.go - ETag 生成测试:
- TestGenerateETag: 表驱动测试,零时间、大尺寸、负值
- TestGenerateETag_Format: 验证引号包裹的 hex-hex 格式
- TestGenerateETag_Deterministic: 相同输入产生相同输出
- TestGenerateETag_DifferentInputs: 不同输入产生不同输出

ipallowlist_test.go - IP 白名单测试:
- TestParseIPAllowList: nil、空、CIDR、单 IP、localhost、无效输入
- TestParseIPAllowList_Localhost: 验证 127.0.0.1/32 + ::1/128 展开
- TestParseIPAllowList_SingleIPv4/IPv6: /32 和 /128 自动转换
- TestParseCIDR/TestParseCIDR_Invalid: 有效/无效 CIDR 和单 IP
- TestIPInAllowList: 匹配/不匹配及边界情况
- TestParseIPAllowList_Integration: 端到端解析+检查
2026-06-04 08:13:34 +08:00
xfy
d6ee721bc8 test(adapter): 为 adapter 包添加完整单元测试(覆盖率 0% → 预计 >80%)
添加 internal/adapter/common_test.go,覆盖 CommonAdapter 的所有公开方法:

- TestDefaultBodyThreshold: 验证 DefaultBodyThreshold 常量值
- TestNewCommonAdapter: 验证构造函数返回非空实例及 CtxPool 初始化
- TestResetContext: 验证请求/响应/用户值状态重置
- TestResetContext_DisableNormalizing: 验证头部规范化禁用
- TestStreamRequestBody: 表驱动测试覆盖 nil body、NoBody、空体、
  小体(≤64KB)、阈值体、大体(>64KB)、未知长度
- TestStreamRequestBody_ReadError: 读取错误不 panic
- TestStreamRequestBody_PartialReadError: 部分读取错误时保留已读数据
- TestGetContext/PutContext: Pool 获取/归还正确性
- TestGetContext_PutAndGet: 完整的 get-put-get 循环
- TestConcurrentPoolAccess: 100 goroutine 并发安全
- TestConcurrentStreamRequestBody: 50 goroutine 并发流式读取
2026-06-04 08:13:25 +08:00
xfy
8e00e63972 chore: fix lint issues from performance optimization
- errcheck: check type assertions from sync.Pool.Get() in
  loadbalance/balancer.go and matcher/radix.go
- errcheck: check type assertion from list.Element.Value in
  resolver/resolver.go evictLRULocked
- revive: add doc comment for exported ReleaseMatchResult function
2026-06-04 00:22:45 +08:00
xfy
7fe1ca6bec perf(loadbalance): eliminate double-lock in ConsistentHash with atomic.Bool rebuild guard
SelectByKey and SelectExcludingByKey previously had a RLock→RUnlock→
rebuildCircle(Lock)→RLock pattern when the hash ring was empty. Under
cold-start concurrency, multiple goroutines could trigger simultaneous
rebuild attempts.

Add atomic.Bool 'rebuilt' flag with ensureRebuilt() check before any
RLock acquisition:
- Fast path: atomic load returns true → skip rebuild, proceed to RLock
- Cold start: first caller rebuilds and sets flag, subsequent callers
  see the flag and skip rebuild
- Rebuild() explicitly resets the flag for explicit ring invalidation

Eliminates the RLock→Unlock→Lock→RLock transition entirely. The ring
is guaranteed ready before RLock is acquired.
2026-06-04 00:20:43 +08:00