684 Commits

Author SHA1 Message Date
xfy
39ff086236 ci: consolidate all steps into single step per job
Some checks failed
CI / Test (push) Failing after 24s
CI / Build (push) Has been skipped
GITHUB_PATH does not reliably update PATH for subsequent steps in
Gitea Actions. Put Go install + all checks in one step with explicit
PATH export. Simplify to 2 jobs: test + build.
2026-06-12 17:00:09 +08:00
xfy
c2fe40330b ci: add debug info to vet step
Some checks failed
CI / Lint (push) Failing after 39s
CI / Test (push) Failing after 9m23s
CI / Build (push) Has been skipped
2026-06-12 16:51:08 +08:00
xfy
8953c0c85a ci: replace golangci-lint with go vet
Some checks failed
CI / Lint (push) Failing after 1m30s
CI / Test (push) Failing after 2m22s
CI / Build (push) Has been skipped
golangci-lint v2 install via 'go install' is too heavy for CI
(compiles from source, needs lots of memory and time).
Use go vet as lightweight alternative for now.
2026-06-12 16:33:56 +08:00
xfy
000a6afc08 ci: use Aliyun Go mirror and goproxy.cn for China network
Some checks failed
CI / Lint (push) Failing after 3m44s
CI / Test (push) Failing after 3m42s
CI / Build (push) Has been skipped
go.dev/dl redirects to dl.google.com which is blocked in China.
Use mirrors.aliyun.com for Go toolchain download and goproxy.cn
for Go module proxy.
2026-06-12 16:23:00 +08:00
xfy
bb0f321849 ci: replace setup-go action with manual Go install from go.dev
Some checks failed
CI / Lint (push) Failing after 3m1s
CI / Test (push) Failing after 2m13s
CI / Build (push) Has been skipped
actions/setup-go@v5 was failing silently (0s steps) likely due to
network issues downloading Go from GitHub. Install Go directly from
go.dev which is more accessible.

Also remove Docker job (not needed yet) and simplify to 3 jobs.
2026-06-12 15:47:51 +08:00
xfy
ff602ee183 ci: install gofumpt and golangci-lint in same step to fix PATH
Some checks failed
CI / Lint (push) Failing after 40s
CI / Test (push) Failing after 3m57s
CI / Build (push) Has been skipped
CI / Docker (push) Has been skipped
Previous attempts failed because go install puts binaries in GOPATH/bin
which may not be in PATH for subsequent steps. Combine into fewer steps.
2026-06-12 15:29:32 +08:00
xfy
a5f65a8040 ci: test with force_pull disabled and debug logging
Some checks failed
CI / Lint (push) Failing after 7m50s
CI / Test (push) Failing after 11s
CI / Build (push) Has been skipped
CI / Docker (push) Has been skipped
2026-06-12 15:18:47 +08:00
xfy
c2768a0f32 ci: use go install for golangci-lint instead of curl
Some checks failed
CI / Lint (push) Failing after 11s
CI / Test (push) Failing after 11s
CI / Build (push) Has been skipped
CI / Docker (push) Has been skipped
The curl-based install script downloads from raw.githubusercontent.com
which is unreliable in some network environments. go install is more
reliable and uses the Go module proxy.
2026-06-12 11:19:59 +08:00
xfy
34f37efa14 style: fix gofumpt formatting in fileinfo_cache and proxy
Some checks failed
CI / Lint (push) Failing after 7m47s
CI / Test (push) Failing after 8m34s
CI / Build (push) Has been skipped
CI / Docker (push) Has been skipped
2026-06-12 11:08:26 +08:00
xfy
a0cb0775f7 ci: remove GitHub Actions workflow, keep Gitea Actions only
Some checks failed
CI / Lint (push) Failing after 11s
CI / Test (push) Failing after 11s
CI / Build (push) Has been skipped
CI / Docker (push) Has been skipped
2026-06-11 23:58:43 +08:00
xfy
7263fcdc22 ci: trigger Gitea Actions re-run
Some checks failed
CI / Test (push) Has been cancelled
CI / Build (push) Has been cancelled
CI / Docker (push) Has been cancelled
CI / Lint (push) Has been cancelled
2026-06-11 23:51:10 +08:00
xfy
09f4ca5755 ci: add Gitea Actions CI pipeline
Some checks failed
CI / Build (push) Blocked by required conditions
CI / Docker (push) Blocked by required conditions
CI / Lint (push) Failing after 3m18s
CI / Test (push) Failing after 18m40s
Mirror of .github/workflows/ci.yml adapted for Gitea Actions:
- Replace golangci-lint-action with direct install script
- Remove upload-artifact (not needed on Gitea)
- Same 4-job structure: lint → test → build → docker
2026-06-11 23:44:46 +08:00
xfy
aaeb9c2c49 ci: add GitHub Actions CI pipeline
Add continuous integration workflow triggered on push to master
and pull requests.

