PAGI
view release on metacpan or search on metacpan
lib/PAGI/Middleware/ETag.pm view on Meta::CPAN
package PAGI::Middleware::ETag;
use strict;
use warnings;
use parent 'PAGI::Middleware';
use Future::AsyncAwait;
use Digest::MD5 qw(md5_hex);
=head1 NAME
PAGI::Middleware::ETag - ETag generation middleware
=head1 SYNOPSIS
use PAGI::Middleware::Builder;
my $app = builder {
enable 'ETag';
$my_app;
};
=head1 DESCRIPTION
PAGI::Middleware::ETag generates ETag headers for responses based on
the response body content. Works best with buffered (non-streaming) responses.
=head1 CONFIGURATION
=over 4
=item * weak (default: 0)
If true, generate weak ETags (W/"...").
=back
=cut
sub _init {
my ($self, $config) = @_;
$self->{weak} = $config->{weak} // 0;
}
sub wrap {
my ($self, $app) = @_;
return async sub {
my ($scope, $receive, $send) = @_;
if ($scope->{type} ne 'http') {
await $app->($scope, $receive, $send);
return;
}
my @body_parts;
my $original_headers;
my $status;
my $is_streaming = 0;
my $wrapped_send = async sub {
my ($event) = @_;
if ($event->{type} eq 'http.response.start') {
$status = $event->{status};
$original_headers = $event->{headers};
# Check if already has ETag
for my $h (@{$original_headers // []}) {
if (lc($h->[0]) eq 'etag') {
# Already has ETag, pass through
await $send->($event);
$is_streaming = 1; # Flag to pass through body
return;
}
}
}
elsif ($event->{type} eq 'http.response.body') {
if ($is_streaming) {
await $send->($event);
return;
}
push @body_parts, $event->{body} // '';
# If streaming, can't generate ETag
if ($event->{more}) {
$is_streaming = 1;
await $send->({
type => 'http.response.start',
status => $status,
headers => $original_headers,
});
for my $part (@body_parts) {
await $send->({
type => 'http.response.body',
body => $part,
more => 1,
});
}
@body_parts = ();
}
}
else {
await $send->($event);
}
};
await $app->($scope, $receive, $wrapped_send);
return if $is_streaming;
# Generate ETag from body
my $body = join('', @body_parts);
my $etag = $self->_generate_etag($body);
# Add ETag to headers
my @new_headers = @{$original_headers // []};
push @new_headers, ['ETag', $etag];
await $send->({
type => 'http.response.start',
status => $status,
headers => \@new_headers,
});
await $send->({
type => 'http.response.body',
body => $body,
more => 0,
});
};
}
sub _generate_etag {
my ($self, $body) = @_;
my $hash = md5_hex($body);
if ($self->{weak}) {
return qq{W/"$hash"};
}
return qq{"$hash"};
}
1;
__END__
=head1 SEE ALSO
L<PAGI::Middleware> - Base class for middleware
L<PAGI::Middleware::ConditionalGet> - Use with ETag for 304 responses
=cut
( run in 0.490 second using v1.01-cache-2.11-cpan-140bd7fdf52 )