EV-YACurl
view release on metacpan or search on metacpan
Returns a new client, which is a binding over one curl multi handle.
The required hashref holds "CURLM*" options such as
"CURLMOPT_MAX_TOTAL_CONNECTIONS"; see the curl documentation
<https://curl.se/libcurl/c/curl_multi_setopt.html> for the full list.
Options this module cannot translate are rejected rather than silently
ignored, as are names belonging to another namespace: the numbers
collide, so a "CURLOPT_*" name here would otherwise set an unrelated
multi option.
A client owns a connection pool, so reusing one across requests is
what enables keep alive and HTTP/2 multiplexing.
"request"
$client->request($callback, \%options);
Starts a request. %options holds "CURLOPT_*" options and must contain
at least "CURLOPT_URL"; see the curl documentation
<https://curl.se/libcurl/c/curl_easy_setopt.html>.
Returns nothing. $callback is invoked once the request finishes, with
two arguments, $response and $error, exactly one of which is defined.
$response is an "EV::YACurl::Response" object; $error is a human
readable description of what went wrong, taken from libcurl's error
buffer when it has something more specific to say than the generic
message for the error code.
The client is kept alive for the duration of the request, so it is
safe to let the last reference to it go out of scope while transfers
are in flight.
A callback that dies does not propagate: the exception is caught and
reported as a warning, and the loop carries on. Record what went wrong
and act on it after "EV::run" returns, as the synopsis does, rather
than dying inside the callback.
Start follow-up requests from this completion callback. "request"
croaks when called from inside a per-request data callback
"EV::MAXPRI" are clamped. Returns the priority that was in effect
before the call.
libcurl invokes the per request callbacks ("CURLOPT_WRITEFUNCTION" and
friends, and the completion callback given to "request") synchronously
from inside those watchers, so this is also the priority at which your
callbacks run.
Priority belongs to the client rather than to an individual request,
because one socket can carry several transfers at once under keep
alive and HTTP/2 multiplexing. To run two groups of transfers at
different priorities, use two clients.
Setting it takes effect immediately, including on watchers the client
already owns. A watcher that has already received an event keeps its
old priority for that one event, since re-sorting it would discard the
event; the new priority reaches it as soon as that event has been
dispatched.
"default_priority"
my $current = EV::YACurl->default_priority;
ST(0) = sv_newmortal();
sv_setref_pv(ST(0), SvPV_nolen(class), (void *)client);
client->loop = loop;
client->priority = MY_CXT.default_priority;
client->timer.client = client;
ev_timer_init(&client->timer.timer, yacurl_timer_cb, 0., 0.);
ev_set_priority(&client->timer.timer, client->priority);
/* Weak, so the client is not kept alive by its own curl callbacks. */
client->weak_self_ref = newSVsv(ST(0));
sv_rvweaken(client->weak_self_ref);
client->multi = curl_multi_init();
if (!client->multi)
croak("Failed to instantiate CURLM object");
curl_multi_setopt(client->multi, CURLMOPT_SOCKETFUNCTION, mcurl_socket_callback);
curl_multi_setopt(client->multi, CURLMOPT_TIMERFUNCTION, mcurl_timer_callback);
curl_multi_setopt(client->multi, CURLMOPT_SOCKETDATA, (void *)client);
eg/concurrent.pl view on Meta::CPAN
#!/usr/bin/env perl
# Fetch many URLs at once through a single client, with a cap on how many are
# in flight. One client means one connection pool, so hosts that appear more
# than once get keep-alive and HTTP/2 multiplexing for free.
use strict;
use warnings;
use EV;
use EV::YACurl ':constants';
my $limit = 4;
my @urls = @ARGV ? @ARGV : map { "https://www.perl.org$_" } qw(
/ /get.html /docs/ /books/ /about.html /learn.html
);
eg/priority.pl view on Meta::CPAN
my $done = 0;
for my $url (@urls) {
my $bytes = 0;
$bulk->request(sub {
my ($response, $error) = @_;
$done++;
printf "%-40s %s\n", $url,
$error ? "failed: $error"
: sprintf('%d, %d bytes', $response->getinfo(CURLINFO_RESPONSE_CODE), $bytes);
# The repeating timer keeps the loop alive for good, so EV::run would
# never return on its own: break out once the last transfer lands.
EV::break if $done == @urls;
}, {
CURLOPT_URL => $url,
CURLOPT_FOLLOWLOCATION => 1,
CURLOPT_WRITEFUNCTION => sub { $bytes += length $_[0] },
});
}
EV::run;
eg/shutdown.pl view on Meta::CPAN
$error ? 'failed' : $response->getinfo(CURLINFO_RESPONSE_CODE) . ", $bytes bytes";
$pump->();
}, {
CURLOPT_URL => $url,
CURLOPT_FOLLOWLOCATION => 1,
CURLOPT_WRITEFUNCTION => sub { $bytes += length $_[0] },
});
}
# A signal watcher keeps the loop alive by itself, so it has to be broken.
EV::break if !$running && ($stopping || !@queue);
};
my $sigint = EV::signal INT => sub {
return if $stopping;
$stopping = 1;
printf "SIGINT: draining %d transfer(s), dropping %d queued\n", $running, scalar @queue;
EV::break unless $running;
};
lib/EV/YACurl.pm view on Meta::CPAN
my $client = EV::YACurl->new(\%options);
Returns a new client, which is a binding over one curl multi handle. The required
hashref holds C<CURLM*> options such as C<CURLMOPT_MAX_TOTAL_CONNECTIONS>; see the
L<curl documentation|https://curl.se/libcurl/c/curl_multi_setopt.html> for the full
list. Options this module cannot translate are rejected rather than silently
ignored, as are names belonging to another namespace: the numbers collide, so a
C<CURLOPT_*> name here would otherwise set an unrelated multi option.
A client owns a connection pool, so reusing one across requests is what enables
keep alive and HTTP/2 multiplexing.
=item C<request>
$client->request($callback, \%options);
Starts a request. C<%options> holds C<CURLOPT_*> options and must contain at least
C<CURLOPT_URL>; see the L<curl documentation|https://curl.se/libcurl/c/curl_easy_setopt.html>.
Returns nothing. C<$callback> is invoked once the request finishes, with two
arguments, C<$response> and C<$error>, exactly one of which is defined. C<$response>
is an L</"EV::YACurl::Response"> object; C<$error> is a human readable description
of what went wrong, taken from libcurl's error buffer when it has something more
specific to say than the generic message for the error code.
The client is kept alive for the duration of the request, so it is safe to let the
last reference to it go out of scope while transfers are in flight.
A callback that dies does not propagate: the exception is caught and reported as
a warning, and the loop carries on. Record what went wrong and act on it after
C<EV::run> returns, as the synopsis does, rather than dying inside the callback.
Start follow-up requests from this completion callback. C<request> croaks when
called from inside a per-request data callback (C<CURLOPT_WRITEFUNCTION> and
friends), for any client, because libcurl is inside its own API while those
run; the croak is reported as a warning like any other callback death. Do not
lib/EV/YACurl.pm view on Meta::CPAN
Queries, and optionally sets, the EV priority of every watcher this client owns:
the C<ev_io> watcher behind each socket libcurl asks to poll, and the client's
C<ev_timer>. Values outside C<EV::MINPRI> .. C<EV::MAXPRI> are clamped. Returns
the priority that was in effect before the call.
libcurl invokes the per request callbacks (C<CURLOPT_WRITEFUNCTION> and friends,
and the completion callback given to C<request>) synchronously from inside those
watchers, so this is also the priority at which your callbacks run.
Priority belongs to the client rather than to an individual request, because one
socket can carry several transfers at once under keep alive and HTTP/2
multiplexing. To run two groups of transfers at different priorities, use two
clients.
Setting it takes effect immediately, including on watchers the client already
owns. A watcher that has already received an event keeps its old priority for
that one event, since re-sorting it would discard the event; the new priority
reaches it as soon as that event has been dispatched.
=item C<default_priority>
t/07-edge-cases.t view on Meta::CPAN
$done = 1;
$completed++;
}, {
CURLOPT_URL => "$base/",
CURLOPT_WRITEFUNCTION => sub { },
});
EV::run until $done;
}
is($completed, 3, "Keep-alive: all requests completed");
# On localhost a reconnect is as fast as the first connect, so timing proves
# nothing here; the count of new connections does.
is_deeply([@connect_times[1, 2]], [0, 0], "Keep-alive: later requests opened no connection")
or diag "num_connects: @connect_times";
}
{
my $client = EV::YACurl->new({});
my ($res, $err);
my $done = 0;
my @headers;
$client->request(sub {
t/10-lifecycle.t view on Meta::CPAN
use Scalar::Util qw(weaken);
use EV;
use TestServer;
use EV::YACurl ':constants';
TestServer::watchdog(120);
my $server = TestServer->new(sub { (200, [], 'body') });
my $base = $server->base_url;
# The client is kept alive by an in-flight request, and released afterwards.
{
my $alive;
my ($done, $error) = (0, undef);
{
my $client = EV::YACurl->new({});
weaken($alive = $client);
$client->request(sub { $error = $_[1]; $done = 1 },
{ CURLOPT_URL => "$base/", CURLOPT_WRITEFUNCTION => sub { } });
}
ok(defined $alive, 'client survives going out of scope mid request');
EV::run until $done;
is($error, undef, 'the abandoned request still completed');
ok(!defined $alive, 'client is released once the request is done');
}
# Dropping the last reference from inside the completion callback must not
# pull the ground out from under the code still unwinding around it.
{
my $client = EV::YACurl->new({});
my $done = 0;
$client->request(sub { undef $client; $done = 1 },
{ CURLOPT_URL => "$base/", CURLOPT_WRITEFUNCTION => sub { } });
EV::run until $done;
t/lib/TestServer.pm view on Meta::CPAN
$self->{pid} = undef;
}
sub DESTROY { local $?; $_[0]->stop }
my $watchdog;
# Turns a wedged test into a failure instead of a hang. It has to be an EV
# watcher, since $SIG{ALRM} is only dispatched between Perl ops and so never
# arrives while blocked in EV::run, and it has to exit rather than die, since EV
# catches exceptions from a watcher callback and carries on. keepalive(0) stops
# it holding the loop open. A wedge that never re-enters EV::run is not covered.
sub watchdog {
my ($seconds) = @_;
$seconds ||= 60;
$watchdog = EV::signal(ALRM => sub {
print STDERR "# watchdog: no progress after ${seconds}s\n";
exit 1;
});
$watchdog->keepalive(0);
alarm $seconds;
}
sub _serve {
my ($listener, $handler) = @_;
$SIG{CHLD} = 'IGNORE';
# One child per connection, so a keep-alive connection sitting idle cannot
# stall the accept loop for the next one.
while (my $conn = $listener->accept) {
my $pid = fork;
if (!defined $pid || $pid) {
close $conn;
next;
}
close $listener;
$conn->autoflush(1);
( run in 1.081 second using v1.01-cache-2.11-cpan-14f38c9f855 )