Jobs:
- lint: gofumpt format check + golangci-lint v2 via action
- test: unit tests with -race flag (no integration/e2e)
- build: static multi-platform build (linux/darwin, amd64/arm64)
  with ldflags version injection, binary upload as artifact
- docker: docker buildx build (only on push to master/tags)

Uses:
- actions/checkout@v4, actions/setup-go@v5, golangci/golangci-lint-action@v7
- Go version auto-detected from go.mod (1.26)
- Module caching for faster CI runs
2026-06-11 23:42:00 +08:00
xfy
6967957299 feat(config): add ${ENV_VAR} interpolation in YAML configuration
Enable environment variable substitution in configuration files using
${VAR} syntax. Supports 12-factor app deployment patterns without
hardcoding secrets or environment-specific values.

Syntax:
- Only ${VAR} with curly braces (avoids conflict with $variable system)
- Missing variables preserved as-is (${MISSING} stays unchanged)
- Multiple variables per line supported
- Adjacent variables ${A}${B} handled correctly

Integration:
- Applied in config.Load() after os.ReadFile, before yaml.Unmarshal
- Applied in processIncludes() for each included file
- 12 unit tests covering single/multiple/missing/empty variables
2026-06-11 23:41:52 +08:00
xfy
f605ef3b44 feat(server): add /healthz and /readyz endpoints for Kubernetes probes
Add lightweight health check endpoints for container orchestration
(Kubernetes liveness/readiness probes, load balancer health checks).

New config under monitoring:
- healthz.enabled (default true), healthz.path (default /healthz)
- readyz.enabled (default true), readyz.path (default /readyz)

/healthz (liveness):
- Always returns 200 {"status":"ok"} if process is alive
- No dependency checks, minimal overhead

/readyz (readiness):
- Returns 200 {"status":"ready"} when server is running
- Returns 503 {"status":"not ready","reasons":[...]} when not ready
- Static-only servers (no proxies) always return 200

Registration:
- Registered alongside status/pprof endpoints
- Available in single mode (LocationEngine) and multi-server mode (Router)
- No IP allowlist required (K8s probes come from localhost)
- 6 unit tests covering all response scenarios
2026-06-11 23:41:45 +08:00
xfy
8224ae7ff3 feat(middleware/cors): add CORS middleware with server-level configuration
Implement Cross-Origin Resource Sharing (CORS) middleware following the
middleware.Middleware interface pattern.

New config under security.cors:
- enabled: toggle CORS handling (default false)
- allowed_origins: exact origin list or ["*"] wildcard
- allowed_methods: allowed HTTP methods for preflight
- allowed_headers: allowed request headers for preflight
- expose_headers: headers visible to frontend JS
- allow_credentials: send cookies (incompatible with wildcard origin)
- max_age: preflight cache duration in seconds

Validation:
- origins+credentials mutual exclusion per CORS spec
- max_age non-negative check

