The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

Net::Curl::Promiser - Asynchronous libcurl, the easy way!

DESCRIPTION

Net::Curl::Multi is powerful but tricky to use: polling, callbacks, timers, etc. This module does all of that for you and puts a Promise interface on top of it, so asynchronous I/O becomes almost as simple as synchronous I/O.

Net::Curl::Promiser itself is a base class; you’ll need to provide an interface to whatever event loop you use. See "SUBCLASS INTERFACE" below.

This distribution provides the following as both demonstrations and portable implementations:

(See the distribution’s /examples directory for one based on Linux’s epoll.)

PROMISE IMPLEMENTATION

This class’s default Promise implementation is Promise::ES6. You can use a different one by overriding the PROMISE_CLASS() method in a subclass, as long as the substitute class’s new() method works the same way as Promise::ES6’s (which itself follows the ECMAScript standard).

(NB: Net::Curl::Promiser::Mojo uses Mojo::Promise instead of Promise::ES6.)

Experimental Promise::XS support

Try out experimental Promise::XS support by running with NET_CURL_PROMISER_PROMISE_ENGINE=Promise::XS in your environment. This will override PROMISE_CLASS().

GENERAL-USE METHODS

The following are of interest to any code that uses this module:

CLASS->new(@ARGS)

Instantiates this class. This creates an underlying Net::Curl::Multi object and calls the subclass’s _INIT() method at the end, passing a reference to @ARGS.

(Most end classes of this module do not require @ARGS.)

promise($EASY) = OBJ->add_handle( $EASY )

A passthrough to the underlying Net::Curl::Multi object’s method of the same name, but the return is given as a Promise object.

That promise resolves with the passed-in $EASY object. It rejects with either the error given to fail_handle() or the error that Net::Curl::Multi object’s info_read() returns.

IMPORTANT: As with libcurl itself, HTTP-level failures (e.g., 4xx and 5xx responses) are NOT considered failures at this level.

$obj = OBJ->cancel_handle( $EASY )

Prematurely cancels $EASY. The associated promise will be abandoned in pending state, never to resolve nor reject.

Returns OBJ.

$obj = OBJ->fail_handle( $EASY, $REASON )

Like cancel_handle() but rejects $EASY’s associated promise with the given $REASON.

Returns OBJ.

$obj = OBJ->setopt( … )

A passthrough to the underlying Net::Curl::Multi object’s method of the same name. Returns OBJ to facilitate chaining.

CURLMOPT_SOCKETFUNCTION or CURLMOPT_SOCKETDATA are set internally; any attempt to set them via this interface will prompt an error.

$obj = OBJ->handles( … )

A passthrough to the underlying Net::Curl::Multi object’s method of the same name.

EVENT LOOP METHODS

The following are needed only when you’re managing an event loop directly:

$num = OBJ->get_timeout()

Returns the underlying Net::Curl::Multi object’s timeout() value, with a suitable (positive) default substituted if that value is less than 0.

(NB: This value is in milliseconds.)

This may not suit your needs; if you wish/need, you can handle timeouts via the CURLMOPT_TIMERFUNCTION callback instead.

This should only be called (if it’s called at all) from event loop logic.

$obj = OBJ->process( @ARGS )

Tell the underlying Net::Curl::Multi object which socket events have happened.

If, in fact, no events have happened, then this calls socket_action(CURL_SOCKET_TIMEOUT) on the Net::Curl::Multi object (similar to time_out()).

Finally, this reaps whatever pending HTTP responses may be ready and resolves or rejects the corresponding Promise objects.

This should only be called from event loop logic.

Returns OBJ.

$is_active = OBJ->time_out();

Tell the underlying Net::Curl::Multi object that a timeout happened, and reap whatever pending HTTP responses may be ready.

Calls socket_action(CURL_SOCKET_TIMEOUT) on the underlying Net::Curl::Multi object. The return is the same as that operation returns.

Since process() can also do the work of this function, a call to this function is just an optimization.

This should only be called from event loop logic.

SUBCLASS INTERFACE

NOTE: The distribution provides several ready-built end classes; unless you’re managing your own event loop, you don’t need to concern yourself with this.

To use Net::Curl::Promiser, you’ll need a subclass that defines the following methods:

  • _INIT(\@ARGS): Called at the end of new(). Receives a reference to the arguments given to new().

  • _SET_POLL_IN($FD): Tells the event loop that the given file descriptor is ready to read.

  • _SET_POLL_OUT($FD): Like _SET_POLL_IN() but for a write event.

  • _SET_POLL_INOUT($FD): Like _SET_POLL_IN() but registers a read and write event simultaneously.

  • _STOP_POLL($FD): Tells the event loop that the given file descriptor is finished.

  • _GET_FD_ACTION(\@ARGS): Receives a reference to the arguments given to process() and returns a reference to a hash of ( $fd => $event_mask ). $event_mask is the sum of Net::Curl::Multi::CURL_CSELECT_IN() and/or Net::Curl::Multi::CURL_CSELECT_OUT(), depending on which events are available.

IMPORTANT: Your event loop MUST NOT close file descriptors. This means that, if you create Perl filehandles from the file descriptors, you need to prevent Perl from closing the underlying file descriptors.

EXAMPLES

See the distribution’s /examples directory.

SEE ALSO

If you use AnyEvent, then AnyEvent::XSPromises with AnyEvent::YACurl may be a nicer fit for you.

REPOSITORY

https://github.com/FGasper/p5-Net-Curl-Promiser

LICENSE & COPYRIGHT

Copyright 2019-2020 Gasper Software Consulting.

This library is licensed under the same terms as Perl itself.