Changes for version 0.002003 - 2026-08-24
- Specification clarifications - resolved ambiguities
- http.response.body payload keys (body / file / fh) are at-most-one per event; omitting all three means an empty body chunk.
- Defined HEAD response semantics: the application responds as it would to GET and the server suppresses the body on every HTTP version - Content-Length passes through, no chunked framing, file/fh bodies are never opened or statted, trailers are validated and discarded.
- A response left incomplete after http.response.start forces abnormal closure on every transport: the server never synthesizes the missing terminal framing, the client observably sees truncation (HTTP/1.1 close without the chunk terminator; HTTP/2 RST_STREAM such as INTERNAL_ERROR), on_disconnect fires with reason server_error, and on_complete never does.
- SSE detection is an exact text/event-stream media-range match with q > 0; wildcards and explicit q=0 refusals never signal SSE.
- Lifespan strict mode (operator-configured startup-failure-is-fatal) is sanctioned; a decline-tolerant mode remains the required default; an "off" switch that skips the protocol entirely is expressly nonconforming.
- Over HTTP/1.1, trailers exist only in chunked framing: a server must reject http.response.trailers on a Content-Length-framed response by failing the send Future, never by silently dropping the trailers.
- Delivery defines completion: an application exception raised after the response's terminal event does not un-complete the request - on_complete fires, disconnect_reason stays undef, the server should log the exception and may close the connection.
- Specification clarifications - send and delivery contracts
- Sends are sequential: an application must not issue a new send on a connection before the previous send's Future resolves; the effect of overlapping sends is unspecified.
- file/fh delivery failure is fail-don't-mutate: a missing or unreadable file, or a read or seek failure, fails that send's Future and leaves the response sequence state as it was, so the application may recover; once body bytes reached the wire the incomplete-response rules govern. The failed Future is the application's notification channel.
- Connection-level and framing response headers belong to the server: HTTP/2 strips the connection-specific set from application responses (RFC 9113 forbids them); HTTP/1.1 must ignore or strip application-supplied Transfer-Encoding and Connection, continuing the WSGI/ASGI hop-by-hop lineage.
- WebSocket denial events after websocket.accept, and sends after the application's own websocket.close, fail the send Future (the fail-don't-mutate rule); only a transport-initiated closure makes a subsequent send a race-friendly no-op.
- Exceptions raised by registered callbacks (on_complete, on_disconnect, and the pagi.transport callbacks) must not prevent remaining callbacks from running or disturb server processing; servers should log them.
- Documentation
- Expanded THE PAGI ECOSYSTEM in PAGI.pm: a Built on PAGI section covering Thunderhorse, PAGI::FastAPI, WebDyne, PAGI::Nano, and Uniform::HTMX::PAGI, and an Announcements section pointing at the pagi-announce mailing list.
- Reworked UTF-8 HANDLING OVERVIEW in PAGI.pm to lead with the PAGI-Tools version of the example (PAGI::Request/PAGI::Response, where decoding and encoding are encapsulated at the boundary) and to present the raw-interface version as the contract toolkits implement rather than a recommended application style; the raw example now percent-decodes with URI::Escape and handles '+' as space.
- Specification additions
- Widened the idle_timeout and keepalive_timeout disconnect-reason definitions to the senses in real-world use: mid-stream idleness on a quiet WebSocket or SSE session, and a WebSocket keepalive ping that received no pong.
- Added 503 Service Unavailable (admission control, with Retry-After) to the generated-response catalog.
- Listed pagi.transport in the HTTP, WebSocket, and SSE scope-key tables, where it was specified but unlisted.
- Added optional WebSocket scope keys max_frame_size and max_receive_queue so applications can size client framing and apply their own flow control against the limits the server actually enforces.
Documentation
building frameworks and toolkits on PAGI
worked, runnable recipes for the PAGI protocol
how PAGI stays loop-agnostic, how not to block, and how a server binds to an event loop
coming to PAGI from PSGI
The base PAGI specification: application interface and core concepts
The PAGI server extension mechanism
The PAGI Lifespan protocol for startup and shutdown events
PAGI server and application-runner integration guidance
The PAGI TLS extension for reporting connection security in scope
PAGI message formats for HTTP, WebSocket, and SSE
Learn the PAGI protocol for async Perl web applications
Modules
Perl Asynchronous Gateway Interface
Examples
- examples/01-hello-http/README.md
- examples/01-hello-http/app.pl
- examples/02-streaming-response/README.md
- examples/02-streaming-response/app.pl
- examples/03-request-body/README.md
- examples/03-request-body/app.pl
- examples/04-websocket-echo/README.md
- examples/04-websocket-echo/app.pl
- examples/05-sse-broadcaster/README.md
- examples/05-sse-broadcaster/app.pl
- examples/06-lifespan-state/README.md
- examples/06-lifespan-state/app.pl
- examples/07-extension-fullflush/README.md
- examples/07-extension-fullflush/app.pl
- examples/08-tls-introspection/README.md
- examples/08-tls-introspection/app.pl
- examples/11-job-runner/README.md
- examples/11-job-runner/app.pl
- examples/11-job-runner/lib/JobRunner/HTTP.pm
- examples/11-job-runner/lib/JobRunner/Jobs.pm
- examples/11-job-runner/lib/JobRunner/Queue.pm
- examples/11-job-runner/lib/JobRunner/SSE.pm
- examples/11-job-runner/lib/JobRunner/WebSocket.pm
- examples/11-job-runner/lib/JobRunner/Worker.pm
- examples/11-job-runner/public/css/style.css
- examples/11-job-runner/public/index.html
- examples/11-job-runner/public/js/app.js
- examples/12-utf8/README.md
- examples/12-utf8/app.pl
- examples/13-flow-control/README.md
- examples/13-flow-control/app.pl
- examples/14-periodic-events/README.md
- examples/14-periodic-events/app.pl
- examples/14-periodic-events/cpanfile
- examples/15-embedded-ioasync/README.md
- examples/15-embedded-ioasync/cpanfile
- examples/15-embedded-ioasync/server.pl
- examples/16-foreign-loop/README.md
- examples/16-foreign-loop/cpanfile
- examples/16-foreign-loop/server.pl
- examples/17-event-middleware/README.md
- examples/17-event-middleware/app.pl
- examples/17-event-middleware/cpanfile
- examples/18-bidirectional-websocket/README.md
- examples/18-bidirectional-websocket/app.pl
- examples/18-bidirectional-websocket/cpanfile
- examples/README.md
- examples/mini-framework/README.md
- examples/mini-framework/app.pl