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, $send interface) 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-Server distribution.

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-Tools distribution; 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-server CLI 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, use raw_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 via query_string and the body stream. If you need strict validation, decode yourself with Encode and FB_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 set Content-Length based 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-announce list (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-Server distribution)
PAGI::Server::Runner - Application runner (PAGI-Server distribution)
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.