Integration:
- Registered after SecurityHeaders, before ErrorIntercept in middleware chain
- Preflight (OPTIONS) returns 204 with CORS headers, skips handler
- Actual requests add CORS headers after handler execution
- Non-matching origins pass through without CORS headers
- 16 unit tests covering all scenarios
2026-06-11 23:41:38 +08:00
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
xfy
ebfa9cc7a8 feat(middleware/requestid): add request ID generation and propagation middleware
Implement Request-ID middleware that generates or propagates X-Request-ID
headers for distributed request tracing.

- Check incoming X-Request-ID header, reuse if present (trust downstream)
- Generate UUID v4 via google/uuid if no incoming ID
- Store ID in ctx.UserValue for variable system and access log access
- Set X-Request-ID response header for client-side tracing
- Add GetRequestID() helper for proxy header propagation
- Registered as first middleware (before AccessLog) so $request_id
  is available throughout the request lifecycle
- 8 unit tests covering generation, propagation, empty header, UUID format
2026-06-11 23:41:24 +08:00
xfy
0c0cfd0485 docs(specs): add P0 production readiness design specification 2026-06-11 23:29:26 +08:00
xfy
f33117b940 fix(handler,http2,loadbalance,logging,resolver,ssl): fix high severity issues
- handler/static.go: add sync.RWMutex to StaticHandler; protect Handle
  with RLock and all setters with Lock to prevent data races
- http2/server.go: delete empty connection slice keys from pool map to
  prevent memory leak under high client churn
- loadbalance/slow_start.go: recreate stopCh in Start() to support
  Start-Stop-Start cycles; guard double-close in Stop()
- resolver/resolver.go: recreate stopCh in Start() to support restart
- logging/logging.go: save *os.File handles from getOutput so Close()
  actually closes log files; exclude os.Stdout/os.Stderr from closing
- ssl/session_tickets.go: protect started/rotateTimer access in
  scheduleRotation with mu; support Start-Stop-Start cycles
- ssl/ssl.go: cache parsed default certificate to avoid re-parsing on
  every TLS handshake for OCSP stapling
2026-06-11 17:03:17 +08:00
xfy
27e00b84a8 fix(proxy,handler,server,stream,ratelimit): fix resource leaks and functional bugs
- proxy/proxy.go: decrement connection count on dangerous path rejection
  (line 724) to prevent connection count leak
- handler/sendfile_linux.go: return *os.File from getSocketFile and let
  linuxSendfile close it, fixing EBADF from deferred close in getSocketFd
- proxy/websocket.go: return bufio.Reader from readWebSocketUpgradeResponse
  and wrap targetConn with bufferedConn to consume pre-buffered frame data,
  preventing first-frame loss
- server/pool.go: use non-blocking send after starting new worker to avoid
  deadlock when queue is full
- stream/stream.go: check stopCh on non-timeout UDP read errors to prevent
  infinite loop and shutdown deadlock
- middleware/ratelimit: replace select-based close guard with sync.Once in
  StopCleanup to prevent double-close panic
2026-06-11 16:35:10 +08:00
xfy
fe0dee4da3 fix(compression,ssl,server,lua): resolve data races and concurrency bugs
- compression: move sync.Pool.New initialization into constructors to
  eliminate lazy-init race in Get()
- ssl/ocsp: copy response fields under RLock before releasing, preventing
  race with concurrent writers in refreshAll
- server: change proxiesMu from sync.Mutex to sync.RWMutex; protect
  getProxyCacheStats and purge handlers with RLock to prevent races
  with proxy registration
- lua/api_timer: fix double-decrement race in Cancel vs executeTimer
  by using timer.Stop() result to determine who decrements active
- lua/api_socket_tcp: fix nil pointer race in ConnectAsync by checking
  currentOp under lock before Connect returns
