Tags: goceleris/celeris
Tags
feat: v1.3.3 utility & serving middleware — pprof, swagger, static, r… …ewrite, adapters (#223) * feat: add pprof, swagger, static, rewrite, and adapters middleware Five new utility & serving middleware for v1.3.3: - pprof (#189): Go profiling endpoints with loopback-only default. Wraps all net/http/pprof handlers via celeris.Adapt(). AuthFunc guard. 10 tests. - swagger (#190): OpenAPI spec + CDN-loaded Swagger UI/Scalar. No bundled assets. SpecContent or fs.FS support. AuthFunc optional. 16 tests. - static (#191): Static file serving with SPA mode, Cache-Control, prefix stripping, directory browse. Path traversal protection via FileFromDir (OS) and path.Clean + .. rejection (fs.FS). 20 tests. - rewrite (#192): Regex-based URL rewriting with capture groups ($1/$2). Pre-routing (Server.Pre). Compile at init, sorted first-match-wins. Optional redirect mode (301/302). 9 tests. - adapters (#193): Bidirectional stdlib ↔ celeris conversion. WrapMiddleware wraps func(http.Handler) http.Handler for celeris chains. ToStdlib delegates to celeris.ToHandler. ReverseProxy via httputil. 8 tests. Documentation: - middleware/doc.go: ordering guide updated (rewrite in Pre, pprof/swagger/static in Use) - README.md: 5 new rows, package count 29 - SECURITY.md: v1.3.3 section (pprof loopback, static traversal, swagger AuthFunc) - CONTRIBUTING.md: new reference implementation categories Closes #189, Closes #190, Closes #191, Closes #192, Closes #193 * fix: resolve all review findings from v1.3.3 audit Security: - swagger: escape SpecURL with html.EscapeString before injecting into HTML/JS templates (prevents XSS via misconfigured SpecURL) Code quality: - pprof: replace manual skipMap with celeris.SkipHelper (per CONTRIBUTING.md) - adapters: fix gofmt formatting + errcheck for resp.Body.Close - .golangci.yml: add pprof var-naming exclusion (package name matches stdlib) * fix: resolve all remaining review findings - swagger/doc.go: add CSP warning for secure middleware interaction (cdn.jsdelivr.net must be allowed in script-src/style-src) - rewrite/doc.go: anchor regex in basic example (^/old$ not /old), add Security section with ReDoS and open redirect warnings - adapters/adapters.go: add 100MB body size cap to responseCapture (prevents OOM from misbehaving stdlib middleware, matches core bridge) * fix: major rework of all 5 middleware to address review findings static: - FIX: Browse XSS — href uses url.PathEscape + ./ prefix (prevents javascript:) - FIX: Prefix matching now segment-boundary-aware (/api no longer matches /api-docs) - ADD: ETag/Last-Modified headers on OS and fs.FS files (conditional GET + 304) - ADD: Range requests (206) for fs.FS files (video seeking, download resume) - ADD: accept-ranges: bytes header - 28 tests covering all new functionality swagger: - FIX: YAML spec auto-detected and served with correct Content-Type - ADD: UIConfig with DocExpansion, DeepLinking, PersistAuthorization, Title - ADD: AssetsPath for air-gapped/self-hosted asset serving (no CDN required) - ADD: RendererScalar/RendererSwaggerUI type for UI engine selection - 26 tests adapters: - FIX: WrapMiddleware now propagates pre-inner headers (rs/cors, gorilla/csrf work) - FIX: buildRequest sets ContentLength, TLS state, Protocol version - ADD: ReverseProxy with Options (WithTransport, WithModifyRequest, WithErrorHandler) - ADD: Pooled responseCapture for zero-alloc hot path - DEL: ToStdlib (was pure alias of celeris.ToHandler) - DOC: Limitations section (no Hijacker/Flusher for WebSocket/streaming) rewrite: - FIX: Rules changed from map[string]string to []Rule slice (insertion-order, not alphabetical — matches user expectations and Echo's approach) - DOC: First-Match-Wins section updated for ordered evaluation * fix: gofmt formatting * fix: security fix + integration review fixes for v1.3.3 middleware Security: - static: fix path traversal in Browse directory listing. serveOS now validates that filepath.Join result stays within root directory (prefix check + symlink escape check), mirroring FileFromDir. Added TestPathTraversalBrowse and TestPathTraversalFile. Core integration: - rewrite: make New() variadic (config ...Config) with defaultConfig and applyDefaults() for pattern consistency with all other middleware - pprof: use cfg := defaultConfig instead of var cfg Config - swagger: use c.Redirect() instead of manual SetHeader + NoContent Tests: - pprof: add TestPprofSkipFunc (was missing Skip callback coverage) - adapters: add TestReverseProxyPanicNilTarget and TestReverseProxyOptions (ReverseProxy had zero test coverage) - rewrite/bench_test.go: fix regex (d+) -> (\d+) so BenchmarkRewriteMatch actually tests the match path Benchmarks: - bench_chain_test.go: add BenchmarkChainPreRoutingWithRewrite, BenchmarkChainRewritePassthrough, BenchmarkChainWithWrapMiddleware - swagger/bench_test.go: fix spec path /swagger/doc.json -> /swagger/spec Documentation: - middleware/doc.go: fix rewrite production stack example ([]Rule syntax), fix swagger description (remove AuthFunc claim), add adapters mention - pprof/swagger/static doc.go: add missing Skip/Ordering sections - adapters doc.go: document ReverseProxy function and Option API - SECURITY.md: fix swagger AuthFunc claim (feature doesn't exist) - README.md: fix static description (no SPA or Cache-Control) * fix: address all 33-agent review findings for v1.3.3 middleware P0 fixes: - adapters: add 100MB body size cap to responseCapture.Write (matches core bridge maxBridgeResponseBytes). Prevents OOM from misbehaving stdlib middleware. - adapters: WrapMiddleware(nil) now panics at init instead of deferred panic on first request. - static: validate() rejects Prefix without leading / (was silently serving nothing). Feature additions: - static: SPA mode (Config.SPA bool) — serves index file for non-existent paths instead of falling through. Works for both OS and fs.FS backends. - static: Cache-Control (Config.MaxAge time.Duration) — sets "public, max-age=N" header alongside ETag/Last-Modified. - swagger: ReDoc renderer (RendererReDoc) with CDN + self-hosted assets support. - swagger: DefaultModelsExpandDepth changed to *int to allow depth=0 (zero-value sentinel collision fixed). - rewrite: per-rule RedirectCode (Rule.RedirectCode) — each rule can override the config-level default (e.g., /old→301, /temp→307). Code quality: - static: precompute filepath.Clean(root) in New() (saves 1 alloc/req) - static: deduplicate ETag computation (setCacheHeaders returns etag, notModified accepts it instead of recomputing) - static: io.ReadAll → make([]byte, size) + io.ReadFull for fs.FS (eliminates O(log N) growth allocations) - static: fix If-None-Match to handle comma-separated ETag lists (RFC 7232 weak comparison with W/ prefix stripping) - static: fix RFC 7232 §6 — skip If-Modified-Since when If-None-Match is present (regardless of match result) - pprof/swagger: use NewHTTPError() for 403/404/405 instead of c.NoContent() (enables global error handler integration) - swagger: HTML-escape Title and AssetsPath in templates - swagger: pin Scalar CDN to @1 major version - rewrite: validate empty Rule.Pattern (panics instead of silently matching everything) Tests (30+ new): - static: SPA mode (OS + FS + disabled), MaxAge, HEAD fs.FS, IMS 304 fs.FS, multi-ETag If-None-Match, Prefix validation panic - swagger: HEAD on /spec, DocExpansion "none", ReDoc renderer + title + assets, DefaultModelsExpandDepth=0 - adapters: WrapMiddleware nil panic, query string round-trip, host propagation, body round-trip, responseCapture body cap, ReverseProxy behavioral (httptest.Server), ReverseProxy error handler - pprof: IPv6 loopback (allow ::1 + deny remote v6), bare prefix, validate Prefix="/" panic - rewrite: per-rule RedirectCode, empty Pattern panic, invalid per-rule RedirectCode panic Benchmarks: - bench_chain_test.go: add BenchmarkChainPprofPassthrough, BenchmarkChainSwaggerPassthrough, BenchmarkChainStaticPassthrough, BenchmarkChainStaticServe (with etag) Documentation: - pprof: fix SkipPaths doc (was "before any other logic", now correctly says "before auth and handler dispatch") - pprof: validate Prefix="/" (would intercept all traffic) - pprof: rename ExampleNew_publicAccess → ExampleNew_tokenAuth, add actual ExampleNew_publicAccess - swagger: doc.go notes UIConfig options are Swagger UI-only - static: doc.go adds SPA Mode + Cache-Control sections - rewrite: Rule.Pattern doc recommends ^ and $ anchors - adapters: doc.go warns about dual CORS + streaming limitation - middleware/doc.go: Pre-routing ordering guidance * fix: goimports ordering + errcheck in lint * feat: address all remaining weakness and feature gaps static: - io.ReadSeeker fast-path: when fs.File implements ReadSeeker, range requests seek to offset and read only the requested bytes instead of loading the entire file into memory. Eliminates 50MB heap allocation for range requests on large files. - Content-type sniffing: when mime.TypeByExtension fails, read first 512 bytes and use http.DetectContentType instead of falling back to application/octet-stream. - Pre-compressed file support (Config.Compress): checks for .br and .gz variants when Accept-Encoding allows, sets Content-Encoding and Vary headers. Brotli preferred over gzip. OS filesystem only. - 7 new tests: ReadSeeker, content-type sniffing (OS+FS+ReadSeeker), pre-compressed (brotli, gzip, not accepted, missing). swagger: - Exported IntPtr helper for DefaultModelsExpandDepth ergonomics. - ReDoc customization: ReDocConfig struct with Theme (dark mode), ExpandResponses, HideDownloadButton, ScrollYOffset, NoAutoAuth. Dark theme maps to full ReDoc color scheme. - OAuth2 UI pre-configuration: OAuth2Config struct with ClientID, ClientSecret, Realm, AppName, Scopes. Generates ui.initOAuth() call in Swagger UI. OAuth2RedirectURL support. - 12 new tests: IntPtr, ReDoc theme/expandResponses/hideDownload/ scrollOffset/noAutoAuth/defaults, OAuth2 full/nil/partial/redirectURL. adapters: - WithModifyResponse option for ReverseProxy: modify response headers from backend before forwarding to client. - http.Flusher support on responseCapture (no-op): prevents panics in stdlib middleware that type-asserts Flusher. Hijacker still unsupported. - Performance section in doc.go: explicitly documents 8-15 alloc cost and recommends native middleware for hot paths. - 3 new tests: ModifyResponse, Flusher type assertion, WrapMiddleware with Flusher. rewrite: - Conditional rewriting: Rule.Methods restricts to specific HTTP methods, Rule.Host restricts to specific Host header value. Methods map built at init time (zero per-request alloc). Both empty means match all. - 4 new tests: method restriction, host restriction, combined, no restriction (backwards compatibility). * fix: gofmt static_test.go * refactor: simplify APIs, fix Flusher footgun, extract static helpers swagger API simplification: - Delete ReDocConfig struct — replaced by generic Options map[string]any that works for ALL renderers (ReDoc, Scalar, Swagger UI). More powerful with fewer types. Users pass raw renderer options as JSON-serializable maps. - Delete IntPtr helper — revert DefaultModelsExpandDepth to plain int. Zero-value (0 = show model names) is a reasonable default. Document clearly. - Add ClientSecret security warning to OAuth2Config doc: values are embedded in page source, only use for dev/test or PKCE public clients. - Net: 3 exported structs (was 4), 0 helper functions (was 1). static refactoring (no behavior changes): - Extract detectContentType() — extension check + http.DetectContentType - Extract sniffContentType() — ReadSeeker-specific with seek-back - Extract serveRangeFromReader() — range handling from io.ReadSeeker - Extract serveFSFullRead() — non-ReadSeeker full-read path - Extract servePreCompressedFS() — pre-compressed file support for fs.FS (was OS-only, now works with embed.FS and custom fs.FS) - serveFS is now a clean orchestrator calling focused helpers - Update Compress doc: works with both OS and fs.FS adapters Flusher fix: - Remove no-op Flush() from responseCapture. A silent no-op is worse than not implementing the interface: SSE middleware calls Flush() expecting data delivery, gets silent success, client receives nothing until response completes. Now the type assertion returns false, so stdlib middleware can take appropriate fallback action. - Remove Unwrap() (returning nil was misleading for ResponseController) - Update doc.go Limitations section * fix: errcheck on rs.Seek in sniffContentType * fix: final polish — fs.FS cache, DefaultModelsExpandDepth default, content-type sniffing static: - Add sync.Map per-file cache for fs.FS content. Since fs.FS (especially embed.FS) is immutable, cache file bytes on first read. Eliminates repeated heap allocations for the same file — second+ requests for any file are O(1) map lookup + Blob. Particularly important for embed.FS which doesn't implement io.ReadSeeker. - Delete serveFSReadSeeker, serveFSFullRead, serveRangeFromReader (3 funcs). Replace with serveFSCached (1 func). Net: 17 -> 15 functions. - Fix content-type sniffing in servePreCompressedFS: when extension lookup fails, open the original uncompressed file and sniff first 512 bytes via http.DetectContentType. Consistent with OS path behavior. swagger: - DefaultModelsExpandDepth: applyDefaults now treats 0 as unset and defaults to 1 (matching Swagger UI's own default). The basic 3-line setup now produces identical behavior to Swagger UI's defaults. Documented: "0 is treated as unset; use -1 to hide models." * fix: DefaultModelsExpandDepth *int + Options validation - Revert DefaultModelsExpandDepth to *int with IntPtr() helper. nil means "use Swagger UI default (1)". IntPtr(0) correctly sets depth to 0 (show model names only). No more zero-value ambiguity. - Add Options JSON-serializability validation in validate(). Panics at init time if Options contains non-serializable values (functions, channels, etc.) instead of silently falling back to {}. - Tests: TestDefaultModelsExpandDepthZero verifies depth=0 works, TestDefaultModelsExpandDepthNilDefault verifies nil renders as 1, TestOptionsValidationPanic verifies non-serializable Options panic.
docs: add v1.3.2 security notes + update CONTRIBUTING references (#220) SECURITY.md: - Add v1.3.2 section documenting singleflight cross-user data leakage fix, multi-value header replay fix, circuit breaker panic recording, and circuit breaker validation hardening CONTRIBUTING.md: - Update middleware reference implementations by complexity level (simple, stateful, response-transform, request-coalescing) - Add circuitbreaker and singleflight as reference patterns
feat: v1.3.3 utility & serving middleware — pprof, swagger, static, r… …ewrite, adapters (#223) * feat: add pprof, swagger, static, rewrite, and adapters middleware Five new utility & serving middleware for v1.3.3: - pprof (#189): Go profiling endpoints with loopback-only default. Wraps all net/http/pprof handlers via celeris.Adapt(). AuthFunc guard. 10 tests. - swagger (#190): OpenAPI spec + CDN-loaded Swagger UI/Scalar. No bundled assets. SpecContent or fs.FS support. AuthFunc optional. 16 tests. - static (#191): Static file serving with SPA mode, Cache-Control, prefix stripping, directory browse. Path traversal protection via FileFromDir (OS) and path.Clean + .. rejection (fs.FS). 20 tests. - rewrite (#192): Regex-based URL rewriting with capture groups ($1/$2). Pre-routing (Server.Pre). Compile at init, sorted first-match-wins. Optional redirect mode (301/302). 9 tests. - adapters (#193): Bidirectional stdlib ↔ celeris conversion. WrapMiddleware wraps func(http.Handler) http.Handler for celeris chains. ToStdlib delegates to celeris.ToHandler. ReverseProxy via httputil. 8 tests. Documentation: - middleware/doc.go: ordering guide updated (rewrite in Pre, pprof/swagger/static in Use) - README.md: 5 new rows, package count 29 - SECURITY.md: v1.3.3 section (pprof loopback, static traversal, swagger AuthFunc) - CONTRIBUTING.md: new reference implementation categories Closes #189, Closes #190, Closes #191, Closes #192, Closes #193 * fix: resolve all review findings from v1.3.3 audit Security: - swagger: escape SpecURL with html.EscapeString before injecting into HTML/JS templates (prevents XSS via misconfigured SpecURL) Code quality: - pprof: replace manual skipMap with celeris.SkipHelper (per CONTRIBUTING.md) - adapters: fix gofmt formatting + errcheck for resp.Body.Close - .golangci.yml: add pprof var-naming exclusion (package name matches stdlib) * fix: resolve all remaining review findings - swagger/doc.go: add CSP warning for secure middleware interaction (cdn.jsdelivr.net must be allowed in script-src/style-src) - rewrite/doc.go: anchor regex in basic example (^/old$ not /old), add Security section with ReDoS and open redirect warnings - adapters/adapters.go: add 100MB body size cap to responseCapture (prevents OOM from misbehaving stdlib middleware, matches core bridge) * fix: major rework of all 5 middleware to address review findings static: - FIX: Browse XSS — href uses url.PathEscape + ./ prefix (prevents javascript:) - FIX: Prefix matching now segment-boundary-aware (/api no longer matches /api-docs) - ADD: ETag/Last-Modified headers on OS and fs.FS files (conditional GET + 304) - ADD: Range requests (206) for fs.FS files (video seeking, download resume) - ADD: accept-ranges: bytes header - 28 tests covering all new functionality swagger: - FIX: YAML spec auto-detected and served with correct Content-Type - ADD: UIConfig with DocExpansion, DeepLinking, PersistAuthorization, Title - ADD: AssetsPath for air-gapped/self-hosted asset serving (no CDN required) - ADD: RendererScalar/RendererSwaggerUI type for UI engine selection - 26 tests adapters: - FIX: WrapMiddleware now propagates pre-inner headers (rs/cors, gorilla/csrf work) - FIX: buildRequest sets ContentLength, TLS state, Protocol version - ADD: ReverseProxy with Options (WithTransport, WithModifyRequest, WithErrorHandler) - ADD: Pooled responseCapture for zero-alloc hot path - DEL: ToStdlib (was pure alias of celeris.ToHandler) - DOC: Limitations section (no Hijacker/Flusher for WebSocket/streaming) rewrite: - FIX: Rules changed from map[string]string to []Rule slice (insertion-order, not alphabetical — matches user expectations and Echo's approach) - DOC: First-Match-Wins section updated for ordered evaluation * fix: gofmt formatting * fix: security fix + integration review fixes for v1.3.3 middleware Security: - static: fix path traversal in Browse directory listing. serveOS now validates that filepath.Join result stays within root directory (prefix check + symlink escape check), mirroring FileFromDir. Added TestPathTraversalBrowse and TestPathTraversalFile. Core integration: - rewrite: make New() variadic (config ...Config) with defaultConfig and applyDefaults() for pattern consistency with all other middleware - pprof: use cfg := defaultConfig instead of var cfg Config - swagger: use c.Redirect() instead of manual SetHeader + NoContent Tests: - pprof: add TestPprofSkipFunc (was missing Skip callback coverage) - adapters: add TestReverseProxyPanicNilTarget and TestReverseProxyOptions (ReverseProxy had zero test coverage) - rewrite/bench_test.go: fix regex (d+) -> (\d+) so BenchmarkRewriteMatch actually tests the match path Benchmarks: - bench_chain_test.go: add BenchmarkChainPreRoutingWithRewrite, BenchmarkChainRewritePassthrough, BenchmarkChainWithWrapMiddleware - swagger/bench_test.go: fix spec path /swagger/doc.json -> /swagger/spec Documentation: - middleware/doc.go: fix rewrite production stack example ([]Rule syntax), fix swagger description (remove AuthFunc claim), add adapters mention - pprof/swagger/static doc.go: add missing Skip/Ordering sections - adapters doc.go: document ReverseProxy function and Option API - SECURITY.md: fix swagger AuthFunc claim (feature doesn't exist) - README.md: fix static description (no SPA or Cache-Control) * fix: address all 33-agent review findings for v1.3.3 middleware P0 fixes: - adapters: add 100MB body size cap to responseCapture.Write (matches core bridge maxBridgeResponseBytes). Prevents OOM from misbehaving stdlib middleware. - adapters: WrapMiddleware(nil) now panics at init instead of deferred panic on first request. - static: validate() rejects Prefix without leading / (was silently serving nothing). Feature additions: - static: SPA mode (Config.SPA bool) — serves index file for non-existent paths instead of falling through. Works for both OS and fs.FS backends. - static: Cache-Control (Config.MaxAge time.Duration) — sets "public, max-age=N" header alongside ETag/Last-Modified. - swagger: ReDoc renderer (RendererReDoc) with CDN + self-hosted assets support. - swagger: DefaultModelsExpandDepth changed to *int to allow depth=0 (zero-value sentinel collision fixed). - rewrite: per-rule RedirectCode (Rule.RedirectCode) — each rule can override the config-level default (e.g., /old→301, /temp→307). Code quality: - static: precompute filepath.Clean(root) in New() (saves 1 alloc/req) - static: deduplicate ETag computation (setCacheHeaders returns etag, notModified accepts it instead of recomputing) - static: io.ReadAll → make([]byte, size) + io.ReadFull for fs.FS (eliminates O(log N) growth allocations) - static: fix If-None-Match to handle comma-separated ETag lists (RFC 7232 weak comparison with W/ prefix stripping) - static: fix RFC 7232 §6 — skip If-Modified-Since when If-None-Match is present (regardless of match result) - pprof/swagger: use NewHTTPError() for 403/404/405 instead of c.NoContent() (enables global error handler integration) - swagger: HTML-escape Title and AssetsPath in templates - swagger: pin Scalar CDN to @1 major version - rewrite: validate empty Rule.Pattern (panics instead of silently matching everything) Tests (30+ new): - static: SPA mode (OS + FS + disabled), MaxAge, HEAD fs.FS, IMS 304 fs.FS, multi-ETag If-None-Match, Prefix validation panic - swagger: HEAD on /spec, DocExpansion "none", ReDoc renderer + title + assets, DefaultModelsExpandDepth=0 - adapters: WrapMiddleware nil panic, query string round-trip, host propagation, body round-trip, responseCapture body cap, ReverseProxy behavioral (httptest.Server), ReverseProxy error handler - pprof: IPv6 loopback (allow ::1 + deny remote v6), bare prefix, validate Prefix="/" panic - rewrite: per-rule RedirectCode, empty Pattern panic, invalid per-rule RedirectCode panic Benchmarks: - bench_chain_test.go: add BenchmarkChainPprofPassthrough, BenchmarkChainSwaggerPassthrough, BenchmarkChainStaticPassthrough, BenchmarkChainStaticServe (with etag) Documentation: - pprof: fix SkipPaths doc (was "before any other logic", now correctly says "before auth and handler dispatch") - pprof: validate Prefix="/" (would intercept all traffic) - pprof: rename ExampleNew_publicAccess → ExampleNew_tokenAuth, add actual ExampleNew_publicAccess - swagger: doc.go notes UIConfig options are Swagger UI-only - static: doc.go adds SPA Mode + Cache-Control sections - rewrite: Rule.Pattern doc recommends ^ and $ anchors - adapters: doc.go warns about dual CORS + streaming limitation - middleware/doc.go: Pre-routing ordering guidance * fix: goimports ordering + errcheck in lint * feat: address all remaining weakness and feature gaps static: - io.ReadSeeker fast-path: when fs.File implements ReadSeeker, range requests seek to offset and read only the requested bytes instead of loading the entire file into memory. Eliminates 50MB heap allocation for range requests on large files. - Content-type sniffing: when mime.TypeByExtension fails, read first 512 bytes and use http.DetectContentType instead of falling back to application/octet-stream. - Pre-compressed file support (Config.Compress): checks for .br and .gz variants when Accept-Encoding allows, sets Content-Encoding and Vary headers. Brotli preferred over gzip. OS filesystem only. - 7 new tests: ReadSeeker, content-type sniffing (OS+FS+ReadSeeker), pre-compressed (brotli, gzip, not accepted, missing). swagger: - Exported IntPtr helper for DefaultModelsExpandDepth ergonomics. - ReDoc customization: ReDocConfig struct with Theme (dark mode), ExpandResponses, HideDownloadButton, ScrollYOffset, NoAutoAuth. Dark theme maps to full ReDoc color scheme. - OAuth2 UI pre-configuration: OAuth2Config struct with ClientID, ClientSecret, Realm, AppName, Scopes. Generates ui.initOAuth() call in Swagger UI. OAuth2RedirectURL support. - 12 new tests: IntPtr, ReDoc theme/expandResponses/hideDownload/ scrollOffset/noAutoAuth/defaults, OAuth2 full/nil/partial/redirectURL. adapters: - WithModifyResponse option for ReverseProxy: modify response headers from backend before forwarding to client. - http.Flusher support on responseCapture (no-op): prevents panics in stdlib middleware that type-asserts Flusher. Hijacker still unsupported. - Performance section in doc.go: explicitly documents 8-15 alloc cost and recommends native middleware for hot paths. - 3 new tests: ModifyResponse, Flusher type assertion, WrapMiddleware with Flusher. rewrite: - Conditional rewriting: Rule.Methods restricts to specific HTTP methods, Rule.Host restricts to specific Host header value. Methods map built at init time (zero per-request alloc). Both empty means match all. - 4 new tests: method restriction, host restriction, combined, no restriction (backwards compatibility). * fix: gofmt static_test.go * refactor: simplify APIs, fix Flusher footgun, extract static helpers swagger API simplification: - Delete ReDocConfig struct — replaced by generic Options map[string]any that works for ALL renderers (ReDoc, Scalar, Swagger UI). More powerful with fewer types. Users pass raw renderer options as JSON-serializable maps. - Delete IntPtr helper — revert DefaultModelsExpandDepth to plain int. Zero-value (0 = show model names) is a reasonable default. Document clearly. - Add ClientSecret security warning to OAuth2Config doc: values are embedded in page source, only use for dev/test or PKCE public clients. - Net: 3 exported structs (was 4), 0 helper functions (was 1). static refactoring (no behavior changes): - Extract detectContentType() — extension check + http.DetectContentType - Extract sniffContentType() — ReadSeeker-specific with seek-back - Extract serveRangeFromReader() — range handling from io.ReadSeeker - Extract serveFSFullRead() — non-ReadSeeker full-read path - Extract servePreCompressedFS() — pre-compressed file support for fs.FS (was OS-only, now works with embed.FS and custom fs.FS) - serveFS is now a clean orchestrator calling focused helpers - Update Compress doc: works with both OS and fs.FS adapters Flusher fix: - Remove no-op Flush() from responseCapture. A silent no-op is worse than not implementing the interface: SSE middleware calls Flush() expecting data delivery, gets silent success, client receives nothing until response completes. Now the type assertion returns false, so stdlib middleware can take appropriate fallback action. - Remove Unwrap() (returning nil was misleading for ResponseController) - Update doc.go Limitations section * fix: errcheck on rs.Seek in sniffContentType * fix: final polish — fs.FS cache, DefaultModelsExpandDepth default, content-type sniffing static: - Add sync.Map per-file cache for fs.FS content. Since fs.FS (especially embed.FS) is immutable, cache file bytes on first read. Eliminates repeated heap allocations for the same file — second+ requests for any file are O(1) map lookup + Blob. Particularly important for embed.FS which doesn't implement io.ReadSeeker. - Delete serveFSReadSeeker, serveFSFullRead, serveRangeFromReader (3 funcs). Replace with serveFSCached (1 func). Net: 17 -> 15 functions. - Fix content-type sniffing in servePreCompressedFS: when extension lookup fails, open the original uncompressed file and sniff first 512 bytes via http.DetectContentType. Consistent with OS path behavior. swagger: - DefaultModelsExpandDepth: applyDefaults now treats 0 as unset and defaults to 1 (matching Swagger UI's own default). The basic 3-line setup now produces identical behavior to Swagger UI's defaults. Documented: "0 is treated as unset; use -1 to hide models." * fix: DefaultModelsExpandDepth *int + Options validation - Revert DefaultModelsExpandDepth to *int with IntPtr() helper. nil means "use Swagger UI default (1)". IntPtr(0) correctly sets depth to 0 (show model names only). No more zero-value ambiguity. - Add Options JSON-serializability validation in validate(). Panics at init time if Options contains non-serializable values (functions, channels, etc.) instead of silently falling back to {}. - Tests: TestDefaultModelsExpandDepthZero verifies depth=0 works, TestDefaultModelsExpandDepthNilDefault verifies nil renders as 1, TestOptionsValidationPanic verifies non-serializable Options panic.
docs: add v1.3.2 security notes + update CONTRIBUTING references (#220) SECURITY.md: - Add v1.3.2 section documenting singleflight cross-user data leakage fix, multi-value header replay fix, circuit breaker panic recording, and circuit breaker validation hardening CONTRIBUTING.md: - Update middleware reference implementations by complexity level (simple, stateful, response-transform, request-coalescing) - Add circuitbreaker and singleflight as reference patterns
feat: v1.3.3 utility & serving middleware — pprof, swagger, static, r… …ewrite, adapters (#223) * feat: add pprof, swagger, static, rewrite, and adapters middleware Five new utility & serving middleware for v1.3.3: - pprof (#189): Go profiling endpoints with loopback-only default. Wraps all net/http/pprof handlers via celeris.Adapt(). AuthFunc guard. 10 tests. - swagger (#190): OpenAPI spec + CDN-loaded Swagger UI/Scalar. No bundled assets. SpecContent or fs.FS support. AuthFunc optional. 16 tests. - static (#191): Static file serving with SPA mode, Cache-Control, prefix stripping, directory browse. Path traversal protection via FileFromDir (OS) and path.Clean + .. rejection (fs.FS). 20 tests. - rewrite (#192): Regex-based URL rewriting with capture groups ($1/$2). Pre-routing (Server.Pre). Compile at init, sorted first-match-wins. Optional redirect mode (301/302). 9 tests. - adapters (#193): Bidirectional stdlib ↔ celeris conversion. WrapMiddleware wraps func(http.Handler) http.Handler for celeris chains. ToStdlib delegates to celeris.ToHandler. ReverseProxy via httputil. 8 tests. Documentation: - middleware/doc.go: ordering guide updated (rewrite in Pre, pprof/swagger/static in Use) - README.md: 5 new rows, package count 29 - SECURITY.md: v1.3.3 section (pprof loopback, static traversal, swagger AuthFunc) - CONTRIBUTING.md: new reference implementation categories Closes #189, Closes #190, Closes #191, Closes #192, Closes #193 * fix: resolve all review findings from v1.3.3 audit Security: - swagger: escape SpecURL with html.EscapeString before injecting into HTML/JS templates (prevents XSS via misconfigured SpecURL) Code quality: - pprof: replace manual skipMap with celeris.SkipHelper (per CONTRIBUTING.md) - adapters: fix gofmt formatting + errcheck for resp.Body.Close - .golangci.yml: add pprof var-naming exclusion (package name matches stdlib) * fix: resolve all remaining review findings - swagger/doc.go: add CSP warning for secure middleware interaction (cdn.jsdelivr.net must be allowed in script-src/style-src) - rewrite/doc.go: anchor regex in basic example (^/old$ not /old), add Security section with ReDoS and open redirect warnings - adapters/adapters.go: add 100MB body size cap to responseCapture (prevents OOM from misbehaving stdlib middleware, matches core bridge) * fix: major rework of all 5 middleware to address review findings static: - FIX: Browse XSS — href uses url.PathEscape + ./ prefix (prevents javascript:) - FIX: Prefix matching now segment-boundary-aware (/api no longer matches /api-docs) - ADD: ETag/Last-Modified headers on OS and fs.FS files (conditional GET + 304) - ADD: Range requests (206) for fs.FS files (video seeking, download resume) - ADD: accept-ranges: bytes header - 28 tests covering all new functionality swagger: - FIX: YAML spec auto-detected and served with correct Content-Type - ADD: UIConfig with DocExpansion, DeepLinking, PersistAuthorization, Title - ADD: AssetsPath for air-gapped/self-hosted asset serving (no CDN required) - ADD: RendererScalar/RendererSwaggerUI type for UI engine selection - 26 tests adapters: - FIX: WrapMiddleware now propagates pre-inner headers (rs/cors, gorilla/csrf work) - FIX: buildRequest sets ContentLength, TLS state, Protocol version - ADD: ReverseProxy with Options (WithTransport, WithModifyRequest, WithErrorHandler) - ADD: Pooled responseCapture for zero-alloc hot path - DEL: ToStdlib (was pure alias of celeris.ToHandler) - DOC: Limitations section (no Hijacker/Flusher for WebSocket/streaming) rewrite: - FIX: Rules changed from map[string]string to []Rule slice (insertion-order, not alphabetical — matches user expectations and Echo's approach) - DOC: First-Match-Wins section updated for ordered evaluation * fix: gofmt formatting * fix: security fix + integration review fixes for v1.3.3 middleware Security: - static: fix path traversal in Browse directory listing. serveOS now validates that filepath.Join result stays within root directory (prefix check + symlink escape check), mirroring FileFromDir. Added TestPathTraversalBrowse and TestPathTraversalFile. Core integration: - rewrite: make New() variadic (config ...Config) with defaultConfig and applyDefaults() for pattern consistency with all other middleware - pprof: use cfg := defaultConfig instead of var cfg Config - swagger: use c.Redirect() instead of manual SetHeader + NoContent Tests: - pprof: add TestPprofSkipFunc (was missing Skip callback coverage) - adapters: add TestReverseProxyPanicNilTarget and TestReverseProxyOptions (ReverseProxy had zero test coverage) - rewrite/bench_test.go: fix regex (d+) -> (\d+) so BenchmarkRewriteMatch actually tests the match path Benchmarks: - bench_chain_test.go: add BenchmarkChainPreRoutingWithRewrite, BenchmarkChainRewritePassthrough, BenchmarkChainWithWrapMiddleware - swagger/bench_test.go: fix spec path /swagger/doc.json -> /swagger/spec Documentation: - middleware/doc.go: fix rewrite production stack example ([]Rule syntax), fix swagger description (remove AuthFunc claim), add adapters mention - pprof/swagger/static doc.go: add missing Skip/Ordering sections - adapters doc.go: document ReverseProxy function and Option API - SECURITY.md: fix swagger AuthFunc claim (feature doesn't exist) - README.md: fix static description (no SPA or Cache-Control) * fix: address all 33-agent review findings for v1.3.3 middleware P0 fixes: - adapters: add 100MB body size cap to responseCapture.Write (matches core bridge maxBridgeResponseBytes). Prevents OOM from misbehaving stdlib middleware. - adapters: WrapMiddleware(nil) now panics at init instead of deferred panic on first request. - static: validate() rejects Prefix without leading / (was silently serving nothing). Feature additions: - static: SPA mode (Config.SPA bool) — serves index file for non-existent paths instead of falling through. Works for both OS and fs.FS backends. - static: Cache-Control (Config.MaxAge time.Duration) — sets "public, max-age=N" header alongside ETag/Last-Modified. - swagger: ReDoc renderer (RendererReDoc) with CDN + self-hosted assets support. - swagger: DefaultModelsExpandDepth changed to *int to allow depth=0 (zero-value sentinel collision fixed). - rewrite: per-rule RedirectCode (Rule.RedirectCode) — each rule can override the config-level default (e.g., /old→301, /temp→307). Code quality: - static: precompute filepath.Clean(root) in New() (saves 1 alloc/req) - static: deduplicate ETag computation (setCacheHeaders returns etag, notModified accepts it instead of recomputing) - static: io.ReadAll → make([]byte, size) + io.ReadFull for fs.FS (eliminates O(log N) growth allocations) - static: fix If-None-Match to handle comma-separated ETag lists (RFC 7232 weak comparison with W/ prefix stripping) - static: fix RFC 7232 §6 — skip If-Modified-Since when If-None-Match is present (regardless of match result) - pprof/swagger: use NewHTTPError() for 403/404/405 instead of c.NoContent() (enables global error handler integration) - swagger: HTML-escape Title and AssetsPath in templates - swagger: pin Scalar CDN to @1 major version - rewrite: validate empty Rule.Pattern (panics instead of silently matching everything) Tests (30+ new): - static: SPA mode (OS + FS + disabled), MaxAge, HEAD fs.FS, IMS 304 fs.FS, multi-ETag If-None-Match, Prefix validation panic - swagger: HEAD on /spec, DocExpansion "none", ReDoc renderer + title + assets, DefaultModelsExpandDepth=0 - adapters: WrapMiddleware nil panic, query string round-trip, host propagation, body round-trip, responseCapture body cap, ReverseProxy behavioral (httptest.Server), ReverseProxy error handler - pprof: IPv6 loopback (allow ::1 + deny remote v6), bare prefix, validate Prefix="/" panic - rewrite: per-rule RedirectCode, empty Pattern panic, invalid per-rule RedirectCode panic Benchmarks: - bench_chain_test.go: add BenchmarkChainPprofPassthrough, BenchmarkChainSwaggerPassthrough, BenchmarkChainStaticPassthrough, BenchmarkChainStaticServe (with etag) Documentation: - pprof: fix SkipPaths doc (was "before any other logic", now correctly says "before auth and handler dispatch") - pprof: validate Prefix="/" (would intercept all traffic) - pprof: rename ExampleNew_publicAccess → ExampleNew_tokenAuth, add actual ExampleNew_publicAccess - swagger: doc.go notes UIConfig options are Swagger UI-only - static: doc.go adds SPA Mode + Cache-Control sections - rewrite: Rule.Pattern doc recommends ^ and $ anchors - adapters: doc.go warns about dual CORS + streaming limitation - middleware/doc.go: Pre-routing ordering guidance * fix: goimports ordering + errcheck in lint * feat: address all remaining weakness and feature gaps static: - io.ReadSeeker fast-path: when fs.File implements ReadSeeker, range requests seek to offset and read only the requested bytes instead of loading the entire file into memory. Eliminates 50MB heap allocation for range requests on large files. - Content-type sniffing: when mime.TypeByExtension fails, read first 512 bytes and use http.DetectContentType instead of falling back to application/octet-stream. - Pre-compressed file support (Config.Compress): checks for .br and .gz variants when Accept-Encoding allows, sets Content-Encoding and Vary headers. Brotli preferred over gzip. OS filesystem only. - 7 new tests: ReadSeeker, content-type sniffing (OS+FS+ReadSeeker), pre-compressed (brotli, gzip, not accepted, missing). swagger: - Exported IntPtr helper for DefaultModelsExpandDepth ergonomics. - ReDoc customization: ReDocConfig struct with Theme (dark mode), ExpandResponses, HideDownloadButton, ScrollYOffset, NoAutoAuth. Dark theme maps to full ReDoc color scheme. - OAuth2 UI pre-configuration: OAuth2Config struct with ClientID, ClientSecret, Realm, AppName, Scopes. Generates ui.initOAuth() call in Swagger UI. OAuth2RedirectURL support. - 12 new tests: IntPtr, ReDoc theme/expandResponses/hideDownload/ scrollOffset/noAutoAuth/defaults, OAuth2 full/nil/partial/redirectURL. adapters: - WithModifyResponse option for ReverseProxy: modify response headers from backend before forwarding to client. - http.Flusher support on responseCapture (no-op): prevents panics in stdlib middleware that type-asserts Flusher. Hijacker still unsupported. - Performance section in doc.go: explicitly documents 8-15 alloc cost and recommends native middleware for hot paths. - 3 new tests: ModifyResponse, Flusher type assertion, WrapMiddleware with Flusher. rewrite: - Conditional rewriting: Rule.Methods restricts to specific HTTP methods, Rule.Host restricts to specific Host header value. Methods map built at init time (zero per-request alloc). Both empty means match all. - 4 new tests: method restriction, host restriction, combined, no restriction (backwards compatibility). * fix: gofmt static_test.go * refactor: simplify APIs, fix Flusher footgun, extract static helpers swagger API simplification: - Delete ReDocConfig struct — replaced by generic Options map[string]any that works for ALL renderers (ReDoc, Scalar, Swagger UI). More powerful with fewer types. Users pass raw renderer options as JSON-serializable maps. - Delete IntPtr helper — revert DefaultModelsExpandDepth to plain int. Zero-value (0 = show model names) is a reasonable default. Document clearly. - Add ClientSecret security warning to OAuth2Config doc: values are embedded in page source, only use for dev/test or PKCE public clients. - Net: 3 exported structs (was 4), 0 helper functions (was 1). static refactoring (no behavior changes): - Extract detectContentType() — extension check + http.DetectContentType - Extract sniffContentType() — ReadSeeker-specific with seek-back - Extract serveRangeFromReader() — range handling from io.ReadSeeker - Extract serveFSFullRead() — non-ReadSeeker full-read path - Extract servePreCompressedFS() — pre-compressed file support for fs.FS (was OS-only, now works with embed.FS and custom fs.FS) - serveFS is now a clean orchestrator calling focused helpers - Update Compress doc: works with both OS and fs.FS adapters Flusher fix: - Remove no-op Flush() from responseCapture. A silent no-op is worse than not implementing the interface: SSE middleware calls Flush() expecting data delivery, gets silent success, client receives nothing until response completes. Now the type assertion returns false, so stdlib middleware can take appropriate fallback action. - Remove Unwrap() (returning nil was misleading for ResponseController) - Update doc.go Limitations section * fix: errcheck on rs.Seek in sniffContentType * fix: final polish — fs.FS cache, DefaultModelsExpandDepth default, content-type sniffing static: - Add sync.Map per-file cache for fs.FS content. Since fs.FS (especially embed.FS) is immutable, cache file bytes on first read. Eliminates repeated heap allocations for the same file — second+ requests for any file are O(1) map lookup + Blob. Particularly important for embed.FS which doesn't implement io.ReadSeeker. - Delete serveFSReadSeeker, serveFSFullRead, serveRangeFromReader (3 funcs). Replace with serveFSCached (1 func). Net: 17 -> 15 functions. - Fix content-type sniffing in servePreCompressedFS: when extension lookup fails, open the original uncompressed file and sniff first 512 bytes via http.DetectContentType. Consistent with OS path behavior. swagger: - DefaultModelsExpandDepth: applyDefaults now treats 0 as unset and defaults to 1 (matching Swagger UI's own default). The basic 3-line setup now produces identical behavior to Swagger UI's defaults. Documented: "0 is treated as unset; use -1 to hide models." * fix: DefaultModelsExpandDepth *int + Options validation - Revert DefaultModelsExpandDepth to *int with IntPtr() helper. nil means "use Swagger UI default (1)". IntPtr(0) correctly sets depth to 0 (show model names only). No more zero-value ambiguity. - Add Options JSON-serializability validation in validate(). Panics at init time if Options contains non-serializable values (functions, channels, etc.) instead of silently falling back to {}. - Tests: TestDefaultModelsExpandDepthZero verifies depth=0 works, TestDefaultModelsExpandDepthNilDefault verifies nil renders as 1, TestOptionsValidationPanic verifies non-serializable Options panic.
docs: add v1.3.2 security notes + update CONTRIBUTING references (#220) SECURITY.md: - Add v1.3.2 section documenting singleflight cross-user data leakage fix, multi-value header replay fix, circuit breaker panic recording, and circuit breaker validation hardening CONTRIBUTING.md: - Update middleware reference implementations by complexity level (simple, stateful, response-transform, request-coalescing) - Add circuitbreaker and singleflight as reference patterns
feat: v1.3.3 utility & serving middleware — pprof, swagger, static, r… …ewrite, adapters (#223) * feat: add pprof, swagger, static, rewrite, and adapters middleware Five new utility & serving middleware for v1.3.3: - pprof (#189): Go profiling endpoints with loopback-only default. Wraps all net/http/pprof handlers via celeris.Adapt(). AuthFunc guard. 10 tests. - swagger (#190): OpenAPI spec + CDN-loaded Swagger UI/Scalar. No bundled assets. SpecContent or fs.FS support. AuthFunc optional. 16 tests. - static (#191): Static file serving with SPA mode, Cache-Control, prefix stripping, directory browse. Path traversal protection via FileFromDir (OS) and path.Clean + .. rejection (fs.FS). 20 tests. - rewrite (#192): Regex-based URL rewriting with capture groups ($1/$2). Pre-routing (Server.Pre). Compile at init, sorted first-match-wins. Optional redirect mode (301/302). 9 tests. - adapters (#193): Bidirectional stdlib ↔ celeris conversion. WrapMiddleware wraps func(http.Handler) http.Handler for celeris chains. ToStdlib delegates to celeris.ToHandler. ReverseProxy via httputil. 8 tests. Documentation: - middleware/doc.go: ordering guide updated (rewrite in Pre, pprof/swagger/static in Use) - README.md: 5 new rows, package count 29 - SECURITY.md: v1.3.3 section (pprof loopback, static traversal, swagger AuthFunc) - CONTRIBUTING.md: new reference implementation categories Closes #189, Closes #190, Closes #191, Closes #192, Closes #193 * fix: resolve all review findings from v1.3.3 audit Security: - swagger: escape SpecURL with html.EscapeString before injecting into HTML/JS templates (prevents XSS via misconfigured SpecURL) Code quality: - pprof: replace manual skipMap with celeris.SkipHelper (per CONTRIBUTING.md) - adapters: fix gofmt formatting + errcheck for resp.Body.Close - .golangci.yml: add pprof var-naming exclusion (package name matches stdlib) * fix: resolve all remaining review findings - swagger/doc.go: add CSP warning for secure middleware interaction (cdn.jsdelivr.net must be allowed in script-src/style-src) - rewrite/doc.go: anchor regex in basic example (^/old$ not /old), add Security section with ReDoS and open redirect warnings - adapters/adapters.go: add 100MB body size cap to responseCapture (prevents OOM from misbehaving stdlib middleware, matches core bridge) * fix: major rework of all 5 middleware to address review findings static: - FIX: Browse XSS — href uses url.PathEscape + ./ prefix (prevents javascript:) - FIX: Prefix matching now segment-boundary-aware (/api no longer matches /api-docs) - ADD: ETag/Last-Modified headers on OS and fs.FS files (conditional GET + 304) - ADD: Range requests (206) for fs.FS files (video seeking, download resume) - ADD: accept-ranges: bytes header - 28 tests covering all new functionality swagger: - FIX: YAML spec auto-detected and served with correct Content-Type - ADD: UIConfig with DocExpansion, DeepLinking, PersistAuthorization, Title - ADD: AssetsPath for air-gapped/self-hosted asset serving (no CDN required) - ADD: RendererScalar/RendererSwaggerUI type for UI engine selection - 26 tests adapters: - FIX: WrapMiddleware now propagates pre-inner headers (rs/cors, gorilla/csrf work) - FIX: buildRequest sets ContentLength, TLS state, Protocol version - ADD: ReverseProxy with Options (WithTransport, WithModifyRequest, WithErrorHandler) - ADD: Pooled responseCapture for zero-alloc hot path - DEL: ToStdlib (was pure alias of celeris.ToHandler) - DOC: Limitations section (no Hijacker/Flusher for WebSocket/streaming) rewrite: - FIX: Rules changed from map[string]string to []Rule slice (insertion-order, not alphabetical — matches user expectations and Echo's approach) - DOC: First-Match-Wins section updated for ordered evaluation * fix: gofmt formatting * fix: security fix + integration review fixes for v1.3.3 middleware Security: - static: fix path traversal in Browse directory listing. serveOS now validates that filepath.Join result stays within root directory (prefix check + symlink escape check), mirroring FileFromDir. Added TestPathTraversalBrowse and TestPathTraversalFile. Core integration: - rewrite: make New() variadic (config ...Config) with defaultConfig and applyDefaults() for pattern consistency with all other middleware - pprof: use cfg := defaultConfig instead of var cfg Config - swagger: use c.Redirect() instead of manual SetHeader + NoContent Tests: - pprof: add TestPprofSkipFunc (was missing Skip callback coverage) - adapters: add TestReverseProxyPanicNilTarget and TestReverseProxyOptions (ReverseProxy had zero test coverage) - rewrite/bench_test.go: fix regex (d+) -> (\d+) so BenchmarkRewriteMatch actually tests the match path Benchmarks: - bench_chain_test.go: add BenchmarkChainPreRoutingWithRewrite, BenchmarkChainRewritePassthrough, BenchmarkChainWithWrapMiddleware - swagger/bench_test.go: fix spec path /swagger/doc.json -> /swagger/spec Documentation: - middleware/doc.go: fix rewrite production stack example ([]Rule syntax), fix swagger description (remove AuthFunc claim), add adapters mention - pprof/swagger/static doc.go: add missing Skip/Ordering sections - adapters doc.go: document ReverseProxy function and Option API - SECURITY.md: fix swagger AuthFunc claim (feature doesn't exist) - README.md: fix static description (no SPA or Cache-Control) * fix: address all 33-agent review findings for v1.3.3 middleware P0 fixes: - adapters: add 100MB body size cap to responseCapture.Write (matches core bridge maxBridgeResponseBytes). Prevents OOM from misbehaving stdlib middleware. - adapters: WrapMiddleware(nil) now panics at init instead of deferred panic on first request. - static: validate() rejects Prefix without leading / (was silently serving nothing). Feature additions: - static: SPA mode (Config.SPA bool) — serves index file for non-existent paths instead of falling through. Works for both OS and fs.FS backends. - static: Cache-Control (Config.MaxAge time.Duration) — sets "public, max-age=N" header alongside ETag/Last-Modified. - swagger: ReDoc renderer (RendererReDoc) with CDN + self-hosted assets support. - swagger: DefaultModelsExpandDepth changed to *int to allow depth=0 (zero-value sentinel collision fixed). - rewrite: per-rule RedirectCode (Rule.RedirectCode) — each rule can override the config-level default (e.g., /old→301, /temp→307). Code quality: - static: precompute filepath.Clean(root) in New() (saves 1 alloc/req) - static: deduplicate ETag computation (setCacheHeaders returns etag, notModified accepts it instead of recomputing) - static: io.ReadAll → make([]byte, size) + io.ReadFull for fs.FS (eliminates O(log N) growth allocations) - static: fix If-None-Match to handle comma-separated ETag lists (RFC 7232 weak comparison with W/ prefix stripping) - static: fix RFC 7232 §6 — skip If-Modified-Since when If-None-Match is present (regardless of match result) - pprof/swagger: use NewHTTPError() for 403/404/405 instead of c.NoContent() (enables global error handler integration) - swagger: HTML-escape Title and AssetsPath in templates - swagger: pin Scalar CDN to @1 major version - rewrite: validate empty Rule.Pattern (panics instead of silently matching everything) Tests (30+ new): - static: SPA mode (OS + FS + disabled), MaxAge, HEAD fs.FS, IMS 304 fs.FS, multi-ETag If-None-Match, Prefix validation panic - swagger: HEAD on /spec, DocExpansion "none", ReDoc renderer + title + assets, DefaultModelsExpandDepth=0 - adapters: WrapMiddleware nil panic, query string round-trip, host propagation, body round-trip, responseCapture body cap, ReverseProxy behavioral (httptest.Server), ReverseProxy error handler - pprof: IPv6 loopback (allow ::1 + deny remote v6), bare prefix, validate Prefix="/" panic - rewrite: per-rule RedirectCode, empty Pattern panic, invalid per-rule RedirectCode panic Benchmarks: - bench_chain_test.go: add BenchmarkChainPprofPassthrough, BenchmarkChainSwaggerPassthrough, BenchmarkChainStaticPassthrough, BenchmarkChainStaticServe (with etag) Documentation: - pprof: fix SkipPaths doc (was "before any other logic", now correctly says "before auth and handler dispatch") - pprof: validate Prefix="/" (would intercept all traffic) - pprof: rename ExampleNew_publicAccess → ExampleNew_tokenAuth, add actual ExampleNew_publicAccess - swagger: doc.go notes UIConfig options are Swagger UI-only - static: doc.go adds SPA Mode + Cache-Control sections - rewrite: Rule.Pattern doc recommends ^ and $ anchors - adapters: doc.go warns about dual CORS + streaming limitation - middleware/doc.go: Pre-routing ordering guidance * fix: goimports ordering + errcheck in lint * feat: address all remaining weakness and feature gaps static: - io.ReadSeeker fast-path: when fs.File implements ReadSeeker, range requests seek to offset and read only the requested bytes instead of loading the entire file into memory. Eliminates 50MB heap allocation for range requests on large files. - Content-type sniffing: when mime.TypeByExtension fails, read first 512 bytes and use http.DetectContentType instead of falling back to application/octet-stream. - Pre-compressed file support (Config.Compress): checks for .br and .gz variants when Accept-Encoding allows, sets Content-Encoding and Vary headers. Brotli preferred over gzip. OS filesystem only. - 7 new tests: ReadSeeker, content-type sniffing (OS+FS+ReadSeeker), pre-compressed (brotli, gzip, not accepted, missing). swagger: - Exported IntPtr helper for DefaultModelsExpandDepth ergonomics. - ReDoc customization: ReDocConfig struct with Theme (dark mode), ExpandResponses, HideDownloadButton, ScrollYOffset, NoAutoAuth. Dark theme maps to full ReDoc color scheme. - OAuth2 UI pre-configuration: OAuth2Config struct with ClientID, ClientSecret, Realm, AppName, Scopes. Generates ui.initOAuth() call in Swagger UI. OAuth2RedirectURL support. - 12 new tests: IntPtr, ReDoc theme/expandResponses/hideDownload/ scrollOffset/noAutoAuth/defaults, OAuth2 full/nil/partial/redirectURL. adapters: - WithModifyResponse option for ReverseProxy: modify response headers from backend before forwarding to client. - http.Flusher support on responseCapture (no-op): prevents panics in stdlib middleware that type-asserts Flusher. Hijacker still unsupported. - Performance section in doc.go: explicitly documents 8-15 alloc cost and recommends native middleware for hot paths. - 3 new tests: ModifyResponse, Flusher type assertion, WrapMiddleware with Flusher. rewrite: - Conditional rewriting: Rule.Methods restricts to specific HTTP methods, Rule.Host restricts to specific Host header value. Methods map built at init time (zero per-request alloc). Both empty means match all. - 4 new tests: method restriction, host restriction, combined, no restriction (backwards compatibility). * fix: gofmt static_test.go * refactor: simplify APIs, fix Flusher footgun, extract static helpers swagger API simplification: - Delete ReDocConfig struct — replaced by generic Options map[string]any that works for ALL renderers (ReDoc, Scalar, Swagger UI). More powerful with fewer types. Users pass raw renderer options as JSON-serializable maps. - Delete IntPtr helper — revert DefaultModelsExpandDepth to plain int. Zero-value (0 = show model names) is a reasonable default. Document clearly. - Add ClientSecret security warning to OAuth2Config doc: values are embedded in page source, only use for dev/test or PKCE public clients. - Net: 3 exported structs (was 4), 0 helper functions (was 1). static refactoring (no behavior changes): - Extract detectContentType() — extension check + http.DetectContentType - Extract sniffContentType() — ReadSeeker-specific with seek-back - Extract serveRangeFromReader() — range handling from io.ReadSeeker - Extract serveFSFullRead() — non-ReadSeeker full-read path - Extract servePreCompressedFS() — pre-compressed file support for fs.FS (was OS-only, now works with embed.FS and custom fs.FS) - serveFS is now a clean orchestrator calling focused helpers - Update Compress doc: works with both OS and fs.FS adapters Flusher fix: - Remove no-op Flush() from responseCapture. A silent no-op is worse than not implementing the interface: SSE middleware calls Flush() expecting data delivery, gets silent success, client receives nothing until response completes. Now the type assertion returns false, so stdlib middleware can take appropriate fallback action. - Remove Unwrap() (returning nil was misleading for ResponseController) - Update doc.go Limitations section * fix: errcheck on rs.Seek in sniffContentType * fix: final polish — fs.FS cache, DefaultModelsExpandDepth default, content-type sniffing static: - Add sync.Map per-file cache for fs.FS content. Since fs.FS (especially embed.FS) is immutable, cache file bytes on first read. Eliminates repeated heap allocations for the same file — second+ requests for any file are O(1) map lookup + Blob. Particularly important for embed.FS which doesn't implement io.ReadSeeker. - Delete serveFSReadSeeker, serveFSFullRead, serveRangeFromReader (3 funcs). Replace with serveFSCached (1 func). Net: 17 -> 15 functions. - Fix content-type sniffing in servePreCompressedFS: when extension lookup fails, open the original uncompressed file and sniff first 512 bytes via http.DetectContentType. Consistent with OS path behavior. swagger: - DefaultModelsExpandDepth: applyDefaults now treats 0 as unset and defaults to 1 (matching Swagger UI's own default). The basic 3-line setup now produces identical behavior to Swagger UI's defaults. Documented: "0 is treated as unset; use -1 to hide models." * fix: DefaultModelsExpandDepth *int + Options validation - Revert DefaultModelsExpandDepth to *int with IntPtr() helper. nil means "use Swagger UI default (1)". IntPtr(0) correctly sets depth to 0 (show model names only). No more zero-value ambiguity. - Add Options JSON-serializability validation in validate(). Panics at init time if Options contains non-serializable values (functions, channels, etc.) instead of silently falling back to {}. - Tests: TestDefaultModelsExpandDepthZero verifies depth=0 works, TestDefaultModelsExpandDepthNilDefault verifies nil renders as 1, TestOptionsValidationPanic verifies non-serializable Options panic.
docs: add v1.3.2 security notes + update CONTRIBUTING references (#220) SECURITY.md: - Add v1.3.2 section documenting singleflight cross-user data leakage fix, multi-value header replay fix, circuit breaker panic recording, and circuit breaker validation hardening CONTRIBUTING.md: - Update middleware reference implementations by complexity level (simple, stateful, response-transform, request-coalescing) - Add circuitbreaker and singleflight as reference patterns
docs: add v1.3.1 security notes to SECURITY.md (#211) - Document Scheme() trust model hardening (XFP no longer trusted from untrusted clients) - Document proxy host/IP validation, method override target restriction, negotiate q=0 exclusion, redirect code validation, BREACH warning - Add middleware/compress to sub-module scope list
arch: v1.3.0 — middleware in-tree, review-driven hardening, API addit… …ions (#174) * arch: respHdrBuf[16] + eliminate ctxkit indirection - Increase respHdrBuf from [8] to [16] entries, eliminating the heap allocation when secure middleware sets 10+ response headers. Adds 512 bytes to pooled Context struct. Closes #167 - Replace ctxkit function-pointer indirection with direct exported test helpers: AcquireTestContext, ReleaseTestContext, SetTestStartTime, SetTestFullPath, SetTestTrustedNets, TestStream, AddTestParam, SetTestHandlers. Removes 10 type-unsafe `any` hooks. ctxkit reduced to single ReleaseContext hook needed by internal/conn. Closes #173 * arch: move middleware in-tree under middleware/ Move all 17 middleware packages from github.com/goceleris/middlewares into github.com/goceleris/celeris/middleware/. This eliminates: - Version coordination between separate repos - Symbol duplication (RequestIDKey, etc.) - Import restrictions across module boundaries - ctxkit hacks for cross-module type sharing Zero-dep packages: basicauth, bodylimit, cors, csrf, debug, healthcheck, jwt, keyauth, logger, ratelimit, recovery, requestid, secure, session, timeout + internal/{extract,fnv1a,randutil,testutil} Heavy-dep submodules (separate go.mod): - middleware/metrics (Prometheus client) - middleware/otel (OpenTelemetry SDK) All 19 test suites pass with -race. Import paths rewritten from github.com/goceleris/middlewares/X to github.com/goceleris/celeris/middleware/X. Closes #164 * perf: profiling-driven optimizations — 6 issues closed - SetCookie: stack [256]byte buffer replaces strings.Builder, inline appendCookieSafe strips unsafe chars during append. Closes #168 - Session MemoryStore: Get returns stored map directly (caller-owned), Save still copies. Deferred make(map) to !loaded branch. Closes #169 - JWT: pool Token and MapClaims via sync.Pool + OnRelease return. ReleaseToken/AcquireMapClaims exported from jwtparse. Closes #170 - HexToken: stack [64]byte buffer for n<=32, single alloc (string conversion only). Closes #171 - requestid: EnableStdContext opt-in for context.WithValue. Default false saves 1 alloc/req for 90% of users. Closes #172 - SkipHelper: exported type in celeris package, used by cors, ratelimit, recovery. Reduces 5-line skip boilerplate to 1-line call. Closes #166 - ResponseWriter split (#165): deferred — 10+ implementors, needs dedicated follow-up PR * fix: review findings — JWT pool leak, session race, cookie null bytes, stale docs - JWT: release pooled Token+MapClaims on validation failure path (prevents pool leak on every rejected JWT request) - Session MemoryStore.Get: restore defensive copy — caller-owned semantics caused data race on concurrent same-session requests - appendCookieSafe: strip null bytes (c == 0) matching stripCRLF - Root doc.go, README.md, observe/doc.go, SECURITY.md: update all stale goceleris/middlewares references to in-tree middleware/ paths - middleware/doc.go: fix jwt.SigningKey compile error in auth example, add healthcheck/metrics/otel/debug to ordering - CSRF CookieHTTPOnly: fix godoc to match security invariant - SkipHelper, ReleaseTestContext: improved godoc * arch: split ResponseWriter into ResponseWriter + H2Controller (#165) ResponseWriter now contains only WriteResponse — the minimal interface all engines, test recorders, and stdlib bridges implement. H2Controller contains the 5 HTTP/2 connection-level operations (SendGoAway, MarkStreamClosed, IsStreamClosed, WriteRSTStreamPriority, CloseConn) that only H2-capable engines implement. Processor uses a private h2Conn composite interface internally. Removed H2 no-op stubs from h1ResponseAdapter, celeristest recorderWriter, stdResponseWriter, toStdlibResponseWriter, and the context_test mock. Removed unused http2 imports from 4 files. * perf: optimize Logger, Recovery, and middleware chains Three targeted optimizations driven by profiling: 1. FastHandler.HandleDirect: new method bypasses slog.Record entirely when the logger middleware detects FastHandler. Avoids closure escape in Record.Attrs and eliminates 2 allocations per request. Logger: 549 → 427 ns/op, 6 → 4 allocs, 481 → 80 B/op. 2. FastHandler: remove copy-before-write in Handle and groupHandler. io.Writer.Write must not retain data per contract, so the defensive append([]byte(nil), buf...) copy was unnecessary. 3. Context.reset: only clear used entries in respHdrBuf and paramBuf. Skipping memclrHasPointers on 16 unused [2]string slots saves ~30 ns. Recovery: 136 → 106 ns/op. Auth chain: 849 → 644 ns/op. * test: add in-tree middleware benchcmp suite with mage targets Comprehensive middleware benchmark comparison across 5 frameworks (Celeris, Fiber v3, Echo v4, Chi v5, net/http stdlib). Separate Go module at test/benchcmp/ with framework dependencies isolated from the main module. Benchmarks: - 14 individual middleware benchmarks per framework - 4 chain benchmarks (API, Auth, Security, FullStack) × 5 frameworks - Mage targets: MiddlewareBenchmark (runs + formats report), MiddlewareProfile (CPU profiles + flamegraphs per middleware) * fix: resolve CI lint failures in middleware test files Add golangci-lint exclusions for revive unused-parameter in test files and var-naming in middleware/debug/ (stdlib name collision is intentional). Rename unused lambda params in ratelimit_test.go config literals. * fix: remove stale blank lines from import blocks after http2 removal * fix: gofmt alignment and trailing whitespace * security: harden CORS MirrorRequestHeaders and credential+wildcard validation MirrorRequestHeaders now validates each echoed header name against RFC 7230 Section 3.2.6 token charset, caps at 20 headers and 128-byte names. AllowCredentials + wildcard subdomain origins (e.g. https://*.example.com) now panics at init. Added UnsafeAllowCredentialsWithWildcard escape hatch. * fix: JWT error differentiation + JWKS eager preload Added ErrJWTExpired and ErrJWTMalformed sentinels — errors.Is can now distinguish expired tokens from malformed/unverifiable ones. Generic ErrTokenInvalid remains as fallback for other validation failures. JWKS fetchers now preload keys eagerly in New() by default, eliminating the first-request blocking fetch. Config.JWKSPreload (*bool, default true) controls this; preload failures log a warning and fall back to lazy loading. * feat: Context API additions — SetMethod, StatusJSON, SetResponseHeaders, FileFromFS SetMethod(m) — overrides HTTP method for pre-routing middleware. BasicAuth() — now case-insensitive per RFC 7617 (accepts basic/BASIC/Basic). StatusJSON/StatusXML/StatusString/StatusBlob — use status from Status(). SetResponseHeaders — bulk header replacement for cache/replay middleware. FileFromFS — serves files from fs.FS (embed.FS support). Fixed respHdrBuf tmp buffer: [6] → [14] to match [16] respHdrBuf capacity. * feat: Server.Pre(), Any() → []*Route, eager Collector Server.Pre() — pre-routing middleware hook for method override and URL rewrite. Runs before router.find(), can modify method/path via SetMethod/SetPath. Any() now returns []*Route (was *Server/*RouteGroup), enabling .Name() on individual method routes. Collector created eagerly in New() instead of lazily in doPrepare(), so middleware can access it at registration time. * fix: middleware review findings — 8 packages improved keyauth: add Vary header on 401 for header-based key lookups. recovery: standardize error prefixes, add sentinel errors (ErrPanic, ErrPanicContextCancelled, ErrPanicResponseCommitted, ErrBrokenPipe) for programmatic error matching. healthcheck/requestid/logger/secure/timeout: adopt SkipHelper for consistency with cors/recovery/ratelimit pattern. secure: doc warning about COEP + CORS interaction. timeout: doc warning about preemptive mode + StreamWriter incompatibility. ratelimit: add ValidateConfig() for pre-New() config validation. * fix: lowercase ratelimit ValidateConfig error strings for staticcheck * test: add CORS security edge-case tests + fix doc accuracy 7 new tests: MirrorRequestHeaders validation (>128 byte, >20 headers, forbidden chars, empty segments), credential+wildcard subdomain panic, UnsafeAllowCredentialsWithWildcard escape hatch suppression and request. Updated doc.go: MirrorRequestHeaders section now accurately states headers are validated against RFC 7230 token charset with size/count limits (was "without validation"). Added UnsafeAllowCredentialsWithWildcard documentation section. * chore: remove 29 redundant tests, fix 4 doc gaps Removed coverage-padding constant checks, true duplicate tests, and tests subsumed by broader table-driven suites across 12 middleware packages. Renamed misleading TestLimitStringEmptyPanics (tests no-panic). Doc fixes: recovery sentinel errors documented, csrf CookieHTTPOnly enforcement noted, debug Skip/config clarified, healthcheck validate() reference rephrased. 952 tests → 923 tests. Net -198 lines. * ci: release workflow publishes middleware sub-modules Added tag-submodules job that creates middleware/metrics/vX.Y.Z and middleware/otel/vX.Y.Z tags on release. Notify-proxy job now pings the Go proxy for all three modules (root + metrics + otel). Warns if sub-module go.mod pins a stale celeris version. * ci: test, lint, build, and vulncheck middleware sub-modules middleware/metrics and middleware/otel have separate go.mod files and were invisible to the root `go list ./...`. Added dedicated steps for: - Test: race-enabled tests for both sub-modules - Lint: golangci-lint with working-directory for each sub-module - Build: go build for each sub-module - Vulncheck: govulncheck for each sub-module * fix: goimports formatting + var-naming exclusion for metrics sub-module * fix: goimports formatting for otel sub-module * docs: update README, CONTRIBUTING, and SECURITY for v1.3.0 README: added middleware table with all 17 packages, Server.Pre() docs, StatusJSON example, FileFromFS feature, benchcmp reference, updated project structure to show middleware/. CONTRIBUTING: added sub-module testing instructions, middleware dev guidelines (SkipHelper pattern), middlewareBenchmark/middlewareProfile mage targets, redundant test policy. SECURITY: minimum supported version raised to v1.3.0. Documented all security improvements from the 24-agent review (CORS hardening, JWT error differentiation, BasicAuth RFC fix, KeyAuth Vary, Recovery sentinels). Added Out of Scope section for deprecated middlewares repo.
PreviousNext