.\" Automatically generated by Pod::Man 4.14 (Pod::Simple 3.43) .\" .\" Standard preamble: .\" ======================================================================== .de Sp \" Vertical space (when we can't use .PP) .if t .sp .5v .if n .sp .. .de Vb \" Begin verbatim text .ft CW .nf .ne \\$1 .. .de Ve \" End verbatim text .ft R .fi .. .\" Set up some character translations and predefined strings. \*(-- will .\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left .\" double quote, and \*(R" will give a right double quote. \*(C+ will .\" give a nicer C++. Capital omega is used to do unbreakable dashes and .\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff, .\" nothing in troff, for use with C<>. .tr \(*W- .ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p' .ie n \{\ . ds -- \(*W- . ds PI pi . if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch . if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch . ds L" "" . ds R" "" . ds C` "" . ds C' "" 'br\} .el\{\ . ds -- \|\(em\| . ds PI \(*p . ds L" `` . ds R" '' . ds C` . ds C' 'br\} .\" .\" Escape single quotes in literal strings from groff's Unicode transform. .ie \n(.g .ds Aq \(aq .el .ds Aq ' .\" .\" If the F register is >0, we'll generate index entries on stderr for .\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index .\" entries marked with X<> in POD. Of course, you'll have to process the .\" output yourself in some meaningful fashion. .\" .\" Avoid warning from groff about undefined register 'F'. .de IX .. .nr rF 0 .if \n(.g .if rF .nr rF 1 .if (\n(rF:(\n(.g==0)) \{\ . if \nF \{\ . de IX . tm Index:\\$1\t\\n%\t"\\$2" .. . if !\nF==2 \{\ . nr % 0 . nr F 2 . \} . \} .\} .rr rF .\" ======================================================================== .\" .IX Title "YAHC 3pm" .TH YAHC 3pm "2022-12-13" "perl v5.36.0" "User Contributed Perl Documentation" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l .nh .SH "NAME" YAHC \- Yet another HTTP client .SH "SYNOPSIS" .IX Header "SYNOPSIS" .Vb 1 \& use YAHC qw/yahc_reinit_conn/; \& \& my @hosts = (\*(Aqwww.booking.com\*(Aq, \*(Aqwww.google.com:80\*(Aq); \& my ($yahc, $yahc_storage) = YAHC\->new({ host => \e@hosts }); \& \& $yahc\->request({ path => \*(Aq/\*(Aq, host => \*(Aqwww.reddit.com\*(Aq }); \& $yahc\->request({ path => \*(Aq/\*(Aq, host => sub { \*(Aqwww.reddit.com\*(Aq } }); \& $yahc\->request({ path => \*(Aq/\*(Aq, host => \e@hosts }); \& $yahc\->request({ path => \*(Aq/\*(Aq, callback => sub { ... } }); \& $yahc\->request({ path => \*(Aq/\*(Aq }); \& $yahc\->request({ \& path => \*(Aq/\*(Aq, \& callback => sub { \& yahc_reinit_conn($_[0], { host => \*(Aqwww.newtarget.com\*(Aq }) \& if $_[0]\->{response}{status} == 301; \& } \& }); \& \& $yahc\->run; .Ve .SH "DESCRIPTION" .IX Header "DESCRIPTION" \&\s-1YAHC\s0 is fast & minimal low-level asynchronous \s-1HTTP\s0 client intended to be used where you control both the client and the server. Is especially suits cases where set of requests need to be executed against group of machines. .PP It is \fB\s-1NOT\s0\fR a general \s-1HTTP\s0 user agent, it doesn't support redirects, proxies and any number of other advanced \s-1HTTP\s0 features like (in roughly descending order of feature completeness) LWP::UserAgent, WWW::Curl, HTTP::Tiny, HTTP::Lite or Furl. This library is basically one step above manually talking \s-1HTTP\s0 over sockets. .PP \&\s-1YAHC\s0 supports \s-1SSL\s0 and socket reuse (latter is in experimental mode). .SH "STATE MACHINE" .IX Header "STATE MACHINE" Each \s-1YAHC\s0 connection goes through following list of states in its lifetime: .PP .Vb 10 \& +\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-+ \& +<<\-| INITIALIZED <\-<<+ \& v +\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-+ ^ \& v | ^ \& v +\-\-\-\-\-\-\-v\-\-\-\-\-\-\-\-\-+ ^ \& +<<\-+ RESOLVE DNS +\->>+ \& v +\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-+ ^ \& v | ^ \& v +\-\-\-\-\-\-\-v\-\-\-\-\-\-\-\-\-+ ^ \& +<<\-+ CONNECTING +\->>+ \& v +\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-+ ^ \& v | ^ \& Path in v +\-\-\-\-\-\-\-v\-\-\-\-\-\-\-\-\-+ ^ Retry \& case of +<<\-+ CONNECTED +\->>+ logic \& failure v +\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-+ ^ path \& v | ^ \& v +\-\-\-\-\-\-\-v\-\-\-\-\-\-\-\-\-+ ^ \& +<<\-+ WRITING +\->>+ \& v +\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-+ ^ \& v | ^ \& v +\-\-\-\-\-\-\-v\-\-\-\-\-\-\-\-\-+ ^ \& +<<\-+ READING +\->>+ \& v +\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-+ ^ \& v | ^ \& v +\-\-\-\-\-\-\-v\-\-\-\-\-\-\-\-\-+ ^ \& +>>\-> USER ACTION +\->>+ \& +\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-+ \& | \& +\-\-\-\-\-\-\-v\-\-\-\-\-\-\-\-\-+ \& | COMPLETED | \& +\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-+ .Ve .PP There are three paths of workflow: .IP "1) Normal execution (central line)." 4 .IX Item "1) Normal execution (central line)." In normal situation a connection after being initialized goes through state: .Sp \&\- \s-1RESOLVE DNS\s0 (not implemented) .Sp \&\- \s-1CONNECTING\s0 \- wait finishing of handshake .Sp \&\- \s-1CONNECTED\s0 .Sp \&\- \s-1WRITING\s0 \- sending request body .Sp \&\- \s-1READING\s0 \- awaiting and reading response .Sp \&\- \s-1USER ACTION\s0 \- see below .Sp \&\- \s-1COMPLETED\s0 \- all done, this is terminal state .Sp \&\s-1SSL\s0 connection has extra state \s-1SSL_HANDSHAKE\s0 after \s-1CONNECTED\s0 state. State \&'\s-1RESOLVE DNS\s0' is not implemented yet. .IP "2) Retry path (right line)." 4 .IX Item "2) Retry path (right line)." In case of \s-1IO\s0 error during normal execution \s-1YAHC\s0 retries connection \&\f(CW\*(C`retries\*(C'\fR times. In practice this means that connection goes back to \&\s-1INITIALIZED\s0 state. .IP "3) Failure path (left line)." 4 .IX Item "3) Failure path (left line)." If all retry attempts did not succeeded a connection goes to state '\s-1USER ACTION\s0' (see below). .SS "State '\s-1USER ACTION\s0'" .IX Subsection "State 'USER ACTION'" \&'\s-1USER ACTION\s0' state is called right before connection if going to enter \&'\s-1COMPLETED\s0' state (with either failed or successful results) and is meant to give a chance to user to interrupt the workflow. .PP \&'\s-1USER ACTION\s0' state is entered in these circumstances: .IP "\(bu" 4 \&\s-1HTTP\s0 response received. Note that non\-200 responses are \s-1NOT\s0 treated as error. .IP "\(bu" 4 unsupported \s-1HTTP\s0 response is received (such as response without Content-Length header) .IP "\(bu" 4 retries limit reached .IP "\(bu" 4 lifetime timeout has expired .IP "\(bu" 4 provided callback has thrown exception .IP "\(bu" 4 internal error has occurred .PP When a connection enters this state \f(CW\*(C`callback\*(C'\fR CodeRef is called: .PP .Vb 8 \& $yahc\->request({ \& ... \& callback => sub { \& my ( \& $conn, # connection \*(Aqobject\*(Aq \& $error, # one of YAHC::Error::* constants \& $strerror # string representation of error \& ) = @_; \& \& # Note that fields in $conn\->{response} are not reliable \& # if $error != YAHC::Error::NO_ERROR() \& \& # HTTP response is stored in $conn\->{response}. \& # It can be also accessed via yahc_conn_response(). \& my $response = $conn\->{response}; \& my $status = $response\->{status}; \& my $body = $response\->{body}; \& } \& }); .Ve .PP If there was no \s-1IO\s0 error \f(CW\*(C`yahc_conn_response\*(C'\fR return \f(CW\*(C`HashRef\*(C'\fR representing response. It contains the following key-value pairs. .PP .Vb 4 \& proto => :Str \& status => :StatusCode \& body => :Str \& head => :HashRef .Ve .PP In case of a error or non\-200 \s-1HTTP\s0 response \f(CW\*(C`yahc_retry_conn\*(C'\fR or \&\f(CW\*(C`yahc_reinit_conn\*(C'\fR may be called to give the request more chances to complete successfully (for example by following redirects or providing new target hosts). Also, note that in case of a error data returned by \&\f(CW\*(C`yahc_conn_response\*(C'\fR cannot be trusted. For example, if an \s-1IO\s0 error happened during receiving \s-1HTTP\s0 body headers would state 200 response code. .PP \&\s-1YAHC\s0 lowercases headers names returned in \f(CW\*(C`head\*(C'\fR. This is done to comply with \&\s-1RFC\s0 which identify \s-1HTTP\s0 headers as case-insensitive. .PP In some cases connection cannot be retried anymore and callback is called for information purposes only. This case can be distinguished by \&\f(CW$error\fR having \fBYAHC::Error::TERMINAL_ERROR()\fR bit set. One can use \&\f(CW\*(C`yahc_terminal_error\*(C'\fR helper to detect such case. .PP Note that \f(CW\*(C`callback\*(C'\fR should \s-1NOT\s0 throw exception. If so the connection will be immediately closed. .SH "METHODS" .IX Header "METHODS" .SS "new" .IX Subsection "new" This method creates \s-1YAHC\s0 object and accompanying storage object: .PP .Vb 1 \& my ($yahc, $yahc_storage) = YAHC\->new(); .Ve .PP This is a radical way of solving all possible memleak because of cyclic references in callbacks. Since all references of callbacks are kept in \&\f(CW$yahc_storage\fR object it's fine to use \s-1YAHC\s0 object inside request callback: .PP .Vb 5 \& my $yahc\->request({ \& callback => sub { \& $yahc\->stop; # this is fine!!! \& }, \& }); .Ve .PP However, user has to guarantee that both \f(CW$yahc\fR and \f(CW$yahc_storage\fR objects are kept in the same scope. So, they will be destroyed at the same time. .PP \&\f(CW\*(C`new\*(C'\fR can be passed with all parameters supported by \f(CW\*(C`request\*(C'\fR. They will be inherited by all requests. .PP Additionally, \f(CW\*(C`new\*(C'\fR supports three parameters: \f(CW\*(C`socket_cache\*(C'\fR, \&\f(CW\*(C`account_for_signals\*(C'\fR, and \f(CW\*(C`loop\*(C'\fR. .PP \fIsocket_cache\fR .IX Subsection "socket_cache" .PP \&\f(CW\*(C`socket_cache\*(C'\fR option controls socket reuse logic. By default socket cache is disabled. If user wants \s-1YAHC\s0 reuse sockets he should set \f(CW\*(C`socket_cache\*(C'\fR to a HashRef. .PP .Vb 1 \& my ($yahc, $yahc_storage) = YAHC\->new({ socket_cache => {} }); .Ve .PP In this case \s-1YAHC\s0 maintains unused sockets keyed on \f(CW\*(C`join($;, $$, $host, $port, $scheme)\*(C'\fR. We use \f(CW$;\fR so we can use the \f(CW\*(C`$socket_cache\->{$$, $host, $port, $scheme}\*(C'\fR idiom to access the cache. .PP It's up to user to control the cache. It's also up to user to set necessary request headers for keep-alive. \s-1YAHC\s0 does not cache socket in cases of an error, \&\s-1HTTP/1.0\s0 and when server explicitly instructs to close connection (i.e. header \&'Connection' = 'close'). .PP \fIloop\fR .IX Subsection "loop" .PP By default, each \s-1YAHC\s0 object will use its own \s-1EV\s0 eventloop. This is normally preferred since it allows for more accurate timing metrics. .PP However, if the process is already using an eventloop, having an inner loop means the outer one stays waiting until the inner one is done. .PP To get around this, one can specify the eventloop that \s-1YAHC\s0 will use: .PP .Vb 3 \& my ($yahc, $storage) = YAHC\->new({ \& loop => EV::default_loop(), # use the default EV eventloop \& }); .Ve .PP Using the above, \s-1YAHC\s0 will be sharing the same eventloop as everyone else, so some operations are now riskier and should be avoided; For example, in most scenarios \f(CW\*(C`account_for_signals\*(C'\fR should not be used alongside \f(CW\*(C`loop\*(C'\fR, as only whatever is entering the eventloop should set the signal handlers. .PP \fIaccount_for_signals\fR .IX Subsection "account_for_signals" .PP Another parameter \f(CW\*(C`account_for_signals\*(C'\fR requires special attention! Here is why: .Sp .RS 4 excerpt from \s-1EV\s0 documentation .Sp While Perl signal handling (%SIG) is not affected by \s-1EV,\s0 the behaviour with \s-1EV\s0 is as the same as any other C library: Perl-signals will only be handled when Perl runs, which means your signal handler might be invoked only the next time an event callback is invoked. .RE .PP In practise this means that none of set \f(CW%SIG\fR handlers will be called until \s-1EV\s0 calls one of perl callbacks. Which, in some cases, may take a long time. By setting \f(CW\*(C`account_for_signals\*(C'\fR \s-1YAHC\s0 adds \f(CW\*(C`EV::check\*(C'\fR watcher with empty callback effectively making \s-1EV\s0 calling the callback on every iteration. The trickery comes at some performance cost. This is what \s-1EV\s0 documentation says about it: .Sp .RS 4 \&... you can also force a watcher to be called on every event loop iteration by installing a EV::check watcher. This ensures that perl gets into control for a short time to handle any pending signals, and also ensures (slightly) slower overall operation. .RE .PP So, if your code or the codes surrounding your code use \f(CW%SIG\fR handlers it's wise to set \f(CW\*(C`account_for_signals\*(C'\fR. .SS "request" .IX Subsection "request" .Vb 9 \& protocol => "HTTP/1.1", # (or "HTTP/1.0") \& scheme => "http" or "https" \& host => see below, \& port => ..., \& method => "GET", \& path => "/", \& query_string => "", \& head => [], \& body => "", \& \& # timeouts \& connect_timeout => undef, \& request_timeout => undef, \& drain_timeout => undef, \& lifetime_timeout => undef, \& \& # burst control \& backoff_delay => undef, \& \& # callbacks \& init_callback => undef, \& connecting_callback => undef, \& connected_callback => undef, \& writing_callback => undef, \& reading_callback => undef, \& callback => undef, \& \& # SSL options \& ssl_options => {}, .Ve .PP Notice how \s-1YAHC\s0 does not take a full \s-1URI\s0 string as input, you have to specify the individual parts of the \s-1URL.\s0 Users who need to parse an existing \s-1URI\s0 string to produce a request should use the \s-1URI\s0 module to do so. .PP For example, to send a request to \f(CW\*(C`http://example.com/flower?color=red\*(C'\fR, pass the following parameters: .PP .Vb 6 \& $yach\->request({ \& host => "example.com", \& port => "80", \& path => "/flower", \& query_string => "color=red" \& }); .Ve .PP \fIrequest building\fR .IX Subsection "request building" .PP \&\s-1YAHC\s0 doesn't escape any values for you, it just passes them through as-is. You can easily produce invalid requests if e.g. any of these strings contain a newline, or aren't otherwise properly escaped. .PP Notice that you do not need to put the leading \f(CW"?"\fR character in the \&\f(CW\*(C`query_string\*(C'\fR. You do, however, need to properly \f(CW\*(C`uri_escape\*(C'\fR the content of \&\f(CW\*(C`query_string\*(C'\fR. .PP The value of \f(CW\*(C`head\*(C'\fR is an \f(CW\*(C`ArrayRef\*(C'\fR of key-value pairs instead of a \&\f(CW\*(C`HashRef\*(C'\fR, this way you can decide in which order the headers are sent, and you can send the same header name multiple times. For example: .PP .Vb 4 \& head => [ \& "Content\-Type" => "application/json", \& "X\-Requested\-With" => "YAHC", \& ] .Ve .PP Will produce these request headers: .PP .Vb 2 \& Content\-Type: application/json \& X\-Requested\-With: YAHC .Ve .PP \fIhost\fR .IX Subsection "host" .PP \&\f(CW\*(C`host\*(C'\fR parameter can accept one of following values: .Sp .Vb 2 \& 1) string \- represents target host. String may have following formats: \& hostname:port, ip:port. \& \& 2) ArrayRef of strings \- YAHC will cycle through items selecting new host \& for each attempt. \& \& 3) CodeRef. The subroutine is invoked for each attempt and should at least \& return a string (hostname or IP address). It can also return array \& containing: ($host, $ip, $port, $scheme). This option effectively give a \& user control over host selection for retries. The CodeRef is passed with \& connection "object" which can be fed to yahc_conn_* family of functions. .Ve .PP \fItimeouts\fR .IX Subsection "timeouts" .PP The value of \f(CW\*(C`connect_timeout\*(C'\fR, \f(CW\*(C`request_timeout\*(C'\fR and \f(CW\*(C`drain_timeout\*(C'\fR is in floating point seconds, and is used as the time limit for connecting to the host (reaching \s-1CONNECTED\s0 state), full request time (reaching \s-1COMPLETED\s0 state) and sending request to remote site (reaching \s-1READING\s0 state) respectively. .PP \&\f(CW\*(C`lifetime_timeout\*(C'\fR has special purpose. Its task is to provide upper bound timeout for a request lifetime. In other words, if a request comes with multiple retries \f(CW\*(C`connect_timeout\*(C'\fR, \f(CW\*(C`request_timeout\*(C'\fR and \f(CW\*(C`drain_timeout\*(C'\fR are per attempt. \f(CW\*(C`lifetime_timeout\*(C'\fR covers all attempts. If by the time \&\f(CW\*(C`lifetime_timeout\*(C'\fR expires a connection is not in \s-1COMPLETED\s0 state a error is generated. Note that after this error the connection cannot be retried anymore. So, it's forced to go to \s-1COMPLETED\s0 state. .PP The default value for all is \f(CW\*(C`undef\*(C'\fR, meaning no timeout limit. .PP \fIbackoff_delay\fR .IX Subsection "backoff_delay" .PP \&\f(CW\*(C`backoff_delay\*(C'\fR can be used to introduce delay between retries. This is a great way to avoid load spikes on server side. Following example creates new request which would be retried twice doing three attempts in total. Second and third attempts will be delay by one second each. .PP .Vb 5 \& $yach\->request({ \& host => "example.com", \& retries => 2, \& backoff_delay => 1, \& }); .Ve .PP \&\f(CW\*(C`backoff_delay\*(C'\fR can be set in two ways: .Sp .Vb 1 \& 1) floating point seconds \- define constant delay between retires. \& \& 2) CodeRef. The subroutine is invoked on each retry and should return \& floating point seconds. This option is useful for having exponentially \& growing delay or, for instance, jitted delays. .Ve .PP The default value is \f(CW\*(C`undef\*(C'\fR, meaning no delay. .PP \fIcallbacks\fR .IX Subsection "callbacks" .PP The value of \f(CW\*(C`init_callback\*(C'\fR, \f(CW\*(C`connecting_callback\*(C'\fR, \f(CW\*(C`connected_callback\*(C'\fR, \&\f(CW\*(C`writing_callback\*(C'\fR, \f(CW\*(C`reading_callback\*(C'\fR is a reference to a subroutine which is called upon reaching corresponding state. Any exception thrown in the subroutine will be ignored. .PP The value of \f(CW\*(C`callback\*(C'\fR defines main request callback which is called when a connection enters '\s-1USER ACTION\s0' state (see '\s-1USER ACTION\s0' state above). .PP Also see \s-1LIMITATIONS\s0 .PP \fIssl_options\fR .IX Subsection "ssl_options" .PP Performing \s-1HTTPS\s0 requires the value of \f(CW\*(C`ssl_options\*(C'\fR extended by two parameters set to current hostname: .PP .Vb 2 \& SSL_verifycn_name => $hostname, \& IO::Socket::SSL\->can_client_sni ? ( SSL_hostname => $hostname ) : (), .Ve .PP Apart of this changes, the value is directly passed to \&\f(CW\*(C`IO::Socket::SSL::start_SSL()\*(C'\fR. For more details refer to IO::Socket::SSL documentation . .SS "drop" .IX Subsection "drop" Given connection HashRef or conn_id move connection to \s-1COMPLETED\s0 state (avoiding \&'\s-1USER ACTION\s0' state) and drop it from internal pool. The function takes two parameters: first is either a connection id or connection HashRef. Second one is a boolean flag indicating whether connection's socket should closed or it might be reused. .SS "run" .IX Subsection "run" Start \s-1YAHC\s0's loop. The loop stops when all connection complete. .PP Note that \f(CW\*(C`run\*(C'\fR can accept two extra parameters: until_state and list of connections. These two parameters tell \s-1YAHC\s0 to break the loop once specified connections reach desired state. .PP For example: .PP .Vb 1 \& $yahc\->run(YAHC::State::READING(), $conn_id); .Ve .PP Will loop until connection '$conn_id' move to state \s-1READING\s0 meaning that the data has been sent to remote side. In order to gather response one should later call: .PP .Vb 1 \& $yahc\->run(YAHC::State::COMPLETED(), $conn_id); .Ve .PP or simply: .PP .Vb 1 \& $yahc\->run(); .Ve .PP Leaving list of connection empty makes \s-1YAHC\s0 waiting for all connection reaching needed until_state. .PP Note that waiting one particular connection to finish doesn't mean that others are not executed. Instead, all active connections are looped at the same time, but \s-1YAHC\s0 breaks the loop once waited connection reaches needed state. .SS "run_once" .IX Subsection "run_once" Same as run but with \s-1EV::RUN_ONCE\s0 set. For more details check .SS "run_tick" .IX Subsection "run_tick" Same as run but with \s-1EV::RUN_NOWAIT\s0 set. For more details check .SS "is_running" .IX Subsection "is_running" Return true if \s-1YAHC\s0 is running, false otherwise. .SS "loop" .IX Subsection "loop" Return underlying \s-1EV\s0 loop object. .SS "break" .IX Subsection "break" Break running \s-1EV\s0 loop if any. .SH "EXPORTED FUNCTIONS" .IX Header "EXPORTED FUNCTIONS" .SS "yahc_reinit_conn" .IX Subsection "yahc_reinit_conn" \&\f(CW\*(C`yahc_reinit_conn\*(C'\fR reinitialize given connection. The attempt counter is reset to 0. The function accepts HashRef as second argument. By passing it one can change host, port, scheme, body, head and others parameters. The format and meaning of these parameters is same as in \f(CW\*(C`request\*(C'\fR method. .PP One of use cases of \f(CW\*(C`yahc_reinit_conn\*(C'\fR, for example, is to handle redirects: .PP .Vb 1 \& use YAHC qw/yahc_reinit_conn/; \& \& my ($yahc, $yahc_storage) = YAHC\->new(); \& $yahc\->request({ \& host => \*(Aqdomain_which_returns_301.com\*(Aq, \& callback => sub { \& ... \& my $conn = $_[0]; \& yahc_reinit_conn($conn, { host => \*(Aqwww.newtarget.com\*(Aq }) \& if $_[0]\->{response}{status} == 301; \& ... \& } \& }); \& \& $yahc\->run; .Ve .PP \&\f(CW\*(C`yahc_reinit_conn\*(C'\fR is meant to be called inside \f(CW\*(C`callback\*(C'\fR i.e. when connection is in '\s-1USER ACTION\s0' state. .SS "yahc_retry_conn" .IX Subsection "yahc_retry_conn" Retries given connection. \f(CW\*(C`yahc_retry_conn\*(C'\fR should be called only if \&\f(CW\*(C`yahc_conn_attempts_left\*(C'\fR returns positive value. Otherwise, it exits silently. The function accepts HashRef as second argument. By passing it one can change \f(CW\*(C`backoff_delay\*(C'\fR parameter. See docs for \f(CW\*(C`request\*(C'\fR for more details about \f(CW\*(C`backoff_delay\*(C'\fR. .PP Intended usage is to retry transient failures or to try different host: .PP .Vb 4 \& use YAHC qw/ \& yahc_retry_conn \& yahc_conn_attempts_left \& /; \& \& my ($yahc, $yahc_storage) = YAHC\->new(); \& $yahc\->request({ \& retries => 2, \& host => [ \*(Aqhost1\*(Aq, \*(Aqhost2\*(Aq ], \& callback => sub { \& ... \& my $conn = $_[0]; \& if ($_[0]\->{response}{status} == 503 && yahc_conn_attempts_left($conn)) { \& yahc_retry_conn($conn); \& return; \& } \& ... \& } \& }); \& \& $yahc\->run; .Ve .PP \&\f(CW\*(C`yahc_retry_conn\*(C'\fR is meant to be called inside \f(CW\*(C`callback\*(C'\fR similarly to \f(CW\*(C`yahc_reinit_conn\*(C'\fR. .SS "yahc_conn_id" .IX Subsection "yahc_conn_id" Return id of given connection. .SS "yahc_conn_state" .IX Subsection "yahc_conn_state" Return state of given connection. .SS "yahc_conn_target" .IX Subsection "yahc_conn_target" Return selected host and port for current attempt for given connection. Format \*(L"host:port\*(R". Default port values are omitted. .SS "yahc_conn_url" .IX Subsection "yahc_conn_url" Same as \f(CW\*(C`yahc_conn_target\*(C'\fR but return full \s-1URL\s0 .SS "yahc_conn_user_data" .IX Subsection "yahc_conn_user_data" Let user associate arbitrary data with a connection. Be aware of not creating cyclic reference! .SS "yahc_conn_errors" .IX Subsection "yahc_conn_errors" Return errors appeared in given connection. Note that the function returns all errors, not only ones happened during current attempt. Returned value is ArrayRef of ArrayRefs. Later one represents a error and contains following items: .Sp .Vb 5 \& error number (see YAHC::Error constants) \& error string \& ArrayRef of host, ip, port, scheme \& time when the error happened \& attempt when the error happened .Ve .SS "yahc_conn_register_error" .IX Subsection "yahc_conn_register_error" \&\f(CW\*(C`yahc_conn_register_error\*(C'\fR adds new record in connection's error list. This functions is used internally for keeping track of all low-level errors during connection's lifetime. It can be also used by users for high-level errors such as 50x responses. The function takes \f(CW$conn\fR, \f(CW$error\fR which is one of \&\f(CW\*(C`YAHC::Error\*(C'\fR constants and error description. Error description can be passed in sprintf manner. For example: .PP .Vb 10 \& $yahc\->request({ \& ... \& callback => sub { \& ... \& my $conn = $_[0]; \& my $status = $conn\->{response}{status} || 0; \& if ($status == 503 || $status == 504) { \& yahc_conn_register_error( \& $conn, \& YAHC::Error::RESPONSE_ERROR(), \& "server returned %d", \& $status \& ); \& \& yahc_retry_conn($conn); \& return; \& } \& ... \& } \& }); .Ve .SS "yahc_conn_last_error" .IX Subsection "yahc_conn_last_error" Return last error appeared in connection. See \f(CW\*(C`yahc_conn_errors\*(C'\fR. .SS "yahc_terminal_error" .IX Subsection "yahc_terminal_error" Given a error return 1 if the error has \fBYAHC::Error::TERMINAL_ERROR()\fR bit set. Otherwise return 0. .SS "yahc_conn_timeline" .IX Subsection "yahc_conn_timeline" Return timeline of given connection. See more about timeline in description of \&\f(CW\*(C`new\*(C'\fR method. .SS "yahc_conn_request" .IX Subsection "yahc_conn_request" Return request of given connection. See \f(CW\*(C`request\*(C'\fR. .SS "yahc_conn_response" .IX Subsection "yahc_conn_response" Return response of given connection. See \f(CW\*(C`request\*(C'\fR. .SS "yahc_conn_attempt" .IX Subsection "yahc_conn_attempt" Return current attempt starting from 1. The function can also return 0 if no attempts were made yet. .SS "yahc_conn_attempts_left" .IX Subsection "yahc_conn_attempts_left" Return number of attempts left. .SS "yahc_conn_socket_cache_id" .IX Subsection "yahc_conn_socket_cache_id" Return socket_cache id for given connection. Should be used to generate key for \&\f(CW\*(C`socket_cache\*(C'\fR. If connection is not initialized yet \f(CW\*(C`undef\*(C'\fR is returned. .SH "ERRORS" .IX Header "ERRORS" \&\s-1YAHC\s0 provides set of constants for errors. Each constant returns bitmask which can be used to detect presence of a particular error, for example, in \&\f(CW\*(C`callback\*(C'\fR. There is one exception: \fBYAHC::Error::NO_ERROR()\fR return 0 indicating no error during request execution. .PP Error handling code can look like following: .PP .Vb 8 \& $yahc\->request({ \& ... \& callback => sub { \& my ( \& $conn, # connection \*(Aqobject\*(Aq \& $error, # one of YAHC::Error::* constants \& $strerror # string representation of error \& ) = @_; \& \& if ($error & YAHC::Error::TIMEOUT()) { \& # A timeout has happened. Use one of YAHC::Error::*_TIMEOUT() \& # constants for more clarification \& } elsif ($error & YAHC::Error::SSL_ERROR()) { \& # We had some issues with SSL. $error might have \& # YAHC::Error::READ_ERROR() or YAHC::Error::WRITE_ERROR() \& # indicating whether is was read or write error. \& } elsif (...) { # etc \& } \& } \& }); .Ve .PP The list of error constants. The names are self-explanatory in many cases: .ie n .IP """YAHC::Error::NO_ERROR()""" 4 .el .IP "\f(CWYAHC::Error::NO_ERROR()\fR" 4 .IX Item "YAHC::Error::NO_ERROR()" Return value 0 (not a bitmask)> meaning no error .ie n .IP """YAHC::Error::REQUEST_TIMEOUT()""" 4 .el .IP "\f(CWYAHC::Error::REQUEST_TIMEOUT()\fR" 4 .IX Item "YAHC::Error::REQUEST_TIMEOUT()" .PD 0 .ie n .IP """YAHC::Error::CONNECT_TIMEOUT()""" 4 .el .IP "\f(CWYAHC::Error::CONNECT_TIMEOUT()\fR" 4 .IX Item "YAHC::Error::CONNECT_TIMEOUT()" .ie n .IP """YAHC::Error::DRAIN_TIMEOUT()""" 4 .el .IP "\f(CWYAHC::Error::DRAIN_TIMEOUT()\fR" 4 .IX Item "YAHC::Error::DRAIN_TIMEOUT()" .ie n .IP """YAHC::Error::LIFETIME_TIMEOUT()""" 4 .el .IP "\f(CWYAHC::Error::LIFETIME_TIMEOUT()\fR" 4 .IX Item "YAHC::Error::LIFETIME_TIMEOUT()" .ie n .IP """YAHC::Error::TIMEOUT()""" 4 .el .IP "\f(CWYAHC::Error::TIMEOUT()\fR" 4 .IX Item "YAHC::Error::TIMEOUT()" .ie n .IP """YAHC::Error::RETRY_LIMIT()""" 4 .el .IP "\f(CWYAHC::Error::RETRY_LIMIT()\fR" 4 .IX Item "YAHC::Error::RETRY_LIMIT()" .PD The connection has exhausted all available retries. This error is usually returned to \f(CW\*(C`callback\*(C'\fR. Check connection's errors via \f(CW\*(C`yahc_conn_errors\*(C'\fR to inspect the reasons of failures for each individual attempt. .ie n .IP """YAHC::Error::CONNECT_ERROR()""" 4 .el .IP "\f(CWYAHC::Error::CONNECT_ERROR()\fR" 4 .IX Item "YAHC::Error::CONNECT_ERROR()" .PD 0 .ie n .IP """YAHC::Error::READ_ERROR()""" 4 .el .IP "\f(CWYAHC::Error::READ_ERROR()\fR" 4 .IX Item "YAHC::Error::READ_ERROR()" .ie n .IP """YAHC::Error::WRITE_ERROR()""" 4 .el .IP "\f(CWYAHC::Error::WRITE_ERROR()\fR" 4 .IX Item "YAHC::Error::WRITE_ERROR()" .ie n .IP """YAHC::Error::SSL_ERROR()""" 4 .el .IP "\f(CWYAHC::Error::SSL_ERROR()\fR" 4 .IX Item "YAHC::Error::SSL_ERROR()" .ie n .IP """YAHC::Error::REQUEST_ERROR()""" 4 .el .IP "\f(CWYAHC::Error::REQUEST_ERROR()\fR" 4 .IX Item "YAHC::Error::REQUEST_ERROR()" .PD not used .ie n .IP """YAHC::Error::RESPONSE_ERROR()""" 4 .el .IP "\f(CWYAHC::Error::RESPONSE_ERROR()\fR" 4 .IX Item "YAHC::Error::RESPONSE_ERROR()" Server returned unparsable response .ie n .IP """YAHC::Error::CALLBACK_ERROR()""" 4 .el .IP "\f(CWYAHC::Error::CALLBACK_ERROR()\fR" 4 .IX Item "YAHC::Error::CALLBACK_ERROR()" Usually represents exception in one of the callbacks .ie n .IP """YAHC::Error::TERMINAL_ERROR()""" 4 .el .IP "\f(CWYAHC::Error::TERMINAL_ERROR()\fR" 4 .IX Item "YAHC::Error::TERMINAL_ERROR()" This bit is set when connection cannot be retried anymore and is forced to complete .ie n .IP """YAHC::Error::INTERNAL_ERROR()""" 4 .el .IP "\f(CWYAHC::Error::INTERNAL_ERROR()\fR" 4 .IX Item "YAHC::Error::INTERNAL_ERROR()" .SH "REPOSITORY" .IX Header "REPOSITORY" .SH "NOTES" .IX Header "NOTES" .SS "\s-1UTF8\s0 flag" .IX Subsection "UTF8 flag" Note that \s-1YAHC\s0 has astonishing reduction in performance if any parameters participating in building \s-1HTTP\s0 message has \s-1UTF8\s0 flag set. Those fields are \&\f(CW\*(C`protocol\*(C'\fR, \f(CW\*(C`host\*(C'\fR, \f(CW\*(C`port\*(C'\fR, \f(CW\*(C`method\*(C'\fR, \f(CW\*(C`path\*(C'\fR, \f(CW\*(C`query_string\*(C'\fR, \f(CW\*(C`head\*(C'\fR, \&\f(CW\*(C`body\*(C'\fR and maybe others. .PP Just one example (check scripts/utf8_test.pl for code). Simple \s-1HTTP\s0 request with 10MB of payload: .PP .Vb 2 \& elapsed without utf8 flag: 0.039s \& elapsed with utf8 flag: 0.540s .Ve .PP Because of this \s-1YAHC\s0 warns if detected UTF8\-flagged payload. The user needs to make sure that *all* data passed to \s-1YAHC\s0 is unflagged binary strings. .SS "\s-1LIMITATIONS\s0" .IX Subsection "LIMITATIONS" .IP "\(bu" 4 State '\s-1RESOLVE DNS\s0' is not implemented yet. .SH "AUTHORS" .IX Header "AUTHORS" Ivan Kruglov .SH "COPYRIGHT" .IX Header "COPYRIGHT" Copyright (c) 2013\-2017 Ivan Kruglov \f(CW\*(C`\*(C'\fR. .SH "ACKNOWLEDGMENT" .IX Header "ACKNOWLEDGMENT" This module derived lots of ideas, code and docs from Hijk . This module was originally developed for Booking.com. .SH "LICENCE" .IX Header "LICENCE" The \s-1MIT\s0 License .SH "DISCLAIMER OF WARRANTY" .IX Header "DISCLAIMER OF WARRANTY" \&\s-1BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE SOFTWARE \*(L"AS IS\*(R" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR, OR CORRECTION.\s0 .PP \&\s-1IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE SOFTWARE\s0 (\s-1INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE\s0), \s-1EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.\s0