PAGI - Perl Asynchronous Gateway Interface

PAGI 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).

⚠️ Beta Software

This distribution has different stability levels:

| Component | Stability | Notes | |-----------|-----------|-------| | PAGI Specification | Stable | The $scope/$receive/$send interface won't change except for critical security fixes | | PAGI::Server | Stable | Compliance-tested, but run behind nginx/Caddy for production | | Everything else | Unstable | Request/Response wrappers, routers, middleware, apps may change between releases |

Repository Contents

Requirements

Quick Start

# Install dependencies
cpanm --installdeps .

# For best performance (fast JSON, zero-copy sendfile, TLS support):
cpanm --installdeps . --with-recommends

# Run tests
prove -l t/

# Start the server with a PAGI app
pagi-server --app examples/01-hello-http/app.pl --port 5000

# Test it
curl http://localhost:5000/

Optional Performance Dependencies

PAGI uses JSON::MaybeXS for JSON encoding/decoding, which automatically uses the fastest available backend:

| Module | Speed | Notes | |--------|-------|-------| | Cpanel::JSON::XS | Fastest | Recommended for production | | JSON::XS | Fast | Good alternative | | JSON::PP | Baseline | Pure Perl fallback (always available) |

Install for best performance:

cpanm Cpanel::JSON::XS

Other optional dependencies:

Optional TLS/HTTPS Support

TLS support is optional and not installed by default. Most production deployments use a reverse proxy (nginx, Caddy, HAProxy) for TLS termination, so PAGI keeps the base installation minimal.

To enable HTTPS support:

# Using cpanm
cpanm IO::Async::SSL IO::Socket::SSL

# Or on Debian/Ubuntu
apt-get install libio-socket-ssl-perl

When you need TLS in PAGI::Server:

When you don't need it:

The startup banner shows TLS status: tls: on|available|not installed|disabled

See perldoc PAGI::Server for TLS configuration details and certificate generation examples.

PAGI Application Interface

PAGI applications are async coderefs with this signature:

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,
    });
}

Parameters

Scope Types

UTF-8 Handling

Minimal example with explicit UTF-8 handling:

use Future::AsyncAwait;
use experimental 'signatures';
use Encode qw(encode decode FB_DEFAULT FB_CROAK);

async sub app ($scope, $receive, $send) {
    die "Unsupported: $scope->{type}" if $scope->{type} ne 'http';

    my $text = '';
    if ($scope->{query_string} =~ /text=([^&]+)/) {
        my $bytes = $1; $bytes =~ s/%([0-9A-Fa-f]{2})/chr hex $1/eg;
        $text = decode('UTF-8', $bytes, FB_DEFAULT);  # replacement for invalid
    }

    my $body    = "You sent: $text";
    my $encoded = encode('UTF-8', $body, 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,
    });
}

Example Applications

Low-Level Protocol Examples

These examples demonstrate the raw PAGI protocol:

| Example | Description | |---------|-------------| | 01-hello-http | Basic HTTP response | | 02-streaming-response | Chunked streaming with trailers | | 03-request-body | POST body handling | | 04-websocket-echo | WebSocket echo server | | 05-sse-broadcaster | Server-Sent Events | | 06-lifespan-state | Shared state via lifespan | | 07-extension-fullflush | TCP flush extension | | 08-tls-introspection | TLS connection info | | 09-psgi-bridge | PSGI compatibility |

Higher-Level Examples

| Example | Description | |---------|-------------| | endpoint-router-demo | Full app with HTTP, WebSocket, SSE using PAGI::Endpoint::Router | | websocket-chat-v2 | Chat application using PAGI::WebSocket wrapper | | sse-dashboard | Dashboard with SSE updates using PAGI::SSE wrapper |

Components

PAGI includes convenience wrappers for common patterns:

| Component | Description | |-----------|-------------| | PAGI::Lifespan | Lifecycle management for apps (startup/shutdown callbacks, state injection) | | PAGI::Request | HTTP request wrapper with body parsing, headers, state/stash accessors | | PAGI::WebSocket | WebSocket wrapper with JSON support, heartbeat, message iteration | | PAGI::SSE | SSE wrapper with event formatting, keepalive, periodic sending | | PAGI::Endpoint::Router | Class-based router for HTTP, WebSocket, and SSE routes | | PAGI::App::Router | Functional router with Express-style routing |

Higher-Level Router Example

# lib/MyApp.pm
package MyApp;
use parent 'PAGI::Endpoint::Router';
use Future::AsyncAwait;

sub routes {
    my ($self, $r) = @_;
    $r->get('/' => 'home');
    $r->websocket('/ws/echo' => 'ws_echo');
    $r->sse('/events' => 'sse_stream');
}

async sub home {
    my ($self, $req, $res) = @_;
    await $res->html('<h1>Hello!</h1>');
}

async sub ws_echo {
    my ($self, $ws) = @_;
    await $ws->accept;
    await $ws->each_json(async sub {
        my ($data) = @_;
        await $ws->send_json({ echo => $data });
    });
}

async sub sse_stream {
    my ($self, $sse) = @_;
    await $sse->every(1, async sub {
        await $sse->send_event(event => 'tick', data => { time => time });
    });
}

1;

# app.pl
use PAGI::Lifespan;
use MyApp;

my $router = MyApp->new;

PAGI::Lifespan->wrap(
    $router->to_app,
    startup => async sub {
        my ($state) = @_;
        $state->{config} = { app_name => 'MyApp' };
    },
);

See examples/endpoint-router-demo/ for a complete working example with HTTP, WebSocket, and SSE.

Middleware

PAGI includes a collection of middleware components in PAGI::Middleware::*:

See lib/PAGI/Middleware/ for the full list.

Development

# Install development dependencies
cpanm --installdeps . --with-develop

# Build distribution
dzil build

# Run distribution tests
dzil test

Specification

See docs/specs/main.mkdn for the complete PAGI specification.

License

This software is licensed under the same terms as Perl itself.

Author

John Napiorkowski jjnapiork@cpan.org