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 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 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 . 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 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 ) 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 ) Called with one header line at a time, as received. CURLOPT_READFUNCTION (See curl documentation ) 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 ) 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 ) 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 ) 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 . 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.