2026-06-11 16:30:11 +08:00
xfy
e733273139 fix(server,app,proxy,resolver,middleware,lua): add nil guards and safe defaults
- server: reject Start() when config is nil to prevent panic
- app_common: guard empty Servers slice in initHTTP2/3 and logServerAddresses
- proxy/health: handle nil HealthCheckConfig with defaults
- resolver: handle nil ResolverConfig by returning noopResolver
- middleware/headers: skip UpdateConfig when cfg is nil
- middleware/sliding_window: enforce minimum window duration of 1s
- lua/api_log: map EMERG/ALERT/CRIT to Error() instead of Fatal()
  to prevent Lua scripts from killing the entire server process
2026-06-11 16:23:04 +08:00
xfy
818aa23739 fix(logging,mimeutil,variable): correct data corruption and behavior bugs
- 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
2026-06-11 16:22:55 +08:00
xfy
b766b98125 docs(plans): add bug fix implementation plan 2026-06-11 15:23:47 +08:00
xfy
dea5e28f5f test(server): fix data race in monitoring endpoint tests
Replace concurrent polling of srv.GetListeners() with pre-created
net.Listener injected via srv.SetListeners(). The previous approach
raced with the Start() goroutine writing to s.listeners and calling
net.Listen().

Also remove unused waitForServerRunning call.
2026-06-11 15:23:46 +08:00
xfy
e8fbbf368c fix(config,server): merge defaults on Load and fix monitoring registration
Two related fixes that must land together:

1. config.Load() now starts from DefaultConfig() before unmarshaling
   YAML. This ensures missing top-level fields (Performance,
   Monitoring, Resolver) use their documented defaults instead of
   zero values. Most importantly, file_cache is no longer silently
   disabled when users omit the performance: section.

2. startSingleMode() now checks Monitoring.Status.Enabled instead of
   Path/Allow to decide whether to register the status endpoint.
   Without this change, fix #1 would have caused a regression where
   the status handler is registered even when monitoring is disabled,
   because DefaultConfig() sets Path and Allow defaults.

Also replace remaining log.Printf in status.go and lua/api_timer.go
with zerolog to follow project logging conventions.

Added tests:
- config/load_test.go: verifies defaults are applied, explicit values
  override defaults, and monitoring stays disabled by default.
- server/monitoring_registration_test.go: verifies /_status is only
  registered when enabled and remains reachable with static handler
  on path: /.
2026-06-11 15:08:57 +08:00
xfy
7cc76f0d5b chore(proxy): remove misplaced go:generate directive
The //go:generate go test -v ./... directive is not what
go:generate is intended for (code generation, not test execution).
Remove it to avoid confusion.
2026-06-11 15:02:19 +08:00
xfy
5e38b63f10 docs(benchmarks): update performance report and add bench-compare script
- Update benchmarks/v0.4.0/REPORT.md with optimization results:
  Task A access-log sampling (-29% latency, -65% allocs),
  Task B file-cache fix (-99.8% allocs, +73% throughput),
  Task C RemoteAddr string caching.
- Add scripts/bench-compare.sh to compare two benchmark summaries
  and flag regressions beyond configurable thresholds.
- Add benchmark-pprof.yaml for reproducible pprof collection.
2026-06-11 14:43:19 +08:00
xfy
9824ad5b57 perf(netutil): cache RemoteAddr string formatting
Add netutil.FormatRemoteAddr() to avoid repeated net.TCPAddr.String()
allocations on the hot logging path.

- IPv4 addresses use a zero-allocation fast path (custom uint8->ASCII).
- IPv6 falls back to addr.String() with a 1024-entry LRU cache.
- Update logging.LogAccess and variable $remote_addr/$remote_port
  getters to use the shared helper.

Eliminates net.JoinHostPort and net.IP.String from top allocators
when access logging is active.
2026-06-11 14:43:12 +08:00
xfy
148f43fcb3 perf(handler): enable file info cache and fix index file cache lookup
- Router now enables FileInfoCache with 2s TTL for all static handlers
  to reduce redundant os.Stat calls.
