EV-YACurl

 view release on metacpan or  search on metacpan

README  view on Meta::CPAN

        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

README  view on Meta::CPAN

        "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;

YACurl.xs  view on Meta::CPAN


        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 )