PAGI
view release on metacpan or search on metacpan
on Unix socket connections
- Validate --socket-mode is a 4-digit octal value (reject ambiguous input)
- Harden --listen parser: paths starting with / or . always treated as Unix
sockets (prevents mis-parsing paths containing colons)
- Runner load_server omits host/port when socket or listen is provided
[Test Fixes]
- Use condition-based waiting in HTTP/2 SSE keepalive test (eliminates flakiness)
- Gate timing-sensitive multiworker/heartbeat tests behind RELEASE_TESTING
0.001020 - 2026-03-16
[Security Hardening]
- Reject requests with both Transfer-Encoding and Content-Length (RFC 9112)
- Validate Transfer-Encoding per RFC 9112 Section 6.1 (chunked must be final)
- Validate trailer headers for CRLF injection
- Validate SSE event/id/retry fields against newline injection
- Prevent code injection via loop_type parameter (block eval, not string eval)
- Fix TLS config to allow TLS 1.3 negotiation (SSL_version trailing colon)
[Session Middleware Refactor]
- Pluggable State/Store architecture for PAGI::Middleware::Session
- State classes: Cookie (default), Header, Bearer, Callback
- Store classes: Memory (default, dev/test), with async Future-based interface
- PAGI::Session standalone helper with strict get() (dies on missing key)
- PAGI::Session accepts raw data, scope hashref, or object with ->scope
- Bulk operations: set(k1 => v1, k2 => v2), delete(@keys), slice(@keys), clear()
- Session destroy lifecycle: deletes from store, clears cookie via State::clear()
- Session regenerate lifecycle: generates new ID, deletes old, preserves data
- Idempotency: middleware skips if pagi.session already in scope
- Reserved keys (_destroyed, _regenerated) documented as middleware-consumed flags
- Store::Cookie extracted to separate CPAN dist (PAGI-Middleware-Session-Store-Cookie)
[Bug Fixes]
- disable_tls skips TLS setup instead of dying
- Recompile access log formatter on configure() changes
- Weaken $self in _pause_accepting timer closure (reference cycle fix)
- Replace Future::IO->load_impl('IOAsync') with use Future::IO::Impl::IOAsync
(fixes compatibility with Future::IO 0.22+)
[Cleanup]
- Remove unused _log_connection_stats method
0.001019 - 2026-02-19
[New Features]
- Router: rewrite _compile_path() as tokenizer with proper regex escaping,
so literal path segments containing regex metacharacters (e.g., /foo.bar)
are matched literally instead of as patterns
- Router: inline constraint syntax {name:pattern} for path parameters
(e.g., /user/{id:\d+}) validated during dispatch
- Router: chainable constraints() method for applying regex constraints
to named path parameters after route definition
- Router: any() multi-method matcher supporting wildcard ('*') and
explicit method lists (e.g., any [qw(GET POST)] => '/path' => $handler)
- Router: named routes and uri_for() now support {name:pattern} syntax
and any() routes
- Router: group() for organizing routes under a common prefix, with three
forms: callback (sub), router-object, and string (auto-require). Supports
middleware, nesting, named route namespacing via as(), and conflict detection
- Router: mount() now accepts a string class name with auto-require and
to_app dispatch
- SSE over HTTP/2 with streaming DATA frames, keepalive comments, and
disconnect handling
- Add PAGI::Utils::Random module for cryptographically secure random bytes
- Rate limiter: periodic cleanup and safety valve for expired buckets
- CORS middleware: warn when configured with wildcard origins and credentials
[Security Fixes]
- Fix XSS in Debug middleware panel by escaping scope values in HTML output
- Add max_chunk_size limit (default 1MB) to HTTP/1.1 chunked transfer
parser to prevent denial-of-service via unbounded chunk sizes
- Use cryptographically secure random bytes (via /dev/urandom with
Crypt::URandom fallback) in RequestId, CSRF, and Session middleware
instead of rand()
- Default session cookie SameSite=Lax to prevent CSRF via cross-site requests
- Fix double URL-decoding in Static middleware that could allow path traversal
[Bug Fixes]
- Fix HTTP/2 path decoding to match HTTP/1.1 decoding pipeline
- Fix HTTP/2 WebSocket path decoding to match HTTP/1.1 pipeline
- Require Net::HTTP2::nghttp2 0.007+ for HTTP/2 support (was unversioned)
[Improvements]
- Extract _format_sse_event() and _format_sse_comment() helpers and
protocol-abstracted keepalive writer for consistent SSE formatting
across HTTP/1.1 and HTTP/2
0.001018 - 2026-02-15
[Bug Fixes]
- Skip HTTP/2 subtests when Net::HTTP2::nghttp2 is not installed, fixing
CPAN tester failures on systems without optional nghttp2 dependency
0.001017 - 2026-02-11
[New Features]
- HTTP/2 support (experimental). Requires Net::HTTP2::nghttp2. Enable with
--http2 flag. Supports ALPN negotiation (h2/http1.1 over TLS), cleartext
h2c via connection preface detection, WebSocket over HTTP/2 (RFC 8441),
streaming responses with backpressure, and configurable protocol settings
(h2_max_concurrent_streams, h2_initial_window_size, etc.)
- Worker heartbeat monitoring for multi-worker mode. Parent process detects
workers with blocked event loops via Unix pipe heartbeat and replaces them
with SIGKILL + respawn. Default 50s timeout (heartbeat_timeout option,
--heartbeat-timeout CLI flag). Only detects event loop starvation â async
handlers using await are unaffected regardless of duration.
- Custom access log format strings (--access-log-format). Supports atoms
like %a (address), %s (status), %D (duration μs), %b (body size).
Enables structured JSON logging via format string.
- Track response body size in access log (%b atom)
[Bug Fixes]
- Fix TLS performance cliff at 8+ concurrent connections caused by
per-connection SSL context creation parsing the entire CA bundle.
Shared SSL context via SSL_reuse_ctx gives ~26x throughput improvement.
- Fix TLS in multi-worker mode â workers now SSL-upgrade connections
in on_stream rather than relying on listener-level TLS
- Fix SSE wire format to handle CRLF, LF, and bare CR line endings
per the SSE specification
- Fix multi-worker shutdown escalation â SIGKILL timer now fires
correctly when workers don't exit after SIGTERM
- Fix multi-worker parameter pass-through for server options
- Fix flaky multi-worker TLS test
[Improvements]
- Add on_ssl_error callback to TLS listener setup for cleaner error
handling (suppresses "EV: error in callback" noise)
- Cache Server header string to avoid per-response interpolation
- Merge double header loop in serialize_response_start
- Cache access log timestamp at per-second granularity
- Cache client_host in access log to avoid per-request getpeername syscall
- Skip HTTP/2 tests when Net::HTTP2::nghttp2 is not installed
- Skip SSE tests when Future::IO is not installed
- Remove unused Sys::Sendfile recommendation from cpanfile
0.001016 - 2026-01-31
[New Features]
- Add query parameter parsing to PAGI::SSE: query_params(), query_param(),
raw_query_params(), raw_query_param() methods mirroring PAGI::Request
and PAGI::WebSocket APIs
- pagi-server now auto-configures Future::IO for IO::Async if installed,
enabling seamless use of Future::IO-based libraries (Async::Redis, etc.)
and PAGI::SSE->every(). Reports configuration in non-production mode.
[Breaking Changes]
- PAGI::Server (the module) no longer auto-configures Future::IO.
pagi-server (the CLI) handles this automatically. Programmatic users
of PAGI::Server must configure Future::IO themselves if using
Future::IO-based libraries. See PAGI::Server "LOOP INTEROPERABILITY".
[Improvements]
- PAGI::SSE->every() now provides a clear error message if Future::IO
backend is not configured, with example code showing how to fix it
- New PAGI::Server documentation section "LOOP INTEROPERABILITY" explains
Future::IO configuration for programmatic usage
0.001015 - 2026-01-18
[New Features]
- Add query parameter parsing to PAGI::WebSocket: query_params(), query_param(),
raw_query_params(), raw_query_param() methods mirroring PAGI::Request API
( run in 0.842 second using v1.01-cache-2.11-cpan-140bd7fdf52 )