NAME
    EV::YACurl - Yet Another curl binding for EV

SYNOPSIS
        use EV;
        use EV::YACurl ':constants';

        my $client = EV::YACurl->new({});
        my ($done, $failed, $body) = (0, undef, '');

        $client->request(sub {
            my ($response, $error) = @_;
            $done = 1;
            return $failed = $error if $error;
            printf "%d, %d bytes\n", $response->getinfo(CURLINFO_RESPONSE_CODE), length $body;
        }, {
            CURLOPT_URL => 'https://www.perl.org',
            CURLOPT_WRITEFUNCTION => sub { $body .= $_[0] },
            CURLOPT_HTTPHEADER => ['My-Super-Awesome-Header: forty-two'],
        });

        EV::run until $done;
        die "Request failed: $failed\n" if $failed;

DESCRIPTION
    This module binds libcurl's "multi" interface
    <https://curl.se/libcurl/c/libcurl-multi.html> to EV. Transfers run on
    EV's default loop alongside everything else it drives, and libcurl's
    sockets and timeouts are managed by C watchers registered directly through
    EV's C API, without a round trip through Perl.

METHODS
  EV::YACurl
    "new"
            my $client = EV::YACurl->new(\%options);

        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
        ("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 run a nested event loop
        from one either, for the same reason.

        A request that fails before it reaches the network, a malformed URL
        for instance, is finished before "request" returns, so its callback
        runs on the caller's stack rather than from the loop. A callback that
        retries such a request unconditionally therefore recurses instead of
        unwinding; bound the retries, or arm an "EV::timer" and retry from
        that.

    "priority"
            my $current  = $client->priority;
            my $previous = $client->priority($new_priority);

        Queries, and optionally sets, the EV priority of every watcher this
        client owns: the "ev_io" watcher behind each socket libcurl asks to
        poll, and the client's "ev_timer". Values outside "EV::MINPRI" ..
        "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;
            my $previous = EV::YACurl->default_priority($new_priority);

        Class method. Queries, and optionally sets, the priority handed to
        clients created from now on. Defaults to 0, which is EV's own default.
        Existing clients are not affected, so set this during startup rather
        than mid run.

  EV::YACurl::Response
    "getinfo"
            my $value = $response->getinfo(CURLINFO_RESPONSE_CODE);

        Queries libcurl for information about the finished transfer. See the
        curl documentation <https://curl.se/libcurl/c/curl_easy_getinfo.html>
        for the available "CURLINFO_*" options.

        String, integer and floating point results come back as plain scalars,
        and the ones that return a "curl_slist", "CURLINFO_COOKIELIST" for
        instance, come back as an array reference of strings. The remaining
        kinds, the ones handing out C pointers and sockets, are refused.

CURL OPTIONS
    Some translation between Perl and curl value types has to be done. Options
    taking a number or a string are converted from plain scalars. Options
    taking a "curl_slist" take an array reference instead, as
    "CURLOPT_HTTPHEADER" does in the synopsis. "CURLOPT_MIMEPOST" takes an
    array of hashrefs, each with a "name" and one of "value" or "file".

    "CURLOPT_POSTFIELDS" is copied by libcurl along with its length, so a body
    containing zero bytes survives; there is no need to set
    "CURLOPT_POSTFIELDSIZE" yourself.

    Each request is its own libcurl easy handle, which is what a cookie jar is
    flushed from. A "CURLOPT_COOKIEJAR" file is therefore not written until
    the response object is freed, so a follow-up request started from inside
    the completion callback will not see it yet: carry the cookie across
    yourself with "CURLOPT_COOKIE", or start the follow-up after the response
    has gone.

    Options that take a function need a Perl signature of their own,
    documented below.

    CURLOPT_WRITEFUNCTION
        (See curl documentation
        <https://curl.se/libcurl/c/CURLOPT_WRITEFUNCTION.html>)

        Called with the received data as its only argument. Its return value
        is ignored, so it cannot abort a transfer; use "CURLOPT_TIMEOUT_MS"
        and friends, or return "undef" from "CURLOPT_READFUNCTION" on an
        upload.

            CURLOPT_WRITEFUNCTION => sub {
                my ($data) = @_;
                print STDERR $data;
            },

    CURLOPT_HEADERFUNCTION
        (See curl documentation
        <https://curl.se/libcurl/c/CURLOPT_HEADERFUNCTION.html>)

        Called with one header line at a time, as received.

    CURLOPT_READFUNCTION
        (See curl documentation
        <https://curl.se/libcurl/c/CURLOPT_READFUNCTION.html>)

        Called with the maximum number of bytes wanted. Return the data, an
        empty string to end the transfer, or "undef" to abort it.

            CURLOPT_READFUNCTION => sub {
                my ($length) = @_;
                return substr($my_data, 0, $length, '');
            },

    CURLOPT_DEBUGFUNCTION
        (See curl documentation
        <https://curl.se/libcurl/c/CURLOPT_DEBUGFUNCTION.html>)

        Called with a "CURLINFO_*" type and the data itself.

            CURLOPT_DEBUGFUNCTION => sub {
                my ($type, $data) = @_;
                print STDERR "curl: $data\n" if $type == CURLINFO_TEXT;
            },

    CURLOPT_TRAILERFUNCTION
        (See curl documentation
        <https://curl.se/libcurl/c/CURLOPT_TRAILERFUNCTION.html>)

        Called without arguments. Return an array reference of trailing
        headers to send, or "undef" to abort the request.

            CURLOPT_TRAILERFUNCTION => sub {
                return ['My-super-awesome-trailer: trailer-stuff'];
            },

    CURLOPT_STDERR
        (See curl documentation
        <https://curl.se/libcurl/c/CURLOPT_STDERR.html>)

        Takes a file descriptor number rather than a filehandle, and
        duplicates it so the destination stays valid for the whole transfer.

            CURLOPT_STDERR => fileno($fh),

    "CURLOPT_PRIVATE" and "CURLOPT_ERRORBUFFER" are used internally and cannot
    be set.

REQUIREMENTS
    libcurl 7.64.0 or newer, built with AsynchDNS. Without AsynchDNS every
    name lookup blocks the event loop, which defeats the point of an
    asynchronous binding; "Makefile.PL" warns when it can tell that support is
    missing.

CAVEATS
    Transfers run on EV's default loop.

    Letting the last reference to a busy client go is safe, since its
    transfers hold it up until they finish. Tearing one down by hand while
    transfers are still in flight is not: it warns, abandons them, and what
    they were holding is only reclaimed when the process exits.

    Clients and responses belong to the interpreter that created them and are
    not cloned into new threads: after "threads->create" they read as "undef"
    there. Create new ones in the new thread.

    A client does not survive a fork, and the hazard is the child's exit
    rather than its use: tearing libcurl down closes connections the parent is
    still using. Create clients after forking, and let forked children leave
    through "POSIX::_exit" so they never run that teardown.

EXAMPLES
    The "eg/" directory in the distribution has runnable programs for the
    common shapes: a single request ("get.pl"), many at once through one
    connection pool ("concurrent.pl"), streaming a response to disk
    ("download.pl"), sending a file with a read callback ("upload.pl"),
    request and response headers with the transfer's timings ("headers.pl"),
    and placing transfers in EV's priority order alongside a latency-sensitive
    watcher ("priority.pl").

    Further in: a JSON API call that carries a session into a second request
    ("json-api.pl"), a multipart upload ("multipart.pl"), retrying with
    backoff on an EV timer ("retry.pl"), per-request deadlines and cancelling
    an upload mid-flight ("timeout.pl"), draining in-flight work on a signal
    ("shutdown.pl"), and HTTP/2 multiplexing measured against one connection
    per transfer ("http2.pl").

SEE ALSO
    EV, AnyEvent::YACurl

ACKNOWLEDGEMENTS
    This distribution is a fork of AnyEvent::YACurl 0.22 by Tom van der
    Woerdt, and would not exist without it. Everything that makes the binding
    useful came from there: the mapping of the whole "CURLOPT_*" table onto
    Perl values, the generators that keep that table in step with libcurl's
    headers, the callback marshalling for "CURLOPT_WRITEFUNCTION" and its
    siblings, the MIME and slist handling, and the response object.

    What this fork changes is the event integration. Where AnyEvent::YACurl
    asks AnyEvent for watchers from Perl, EV::YACurl creates "ev_io" and
    "ev_timer" watchers from XS through EV's C API, which is what makes
    "priority" possible and what keeps Perl out of the per-socket path.

AUTHOR
    vividsnow

    Original AnyEvent::YACurl by Tom van der Woerdt <tvdw@cpan.org>.

COPYRIGHT AND LICENSE
    This software is copyright (c) 2020 by Tom van der Woerdt.

    This is free software; you can redistribute it and/or modify it under the
    same terms as the Perl 5 programming language system itself.