- FileInfoCache supports negative caching: missing files are cached
  with a shorter TTL to avoid repeated stat on non-existent paths.
- Fix missing fileCache lookup for directory index files (index.html).
  Previously handleStandard/handleTryFiles skipped fileCache when
  serving index files, causing os.ReadFile on every request even
  with file_cache configured.
- Extract tryServeFromFileCache() helper to unify cache hit logic
  across file and index-file serving paths.

Verified with wrk 200 conn / 20s on static /index.html:
- Throughput: 140k -> 242k req/sec (+73%)
- alloc_space: 2.6 GB -> 4.6 MB (-99.8%)
2026-06-11 14:43:04 +08:00
xfy
047e033af5 feat(accesslog): add deterministic sampling with sample_rate config
Add logging.access.sample_rate config (0.0-1.0) for deterministic
request sampling. 5xx errors are always logged; 2xx/3xx/4xx follow
the configured rate. Uses atomic.Uint64 counter for lock-free,
zero-allocation sampling decisions.

Includes test updates to verify:
- sample_rate=1.0 logs all requests
- sample_rate=0.0 logs only 5xx
- 5xx are always logged regardless of rate
2026-06-11 14:42:55 +08:00
xfy
1128eb644f perf(static): enable FileInfoCache by default with negative caching
Production static file serving now uses FileInfoCache by default
with a 2-second TTL in router.go, dramatically reducing os.Stat
syscalls for missing files and repeated paths.

Changes:
- Add negative cache support to FileInfoCache (caches 'not found' results)
- Introduce statWithCache() helper in StaticHandler for uniform caching
- Make FileInfoCache TTL configurable via SetTTL()
- Default cacheTTL=0 disables caching in NewStaticHandler (tests compat)
- router.go enables fileInfoCache with 2s TTL for all static handlers

Benchmark (repeated 404s):
  No cache:    ~2651 ns/op, 2225 B/op, 15 allocs/op
  With cache:  ~1505 ns/op, 1905 B/op, 12 allocs/op
  Improvement: -43% latency, -14% allocations

This addresses the dominant allocation source in v0.4.0 profile
(os.statNolog at 74.95% of allocations).
2026-06-11 14:05:56 +08:00
xfy
445401c40f perf(accesslog): add sample_rate for access log to reduce CPU and allocations
Add configurable access log sampling via :
- 0.0-1.0 range; defaults to 1.0 (record all) for backward compatibility
- Uses lock-free atomic counter for deterministic sampling
- Non-2xx responses always logged regardless of sample rate

Benchmark results (combined format, /dev/null):
  Full logging:    ~2245 ns/op, 1987 B/op, 17 allocs/op
  10% sampling:    ~1593 ns/op, 1633 B/op,  6 allocs/op
  Improvement:     -29% latency, -65% allocations/op

This addresses the top application-layer CPU hotspot identified
in the v0.4.0 profile (LogAccess at 16.36% cumulative CPU).
2026-06-11 13:53:41 +08:00
xfy
88bb7bf267 docs(benchmark): add v0.4.0 performance analysis report
Key findings from CPU/allocs/heap profiling:
- LogAccess consumes 16.36% cumulative CPU (top app-layer hotspot)
- os.statNolog dominates 74.95% of allocations (static file path checks)
- net.IP.String + net.JoinHostPort account for 9.34% allocations
- bufio.NewReader/Writer hold 54.6% of heap memory

Includes detailed optimization priorities and next steps.
2026-06-11 13:49:57 +08:00
xfy
58e095a35b feat(pprof): add /debug/pprof/allocs endpoint for allocation profiling
- Add writeAllocsProfile() helper in pprof_impl.go
- Register /allocs route in PprofHandler.ServeHTTP
- Add handleAllocs() method with proper streaming response
- Update index page to list the new allocs profile link

