Mojolicious/t/mojo/user_agent.t
use Mojo::Base -strict;
BEGIN {
$ENV{MOJO_NO_NNR} = $ENV{MOJO_NO_SOCKS} = $ENV{MOJO_NO_TLS} = 1;
$ENV{MOJO_REACTOR} = 'Mojo::Reactor::Poll';
}
use Test::More;
use Mojo::IOLoop;
use Mojo::Message::Request;
use Mojo::Promise;
use Mojo::Server::Daemon;
use Mojo::UserAgent;
use Mojo::UserAgent::Server;
use Mojo::Util qw(gzip);
use Mojolicious::Lite;
# Silence
app->log->level('trace')->unsubscribe('message');
get '/' => {text => 'works!'};
get '/huge' => {text => 'x' x 262144};
my $timeout = undef;
get '/timeout' => sub {
my $c = shift;
$c->inactivity_timeout($c->param('timeout'));
$c->on(finish => sub { $timeout = 1 });
};
get '/no_length' => sub {
my $c = shift;
$c->finish('works too!');
$c->rendered(200);
};
get '/no_content' => {text => 'fail!', status => 204};
get '/echo' => sub {
my $c = shift;
my $compressed = gzip $c->req->body;
$c->res->headers->content_encoding($c->req->headers->accept_encoding);
$c->render(data => $compressed);
};
post '/echo' => sub {
my $c = shift;
$c->render(data => $c->req->body);
};
any '/method' => {inline => '<%= $c->req->method =%>'};
get '/one' => sub {
my $c = shift;
$c->res->version('1.0');
if (my $connection = $c->param('connection')) {
$c->res->headers->connection($connection);
}
$c->render(text => 'One!');
};
get '/redirect_close' => sub {
my $c = shift;
$c->res->headers->connection('close');
$c->res->headers->location($c->url_for('/')->to_abs);
$c->rendered(302);
$c->res->fix_headers->headers->remove('Content-Length');
};
# Max redirects
{
local $ENV{MOJO_MAX_REDIRECTS} = 25;
is(Mojo::UserAgent->new->max_redirects, 25, 'right value');
$ENV{MOJO_MAX_REDIRECTS} = 0;
is(Mojo::UserAgent->new->max_redirects, 0, 'right value');
}
# Timeouts
{
is(Mojo::UserAgent->new->connect_timeout, 10, 'right value');
local $ENV{MOJO_CONNECT_TIMEOUT} = 25;
is(Mojo::UserAgent->new->connect_timeout, 25, 'right value');
is(Mojo::UserAgent->new->inactivity_timeout, 40, 'right value');
local $ENV{MOJO_INACTIVITY_TIMEOUT} = 25;
is(Mojo::UserAgent->new->inactivity_timeout, 25, 'right value');
$ENV{MOJO_INACTIVITY_TIMEOUT} = 0;
is(Mojo::UserAgent->new->inactivity_timeout, 0, 'right value');
is(Mojo::UserAgent->new->request_timeout, 0, 'right value');
local $ENV{MOJO_REQUEST_TIMEOUT} = 25;
is(Mojo::UserAgent->new->request_timeout, 25, 'right value');
$ENV{MOJO_REQUEST_TIMEOUT} = 0;
is(Mojo::UserAgent->new->request_timeout, 0, 'right value');
}
# Default application
is(Mojo::UserAgent::Server->app, app, 'applications are equal');
is(Mojo::UserAgent->new->server->app, app, 'applications are equal');
Mojo::UserAgent::Server->app(app);
is(Mojo::UserAgent::Server->app, app, 'applications are equal');
my $dummy = Mojolicious::Lite->new;
isnt(Mojo::UserAgent->new->server->app($dummy)->app, app, 'applications are not equal');
is(Mojo::UserAgent::Server->app, app, 'applications are still equal');
Mojo::UserAgent::Server->app($dummy);
isnt(Mojo::UserAgent::Server->app, app, 'applications are not equal');
is(Mojo::UserAgent::Server->app, $dummy, 'application are equal');
Mojo::UserAgent::Server->app(app);
is(Mojo::UserAgent::Server->app, app, 'applications are equal again');
# Clean up non-blocking requests
my $ua = Mojo::UserAgent->new;
my $get = my $post = '';
$ua->get('/' => sub { $get = pop->error });
$ua->post('/' => sub { $post = pop->error });
undef $ua;
is $get->{message}, 'Premature connection close', 'right error';
is $post->{message}, 'Premature connection close', 'right error';
# The poll reactor stops when there are no events being watched anymore
my $time = time;
Mojo::IOLoop->start;
ok time < ($time + 10), 'stopped automatically';
# Blocking and non-blocking
$ua = Mojo::UserAgent->new;
my ($success, $code, $body);
$ua->get(
'/' => sub {
my ($ua, $tx) = @_;
$success = !$tx->error;
$code = $tx->res->code;
$body = $tx->res->body;
Mojo::IOLoop->stop;
}
);
is $ua->get('/')->res->code, 200, 'right status';
Mojo::IOLoop->start;
ok $success, 'successful';
is $code, 200, 'right status';
is $body, 'works!', 'right content';
# Promises
my @results;
my $p1 = $ua->get_p('/');
my $p2 = $ua->get_p('/');
Mojo::Promise->all($p1, $p2)->then(sub {
my ($first, $second) = @_;
push @results, $first, $second;
})->wait;
ok !$results[0][0]->error, 'no error';
is $results[0][0]->res->code, 200, 'right status';
is $results[0][0]->res->body, 'works!', 'right content';
ok !$results[1][0]->error, 'no error';
is $results[1][0]->res->code, 200, 'right status';
is $results[1][0]->res->body, 'works!', 'right content';
# Promises (shortcut methods)
my $result;
$ua->delete_p('/method')->then(sub { $result = shift->res->body })->wait;
is $result, 'DELETE', 'right result';
$ua->get_p('/method')->then(sub { $result = shift->res->body })->wait;
is $result, 'GET', 'right result';
$ua->head_p('/method')->then(sub { $result = shift->res->body })->wait;
is $result, '', 'no result';
$ua->options_p('/method')->then(sub { $result = shift->res->body })->wait;
is $result, 'OPTIONS', 'right result';
$ua->patch_p('/method')->then(sub { $result = shift->res->body })->wait;
is $result, 'PATCH', 'right result';
$ua->post_p('/method')->then(sub { $result = shift->res->body })->wait;
is $result, 'POST', 'right result';
$ua->put_p('/method')->then(sub { $result = shift->res->body })->wait;
is $result, 'PUT', 'right result';
$ua->start_p($ua->build_tx(TEST => '/method'))->then(sub { $result = shift->res->body })->wait;
is $result, 'TEST', 'right result';
# No timeout
$ua = Mojo::UserAgent->new(inactivity_timeout => 0);
my $tx = $ua->get('/');
ok $tx->keep_alive, 'keep connection alive';
is $tx->res->code, 200, 'right status';
is $tx->res->body, 'works!', 'right content';
$tx = $ua->get('/');
ok $tx->kept_alive, 'kept connection alive';
ok $tx->keep_alive, 'keep connection alive';
is $tx->res->code, 200, 'right status';
is $tx->res->body, 'works!', 'right content';
# SOCKS proxy request without SOCKS support
$ua = Mojo::UserAgent->new;
$tx = $ua->build_tx(GET => '/');
$tx->req->proxy($ua->server->url->scheme('socks'));
$tx = $ua->start($tx);
like $tx->error->{message}, qr/IO::Socket::Socks/, 'right error';
ok !Mojo::IOLoop::Client->can_socks, 'no SOCKS5 support';
# HTTPS request without TLS support
$ua = Mojo::UserAgent->new;
$tx = $ua->get($ua->server->url->scheme('https'));
like $tx->error->{message}, qr/IO::Socket::SSL/, 'right error';
ok !Mojo::IOLoop::TLS->can_tls, 'no TLS support';
# Promises (rejected)
my $error;
$ua->get_p($ua->server->url->scheme('https'))->catch(sub { $error = shift })->wait;
like $error, qr/IO::Socket::SSL/, 'right error';
# No non-blocking name resolution
ok !Mojo::IOLoop::Client->can_nnr, 'no non-blocking name resolution support';
# Blocking
$tx = $ua->get('/');
ok !$tx->error, 'no error';
ok !$tx->kept_alive, 'kept connection not alive';
is $tx->res->version, '1.1', 'right version';
is $tx->res->code, 200, 'right status';
ok !$tx->res->headers->connection, 'no "Connection" value';
is $tx->res->max_message_size, 2147483648, 'right value';
is $tx->res->body, 'works!', 'right content';
# Again
$tx = $ua->get('/');
ok !$tx->error, 'no error';
ok $tx->kept_alive, 'kept connection alive';
is $tx->res->version, '1.1', 'right version';
is $tx->res->code, 200, 'right status';
ok !$tx->res->headers->connection, 'no "Connection" value';
is $tx->res->body, 'works!', 'right content';
$tx = $ua->max_response_size(0)->get('/');
ok !$tx->error, 'no error';
is $tx->res->version, '1.1', 'right version';
is $tx->res->code, 200, 'right status';
ok !$tx->res->headers->connection, 'no "Connection" value';
is $tx->res->max_message_size, 0, 'right value';
is $tx->res->body, 'works!', 'right content';
# Unsupported protocol
$tx = $ua->request_timeout(3600)->get('htttp://example.com');
is $tx->error->{message}, 'Unsupported protocol: htttp', 'right error';
eval { $tx->result };
like $@, qr/Unsupported protocol: htttp/, 'right error';
# Shortcuts for common request methods
is $ua->delete('/method')->res->body, 'DELETE', 'right content';
is $ua->get('/method')->res->body, 'GET', 'right content';
is $ua->head('/method')->res->body, '', 'no content';
is $ua->options('/method')->res->body, 'OPTIONS', 'right method';
is $ua->patch('/method')->res->body, 'PATCH', 'right method';
is $ua->post('/method')->res->body, 'POST', 'right method';
is $ua->put('/method')->res->body, 'PUT', 'right method';
# No keep-alive
$tx = $ua->get('/one?connection=test');
ok !$tx->error, 'no error';
ok !$tx->keep_alive, 'connection will not be kept alive';
is $tx->res->version, '1.0', 'right version';
is $tx->res->code, 200, 'right status';
is $tx->res->headers->connection, 'test', 'right "Connection" value';
is $tx->res->body, 'One!', 'right content';
$tx = $ua->get('/one?connection=test');
ok !$tx->error, 'no error';
ok !$tx->kept_alive, 'kept connection not alive';
ok !$tx->keep_alive, 'connection will not be kept alive';
is $tx->res->version, '1.0', 'right version';
is $tx->res->code, 200, 'right status';
is $tx->res->headers->connection, 'test', 'right "Connection" value';
is $tx->res->body, 'One!', 'right content';
$tx = $ua->get('/one');
ok !$tx->error, 'no error';
ok !$tx->kept_alive, 'kept connection not alive';
ok !$tx->keep_alive, 'connection will not be kept alive';
is $tx->res->version, '1.0', 'right version';
is $tx->res->code, 200, 'right status';
ok !$tx->res->headers->connection, 'no "Connection" value';
is $tx->res->body, 'One!', 'right content';
# Error in callback
Mojo::IOLoop->singleton->reactor->unsubscribe('error');
my $err;
Mojo::IOLoop->singleton->reactor->once(error => sub { $err .= pop; Mojo::IOLoop->stop });
app->ua->get('/' => sub { die 'error event works' });
Mojo::IOLoop->start;
like $err, qr/error event works/, 'right error';
# Events
my ($finished_req, $finished_tx, $finished_res);
$tx = $ua->build_tx(GET => '/does_not_exist');
ok !$tx->is_finished, 'transaction is not finished';
$ua->once(
prepare => sub {
my ($ua, $tx) = @_;
$tx->req->url->path('/');
}
);
$ua->once(
start => sub {
my ($ua, $tx) = @_;
$tx->req->on(finish => sub { $finished_req++ });
$tx->on(finish => sub { $finished_tx++ });
$tx->res->on(finish => sub { $finished_res++ });
}
);
$tx = $ua->start($tx);
ok !$tx->error, 'no error';
is $finished_req, 1, 'finish event has been emitted once';
is $finished_tx, 1, 'finish event has been emitted once';
is $finished_res, 1, 'finish event has been emitted once';
ok $tx->req->is_finished, 'request is finished';
ok $tx->is_finished, 'transaction is finished';
ok $tx->res->is_finished, 'response is finished';
is $tx->res->code, 200, 'right status';
is $tx->res->body, 'works!', 'right content';
# Missing Content-Length header
($finished_req, $finished_tx, $finished_res) = ();
$tx = $ua->build_tx(GET => '/no_length');
ok !$tx->is_finished, 'transaction is not finished';
$ua->once(
start => sub {
my ($ua, $tx) = @_;
$tx->req->on(finish => sub { $finished_req++ });
$tx->on(finish => sub { $finished_tx++ });
$tx->res->on(finish => sub { $finished_res++ });
}
);
$tx = $ua->start($tx);
ok !$tx->error, 'no error';
is $finished_req, 1, 'finish event has been emitted once';
is $finished_tx, 1, 'finish event has been emitted once';
is $finished_res, 1, 'finish event has been emitted once';
ok $tx->req->is_finished, 'request is finished';
ok $tx->is_finished, 'transaction is finished';
ok $tx->res->is_finished, 'response is finished';
ok !$tx->error, 'no error';
ok $tx->kept_alive, 'kept connection alive';
ok !$tx->keep_alive, 'keep connection not alive';
is $tx->res->code, 200, 'right status';
is $tx->res->body, 'works too!', 'right content';
# 204 No Content
$tx = $ua->get('/no_content');
ok !$tx->error, 'no error';
ok !$tx->kept_alive, 'kept connection not alive';
ok $tx->keep_alive, 'keep connection alive';
is $tx->res->code, 204, 'right status';
ok $tx->is_empty, 'transaction is empty';
is $tx->res->body, '', 'no content';
# Connection was kept alive
$tx = $ua->head('/huge');
ok !$tx->error, 'no error';
ok $tx->kept_alive, 'kept connection alive';
is $tx->res->code, 200, 'right status';
ok $tx->res->headers->content_length, 'has "Content-Length" value';
ok $tx->is_empty, 'transaction is empty';
is $tx->res->body, '', 'no content';
$tx = $ua->get('/huge');
ok !$tx->error, 'no error';
ok $tx->kept_alive, 'kept connection alive';
is $tx->res->code, 200, 'right status';
ok $tx->res->headers->content_length, 'has "Content-Length" value';
ok !$tx->is_empty, 'transaction is not empty';
is $tx->res->body, 'x' x 262144, 'right content';
# Non-blocking form
($success, $code, $body) = ();
$ua->post(
'/echo' => form => {hello => 'world'} => sub {
my ($ua, $tx) = @_;
$success = !$tx->error;
$code = $tx->res->code;
$body = $tx->res->body;
Mojo::IOLoop->stop;
}
);
Mojo::IOLoop->start;
ok $success, 'successful';
is $code, 200, 'right status';
is $body, 'hello=world', 'right content';
# Non-blocking JSON
($success, $code, $body) = ();
$ua->post(
'/echo' => json => {hello => 'world'} => sub {
my ($ua, $tx) = @_;
$success = !$tx->error;
$code = $tx->res->code;
$body = $tx->res->body;
Mojo::IOLoop->stop;
}
);
Mojo::IOLoop->start;
ok $success, 'successful';
is $code, 200, 'right status';
is $body, '{"hello":"world"}', 'right content';
# Built-in web server times out
$ua = Mojo::UserAgent->new(ioloop => Mojo::IOLoop->singleton);
my $logs = app->log->capture('trace');
$tx = $ua->get('/timeout?timeout=0.25');
is $tx->error->{message}, 'Premature connection close', 'right error';
is $timeout, 1, 'finish event has been emitted';
like $logs, qr/Inactivity timeout/, 'right log message';
undef $logs;
eval { $tx->result };
like $@, qr/Premature connection close/, 'right error';
# Client times out
$ua->once(
start => sub {
my ($ua, $tx) = @_;
$tx->on(
connection => sub {
my ($tx, $connection) = @_;
Mojo::IOLoop->stream($connection)->timeout(0.25);
}
);
}
);
$tx = $ua->get('/timeout?timeout=5');
is $tx->error->{message}, 'Inactivity timeout', 'right error';
eval { $tx->result };
like $@, qr/Inactivity timeout/, 'right error';
# Keep alive connection times out
my $id;
$ua->get(
'/' => sub {
my ($ua, $tx) = @_;
Mojo::IOLoop->timer(0.5 => sub { Mojo::IOLoop->stop });
$id = $tx->connection;
Mojo::IOLoop->stream($id)->timeout(0.25);
}
);
Mojo::IOLoop->start;
ok !Mojo::IOLoop->stream($id), 'connection timed out';
# Request timeout with keep-alive
$ua->request_timeout(3600);
ok !$ua->get('/')->error, 'priming the keep-alive connection';
$ua->request_timeout(0.01);
$tx = $ua->get('/timeout?timeout=5');
is $tx->error->{message}, 'Request timeout', 'right error message';
is $tx->error->{code}, undef, 'no status';
$ua->request_timeout(0);
# Response exceeding message size limit
$ua->once(
start => sub {
my ($ua, $tx) = @_;
$tx->res->max_message_size(12);
}
);
$tx = $ua->get('/echo' => 'Hello World!');
is $tx->error->{message}, 'Maximum message size exceeded', 'right error';
is $tx->error->{code}, undef, 'no status';
ok $tx->res->is_limit_exceeded, 'limit is exceeded';
# 404 response
$tx = $ua->get('/does_not_exist');
ok $tx->result, 'has a result';
is $tx->result->code, 404, 'right status';
ok !$tx->kept_alive, 'kept connection not alive';
ok $tx->keep_alive, 'keep connection alive';
is $tx->error->{message}, 'Not Found', 'right error';
is $tx->error->{code}, 404, 'right status';
$tx = $ua->get('/does_not_exist');
ok $tx->kept_alive, 'kept connection alive';
ok $tx->keep_alive, 'keep connection alive';
is $tx->error->{message}, 'Not Found', 'right error';
is $tx->error->{code}, 404, 'right status';
# Redirect with connection close
$tx = $ua->max_redirects(3)->get('/redirect_close');
ok !$tx->error, 'no error';
ok !$tx->kept_alive, 'kept connection not alive';
is $tx->res->version, '1.1', 'right version';
is $tx->res->code, 200, 'right status';
is $tx->res->body, 'works!', 'right content';
$ua->max_redirects(0);
# Compressed response
$tx = $ua->build_tx(GET => '/echo' => 'Hello GZip!');
$tx = $ua->start($ua->build_tx(GET => '/echo' => 'Hello GZip!'));
ok !$tx->error, 'no error';
ok $tx->result, 'has a result';
is $tx->result->code, 200, 'right status';
is $tx->res->code, 200, 'right status';
is $tx->res->headers->content_encoding, undef, 'no "Content-Encoding" value';
is $tx->res->body, 'Hello GZip!', 'right content';
$tx = $ua->build_tx(GET => '/echo' => 'Hello GZip!');
$tx->res->content->auto_decompress(0);
$tx = $ua->start($tx);
ok !$tx->error, 'no error';
is $tx->res->code, 200, 'right status';
is $tx->res->headers->content_encoding, 'gzip', 'right "Content-Encoding" value';
isnt $tx->res->body, 'Hello GZip!', 'different content';
# Keep-alive timeout in between requests
my $daemon = Mojo::Server::Daemon->new(
app => app,
ioloop => $ua->ioloop,
keep_alive_timeout => 0.5,
listen => ['http://127.0.0.1'],
silent => 1
);
my $port = $daemon->start->ports->[0];
$tx = $ua->get("http://127.0.0.1:$port");
ok $tx->keep_alive, 'keep connection alive';
is $tx->res->code, 200, 'right status';
is $tx->res->body, 'works!', 'right content';
Mojo::Promise->new->ioloop($ua->ioloop)->timer(1)->wait;
$tx = $ua->get("http://127.0.0.1:$port");
ok !$tx->kept_alive, 'kept connection not alive';
ok $tx->keep_alive, 'keep connection alive';
is $tx->res->code, 200, 'right status';
is $tx->res->body, 'works!', 'right content';
subtest 'Fork-safety' => sub {
my $tx = $ua->get('/');
ok $tx->keep_alive, 'keep connection alive';
is $tx->res->body, 'works!', 'right content';
my $last = $tx->connection;
my $port = $ua->server->url->port;
$tx = $ua->get('/');
is $tx->res->body, 'works!', 'right content';
is $tx->connection, $last, 'same connection';
is $ua->server->url->port, $port, 'same port';
{
local $$ = -23;
my $tx = $ua->get('/');
ok !$tx->kept_alive, 'kept connection not alive';
ok $tx->keep_alive, 'keep connection alive';
is $tx->res->body, 'works!', 'right content';
isnt $tx->connection, $last, 'new connection';
isnt $ua->server->url->port, $port, 'new port';
my $port2 = $ua->server->url->port;
my $last2 = $tx->connection;
{
local $$ = -24;
my $tx = $ua->get('/');
ok !$tx->kept_alive, 'kept connection not alive';
ok $tx->keep_alive, 'keep connection alive';
is $tx->res->body, 'works!', 'right content';
isnt $tx->connection, $last, 'new connection';
isnt $tx->connection, $last2, 'new connection';
isnt $ua->server->url->port, $port, 'new port';
isnt $ua->server->url->port, $port2, 'new port';
}
}
};
# Introspect
my $req = my $res = '';
my @num;
my $start = $ua->on(
start => sub {
my ($ua, $tx) = @_;
$tx->on(
connection => sub {
my ($tx, $connection) = @_;
my $stream = Mojo::IOLoop->stream($connection);
push @num, $stream->bytes_read, $stream->bytes_written;
my $read = $stream->on(
read => sub {
my ($stream, $chunk) = @_;
$res .= $chunk;
}
);
my $write = $stream->on(
write => sub {
my ($stream, $chunk) = @_;
$req .= $chunk;
}
);
$tx->on(
finish => sub {
push @num, $stream->bytes_read, $stream->bytes_written;
$stream->unsubscribe(read => $read);
$stream->unsubscribe(write => $write);
}
);
}
);
}
);
$tx = $ua->get('/', 'whatever');
ok !$tx->error, 'no error';
is $tx->res->code, 200, 'right status';
is $tx->res->body, 'works!', 'right content';
is scalar @{Mojo::IOLoop->stream($tx->connection)->subscribers('write')}, 0, 'unsubscribed successfully';
is scalar @{Mojo::IOLoop->stream($tx->connection)->subscribers('read')}, 1, 'unsubscribed successfully';
like $req, qr!^GET / .*whatever$!s, 'right request';
like $res, qr|^HTTP/.*200 OK.*works!$|s, 'right response';
is_deeply \@num, [0, 0, length $res, length $req], 'right structure';
$ua->unsubscribe(start => $start);
ok !$ua->has_subscribers('start'), 'unsubscribed successfully';
# Stream with drain callback and compressed response
$tx = $ua->build_tx(GET => '/echo');
my $i = 0;
my ($stream, $drain);
$drain = sub {
my $content = shift;
return $ua->ioloop->timer(
0.25 => sub {
$content->write_chunk('');
$tx->resume;
$stream += @{Mojo::IOLoop->stream($tx->connection)->subscribers('drain')};
}
) if $i >= 10;
$content->write_chunk($i++, $drain);
$tx->resume;
return unless my $id = $tx->connection;
$stream += @{Mojo::IOLoop->stream($id)->subscribers('drain')};
};
$tx->req->content->$drain;
$ua->start($tx);
ok !$tx->error, 'no error';
ok $tx->kept_alive, 'kept connection alive';
ok $tx->keep_alive, 'keep connection alive';
is $tx->res->code, 200, 'right status';
is $tx->res->body, '0123456789', 'right content';
is $stream, 1, 'no leaking subscribers';
# Upload progress
$ua = Mojo::UserAgent->new;
my $progress = {};
$ua->on(
start => sub {
my ($ua, $tx) = @_;
$tx->req->on(
progress => sub {
my ($req, $state, $offset) = @_;
$progress->{$state} = 1;
}
);
$tx->req->on(finish => sub { $progress->{finish} = 1 });
}
);
$tx = $ua->post('/echo' => 'Hello Mojo!');
ok !$tx->error, 'no error';
is $tx->res->code, 200, 'right status';
is $tx->res->body, 'Hello Mojo!', 'right content';
is_deeply $progress, {start_line => 1, headers => 1, body => 1, finish => 1}, 'right structure';
# Mixed blocking and non-blocking requests, with custom URL
$ua = Mojo::UserAgent->new(ioloop => Mojo::IOLoop->singleton);
$tx = $ua->get($ua->server->url);
ok !$tx->error, 'no error';
ok !$tx->kept_alive, 'kept connection not alive';
ok $tx->keep_alive, 'keep connection alive';
is $tx->res->code, 200, 'right status';
is $tx->res->body, 'works!', 'right content';
my @kept_alive;
$ua->get(
$ua->server->nb_url => sub {
my ($ua, $tx) = @_;
push @kept_alive, $tx->kept_alive;
$ua->get(
'/' => sub {
my ($ua, $tx) = @_;
push @kept_alive, $tx->kept_alive;
$ua->get(
$ua->server->nb_url => sub {
my ($ua, $tx) = @_;
push @kept_alive, $tx->kept_alive;
Mojo::IOLoop->stop;
}
);
}
);
}
);
Mojo::IOLoop->start;
is_deeply \@kept_alive, [undef, 1, 1], 'connections kept alive';
$tx = $ua->get($ua->server->url);
ok !$tx->error, 'no error';
ok $tx->kept_alive, 'kept connection alive';
ok $tx->keep_alive, 'keep connection alive';
is $tx->res->code, 200, 'right status';
is $tx->res->body, 'works!', 'right content';
# Simple nested non-blocking requests with timers
@kept_alive = ();
$ua->get(
'/' => sub {
push @kept_alive, pop->kept_alive;
Mojo::IOLoop->next_tick(sub {
$ua->get(
'/' => sub {
push @kept_alive, pop->kept_alive;
Mojo::IOLoop->next_tick(sub { Mojo::IOLoop->stop });
}
);
});
}
);
Mojo::IOLoop->start;
is_deeply \@kept_alive, [1, 1], 'connections kept alive';
# Unexpected 1xx responses
$req = Mojo::Message::Request->new;
$id = Mojo::IOLoop->server(
{address => '127.0.0.1'} => sub {
my ($loop, $stream) = @_;
$stream->on(
read => sub {
my ($stream, $chunk) = @_;
$stream->write("HTTP/1.1 100 Continue\x0d\x0a"
. "X-Foo: Bar\x0d\x0a\x0d\x0a"
. "HTTP/1.1 101 Switching Protocols\x0d\x0a\x0d\x0a"
. "HTTP/1.1 200 OK\x0d\x0a"
. "Content-Length: 3\x0d\x0a\x0d\x0a" . 'Hi!')
if $req->parse($chunk)->is_finished;
}
);
}
);
$port = Mojo::IOLoop->acceptor($id)->port;
$tx = $ua->build_tx(GET => "http://127.0.0.1:$port/");
my @unexpected;
$tx->on(unexpected => sub { push @unexpected, pop });
$tx = $ua->start($tx);
is $unexpected[0]->code, 100, 'right status';
is $unexpected[0]->headers->header('X-Foo'), 'Bar', 'right "X-Foo" value';
is $unexpected[1]->code, 101, 'right status';
ok !$tx->error, 'no error';
is $tx->res->code, 200, 'right status';
is $tx->res->body, 'Hi!', 'right content';
# Connection limit
$ua = Mojo::UserAgent->new(max_connections => 2);
$result = undef;
my @txs = map { $ua->get_p('/') } 1 .. 5;
Mojo::Promise->all(@txs)->then(sub {
$result = [grep {defined} map { Mojo::IOLoop->stream($_->connection) } map { $_->[0] } @_];
})->wait;
is scalar @$result, 2, 'two active connections';
done_testing();