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