PAGI-Tools
view release on metacpan or search on metacpan
lib/PAGI/Tools/Tutorial.pod view on Meta::CPAN
=encoding UTF-8
=head1 NAME
PAGI::Tools::Tutorial - Optional convenience helpers for PAGI applications
=head1 DESCRIPTION
PAGI itself is a protocol; see L<PAGI::Tutorial> for the protocol tutorial.
The C<PAGI-Tools> distribution adds B<optional> convenience helpers built on
top of that protocol: middleware, request/response sugar, WebSocket and SSE
helpers, routers, and ready-made applications. None of these are required to
write a complete PAGI application; they exist to save you boilerplate when you
want it, and every one of them is an ordinary PAGI application or a plain
wrapper around the C<$scope>/C<$receive>/C<$send> protocol. This guide covers
them.
=head1 PART 1: HELLO WORLD
A PAGI application is an C<async> sub that receives three arguments: C<$scope>
(the request metadata), C<$receive> (pull request-body events), and C<$send>
(push response events). At the raw protocol level, "hello world" emits two
events â the response start (status and headers) and the body:
use Future::AsyncAwait;
my $app = async sub {
my ($scope, $receive, $send) = @_;
await $send->({
type => 'http.response.start',
status => 200,
headers => [['content-type', 'text/plain']],
});
await $send->({
type => 'http.response.body',
body => 'Hello, world!',
});
};
That is a complete, server-ready application â no toolkit required. The helpers
in this distribution simply make the common cases shorter. Here is the same
response built as a L<PAGI::Response> value:
use Future::AsyncAwait;
use PAGI::Response;
my $app = async sub {
my ($scope, $receive, $send) = @_;
await PAGI::Response->text('Hello, world!')->respond($send);
};
C<PAGI::Response> assembles the two events for you, and C<respond> sends them.
Returning JSON is just as short:
await PAGI::Response->json({ hello => 'world' })->respond($send);
From here the tutorial builds up: routing requests to handlers (PART 2),
reading form and JSON input (PART 3), the full response builder (PART 4),
real-time and streaming (PART 5), middleware (PART 6), and composing larger
applications (PART 7).
=head1 PART 2: ROUTING
=head2 2.1 PAGI::App::Router - Basic Routing
L<PAGI::App::Router> provides lightweight functional routing:
use PAGI::App::Router;
use PAGI::Response;
my $router = PAGI::App::Router->new;
# Mount a static response value â dispatch sends it automatically
$router->mount('/health' => PAGI::Response->json({ ok => \1 }));
# HTTP routes
$router->get('/' => async sub {
my ($scope, $receive, $send) = @_;
my $res = PAGI::Response->new($scope);
await $res->text('Home')->respond($send);
});
$router->post('/users' => async sub {
my ($scope, $receive, $send) = @_;
my $res = PAGI::Response->new($scope);
await $res->json({ created => 1 }, status => 201)->respond($send);
});
# Path parameters
$router->get('/users/:id' => async sub {
my ($scope, $receive, $send) = @_;
my $req = PAGI::Request->new($scope, $receive);
my $res = $req->response;
my $id = $req->path_param('id');
await $res->json({ id => $id })->respond($send);
});
# WebSocket and SSE handlers drive $send imperatively â no return value
$router->websocket('/ws' => async sub { ... });
$router->sse('/events' => async sub { ... });
$router->to_app;
For advanced routing patterns (nested routers, route-level middleware, class-based routing), see L<PAGI::Tools::Cookbook>.
=head1 PART 3: HANDLING REQUESTS
Once a request is routed to a handler, L<PAGI::Request> turns the raw C<$scope>
and C<$receive> into a convenient object: query and path parameters, headers,
cookies, and â the focus of this part â form bodies, JSON bodies, and file
uploads. It handles UTF-8 decoding and body parsing for you.
=head2 3.1 PAGI::Request - Request Parsing
L<PAGI::Request> parses HTTP requests and provides convenient accessors for headers, query parameters, cookies, and request bodies.
=head3 Creating a Request
my $req = PAGI::Request->new($scope, $receive);
lib/PAGI/Tools/Tutorial.pod view on Meta::CPAN
await $app->($scope, $receive, $send);
};
}
=item * Intercept responses
Modify outgoing events:
sub wrap {
my ($self, $app) = @_;
return async sub {
my ($scope, $receive, $send) = @_;
my $wrapped_send = $self->intercept_send($send, async sub {
my ($event, $original_send) = @_;
if ($event->{type} eq 'http.response.start') {
push @{$event->{headers}}, ['x-custom', 'value'];
}
await $original_send->($event);
});
await $app->($scope, $receive, $wrapped_send);
};
}
=item * Short-circuit requests
Return early without calling inner app:
sub wrap {
my ($self, $app) = @_;
return async sub {
my ($scope, $receive, $send) = @_;
# Check condition
unless ($self->is_authorized($scope)) {
await $send->({
type => 'http.response.start',
status => 403,
headers => [['content-type', 'text/plain']],
});
await $send->({
type => 'http.response.body',
body => 'Forbidden',
});
return; # Don't call $app
}
await $app->($scope, $receive, $send);
};
}
=back
=head1 PART 7: COMPOSING APPLICATIONS
PAGI ships with several ready-to-use applications for common tasks. These can be mounted with routers or used standalone.
=head2 7.1 PAGI::App::File - Static Files
L<PAGI::App::File> serves static files with security, caching, and streaming:
use PAGI::App::File;
my $static = PAGI::App::File->new(root => './public');
$router->mount('/static' => $static);
Features:
=over 4
=item * Efficient streaming (large files don't consume memory)
=item * ETag caching with 304 Not Modified support
=item * HTTP Range requests for resume support
=item * Automatic MIME type detection
=item * Security: path traversal protection
=back
=head2 7.2 PAGI::App::Healthcheck - Health Endpoints
L<PAGI::App::Healthcheck> creates health check endpoints:
use PAGI::App::Healthcheck;
my $health = PAGI::App::Healthcheck->new(
version => '1.0.0',
checks => {
database => sub { $db && $db->ping },
cache => sub { $redis && $redis->ping },
},
);
$router->mount('/health' => $health);
=head2 7.3 PAGI::App::URLMap - Mount Applications
L<PAGI::App::URLMap> routes requests to different apps based on URL prefix:
use PAGI::App::URLMap;
my $urlmap = PAGI::App::URLMap->new;
$urlmap->mount('/api' => $api_app);
$urlmap->mount('/admin' => $admin_app);
$urlmap->mount('/static' => $static);
$urlmap->to_app;
=head2 7.4 PAGI::App::Cascade - Try Apps in Sequence
L<PAGI::App::Cascade> tries apps in order until one returns a non-404:
use PAGI::App::Cascade;
my $app = PAGI::App::Cascade->new(
apps => [$static, $api, $fallback],
catch => [404, 405],
);
=head2 7.5 PAGI::App::Proxy - Reverse Proxy
L<PAGI::App::Proxy> forwards requests to backend servers:
use PAGI::App::Proxy;
my $proxy = PAGI::App::Proxy->new(
backend => 'http://localhost:8080',
timeout => 30,
);
( run in 0.817 second using v1.01-cache-2.11-cpan-6aa56a78535 )