Feersum
view release on metacpan or search on metacpan
t/66-pipelining.t view on Meta::CPAN
#!perl
use warnings;
use strict;
use Test::More tests => 9;
use Test::Fatal;
use utf8;
use lib 't'; use Utils;
BEGIN { use_ok('Feersum') };
my ($listen_socket, $port) = get_listen_socket();
ok $listen_socket, "made listen socket";
ok $listen_socket->fileno, "has a fileno";
my $evh = Feersum->new();
# Enable keep-alive which is needed for all tests
$evh->set_keepalive(1);
# Set a shorter read timeout to fail faster in case of problems
$evh->read_timeout(2.0);
my $request_count = 0;
$evh->request_handler(sub {
my $r = shift;
isa_ok $r, 'Feersum::Connection', 'got an object!';
$request_count++;
my $method = $r->method;
my $path = $r->path;
my $body = '';
# Read POST body if applicable
if ($method eq 'POST') {
my $input = $r->input;
if ($input) {
my $cl = $r->content_length;
$input->read($body, $cl) if $cl > 0;
}
}
# Add small delays to verify ordering for specific requests
my @res = (
200,
['Content-Type' => 'text/plain'],
["Response $request_count: $method $path" . ($body ? " Body: $body" : "")]
);
# Add delay for certain paths to test response ordering
if ($path =~ /\/delay|\/test2|\/keepalive2/) {
my $w; $w = AE::timer 0.1, 0, sub {
undef $w;
$r->send_response(@res);
};
} else {
$r->send_response(@res)
}
});
is exception {
$evh->use_socket($listen_socket);
}, undef, 'assigned socket';
# Part 1: Test pipelined requests
subtest 'Pipelined Requests' => sub {
plan tests => 23;
my $cv = AE::cv;
$cv->begin;
# Create connection using AnyEvent::Handle
my @responses;
my $h; $h = AnyEvent::Handle->new(
connect => ['localhost', $port],
timeout => 5,
on_error => sub {
my ($h, $fatal, $msg) = @_;
fail "client error: $msg";
$cv->send;
},
on_eof => sub {
# Done handling all responses
my $responses = join "\n\n", @responses;
like($responses, qr/Response \d+: GET \/test1.*Response \d+: GET \/test2.*Response \d+: POST \/post1.*Response \d+: POST \/post2.*Response \d+: GET \/test3/s,
'Got all pipelined responses in correct order');
# Verify each response had proper headers
is(scalar(@responses), 5, 'Got expected number of response parts');
my @parts = map { split /\r\n\r\n/ } @responses;
# First response (GET)
like($parts[0], qr/^HTTP\/1\.1 200 OK/, 'First response has correct status');
like($parts[0], qr/Content-Type: text\/plain/, 'First response has content type');
like($parts[1], qr/Response \d+: GET \/test1/, 'First response has correct body');
# Second response (GET with delay)
like($parts[2], qr/^HTTP\/1\.1 200 OK/, 'Second response has correct status');
like($parts[2], qr/Content-Type: text\/plain/, 'Second response has content type');
like($parts[3], qr/Response \d+: GET \/test2/, 'Second response has correct body');
# Third response (POST with small body)
like($parts[4], qr/^HTTP\/1\.1 200 OK/, 'Third response has correct status');
like($parts[4], qr/Content-Type: text\/plain/, 'Third response has content type');
like($parts[5], qr/Response \d+: POST \/post1 Body: Hello, world!/, 'Third response has correct body with POST data');
# Fourth response (POST with larger body)
like($parts[6], qr/^HTTP\/1\.1 200 OK/, 'Fourth response has correct status');
like($parts[6], qr/Content-Type: text\/plain/, 'Fourth response has content type');
like($parts[7], qr/Response \d+: POST \/post2 Body: This is a larger test body/, 'Fourth response has correct body with POST data');
# Fifth response (GET with close)
like($parts[8], qr/^HTTP\/1\.1 200 OK/, 'Fifth response has correct status');
like($parts[8], qr/Content-Type: text\/plain/, 'Fifth response has content type');
like($parts[8], qr/Connection: close/, 'Fifth response has Connection: close');
like($parts[9], qr/Response \d+: GET \/test3/, 'Fifth response has correct body');
$cv->end;
$h->destroy;
},
on_read => sub {
last unless my $len = length(my $buf = $_[0]->rbuf);
push @responses, $buf;
substr $_[0]->rbuf, 0, $len, '';
}
);
# Create small and larger POST bodies
my $post_body1 = "Hello, world!";
my $post_body2 = "This is a larger test body";
# Send pipelined requests including GETs and POSTs
$h->push_write(
"GET /test1 HTTP/1.1\r\nHost: localhost\r\n\r\n" .
"GET /test2 HTTP/1.1\r\nHost: localhost\r\n\r\n" .
"POST /post1 HTTP/1.1\r\nHost: localhost\r\nContent-Length: " . length($post_body1) . "\r\n\r\n" . $post_body1 .
"POST /post2 HTTP/1.1\r\nHost: localhost\r\nContent-Length: " . length($post_body2) . "\r\n\r\n" . $post_body2 .
"GET /test3 HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n"
);
$cv->recv;
};
# Part 2: Test keepalive requests (sequential requests on same connection)
subtest 'Keepalive Requests' => sub {
plan tests => 14;
my $cv = AE::cv;
$cv->begin;
my @responses;
my $request_index = 0;
my @requests = (
"GET /keepalive1 HTTP/1.1\r\nHost: localhost\r\n\r\n",
"GET /keepalive2 HTTP/1.1\r\nHost: localhost\r\n\r\n",
"POST /keepalive-post HTTP/1.1\r\nHost: localhost\r\nContent-Length: 10\r\n\r\nKeepAlive!",
"GET /keepalive-end HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n"
);
my $h; $h = AnyEvent::Handle->new(
connect => ['localhost', $port],
timeout => 5,
on_error => sub {
my ($h, $fatal, $msg) = @_;
fail "client error in keepalive test: $msg";
$cv->send;
},
on_eof => sub {
# Done handling all responses
is(scalar(@responses), 4, 'Got expected number of keepalive responses');
my @parts = map { split /\r\n\r\n/ } @responses;
# First keepalive response
like($parts[0], qr/^HTTP\/1\.1 200 OK/, 'First keepalive response has correct status');
like($parts[1], qr/Response \d+: GET \/keepalive1/, 'First keepalive response has correct body');
# Second keepalive response (with delay)
like($parts[2], qr/^HTTP\/1\.1 200 OK/, 'Second keepalive response has correct status');
like($parts[3], qr/Response \d+: GET \/keepalive2/, 'Second keepalive response has correct body');
# Third keepalive response (POST)
like($parts[4], qr/^HTTP\/1\.1 200 OK/, 'Third keepalive response has correct status');
like($parts[5], qr/Response \d+: POST \/keepalive-post Body: KeepAlive!/, 'Third keepalive response has correct POST body');
# Fourth keepalive response (with close)
like($parts[6], qr/^HTTP\/1\.1 200 OK/, 'Fourth keepalive response has correct status');
like($parts[6], qr/Connection: close/, 'Fourth keepalive response has Connection: close');
like($parts[7], qr/Response \d+: GET \/keepalive-end/, 'Fourth keepalive response has correct body');
$cv->end;
$h->destroy;
},
on_read => sub {
my ($handle) = @_;
last unless my $len = length(my $buf = $handle->rbuf);
# Store response
push @responses, $buf;
substr $handle->rbuf, 0, $len, '';
# Send next request when we get a complete response
if ($request_index < scalar(@requests) - 1 && $buf =~ /\r\n\r\n/) {
$request_index++;
$handle->push_write($requests[$request_index]);
}
}
);
# Send first request
$h->push_write($requests[0]);
$cv->recv;
};
# Part 3: Test mixed keepalive and pipelined requests with more POST requests
subtest 'Mixed Keepalive and Pipelined Requests' => sub {
plan tests => 20;
my $cv = AE::cv;
$cv->begin;
my @responses;
my $mixed_phase = 0;
my $h; $h = AnyEvent::Handle->new(
connect => ['localhost', $port],
timeout => 5,
on_error => sub {
my ($h, $fatal, $msg) = @_;
fail "client error in mixed test: $msg";
$cv->send;
},
on_eof => sub {
# Done handling all responses
my $full_response = join('', @responses);
# Count how many responses we actually got (should be 7)
my $response_count = () = $full_response =~ /Response \d+:/g;
is($response_count, 7, "Got all $response_count responses");
# Verify we got responses for all expected requests
like($full_response, qr/Response \d+: POST \/mixed\/initial Body: InitialPost/,
'Contains initial POST response');
like($full_response, qr/Response \d+: GET \/mixed1/,
'Contains mixed1 response');
like($full_response, qr/Response \d+: GET \/mixed\/delay/,
'Contains mixed/delay response');
like($full_response, qr/Response \d+: POST \/mixed\/post1 Body: FirstPost/,
'Contains POST1 response');
like($full_response, qr/Response \d+: POST \/mixed\/post2 Body: SecondPost/,
'Contains POST2 response');
like($full_response, qr/Response \d+: GET \/mixed\/get1/,
'Contains mixed/get1 response');
like($full_response, qr/Response \d+: GET \/mixed\/final/,
'Contains mixed/final response');
# Check for correct status codes
my $status_count = () = $full_response =~ /HTTP\/1\.1 200 OK/g;
is($status_count, 7, 'All responses have correct status code');
# Check for content type headers
my $ct_count = () = $full_response =~ /Content-Type: text\/plain/g;
is($ct_count, 7, 'All responses have correct content type');
# Check Connection: close in the final response
like($full_response, qr/Connection: close.*Response \d+: GET \/mixed\/final/s,
'Final response has Connection: close');
# Validate order of responses
my @response_numbers = $full_response =~ /Response (\d+):/g;
is(scalar(@response_numbers), 7, 'Captured 7 response numbers');
# Check response numbers are in ascending order
my $is_ascending = 1;
for (my $i = 1; $i < scalar(@response_numbers); $i++) {
if ($response_numbers[$i] <= $response_numbers[$i-1]) {
$is_ascending = 0;
last;
( run in 0.812 second using v1.01-cache-2.11-cpan-14f38c9f855 )