This aligns lolly's pprof endpoints with net/http/pprof and enables
allocation hotspot analysis during performance benchmarking.
2026-06-11 13:47:47 +08:00
xfy
ebeb258c58 docs(benchmark): add v0.4.0 baseline summary and update gitignore
- Collect baseline benchmark summary across all core modules
- Save key results to benchmarks/v0.4.0/summary.txt
- Update .gitignore to track benchmark summaries/reports
- Include performance optimization design docs and plan
2026-06-11 13:43:28 +08:00
xfy
bc57e5b656 chore(benchmark): establish benchmark directory structure 2026-06-10 14:11:19 +08:00
xfy
afbbc3a951 chore: release v0.4.1 v0.4.1 2026-06-10 13:54:26 +08:00
xfy
66ea93e3c1 fix(stream): reset stopCh after Stop for restartability 2026-06-10 13:48:07 +08:00
xfy
7204432ca0 fix(stream): correct upstream selection and add graceful shutdown
- Fix handleConnection to use addr parameter for direct upstream map
  lookup instead of always selecting the first upstream
- Add Server.Stop() for graceful shutdown with listener closing, UDP
  server cleanup, health checker termination, and goroutine joining
- Add shutdownStream() to App and call it in SIGTERM/SIGQUIT/SIGUSR2
  signal handlers to prevent goroutine and port leaks on shutdown
2026-06-10 13:45:35 +08:00
xfy
f12ffd180f chore: release v0.4.0
- Update CHANGELOG.md for v0.4.0
- Update Makefile FALLBACK_VERSION to 0.4.0
- Fix lint warnings (godoc comments, goconst)
- Clean up code formatting
v0.4.0
2026-06-09 15:59:36 +08:00
xfy
503daf65d3 perf(loadbalance): add benchmarks for Least Time and Sticky
- Benchmark Select and Record operations
- Concurrent benchmark for realistic load testing
- Baseline performance:
  - LeastTime.Select: ~33ns/op, 0 allocs
  - LeastTime.Record: ~5.6ns/op, 0 allocs
  - StickySession.Select: ~205ns/op (with cookie lookup)
2026-06-08 18:21:03 +08:00
xfy
ef871f1d39 test(loadbalance): add integration tests for Least Time and Sticky
- Verify Least Time picks faster target consistently
- Verify Sticky fallback when target becomes unhealthy
- Test cookie encoding and session persistence
2026-06-08 18:19:20 +08:00
xfy
e5885ce888 fix(proxy): correct response time recording for Least Time
- Record headerTime when header is received
- Record lastByteTime when response is complete
- Use correct timing calculations (headerReceived/connectEnd/responseEnd)
2026-06-08 18:17:08 +08:00
xfy
72f189bba8 feat(proxy): integrate Least Time and Sticky balancers
- Add least_time and sticky to createBalancerByName
- Implement response time recording for Least Time
- Support StickySession in target selector with request context
- StickySession auto-starts when created
2026-06-08 18:11:47 +08:00
xfy
3b6b70a491 fix(config): validate least_time default_time is not negative 2026-06-08 18:03:52 +08:00
xfy
cb1f86298e fix: add missing test coverage for Task 4 config integration
- Add validation tests for least_time and sticky configs
- Add algorithm tests for least_time and sticky
- Add SameSite validation in validateProxy
2026-06-08 18:01:21 +08:00
xfy
88a2c1fc1b feat(config): add Least Time and Sticky configuration support
- Add least_time and sticky to valid algorithms list
- Add LeastTimeConfig and StickyConfig structures
- Update default config generation with new options
- Add configuration validation for new fields
2026-06-08 17:57:06 +08:00
xfy
a73da4e14a fix(sticky): recreate stopCh on Start to support restart 2026-06-08 17:52:20 +08:00