PAGI-Tools

 view release on metacpan or  search on metacpan

lib/PAGI/Middleware.pm  view on Meta::CPAN


sub new {
    my ($class, %config) = @_;

    my $self = bless {
        config => \%config,
    }, $class;
    $self->_init(\%config);
    return $self;
}

=head2 _init

    $self->_init(\%config);

Hook for subclasses to perform initialization. Called by new().
Default implementation does nothing.

=cut

sub _init {
    my ($self, $config) = @_;

    # Subclasses can override
}

=head2 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.

=cut

sub wrap {
    my ($self, $app) = @_;

    die "Subclass must implement wrap()";
}

=head2 modify_scope

    my $new_scope = $self->modify_scope($scope, \%additions);

Return a new scope -- a shallow copy of C<$scope> with C<%additions> merged in --
B<without mutating the original>. This is the supported way to pass extra data to
inner apps, and the canonical middleware in this distribution use it.

Why copy instead of writing C<< $scope->{key} = ... >> directly? Middleware form a
stack, and the scope you are handed is shared with the layers around you. Mutating
it in place lets your change leak B<upward> to parent and sibling layers (and, for
a long-lived WebSocket scope, persist across every event on the connection).
Copying keeps your additions B<downward only> -- seen by the inner app you call,
invisible to everyone above.

The copy is B<shallow> on purpose. Top-level keys you add are private to the new
scope, but values that are B<references> -- the C<pagi.connection> object, the
lifespan C<state> namespace, the stash -- are shared, so the inner app still sees
the same connection state and shared objects. A deep copy would sever those. One
corollary worth knowing: a plain B<scalar> set as a top-level scope key does not
propagate back to outer layers, so to share mutable state across layers you mutate
B<through a reference> (see L<PAGI::Stash>). The full model -- and why it works
this way -- is in L<PAGI::Building/MIDDLEWARE>.

=cut

sub modify_scope {
    my ($self, $scope, $additions) = @_;
    $additions //= {};

    return { %$scope, %$additions };
}

=head2 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);
    });

=cut

sub intercept_send {
    my ($self, $send, $interceptor) = @_;

    return async sub  {
        my ($event) = @_;
        await $interceptor->($event, $send);
    };
}

=head2 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.

=cut

async sub buffer_request_body {
    my ($self, $receive) = @_;

    my $body = '';
    my $event;

    while (1) {
        $event = await $receive->();

        if ($event->{type} eq 'http.request') {



( run in 3.780 seconds using v1.01-cache-2.11-cpan-7fcb06a456a )