NAME
PAGI - Perl Asynchronous Gateway Interface
DEDICATION
This project is dedicated to the memory of Matt S. Trout (mst), who I wish was still around to tell me all the things wrong with my code while simultaneously offering brilliant ideas to make it better.
Matt encouraged my first CPAN contribution. Without that encouragement, PAGI and pretty much everything I've released on CPAN over 20+ years would never have happened.
Thank you, Matt. The Perl community misses you.
SYNOPSIS
# Raw PAGI application
use Future::AsyncAwait;
async sub app {
my ($scope, $receive, $send) = @_;
die "Unsupported: $scope->{type}" if $scope->{type} ne 'http';
await $send->({
type => 'http.response.start',
status => 200,
headers => [['content-type', 'text/plain']],
});
await $send->({
type => 'http.response.body',
body => 'Hello from PAGI!',
more => 0,
});
}
DESCRIPTION
PAGI (Perl Asynchronous Gateway Interface) is a specification for asynchronous Perl web applications, designed as a spiritual successor to PSGI. It defines a standard interface between async-capable Perl web servers, frameworks, and applications, supporting HTTP/1.1, WebSocket, and Server-Sent Events (SSE).
This document presents a high level overview of PAGI. If you are a web developer who is looking to write PAGI compliant apps, you should also review the tutorial: PAGI::Tutorial. Coming from PSGI? See PAGI::PSGI. Upgrading between sub-spec versions? See PAGI::Upgrading. Building a framework on PAGI? See PAGI::Building.
Why PAGI?
PSGI models an application as a single, synchronous coderef that takes a request and returns a response. That model has served Perl well, but it cannot express long-lived connections such as long-poll HTTP or WebSockets: there is only one path in (the request) and one path out (the response). Even made non-blocking, a single request/response path cannot represent protocols that deliver multiple incoming events over the life of a connection, such as WebSocket frames.
PAGI keeps the simple "your application is a coderef" idea but makes it asynchronous and message-based. An application receives a $scope describing the connection and two async coderefs: $receive for events arriving from the client and $send for events going back. Both return Futures, so backpressure is explicit. This allows any number of incoming and outgoing events per connection, and leaves room for background work (for example, listening on an external trigger like a message queue) alongside the request/response flow.
PAGI is also a superset of PSGI: there is a defined translation between the two, so existing PSGI applications can run under a PAGI server through a PSGI adapter provided in the PAGI-Tools distribution.
Beta Software Notice
WARNING: This is beta software.
This distribution (PAGI) is the specification only: this module plus the PAGI::Spec documentation. The reference implementations live in separate distributions, each with its own stability level:
- Stable: PAGI Specification
-
The PAGI specification (
$scope,$receive,$sendinterface) is stable. Breaking changes will not be made except for critical security issues. Raw PAGI applications you write today will continue to work.See PAGI::Spec.
- Beta: PAGI-Server distribution
-
The reference server (PAGI::Server, validated against PAGI::Server::Compliance) handles HTTP/1.1, HTTP/2, WebSocket, and SSE correctly, but has not been battle-tested in production. Recommendation: run behind a reverse proxy like nginx, Apache, or Caddy. Rely only on the PAGI::Spec interface, not on the server's internals. Ships in the
PAGI-Serverdistribution. - Beta: PAGI-Tools distribution
-
The application toolkit is convenience built on top of the spec. Its APIs may change between releases as the helpers evolve. Ships in the
PAGI-Toolsdistribution; see PAGI::Tools.
If you are interested in contributing to the future of async Perl web development, your feedback, bug reports, and contributions are welcome.
THE PAGI ECOSYSTEM
PAGI is split across three distributions so that applications can depend on the specification without pulling in a particular server or toolkit:
PAGI(this distribution)-
The specification: this module plus the PAGI::Spec documents (PAGI::Spec, PAGI::Spec::Www, PAGI::Spec::Lifespan, PAGI::Spec::Extensions, PAGI::Spec::Tls, PAGI::Spec::Server). The specification modules are pure documentation, and installing this distribution installs no server or toolkit (see "INSTALLATION").
PAGI-Server-
The reference server (PAGI::Server): an IO::Async-based implementation supporting HTTP/1.1, HTTP/2, WebSocket, SSE, TLS, and multi-worker pre-forking, validated against PAGI::Server::Compliance. Provides the
pagi-serverCLI and PAGI::Server::Runner. PAGI::Spec::Server documents the boundary between application loading and the runtime PAGI application; server selection and plugin compatibility are runner-specific APIs. PAGI-Tools-
The application toolkit: conveniences built on the specification, documented in that distribution (see PAGI::Tools).
This PAGI distribution is the canonical starting point for the ecosystem: the specification, the tutorial, a cookbook of worked recipes, a migration guide for people coming from PSGI, a guide for framework authors, and a set of raw-protocol example applications under examples/ (including a complete little web framework built on PAGI). The reference server lives in the PAGI-Server distribution and the application toolkit in PAGI-Tools. The project repository is https://github.com/jjn1056/pagi; its history holds the original combined distribution from before the split.
Built on PAGI
Beyond the core distributions, a growing set of projects target PAGI directly. A sampling, roughly from full frameworks down to focused tools:
- Thunderhorse
-
An asynchronous web framework built for PAGI from the ground up.
- PAGI::FastAPI
-
A FastAPI-style asynchronous microframework:
Future::AsyncAwait-native handlers, type-safe request validation via Type::Tiny, automatic OpenAPI documentation, WebSocket support, and dependency injection, with concerns like CORS, rate limiting, and authentication composed as middleware and route dependencies. - WebDyne
-
A long-standing server-page framework rendering .psp files with embedded Perl; it runs under Apache/mod_perl, PSGI, or PAGI as its application runtime.
- PAGI::Nano
-
A compact micro-framework front door over
PAGI-Tools, aimed at demos and small applications: routing, middleware, lifecycle, static files, streaming, WebSocket, and SSE, arranged so a whole small app fits on one screen and reads top to bottom. - Uniform::HTMX::PAGI
-
Bridges Uniform::HTMX into native PAGI web-context routines, with chainable helpers (for example
res_retarget,res_trigger) for shaping HTMX response headers from PAGI applications.
See the project repository for an up-to-date list of conforming servers, frameworks, and tools -- and if you have built something on PAGI, open a pull request adding it here.
Announcements
Release and specification announcements are posted to the pagi-announce mailing list at https://groups.google.com/g/pagi-announce, and to the project repository at https://github.com/jjn1056/pagi.
INSTALLATION
Before the split, the PAGI distribution bundled the reference server, the application toolkit, and the specification together, so cpanm PAGI (or a requires 'PAGI' line in a cpanfile) installed all of them. Through the 0.002 releases it kept installing PAGI::Server (from the PAGI-Server distribution) and PAGI::Tools (from the PAGI-Tools distribution) for those dependents.
Since 0.003000 it no longer does: installing PAGI installs the specification only. If your code uses the reference server or the toolkit, require PAGI::Server and/or PAGI::Tools directly:
cpanm PAGI::Server PAGI::Tools
and depend on PAGI only when you want the specification itself.
PAGI APPLICATION INTERFACE
PAGI applications are async coderefs with this signature:
async sub app {
my ($scope, $receive, $send) = @_;
... }
Parameters
$scope-
Hashref containing connection metadata including type, headers, path, method, query string, and server-advertised extensions.
$receive-
Async coderef that returns a Future resolving to the next event from the client (e.g., request body chunks, WebSocket messages).
$send-
Async coderef that takes an event hashref and returns a Future. Used to send responses back to the client.
Scope Types
Applications dispatch on $scope->{type}:
http-
HTTP request/response (one scope per request)
websocket-
Persistent WebSocket connection
sse-
Server-Sent Events stream
lifespan-
Process startup/shutdown lifecycle events
UTF-8 HANDLING OVERVIEW
Toolkits and frameworks usually handle encoding for the applications built on them. The rules below govern the raw interface underneath -- the contract that toolkits, frameworks, and servers implement.
PAGI scopes provide decoded text where mandated by the spec and preserve raw bytes where the application must decide. Broad guidance:
$scope->{path}is UTF-8 decoded from the percent-encoded$scope->{raw_path}. If UTF-8 decoding fails (invalid byte sequences), the original bytes are preserved as-is (Mojolicious-style fallback). If you need exact on-the-wire bytes, useraw_path.$scope->{query_string}and request bodies arrive as percent-encoded or raw bytes. Higher-level frameworks may auto-decode with replacement by default, but raw values remain available viaquery_stringand the body stream. If you need strict validation, decode yourself withEncodeandFB_CROAK.Response bodies and header values sent over the wire must be encoded to bytes. If you construct raw events, encode with
Encode::encode('UTF-8', $str, FB_CROAK)(or another charset you set in Content-Type) and setContent-Lengthbased on byte length.
Here is what that means for an application that decodes a query parameter and answers in UTF-8 text:
use Future::AsyncAwait;
use Encode qw(encode decode);
use URI::Escape qw(uri_unescape);
async sub app {
my ($scope, $receive, $send) = @_;
# Handle lifespan if your server sends it; otherwise fail on unsupported types.
die "Unsupported type: $scope->{type}" unless $scope->{type} eq 'http';
# Decode a query param by hand: split, '+' to space, percent-decode
# to bytes, then UTF-8 decode.
my $text = '';
if ($scope->{query_string} =~ /(?:^|&)text=([^&]*)/) {
(my $bytes = $1) =~ tr/+/ /;
$bytes = uri_unescape($bytes);
$text = decode('UTF-8', $bytes, Encode::FB_DEFAULT); # replacement for invalid
}
my $body = "You sent: $text";
my $encoded = encode('UTF-8', $body, Encode::FB_CROAK);
await $send->({
type => 'http.response.start',
status => 200,
headers => [
['content-type', 'text/plain; charset=utf-8'],
['content-length', length($encoded)],
],
});
await $send->({
type => 'http.response.body',
body => $encoded,
more => 0,
});
}
QUICK START
This distribution is the specification. To write and run PAGI applications, install the reference server and toolkit:
# Reference server (provides the pagi-server CLI) and toolkit
cpanm PAGI::Server PAGI::Tools
# Start a PAGI app
pagi-server --app ./app.pl --port 5000
# Test it
curl http://localhost:5000/
See PAGI::Tutorial for a step-by-step guide to the protocol, and PAGI::Spec for the full specification.
REQUIREMENTS
PAGI requires Perl 5.24 or later: the specification, the reference server (the PAGI-Server distribution) and the toolkit (the PAGI-Tools distribution) alike. The releases up to 0.003000 declared Perl 5.18.
The specification modules (PAGI.pm and the PAGI::Spec::* POD) are pure documentation. The PAGI-Server and PAGI-Tools distributions, installed separately (see "INSTALLATION"), declare their own dependencies (the PAGI-Server distribution requires IO::Async and Future::AsyncAwait; the PAGI-Tools distribution requires Future::AsyncAwait). PAGI itself requires no event loop: IO::Async is the event loop the reference server runs on, an implementation detail of that server, and an application written to the specification (using Future::IO for timers and I/O) runs on any conforming server.
Perl version support
The specification is defined in terms of Future: $receive, $send and the application itself all return Futures. Future is the one hard dependency of a PAGI application, and so Future's minimum Perl is PAGI's: when Future raises its floor, PAGI has no choice but to follow. Future::AsyncAwait is not required, but it is how PAGI applications are meant to be written (the examples, the guides and the toolkit all use it), so its behaviour on a given Perl matters as much.
The floor is Perl 5.24, rather than the 5.20 that Future itself needs, because 5.24 is the first Perl on which the async stack behaves the same everywhere: Future::AsyncAwait passes a cancel on to what an async sub awaits (below 5.24 a cancelled async sub stays suspended), and Future::XS is available.
The horizon PAGI works to is Future's maintainer's: Future supports Perl versions at least ten years old.
PAGI does not raise its floor automatically when a Perl turns ten.
A Perl more than ten years old is in grace time: it still works, and it will not be broken casually, but it is the next floor that can go. Perl 5.24 (released 2016) is in grace time now.
PAGI's floor follows its upstream dependencies, which PAGI does not own, so advance notice of every change cannot be promised. What PAGI does promise is to work with those projects and pass on what it learns as early and as openly as it can, on the
pagi-announcelist (see "Announcements").
SEE ALSO
- PAGI::Tutorial - A step-by-step guide to the protocol
- PAGI::Cookbook - Worked, runnable recipes for each protocol feature
- PAGI::PSGI - Coming to PAGI from PSGI
- PAGI::Upgrading - What changed between sub-spec versions
- PAGI::Building - Building frameworks and toolkits on PAGI
- PAGI::Spec - The full PAGI specification
- PAGI::Spec::Extensions - The server extension mechanism
- PAGI::Spec::Server - Server and application-runner integration guidance
- PAGI::Server - Reference server (
PAGI-Serverdistribution) - PAGI::Server::Runner - Application runner (
PAGI-Serverdistribution) - PSGI - The synchronous predecessor to PAGI
- Future::AsyncAwait - Async/await for Perl
CONTRIBUTING
This project is in active development. If you're interested in advancing async web programming in Perl, contributions are welcome:
Bug reports and feature requests
Documentation improvements
Test coverage
Protocol support (HTTP/2, HTTP/3)
Performance optimizations
AUTHOR
John Napiorkowski <jjnapiork@cpan.org>
LICENSE
This software is licensed under the same terms as Perl itself.