NAME
PAGI::Middleware - Base class for PAGI middleware
SYNOPSIS
# Create a custom middleware
package My::Middleware;
use parent 'PAGI::Middleware';
sub wrap {
my ($self, $app) = @_;
return async sub {
my ($scope, $receive, $send) = @_;
# Modify scope for inner app
my $modified_scope = $self->modify_scope($scope, {
custom_key => 'custom_value',
});
# Call inner app with modified scope
await $app->($modified_scope, $receive, $send);
};
}
# Use the builder DSL
use PAGI::Middleware::Builder;
my $app = builder {
enable 'My::Middleware', option => 'value';
enable_if { $_[0]->{path} =~ m{^/api/} } 'RateLimit', limit => 100;
mount '/static' => $static_app;
$my_app;
};
DESCRIPTION
PAGI::Middleware provides a base class for implementing middleware that wraps PAGI applications. Middleware can modify the request scope, intercept responses, or perform cross-cutting concerns like logging, authentication, or compression.
METHODS
new
my $middleware = PAGI::Middleware->new(%config);
Create a new middleware instance. Configuration options are stored and accessible via $self->{config}.
_init
$self->_init(\%config);
Hook for subclasses to perform initialization. Called by new(). Default implementation does nothing.
wrap
my $wrapped_app = $middleware->wrap($app);
Wrap a PAGI application. Returns a new async sub that handles the middleware logic. Subclasses MUST override this method.
modify_scope
my $new_scope = $self->modify_scope($scope, \%additions);
Create a new scope with additional keys, without mutating the original. This is the recommended way to pass additional data to inner apps.
intercept_send
my $wrapped_send = $self->intercept_send($send, \&interceptor);
Wrap the $send callback to intercept outgoing events. The interceptor is called with ($event, $original_send) and should return a Future.
my $wrapped_send = $self->intercept_send($send, async sub {
my ($event, $original_send) = @_;
if ($event->{type} eq 'http.response.start') {
# Modify headers
push @{$event->{headers}}, ['x-custom', 'value'];
}
await $original_send->($event);
});
buffer_request_body
my ($body, $final_event) = await $self->buffer_request_body($receive);
Collect all request body chunks into a single string. Returns the complete body and the final http.request event.
call
await $middleware->call($scope, $receive, $send, $app);
Convenience method to invoke the middleware with an app. Equivalent to:
my $wrapped = $middleware->wrap($app);
await $wrapped->($scope, $receive, $send);
WRITING MIDDLEWARE
Middleware follows the decorator pattern. Each middleware wraps an inner application and can:
Modify the scope before passing to the inner app
Intercept and modify the $send callback
Short-circuit the request (return early without calling inner app)
Perform actions before and after the inner app runs
Example: Logging Middleware
package PAGI::Middleware::Logger;
use parent 'PAGI::Middleware';
use Future::AsyncAwait;
use Time::HiRes 'time';
sub wrap {
my ($self, $app) = @_;
return async sub {
my ($scope, $receive, $send) = @_;
my $start = time();
my $status;
# Intercept send to capture status
my $wrapped_send = $self->intercept_send($send, async sub {
my ($event, $orig_send) = @_;
if ($event->{type} eq 'http.response.start') {
$status = $event->{status};
}
await $orig_send->($event);
});
# Call inner app
await $app->($scope, $receive, $wrapped_send);
# Log after completion
my $duration = time() - $start;
warn sprintf("%s %s %d %.3fs\n",
$scope->{method}, $scope->{path}, $status // 0, $duration);
};
}
SEE ALSO
PAGI::Middleware::Builder - DSL for composing middleware