Introducing PAGI: Async Web Development for Perl
TL;DR: PAGI (Perl Asynchronous Gateway Interface) is a new specification for async Perl web applications, inspired by Python's ASGI. It supports HTTP, WebSockets, and Server-Sent Events natively, and can wrap existing PSGI applications for backward compatibility.
The Problem
Modern web applications need more than traditional request-response cycles. Real-time features like live notifications, collaborative editing, and streaming data require persistent connections. This means:
- WebSockets for bidirectional communication
- Server-Sent Events for efficient server push
- Streaming responses for large payloads
- Connection lifecycle management for resource pooling
PSGI, Perl's venerable web server interface, assumes a synchronous world. While frameworks like Mojolicious have built async capabilities on top, there's no shared standard that allows different async frameworks and servers to interoperate.
PAGI aims to fill that gap.
What is PAGI?
PAGI defines a standard interface between async-capable Perl web servers and applications. If you're familiar with Python's ecosystem, think of it as Perl's answer to ASGI.
A PAGI application is an async coderef with three parameters:
use Future::AsyncAwait;
use experimental 'signatures';
async sub app ($scope, $receive, $send) {
# $scope - connection metadata (type, headers, path, etc.)
# $receive - async coderef to get events from the client
# $send - async coderef to send events to the client
}
The $scope->{type} tells you what kind of connection you're handling:
| Type | Description |
|---|---|
http |
Standard HTTP request/response |
websocket |
Persistent WebSocket connection |
sse |
Server-Sent Events stream |
lifespan |
Application startup/shutdown lifecycle |
A Simple HTTP Example
Here's "Hello World" in raw PAGI:
use Future::AsyncAwait;
async sub app ($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,
});
}
Run it:
pagi-server --app app.pl --port 5000
curl http://localhost:5000/
# => Hello from PAGI!
The response is split into http.response.start (headers) and http.response.body (content). This separation enables streaming—send multiple body chunks with more => 1 before the final more => 0.
WebSocket Support
WebSockets are first-class citizens in PAGI:
async sub app ($scope, $receive, $send) {
if ($scope->{type} eq 'websocket') {
await $send->({ type => 'websocket.accept' });
while (1) {
my $event = await $receive->();
if ($event->{type} eq 'websocket.receive') {
my $msg = $event->{text} // $event->{bytes};
await $send->({
type => 'websocket.send',
text => "Echo: $msg",
});
}
elsif ($event->{type} eq 'websocket.disconnect') {
last;
}
}
}
else {
die "Unsupported: $scope->{type}";
}
}
The event loop pattern is consistent across all connection types: await events from $receive, send responses via $send.
PSGI Compatibility
One of PAGI's key features is backward compatibility with PSGI. The PAGI::App::WrapPSGI adapter lets you run existing PSGI applications on a PAGI server:
use PAGI::App::WrapPSGI;
# Your existing Catalyst/Dancer/Plack app
my $psgi_app = MyApp->psgi_app;
my $wrapper = PAGI::App::WrapPSGI->new(psgi_app => $psgi_app);
$wrapper->to_app;
The wrapper handles all the translation: building %env from PAGI scope, collecting request bodies, and converting responses back to PAGI events.
This means you can:
- Run legacy applications on a PAGI server
- Add WebSocket endpoints alongside existing routes
- Migrate incrementally from PSGI to PAGI
- Share connection pools between old and new code
PAGI::Simple Micro-Framework
For rapid development, PAGI ships with a micro-framework inspired by Express.js:
use PAGI::Simple;
my $app = PAGI::Simple->new(name => 'My API');
$app->get('/' => sub ($c) {
$c->text('Hello, World!');
});
$app->get('/users/:id' => sub ($c) {
my $id = $c->path_params->{id};
$c->json({ user_id => $id });
});
$app->post('/api/data' => sub ($c) {
my $data = $c->json_body;
$c->json({ received => $data, status => 'ok' });
});
$app->to_app;
WebSockets are equally clean:
$app->websocket('/chat' => sub ($ws) {
$ws->on(message => sub ($data) {
$ws->broadcast("Someone said: $data");
});
});
PAGI::Simple includes:
- Express-style routing with path parameters
- JSON request/response helpers
- Session management
- Middleware support (CORS, logging, rate limiting, etc.)
- Static file serving
- WebSocket rooms and broadcasting
- SSE channels with pub/sub
Current Status
PAGI is currently in early beta. The test suite passes, the examples work, but it hasn't been battle-tested in production.
What exists today:
- Complete PAGI specification
- Reference server implementation (
PAGI::Server) - PAGI::Simple micro-framework
- 13 example applications
- PSGI compatibility layer
- 483 passing tests
What it needs:
- Developers willing to experiment and provide feedback
- Real-world testing
- Framework authors interested in building on PAGI
- Performance profiling and optimization
Getting Started
git clone https://github.com/jjn1056/pagi.git
cd pagi
cpanm --installdeps .
prove -l t/
# Try the examples
pagi-server --app examples/01-hello-http/app.pl --port 5000
pagi-server --app examples/simple-01-hello/app.pl --port 5000
Why This Matters
Perl has excellent async primitives (IO::Async, Future::AsyncAwait), but no shared specification for async web applications. Each framework implements its own approach, which limits interoperability.
PAGI provides that shared foundation. By standardizing on a common interface:
- Servers can focus on performance and protocol handling
- Frameworks can focus on developer experience
- Middleware becomes portable across implementations
- The ecosystem can grow together rather than in isolation
If you're interested in the future of async Perl web development, I'd love your feedback. Check out the repository, try the examples, and let me know what you think.
Repository: github.com/jjn1056/pagi
PAGI is not yet on CPAN. It's experimental software—please don't use it in production unless you really know what you're doing.
Top comments (2)
Does PAGI's PSGI compatability wrapper extend to "Delayed Response and Streaming Body"? Will there be a way to do the reverse, i.e. be able to run PAGI applications on a PSGI server like Starman, using the responder and writer?
I have no plan to write a PAGI to PSGI wrapper mostly because of things like web socket support, etc., it would be very hard to get it to work. I guess it's feasible but I personally don't need it. My testing on PAGI::Server is showing its faster than Starman and scales better, abet with slightly higher per request latency at this time. So I don't think I'd need such a thing. I'd be happy to mentor someone on it or if a company really wants this I'd be happy to propose a bid on the work.