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
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
- 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
- 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
- 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
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
- 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.
- 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
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.
- Add comments for lua/api_log.go HTTP status codes and log levels
- Add comments for variable/builtin.go and ssl.go constants
- Add comments for utils/httperror.go error variables
- Add comments for matcher/matcher.go location types
- Add comments for compression/compression.go algorithms
- Include author attribution (xfy)
- Remove unused benchmark/tools package
- Make ValidAlgorithms private (validAlgorithms) in loadbalance
- Remove dead code (_ = result) in lua/api_socket_tcp.go
- Fix code formatting with goimports
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Enhance parseCIDR in utils/ipallowlist.go to support single IP addresses
(without CIDR prefix) and ensure IP is in canonical form. This matches
the functionality previously in access.go.
- Add ParseCIDR as public function supporting CIDR and single IP
- Update access.go to use utils.ParseCIDR instead of local implementation
- Remove duplicate parseCIDR function from access.go
- Update tests to use utils.ParseCIDR
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Replace strings.ToLower(string(...)) with bytes.ToLower
- Reduces memory allocation in hot path for Accept-Encoding checks
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Apply modern Go patterns across the codebase:
- Replace `interface{}` with `any` (Go 1.18+)
- Use `for range n` instead of `for i := 0; i < n; i++` (Go 1.22+)
- Replace `sort.Slice` with `slices.Sort` from slices package
- Simplify sync.WaitGroup patterns with errgroup where appropriate
- Add Makefile targets for modernize analyzer
Total: 84 files updated, net reduction of 79 lines
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- ConsistentHash: reuse main hash ring in SelectExcludingByKey instead of
rebuilding per call, reducing memory allocation from 369KB to 1.8KB (99.5%)
- RateLimiter: replace single RWMutex with 16-segment sharded locks to
reduce lock contention in high-concurrency scenarios
- TLS SessionTickets: add warning log when KeyFile is empty to alert
users about session invalidation after restart
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Extract applyDefaults function to unify configuration initialization
logic between NewAuthRequest and UpdateConfig methods.
Eliminates ~20 lines of duplicate default value setting code.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>