# This file documents the revision history for Perl extension PAGI 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 - Add form_param($name, %opts) and raw_form_param($name) singular accessors to PAGI::Request for fetching individual form values [Deprecations] - Rename PAGI::Request param methods for naming consistency (old names deprecated with warnings): - query() -> query_param() - raw_query() -> raw_query_param() - form() -> form_params() - raw_form() -> raw_form_params() [Improvements] - Improve PAGI::Test::Response json() error diagnostics: now shows HTTP status code, Content-Type header, and body preview when JSON parsing fails. Uses croak for better stack traces pointing to test file location. - Add $JSON_ERROR_BODY_LIMIT package variable to control body preview length in json() error messages (default 1500 bytes) 0.001014 - 2026-01-16 [New Features] - Add Future::XS opt-in support for improved performance (~4% multi-worker) Use --future-xs flag or PAGI_FUTURE_XS=1 environment variable - path_param() now strict by default - dies with helpful message on missing keys, catching typos early. Use strict => 0 to opt out. [Bug Fixes] - Fix connection leak where connections weren't removed from tracking hash on EOF/disconnect, causing memory growth under sustained load - Fix POD reference to connection state specification (was pointing to non-existent file) [Improvements] - Simplify max_connections default to 1000 (removed unreliable auto-detection) - Lazy disconnect_future() allocation - Future only created when called, reducing per-request overhead for simple handlers - path_param() now delegates to path_params(), allowing subclasses to override path_params for custom parameter handling (e.g., lazy conversion from positional to named parameters) - Add _default_path_param_strict_opt() hook for subclasses to change default - Document query_params/query strict and raw options in POD 0.001013 - 2026-01-04 [New Features] - Add PAGI::Utils::handle_lifespan utility for simplified lifespan handling - Add Response getters (status, headers, content_type, body) and try setters - Support static path rewrites in PAGI::App::File - Add full-demo example demonstrating lifespan, HTTP, WebSocket, and SSE [Bug Fixes] - Fix POD formatting of nested angle brackets - Make handle_lifespan croak on wrong scope type for clearer error messages [Improvements] - Match plackup FindBin behavior for app loading - Refactor PAGI::Response to use exists() for lazy defaults - Remove deprecated path_param/path_params from PAGI::Response - Document state method in PAGI::Request - Normalize spec docs to ASCII [Build/Distribution] - Add .editorconfig for consistent editor settings - Add Thunderhorse to implementations documentation 0.001012 - 2026-01-01 [New Features] - PAGI 0.2 specification: Loop-agnostic apps, SSE POST support, keepalive events - Send-side backpressure to prevent unbounded memory growth under slow clients - Configurable write_high_watermark (64KB) and write_low_watermark (16KB) matching Python asyncio defaults for optimal memory/throughput balance - SSE disconnect reasons (client_closed, timeout, write_error, shutdown) [Bug Fixes] - Fix Lint middleware to preserve original app errors instead of masking them - Fix SSE keepalive to use chunked transfer encoding - Fix missing IO::Async::Timer::Periodic use statement - Fix missing 'method' field in SSE test scopes (per spec line 624) - Fix WebSocket scope incorrectly including 'method' field in test client - Fix middleware class resolution for nested namespaces (Auth::Basic, etc.) using ^ prefix for fully-qualified class names [Improvements] - Improved event loop portability (Future::IO now fully optional) - Improve max_connections auto-detection using getrlimit(RLIMIT_NOFILE) instead of sysconf(_SC_OPEN_MAX) for accuracy - Add fd_headroom config option (default 100) for max_connections calculation - Add TTY warning at startup when access_log goes to terminal - Security fixes from code review - PAGI spec compliance fixes for event validation [Build/Distribution] - Replace Markdown::Pod with App::sdview for spec POD generation - Fixes =item - to =item * (proper POD bullet points) - Converts module names to L<> links for metacpan 0.001011 - 2025-12-28 - Switch license from Perl_5 to Artistic_2_0 (cleaner language, OSI recommended) - Fix Ctrl-C signal handling for multi-worker mode: lifespan.shutdown now runs for all workers ensuring proper cleanup - Add -e and -M flags to Runner for inline apps (like perl -e/-M) - Add .pl and .md to MIME types in PAGI::App::File - Add exception trapping to PAGI::Test::Client (default behavior) - Add SIGNAL HANDLING documentation to PAGI::Server POD - Add SECURITY.md and CONTRIBUTING.md to repository - Document async completion contract and detect missing responses - Cookbook improvements: JWT validation, Redis sessions, redirect behavior, WebSocket heartbeat example, IO::File::WithPath for filehandles 0.001010 - 2025-12-26 - BREAKING: Removed sendfile() from PAGI::Server - use PAGI::Middleware::XSendfile with nginx/Apache X-Sendfile/X-Accel-Redirect instead for large file serving - Added handle_ranges option to PAGI::App::File for byte-range request support - Added FreeBSD CI testing - Documentation fixes for phantom module references 0.001009 - 2025-12-26 - BREAKING: PAGI::Response constructor now takes ($scope, $send) instead of ($send, $scope) for consistency with Request, SSE, and WebSocket APIs - Added request_timeout for stalled requests, ws_idle_timeout and sse_idle_timeout for idle connection cleanup (all disabled by default for performance) - Test::Client: multi-value headers/query/form support, fixed arrayref headers with form/json - Scope-based caching for Request, Response, SSE, WebSocket objects - Replace retain() with adopt_future() for proper async error handling - Added on_error callback to heartbeat and rate limit middleware - Restructured Tutorial, added Cookbook for advanced patterns 0.001008 - 2025-12-25 - Fixed 32-bit Perl compatibility in WebSocket test (pack 'Q' → 'NN') - Fixed sendfile on non-blocking sockets for FreeBSD/BSD systems - Added sendfile documentation and production recommendations - Improved test reliability: skip lsof/timing-dependent tests by default - Normalized test environment variables to use RELEASE_TESTING 0.001007 - 2025-12-24 - More broken dist metadata fixes (part 2) 0.001006 - 2025-12-24 - More broken dist metadata fixes 0.001005 - 2025-12-24 - trying to get the indexer to ignore my test libs 0.001004 - 2025-12-24 - More fixes to the code that generates the POD version of the specification in improve readability - exclude test and example libs from the CPAN indexer. 0.001003 - 2025-12-24 - Fixes to the code that generates the POD version of the specification to try and fix UTF8 and other formatting issues. 0.001002 - 2025-12-24 - Initial release. See README.md for a starting point 0.001001 - 2025-12-17 - Placeholder release