PAGI-Server
view release on metacpan or search on metacpan
t/53-trailers-framing.t view on Meta::CPAN
my $http = Net::Async::HTTP->new;
$loop->add($http);
# --- /cl-trailers: content-length response, trailers fail loudly, and the --
# app-return afterward now closes the connection abnormally (Phase 3 Task 2).
# Raw socket, not $http: we need to observe the connection actually close
# (EOF with no further bytes) rather than have a pooled client's transparent
# reconnect hide it. Deliberately no "Connection: close" request header --
# HTTP/1.1 keep-alive is the client's default; the server must veto it
# anyway. $http itself is left untouched here, so its first use below (for
# /head-trailers) is naturally a fresh connection.
my @cl_warnings;
{
local $SIG{__WARN__} = sub { push @cl_warnings, $_[0] };
my $cl_sock = IO::Socket::INET->new(
PeerAddr => '127.0.0.1',
PeerPort => $port,
Proto => 'tcp',
Timeout => 5,
) or die "connect failed: $!";
print $cl_sock "GET /cl-trailers HTTP/1.1\r\n";
print $cl_sock "Host: 127.0.0.1:$port\r\n";
print $cl_sock "\r\n";
$cl_sock->blocking(0);
my $cl_response = '';
my $deadline = time + 5;
while (time < $deadline) {
my $buf;
my $n = sysread($cl_sock, $buf, 4096);
if (defined $n) {
last if $n == 0; # EOF: server closed the connection
$cl_response .= $buf;
}
$loop->loop_once(0.1);
}
close $cl_sock;
like( $cl_response, qr/^HTTP\/1\.1 200/, '/cl-trailers: status line 200' );
like( $cl_response, qr/\r\n\r\nok\z/,
'/cl-trailers: content-length-framed body "ok" fully delivered, then EOF (connection closed, no more bytes)' );
}
like( $T::CL_ERR, qr/requires chunked framing/,
'trailers on content-length response fail the Future' );
$loop->loop_once(0.1) for 1..3;
is( $T::CL_DISC_REASON, 'server_error',
'/cl-trailers: app-return after the failed trailers send reports on_disconnect(server_error)' );
ok(
(scalar grep { /returned with an incomplete response/i } @cl_warnings),
'/cl-trailers: incomplete-response warning logged'
);
# --- /chunked-trailers: control -- raw socket, trailers actually on the wire
# Net::Async::HTTP doesn't expose trailers (see t/02-streaming.t), so read
# the raw chunked framing off the wire ourselves. Socket-pump idiom lifted
# from t/52-mandatory-validation.t's $ws_handshake_and_drain; the chunk
# grammar being asserted against (hex chunk-size line, CRLF-terminated
# chunks, "0\r\n" + trailer headers + CRLF as the terminator) is the same
# grammar t/16-chunked-validation.t exercises against the parser.
my $raw_request = sub {
my ($port, $method, $path) = @_;
my $sock = IO::Socket::INET->new(
PeerAddr => '127.0.0.1',
PeerPort => $port,
Proto => 'tcp',
Timeout => 5,
) or return undef;
print $sock "$method $path HTTP/1.1\r\n";
print $sock "Host: 127.0.0.1:$port\r\n";
print $sock "Connection: close\r\n";
print $sock "\r\n";
$sock->blocking(0);
my $response = '';
my $deadline = time + 5;
while (time < $deadline) {
my $buf;
my $n = sysread($sock, $buf, 4096);
if (defined $n) {
last if $n == 0; # EOF: server closed after Connection: close
$response .= $buf;
}
$loop->loop_once(0.1);
}
close $sock;
return $response;
};
my $chunked_raw = $raw_request->($port, 'GET', '/chunked-trailers');
ok( defined $chunked_raw, '/chunked-trailers: connected' ) or diag('connection failed');
like( $chunked_raw, qr/Transfer-Encoding:\s*chunked/i,
'/chunked-trailers uses chunked framing' );
like( $chunked_raw, qr/\r\n2\r\nok\r\n/,
'/chunked-trailers body chunk "ok" present' );
like( $chunked_raw, qr/0\r\nx-t: 1\r\n\r\n/,
'/chunked-trailers trailer header actually transmitted after the final chunk (control, unchanged)' );
# --- /empty-trailers, /absent-trailers: empty/absent trailer headers yield --
# the same bare "0\r\n\r\n" terminator as the no-trailers chunked end
# (validator: headers OPTIONAL, empty list validates via a zero-iteration
# loop; h1 writer: `$event->{headers} // []` makes absent and explicit-empty
# byte-identical on the wire).
for my $path (qw(/empty-trailers /absent-trailers)) {
my $raw = $raw_request->($port, 'GET', $path);
ok( defined $raw, "$path: connected" ) or diag('connection failed');
like( $raw, qr/Transfer-Encoding:\s*chunked/i, "$path uses chunked framing" );
like( $raw, qr/\r\n2\r\nok\r\n0\r\n\r\n\z/,
"$path terminates with a bare 0\\r\\n\\r\\n (no trailer header lines) immediately after the final chunk" );
}
# --- /head-trailers: HEAD accept-and-discard --------------------------------
my $head_res = $http->HEAD("http://127.0.0.1:$port/head-trailers")->get;
( run in 1.430 second using v1.01-cache-2.11-cpan-5c0b1e786e0 )