PAGI-Server
view release on metacpan or search on metacpan
t/http2/22-denial-response.t view on Meta::CPAN
$client->mem_recv($extra) if length($extra);
}
sub exchange_frames {
my ($client, $client_sock, $rounds) = @_;
$rounds //= 10;
for (1 .. $rounds) {
$loop->loop_once(0.1);
my $buf = '';
$client_sock->sysread($buf, 16384);
$client->mem_recv($buf) if length($buf);
my $out = $client->mem_send;
$client_sock->syswrite($out) if length($out);
}
}
# ============================================================
# Extension is advertised on h2 WebSocket scope; custom denial response
# ============================================================
subtest 'h2 WebSocket scope advertises denial-response extension' => sub {
my $captured_scope;
my $app = async sub {
my ($scope, $receive, $send) = @_;
$captured_scope = $scope if $scope->{type} eq 'websocket';
await $receive->(); # websocket.connect
await $send->({
type => 'websocket.http.response.start',
status => 401,
headers => [['x-deny', 'auth']],
});
await $send->({
type => 'websocket.http.response.body',
body => 'nope',
});
return;
};
my ($conn, $stream_io, $client_sock, $server) = create_h2_connection(app => $app);
my %headers;
my $body = '';
my $client = create_client(
on_header => sub { my ($sid, $n, $v) = @_; $headers{$n} = $v; return 0 },
on_data_chunk_recv => sub { my ($sid, $d) = @_; $body .= $d; return 0 },
);
complete_h2_handshake($client, $client_sock);
# Extended CONNECT (RFC 8441) â matches t/http2/07-websocket.t submit_request
$client->submit_request(
method => 'CONNECT',
path => '/ws/test',
scheme => 'https',
authority => 'localhost',
headers => [
[':protocol', 'websocket'],
['sec-websocket-version', '13'],
],
body => sub { return undef }, # streaming: keep open
);
$client_sock->syswrite($client->mem_send);
exchange_frames($client, $client_sock, 20);
ok(defined $captured_scope, 'app was called with a websocket scope');
ok(
$captured_scope && $captured_scope->{extensions}{'websocket.http.response'},
'extension websocket.http.response is advertised on h2 ws scope',
);
is($headers{':status'}, '401', 'h2 denial uses custom 401, not 200 or 403');
is($headers{'x-deny'}, 'auth', 'custom denial header is present');
like($body, qr/nope/, 'custom body is present');
$stream_io->close_now;
$loop->remove($server);
};
# ============================================================
# Denial with multi-chunk body (more => 1 then more => 0)
# ============================================================
subtest 'h2 denial buffers multiple body chunks until more=>0' => sub {
my $app = async sub {
my ($scope, $receive, $send) = @_;
await $receive->();
await $send->({
type => 'websocket.http.response.start',
status => 429,
headers => [['retry-after', '60']],
});
await $send->({ type => 'websocket.http.response.body', body => 'try ', more => 1 });
await $send->({ type => 'websocket.http.response.body', body => 'later', more => 0 });
return;
};
my ($conn, $stream_io, $client_sock, $server) = create_h2_connection(app => $app);
my %headers;
my $body = '';
my $client = create_client(
on_header => sub { my ($sid, $n, $v) = @_; $headers{$n} = $v; return 0 },
on_data_chunk_recv => sub { my ($sid, $d) = @_; $body .= $d; return 0 },
);
complete_h2_handshake($client, $client_sock);
$client->submit_request(
method => 'CONNECT',
path => '/ws/rate-limited',
scheme => 'https',
authority => 'localhost',
headers => [
[':protocol', 'websocket'],
['sec-websocket-version', '13'],
],
body => sub { return undef },
);
$client_sock->syswrite($client->mem_send);
exchange_frames($client, $client_sock, 20);
( run in 0.850 second using v1.01-cache-2.11-cpan-995e09ba956 )