NAME
PAGI::Spec - PAGI Specification Documentation
NOTICE
This documentation is auto-generated from the PAGI specification markdown files. For the authoritative source, see:
https://github.com/jjn1056/PAGI/tree/main/docs/specs
PAGI (Perl Asynchronous Gateway Interface) Specification
Note: All code examples use modern Perl with subroutine signatures and `` for clarity. This is a documentation preference and not a requirement of the PAGI specification.
Version: 0.1 (Draft)
Introduction
While work has been done to support asynchronous response handling in PSGI (the psgi.nonblocking environment key), support for protocols with multiple input events (such as WebSockets) remained hacks. The use of non-blocking or asynchronous handling based on PSGI never took off. The PSGI specification hasn't changed since July 2013. Since then, asynchronous programming in has taken off in many programming languages, including in Perl: although Perl does not natively support the async/await pattern like Python or JavaScript, the module Future::AsyncAwait implements the pattern since 24 January 2018 for Perl versions 5.16 and up.
Considering the above and the fact that similar specifications have existed for several years now for other languages, the time has arrived to create a fully asynchronous webserver gateway interface specification for Perl.
================================================================================ Perl does not natively support Pythonâs async/await pattern, but we can achieve similar functionality using Futures (from Future.pm) and IO::Async (from IO::Async::Loop). Below is how we translate key ASGI components into Perl.
================================================================================
Abstract
This document proposes a standard interface between network protocol servers (particularly web servers) and Perl applications, intended to support multiple common protocol styles (including HTTP/1.1, HTTP/2, and WebSocket).
This base specification defines the APIs by which servers interact with and run application code. Each supported protocol (such as HTTP) includes a sub-specification detailing how to encode and decode that protocol into structured messages.
Rationale
PSGI has worked well as a standard interface for synchronous Perl web applications. However, its design is tied to the HTTP-style request/response cycle, and cannot support full-duplex protocols such as WebSockets.
PAGI preserves a simple application interface while introducing a fully asynchronous message-based abstraction, enabling data to be sent and received at any time during a connection's lifecycle.
It defines:
- -
-
A standardized interface for communication between server and application.
- -
-
A set of message formats per protocol.
The primary goal is to support WebSockets, HTTP/2, and Server-Sent Events (SSE) alongside HTTP/1.1, while maintaining compatibility with existing PSGI applications through a transitional adapter layer.
Overview
PAGI consists of two main components:
- -
-
A protocol server which manages sockets and translates network events into connection-level messages.
- -
-
An application which is invoked once per connection and communicates via asynchronous message passing.
Applications are written as asynchronous subroutines using Future and IO::Async. They receive a scope describing the connection, and two coderefs, $recv and $send, which return Futures representing event input and output.
Unlike PSGI, PAGI applications persist for the entire connection lifecycle. They process incoming events from the server and emit outgoing events in response.
Two important concepts in PAGI:
- -
-
Connection scope: A hashref describing the connection.
- -
-
Events: Hashrefs representing messages sent/received during the connection.
Specification Details
Connection Scope
Each incoming connection causes the application to be invoked with a scope hashref. Its keys include:
- -
-
type: Protocol type, e.g.,http,websocket - -
-
pagi: A hashref with at least: - -
-
version => '0.1' - -
-
spec_version => '0.1'(optional; protocol-specific override if it diverges) - -
-
features(optional): a hashref of server-reported capabilities such as:
Clarification on PAGI version keys: - version: Signals the core PAGI specification version that governs scopes/events. This value must be a string. - spec_version: Indicates the version for the specific protocol (such as HTTP or WebSocket). Servers may omit it, in which case clients should assume it matches version. - features: Server-defined key/value pairs that describe optional behaviors for the connection. Values should use the permitted data types from this specification. - Additional keys may be defined per protocol specification (e.g., HTTP, WebSocket) and must be documented there.
The scope describes the full lifetime of the connection, and persists until the connection is closed. Some protocols (e.g., HTTP) may treat each request as a distinct scope, while others (e.g., WebSocket) maintain one persistent scope for the entire session.
Specifically: - HTTP treats each incoming request (for example, GET or POST requests) as a separate scope that exists only for the life of that request-response cycle. After the response is sent fully, the scope is discarded. - WebSocket connection establishes a single scope at connection initiation, persisting for the full duration of the continuous message exchanges until the WebSocket connection closes.
Applications may need to wait for an initial event before sending any messages, depending on the protocol specification.
If the application does not recognize or support scope->{type}, it MUST throw an exception. The PAGI server must handle such exceptions gracefully, by closing the connection promptly, optionally logging the issue and providing relevant protocol-specific error responses or messages.
This explicit requirement ensures that servers never assume application-protocol compatibility without explicit support declared by implementations.
Events
PAGI defines communication in terms of discrete events.
The type key in each event must be a namespaced string of the form protocol.message_type, such as http.request or websocket.send. This convention ensures clear protocol dispatching and avoids naming collisions.
Reserved type prefixes include:
- -
-
httpâ standard HTTP events - -
-
websocketâ WebSocket events - -
-
lifespanâ process lifecycle events - -
-
pagiâ reserved for future PAGI-defined system events - -
-
ext.â reserved for experimental or nonstandard events
Custom user-defined protocols should avoid clashing with these prefixes.
my $event = await $recv->();
await $send->({ type => 'http.response.start', ... });
Permitted Event Data Types
The following types are permitted in event data structures:
- -
-
Strings (Perl scalars):
- -
-
Text strings: Strings containing decoded Unicode characters. These must be explicitly encoded according to the relevant protocol before serialization or transmission.
- -
-
Byte strings: Strings containing opaque binary data. Their intended encoding or interpretation, if applicable, must be explicitly specified by the relevant protocol.
I<Note>: Do B<not> rely on Perl's internal UTF-8 flag (C<utf8::is_utf8>) to distinguish between text and binary strings. The protocol or application context must explicitly indicate the intent. - -
-
Integers: Signed 64-bit integers. While Perl supports arbitrary-size integers, PAGI restricts integers to this range to ensure interoperability across serialization formats and programming languages.
Integers commonly stand in for boolean values (C<0> or C<1>). When a protocol flag is described as boolean it B<must> still use integers (never custom barewords) so transports remain consistent. - -
-
Floating-point numbers: IEEE 754 double-precision floats. Special values (
NaN,Inf,-Inf) are not permitted, due to inconsistent cross-platform and serialization support. - -
-
undef: Represents the absence of a value. - -
-
Arrayrefs: Arrays containing only permitted data types.
- -
-
Hashrefs: Hashes with keys stored as byte strings.
- -
-
If keys represent textual data, they must be explicitly encoded into UTF-8 byte strings by the application.
- -
-
Values must contain only permitted data types.
Note: Booleans are intentionally omitted from the core spec to avoid ambiguity in Perl, which lacks a native boolean type. In contexts where ASGI would use true/false, PAGI protocol specifications will define expected values explicitly â usually 0 or 1, or by the presence/absence of a defined value.
Applications
PAGI applications are single coderefs returning Futures.
Applications may optionally support lifecycle hooks such as shutdown. If supported, the PAGI server should call:
$app->on_shutdown(sub {
# Optional shutdown logic (e.g., flush logs, close DB)
});
This pattern is reserved and may be formalized in a future PAGI extension.
Applications must throw an exception if the incoming scope->{type} is not supported. This prevents the server from assuming a protocol is handled when it is not, and avoids ambiguity in multi-protocol deployments.
Each application is expected to run for the duration of a connection and may remain alive briefly after disconnect to perform cleanup. The interface is:
use Future::AsyncAwait;
async sub application ($scope, $recv, $send) {
if ($scope->{type} ne 'http') {
die "Unsupported protocol type: $scope->{type}";
}
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,
});
return;
}
=over
- -
-
$scope: Hashref with connection metadata - -
-
$recv: Coderef returning aFutureresolving to the next event - -
-
$send: Coderef taking an event hashref and returning aFuture
Each application is called per-connection, and remains active until the connection closes and cleanup completes.
Legacy Applications
Legacy (PSGI) applications are not async and follow a synchronous interface. Support for these can be implemented via a compatibility adapter.
Adapters must call the PSGI app once with $env and transform the response into PAGI event messages.
Protocol Specifications
Each protocol defines:
Common examples:
- -
-
http.request,http.response.start,http.response.body - -
-
websocket.connect,websocket.receive,websocket.send
The type field in scope and events identifies the protocol and message:
$event->{type} eq 'http.request';
$scope->{type} eq 'websocket';
Applications B<must> throw an exception if the protocol is unknown.
Current protocol specifications:
Middleware
PAGI middleware wraps an application:
use Future::AsyncAwait;
sub middleware ($app) {
return async sub ($scope, $recv, $send) {
my $modified_scope = { %$scope };
$modified_scope->{custom} = 1;
my $result = eval { await $app->($modified_scope, $recv, $send) };
if (my $error = $@) {
# Middleware MUST propagate exceptions clearly without silently swallowing them.
# Optionally, middleware can log or handle the error explicitly before rethrowing.
warn "Middleware encountered an application error: $error";
die $error;
}
return $result;
# Middleware should default to shallow cloning and explicitly deep clone if required.
};
}
Middleware must not mutate the original $scope in-place; always clone before modification.
Cancellation and Disconnects
If the server detects client-driven cancellation (e.g., HTTP/2 resets or WebSocket disconnects), it must deliver the appropriate disconnect event to the application:
- -
-
HTTP:
{ type => 'http.disconnect' } - -
-
WebSocket:
{ type => 'websocket.disconnect' } - -
-
Other protocols: see their respective specifications for the canonical event name.
Applications receiving these events should immediately halt unnecessary work, optionally send minimal acknowledgments if the protocol allows it, and perform cleanup for open resources or background tasks. Servers must also ensure timely cleanup after the application finishes its cancellation handling.
Error Handling
Servers must raise exceptions if:
- -
-
Events are missing required fields
- -
-
Event fields are of the wrong type
- -
-
The
typefield is unrecognized
Applications should do the same when receiving malformed events.
Extra fields in events must not cause errors â this supports forward-compatible upgrades.
If $send is called after connection closure, it should be a no-op unless specified otherwise.
Extensions
Servers may expose additional features via the extensions key in the scope:
$scope->{extensions} = {
fullflush => {},
};
Applications may send new events such as:
await $send->({ type => 'http.fullflush' });
Only if the server declares support in C<extensions>.
Strings and Unicode
All keys in scope and event hashrefs must be strings that are valid UTF-8 when interpreted as bytes. The UTF-8 flag is not required, but the keys must decode cleanly as UTF-8.
Byte content (e.g., body payloads) must be passed as Perl scalars without the UTF-8 flag set. Applications are responsible for encoding/decoding appropriately.
Version History
Copyright
This document is placed in the public domain.
1 POD Error
The following errors were encountered while parsing the POD:
- Around line 339:
'=item' outside of any '=over'