NAME
PAGI::Context - Per-request context with protocol-specific subclasses
SYNOPSIS
use PAGI::Context;
use Future::AsyncAwait;
# Factory returns the right subclass based on scope type
my $ctx = PAGI::Context->new($scope, $receive, $send);
# Shared methods (all protocol types)
my $type = $ctx->type; # 'http', 'websocket', 'sse'
my $path = $ctx->path;
my $stash = $ctx->stash; # PAGI::Stash
my $session = $ctx->session; # PAGI::Session
# WebSocket context - protocol ops directly on $ctx
await $ctx->accept;
await $ctx->send_json({ msg => 'hello' });
my $text = await $ctx->receive_text;
await $ctx->close;
# SSE context - same idea
await $ctx->send_event(event => 'update', data => $payload);
await $ctx->keepalive(25);
# Event dispatcher - works on any protocol type
my $reason = await $ctx
->on('websocket.receive', async sub { ... })
->on('chat.message', async sub { ... })
->on_error(sub { ... })
->run; # returns 'disconnect', 'stop', or 'error'
# Underlying protocol objects still available
my $ws = $ctx->websocket; # PAGI::WebSocket (WS only)
my $sse = $ctx->sse; # PAGI::SSE (SSE only)
my $req = $ctx->request; # PAGI::Request (HTTP only)
my $res = $ctx->response; # PAGI::Response (HTTP only)
DESCRIPTION
PAGI::Context is a factory and base class that provides a unified entry point for per-request context. Calling PAGI::Context->new(...) inspects $scope->{type} and returns the appropriate subclass: PAGI::Context::HTTP, PAGI::Context::WebSocket, or PAGI::Context::SSE.
Shared methods (scope accessors, stash, session, event dispatcher) live on the base class. Protocol-specific methods are delegated from subclasses so you can use $ctx as your single object:
# Instead of:
my $ws = $ctx->websocket;
await $ws->send_json($data); # closes over $ws in every handler
# Just do:
await $ctx->send_json($data); # $ctx is already in scope
Protocol Shape
Each context type has a different set of available methods. Calling a method that belongs to a different protocol type raises a standard Perl Can't locate object method error.
Method HTTP WebSocket SSE
────────────────── ────── ────────── ──────
request, response yes - -
method yes - -
accept - yes -
send_text - yes -
send_bytes - yes -
send_json - yes yes
send - - yes
send_event - - yes
send_comment - - yes
start - - yes
close - yes yes
query / query_param - yes(query) yes(query_param)
is_connected base* WS override -
is_closed - yes yes
is_started - - yes
keepalive - yes yes
each_text, etc. - yes -
each, every - - yes
*is_connected on WebSocket contexts checks WS handshake state,
not the TCP-level pagi.connection that the base class uses.
See PAGI::Context::WebSocket and PAGI::Context::SSE for the full method reference on each subclass.
EXTENSIBILITY
Override _type_map to add or replace protocol types:
package MyApp::Context;
our @ISA = ('PAGI::Context');
sub _type_map {
my ($class) = @_;
return {
%{ $class->SUPER::_type_map },
grpc => 'MyApp::Context::GRPC',
};
}
Override _resolve_class for custom resolution logic beyond the type map.
CONSTRUCTOR
new
my $ctx = PAGI::Context->new($scope, $receive, $send);
Factory constructor. Returns a subclass instance based on $scope->{type}. Defaults to HTTP if type is missing or unknown.
CLASS METHODS
_type_map
my $map = PAGI::Context->_type_map;
Returns a hashref mapping scope type strings to subclass package names. Override in a subclass to add or replace protocol types.
_resolve_class
my $class = PAGI::Context->_resolve_class($scope);
Resolves the scope to a subclass package name. Looks up $scope->{type} in _type_map; defaults to the http mapping if the type is missing or unknown. Override for custom resolution logic.
METHODS
Scope Accessors
$ctx->scope; # raw $scope hashref
$ctx->type; # $scope->{type}
$ctx->path; # $scope->{path}
$ctx->raw_path; # $scope->{raw_path} // $scope->{path}
$ctx->query_string; # $scope->{query_string} // ''
$ctx->scheme; # $scope->{scheme} // 'http'
$ctx->client; # $scope->{client}
$ctx->server; # $scope->{server}
$ctx->headers; # $scope->{headers} arrayref of [name, value]
Path Parameters
my $params = $ctx->path_params; # hashref
my $id = $ctx->path_param('id'); # strict: dies if missing
my $id = $ctx->path_param('id', strict => 0); # returns undef
path_params returns the $scope->{path_params} hashref (set by the router), defaulting to {} if not present.
path_param returns a single parameter by name. By default it dies if the key is not found (strict mode). Pass strict => 0 to return undef for missing keys instead.
Protocol Introspection
$ctx->is_http; # true if type eq 'http'
$ctx->is_websocket; # true if type eq 'websocket'
$ctx->is_sse; # true if type eq 'sse'
header
my $value = $ctx->header('Content-Type');
Returns the last value for the named header (case-insensitive), or undef if not found.
receive
my $receive = $ctx->receive;
Returns the raw $receive coderef. Calling it returns a Future that resolves to the next protocol event hashref from the client.
# Read an HTTP request body event
my $event = await $ctx->receive->();
# $event = { type => 'http.request', body => '...' }
# Read a WebSocket message
my $msg = await $ctx->receive->();
# $msg = { type => 'websocket.receive', text => 'hello' }
Most users should prefer the protocol helpers ($ctx->request, $ctx->websocket, $ctx->sse) which handle the event protocol internally. Use receive only for raw protocol access.
send
my $send = $ctx->send;
Returns the raw $send coderef. Calling it with an event hashref returns a Future that resolves when the event has been sent.
# Send an HTTP response (two events: start + body)
await $ctx->send->({ type => 'http.response.start', status => 200,
headers => [['content-type', 'text/plain']] });
await $ctx->send->({ type => 'http.response.body', body => 'Hello' });
# Accept a WebSocket connection
await $ctx->send->({ type => 'websocket.accept' });
Most users should prefer the protocol helpers ($ctx->response, $ctx->websocket, $ctx->sse) which build and send events for you. Use send only for raw protocol access.
stash
my $stash = $ctx->stash; # PAGI::Stash instance
Returns a PAGI::Stash wrapping $scope->{'pagi.stash'}. Lazy-constructed and cached.
session
my $session = $ctx->session; # PAGI::Session instance
Returns a PAGI::Session wrapping $scope->{'pagi.session'}. Lazy-constructed and cached. Dies if session middleware has not run. Use has_session to check availability first.
has_session
if ($ctx->has_session) {
my $user_id = $ctx->session->get('user_id');
}
Returns true if session middleware has populated $scope->{'pagi.session'}.
state
my $state = $ctx->state; # hashref
Returns $scope->{state} - the app/endpoint-level shared state.
Connection State
$ctx->connection; # PAGI::Server::ConnectionState object
$ctx->is_connected; # boolean
$ctx->is_disconnected; # boolean
$ctx->disconnect_reason; # string or undef
$ctx->on_disconnect($cb); # register callback
Delegates to $scope->{'pagi.connection'}.
EVENT DISPATCHER
The event dispatcher provides a generic, protocol-agnostic way to handle PAGI events. It is most useful when the receive stream carries a mix of protocol events and application-level events injected by middleware such as PAGI::Middleware::Channels.
my $ctx = PAGI::Context->new($scope, $receive, $send);
$ctx->on('websocket.receive', async sub {
my ($ctx, $event) = @_;
my $text = $event->{text} // '';
await $ctx->send->({ type => 'websocket.send', text => "echo: $text" });
});
$ctx->on('chat.message', async sub {
my ($ctx, $event) = @_;
# handle a channel-injected event
});
$ctx->on_error(sub {
my ($ctx, $error, $source) = @_;
warn "[$source] $error";
});
my $reason = await $ctx->run; # 'disconnect', 'stop', or 'error'
on
$ctx->on($event_type, $callback); # returns $ctx
Register a handler for a raw PAGI event type string. Multiple handlers may be registered for the same type; they are called in registration order. Handlers receive ($ctx, $event). Handlers may be plain coderefs or async subs; if a handler returns a Future, run() awaits it before continuing.
Returns $ctx for chaining.
on_error
$ctx->on_error($callback); # returns $ctx
Register an error callback. It is called when $receive->() fails ($source = 'receive') or when a registered handler throws ($source = 'handler'). Callbacks receive ($ctx, $error, $source).
Multiple callbacks may be registered and are called in order. Callbacks may be async subs; if a callback returns a Future, it is awaited. If no callbacks are registered, errors are emitted via warn.
Returns $ctx for chaining.
# Avoid circular references - weaken if the callback closes over $ctx
use Scalar::Util qw(weaken);
my $weak = $ctx;
weaken $weak;
$ctx->on_error(sub { my ($ctx, $err, $src) = @_; warn "[$src] $err" });
stop
$ctx->stop; # returns $ctx
Signal the run() loop to exit cleanly after the current handler finishes. run() will resolve with reason 'stop'.
Returns $ctx for chaining.
run
my $reason = await $ctx->run;
Start the event dispatch loop. Reads events from the receive stream and dispatches each to registered handlers. The loop runs until one of:
The protocol's terminal disconnect event arrives (
websocket.disconnect,sse.disconnect,http.disconnect) - resolves with'disconnect'stop()was called - resolves with'stop'$receive->()fails - fireson_errorcallbacks and resolves with'error'
run() always resolves successfully (never rejects). The caller does not need to catch it.
Calling run() a second time while already running throws synchronously.
When run() resolves, all registered handlers and error callbacks are cleared to break closure-based reference cycles.
SEE ALSO
Protocol subclasses (full method reference for each protocol):
PAGI::Context::HTTP, PAGI::Context::WebSocket, PAGI::Context::SSE
Underlying protocol objects (standalone use, or direct access via $ctx->websocket, $ctx->sse, $ctx->request, $ctx->response):
PAGI::WebSocket, PAGI::SSE, PAGI::Request, PAGI::Response
Shared services:
1 POD Error
The following errors were encountered while parsing the POD:
- Around line 77:
Non-ASCII character seen before =encoding in '──────────────────'. Assuming UTF-8