HTTP-Promise

 view release on metacpan or  search on metacpan

lib/HTTP/Promise.pm  view on Meta::CPAN

    our $MAX_HEADERS_SIZE = 8192;
    # 256Kb
    our $MAX_BODY_IN_MEMORY_SIZE = 102400;
    # 1Mb
    our $EXPECT_THRESHOLD = 1024000000;
    our $EXTENSION_VARY = 1;
    our $DEFAULT_MIME_TYPE = 'application/octet-stream';
    our $SERIALISER = $Promise::Me::SERIALISER;
    our $VERSION = 'v0.7.4';
};

use strict;
use warnings;

sub init
{
    my $self = shift( @_ );
    $self->{accept_language}        = [];
    $self->{accept_encoding}        = 'auto';
    $self->{agent}                  = qq{HTTP-Promise/$VERSION (perl; +https://metacpan.org/pod/HTTP::Promise)};
    $self->{auto_switch_https}      = 1;
    $self->{buffer_size}            = $BUFFER_SIZE;
    $self->{cookie_jar}             = Cookie::Jar->new;
    $self->{default_headers}        = undef;
    $self->{default_protocol}       = ( $DEFAULT_PROTOCOL || 'HTTP/1.1' );
    # DNT -> Do not track header field
    $self->{dnt}                    = undef;
    $self->{expect_threshold}       = $EXPECT_THRESHOLD;
    $self->{ext_vary}               = $EXTENSION_VARY;
    $self->{from}                   = undef;
    $self->{inactivity_timeout}     = 600;
    $self->{local_host}             = undef;
    $self->{local_port}             = undef;
    $self->{max_body_in_memory_size} = $MAX_BODY_IN_MEMORY_SIZE;
    $self->{max_headers_size}       = $MAX_HEADERS_SIZE;
    $self->{max_redirect}           = 7;
    $self->{max_size}               = undef;
    $self->{medium}                 = $Promise::Me::SHARE_MEDIUM;
    $self->{no_proxy}               = [];
    $self->{proxy}                  = $ENV{http_proxy} || $ENV{HTTP_PROXY} || undef;
    $self->{proxy_authorization}    = undef;
    $self->{requests_redirectable}  = [qw( GET HEAD )];
    $self->{send_te}                = 1;
    $self->{serialiser}             = $SERIALISER;
    $self->{shared_mem_size}        = $Promise::Me::RESULT_MEMORY_SIZE;
    $self->{ssl_opts}               = undef;
    $self->{stop_if}                = sub{};
    $self->{threshold}              = $CONTENT_SIZE_THRESHOLD;
    # 3 minutes
    $self->{timeout}                = 180;
    $self->{use_content_file}       = 0;
    $self->{use_promise}            = 1;
    $self->{_init_strict_use_sub}   = 1;
    $self->{_exception_class}       = $EXCEPTION_CLASS;
    $self->SUPER::init( @_ ) || return( $self->pass_error );
    my $headers = $self->default_headers;
    if( $headers )
    {
        unless( $self->connection_header )
        {
            my $connection_header = 'keep-alive';
            if( $headers->exists( 'connection' ) )
            {
                $connection_header = $headers->get( 'connection' );
            }
            $self->{connection_header} = $connection_header;
        }
    }
    else
    {
        $self->default_headers( HTTP::Promise::Headers->new ) ||
            return( $self->pass_error( HTTP::Promise::Headers->error ) );
    }
    $self->{_pool} = HTTP::Promise::Pool->new;
    return( $self );
}

sub accept_language { return( shift->_set_get_array_as_object( 'accept_language', @_ ) ); }

sub accept_encoding { return( shift->_set_get_scalar_as_object( 'accept_encoding', @_ ) ); }

# NOTE: request parameter
sub agent { return( shift->_set_get_scalar_as_object( 'agent', @_ ) ); }

sub auto_switch_https { return( shift->_set_get_boolean( 'auto_switch_https', @_ ) ); }

sub buffer_size { return( shift->_set_get_number( 'buffer_size', @_ ) ); }

sub clone
{
    my $self = shift( @_ );
    my $new = $self->SUPER::clone;
    if( $self->{default_headers} )
    {
        $new->{default_headers} = $self->{default_headers}->clone;
    }
    $new->{_pool} = HTTP::Promise::Pool->new;
    return( $new );
}

sub connection_header { return( shift->_set_get_scalar_as_object( 'connection_header', @_ ) ); }

# NOTE: request parameter
sub cookie_jar { return( shift->_set_get_scalar( 'cookie_jar', @_ ) ); }

sub decodable { return( HTTP::Promise::Stream->decodable( @_ ) ); }

# NOTE: request parameter
sub default_header { return( shift->default_headers->header( @_ ) ); }

# NOTE: request parameter
sub default_headers { return( shift->_set_get_object_without_init( 'default_headers', [qw( HTTP::Promise::Headers HTTP::Headers )], @_ ) ); }

sub default_protocol { return( shift->_set_get_scalar_as_object( 'default_protocol', @_ ) ); }

# As per rfc7231, a body is allowed for DELETE, but usually discarded by most of the servers.
sub delete
{
    my $self = shift( @_ );
    if( $self->use_promise )
    {

lib/HTTP/Promise.pm  view on Meta::CPAN

    # my $timeout = time() + $self->timeout;
    my $timeout = $self->timeout;
    my $uri = $req->uri;
    if( !$uri->scheme )
    {
        $uri->scheme( 'http' );
    }
    elsif( $uri->scheme ne 'http' && $uri->scheme ne 'https' )
    {
        return( $self->error( "Unsupported scheme: ", $uri->scheme ) );
    }
    my $default_port = $uri->scheme eq 'http'
        ? 80
        : 443;
    if( !$uri->can( 'port' ) || !defined( $uri->port ) || !length( $uri->port ) )
    {
        $p->{port} = $default_port;
    }
    else
    {
        $p->{port} = $uri->port;
    }
    $uri->path( '/' ) if( !length( $uri->path ) );

    $p->{host} = $uri->host ||
        return( $self->error( "No host set for request uri \"$uri\"." ) );
    
    if( my $local_host = $self->local_host )
    {
        $p->{local_host} = $local_host;
    }
    if( my $local_port = $self->local_port )
    {
        $p->{local_port} = $local_port;
    }

    my $proxy = $self->proxy;
    my $no_proxy = $self->no_proxy;
    if( $proxy && $no_proxy )
    {
        if( $self->_match_no_proxy( $no_proxy, $p->{host} ) )
        {
            undef( $proxy );
        }
    }

    local $SIG{PIPE} = 'IGNORE';
    my $io;
    my $sock = $self->_pool->steal( @$p{qw( host port )} );
    if( defined( $sock ) && Scalar::Util::openhandle( $sock ) )
    {
        $io = HTTP::Promise::IO->new( $sock, stop_if => $self->stop_if ) ||
            return( $self->pass_error( HTTP::Promise::IO->error ) );
        if( !$io->make_select( write => 0, timeout => 0 ) )
        {
            close( $sock );
            undef( $sock );
        }
        else
        {
            $p->{in_keepalive} = 1;
        }
    }
    if( !$p->{in_keepalive} )
    {
        if( $proxy )
        {
            # my( undef, $proxy_user, $proxy_pass, $proxy_host, $proxy_port, undef) = $self->_parse_url($proxy);
            return( $self->error( "Proxy set '$proxy' (", overload::StrVal( $proxy ), ") is not URI object." ) ) if( !$self->_is_a( $proxy => 'URI' ) );
            my $proxy_auth = $proxy->userinfo;
            my( $proxy_user, $proxy_pass ) = split( /:/, $proxy_auth, 2 );
            my $proxy_authorization;
            if( defined( $proxy_user ) && length( $proxy_user ) )
            {
                $self->_load_class( 'URI::Escape::XS' ) || return( $self->pass_error );
                $p->{proxy_user} = URI::Escape::XS::uri_unescape( $proxy_user );
                $p->{proxy_pass} = URI::Escape::XS::uri_unescape( $proxy_pass );
                $self->_load_class( 'Crypt::Misc' ) || return( $self->pass_error );
                $proxy_authorization = 'Basic ' . Crypt::Misc::encode_b64( join( ':', @$p{qw( proxy_user proxy_pass )} ), '' );
            }
            local $HTTP::Promise::IO::DEBUG = $self->debug; # REMOVE ME
            if( $uri->scheme eq 'http' )
            {
                $io = HTTP::Promise::IO->connect(
                    host => $proxy->host,
                    port => $proxy->port,
                    stop_if => $self->stop_if,
                    timeout => $timeout,
                    debug   => $self->debug,
                    ( defined( $p->{local_host} ) ? ( local_host => $p->{local_host} ) : () ),
                    ( defined( $p->{local_port} ) ? ( local_port => $p->{local_port} ) : () ),
                ) || return( $self->pass_error( HTTP::Promise::IO->error ) );
                if( defined( $proxy_authorization ) )
                {
                    $self->proxy_authorization( $proxy_authorization );
                }
            }
            else
            {
                $io = HTTP::Promise::IO->connect_ssl_over_proxy(
                    proxy_host  => $proxy->host,
                    proxy_port  => $proxy->port,
                    host        => $p->{host},
                    port        => $p->{port},
                    stop_if     => $self->stop_if,
                    timeout     => $timeout,
                    proxy_authorization => $proxy_authorization,
                    debug       => $self->debug,
                    ( defined( $p->{local_host} ) ? ( local_host => $p->{local_host} ) : () ),
                    ( defined( $p->{local_port} ) ? ( local_port => $p->{local_port} ) : () ),
                ) || return( $self->pass_error( HTTP::Promise::IO->error ) );
            }
        }
        else
        {
            local $HTTP::Promise::IO::DEBUG = $self->debug; # REMOVE ME
            if( $uri->scheme eq 'http' )
            {
                $io = HTTP::Promise::IO->connect(
                    host => $uri->host,
                    port => $uri->port,
                    stop_if => $self->stop_if,
                    timeout => $timeout,
                    debug   => $self->debug,
                    ( defined( $p->{local_host} ) ? ( local_host => $p->{local_host} ) : () ),
                    ( defined( $p->{local_port} ) ? ( local_port => $p->{local_port} ) : () ),
                ) || return( $self->pass_error( HTTP::Promise::IO->error ) );
            }
            else
            {
                my $ssl_opts = $self->ssl_opts;
                $io = HTTP::Promise::IO->connect_ssl(
                    host => $uri->host,
                    port => $uri->port,
                    stop_if => $self->stop_if,
                    timeout => $timeout,
                    debug   => $self->debug,
                    ( defined( $p->{local_host} ) ? ( local_host => $p->{local_host} ) : () ),
                    ( defined( $p->{local_port} ) ? ( local_port => $p->{local_port} ) : () ),
                    ( ( defined( $ssl_opts ) && scalar( keys( %$ssl_opts ) ) ) ? ( ssl_opts => $ssl_opts ) : () ),
                ) || return( $self->pass_error( HTTP::Promise::IO->error ) );
            }
        }
        # return( $self->pass_error ) unless( $io );
    }

    my $total_bytes_sent = 0;
    my $total_bytes_read = 0;

    my $send_body = sub
    {
        my $entity = shift( @_ );
        my $body = $entity->body;
        my $body_len = $body->length;
        my $ct_len = $req->headers->content_length;
        if( $body_len != $ct_len )
        {
            warn( "Content-Length set (${ct_len}) does not match the actual body size (${body_len})\n" ) if( warnings::enabled() );
        }
        
        my $sock = $io->filehandle;
        my $bytes_sent = 0;
        $entity->print_body( $io ) || return( $self->pass_error( $entity->error ) );
        # NOTE: Hmmmm, really not great, but otherwise I would need to change a lot of code
        $bytes_sent = $body->length;
        return( $bytes_sent );
    };

    # write request
    my $method = $req->method;
    my $connection_header = $self->connection_header;
    # If no connection_header value was provided, let's guess it based on the protocol used
    unless( $connection_header )
    {
        if( uc( $method ) eq 'HEAD' )
        {
            $connection_header = 'close';
        }
        elsif( $req->version && $req->version > 1.0 )
        {
            $connection_header = 'keep-alive';
        }
        else
        {
            $connection_header = 'close';
        }
    }
    
    my $cookie_jar = $self->cookie_jar;
    {
        my $headers = $req->headers;
        # Add headers that were provided as parameters
        my $in_headers = $opts->{headers};
        if( $self->_is_array( $in_headers ) )
        {
            for( my $i = 0; $i < @$in_headers; $i += 2 )
            {
                my $name = $in_headers->[$i];
                if( lc( $name ) eq 'connection' )
                {
                    $connection_header = $in_headers->[$i + 1];
                }
                else
                {
                    $headers->push_header( $name => $in_headers->[$i + 1] );
                }
            }
        }
        $headers->header( Connection => $connection_header );
        
        if( my $pa = $self->proxy_authorization )
        {
            $headers->header( 'Proxy-Authorization' => $pa );
        }
        my $userinfo = $uri->userinfo;
        if( defined( $userinfo ) && length( $userinfo ) )
        {
            my( $username, $password ) = split( /:/, $userinfo, 2 );
            $self->_load_class( 'URI::Escape' ) || return( $self->pass_error );
            my $unescape_username = URI::Escape::uri_unescape( $username );
            my $unescape_password = URI::Escape::uri_unescape( $password );
            $self->_load_class( 'Crypt::Misc' ) || return( $self->pass_error );
            my $authorization = 'Basic ' . Crypt::Misc::encode_b64( "${unescape_username}:${unescape_password}" );
            $headers->header( Authorization => 'Basic ' . $authorization );
            $uri->userinfo( undef );
        }

        # set Cookie header
        if( defined( $cookie_jar ) )
        {
            if( $self->_can( $cookie_jar => 'add_request_header' ) )
            {
                $cookie_jar->add_request_header( $req ) ||
                    return( $self->pass_error( $cookie_jar->error ) );
            }
            else
            {
                warn( "Cookie Jar object ", $self->_str_val( $cookie_jar ), " does not support the method 'add_request_header'." );
            }
        }

lib/HTTP/Promise.pm  view on Meta::CPAN

            }
            else
            {
                undef( $expect_threshold );
            }
        }
        
        if( $req->version && 
            $req->version > 1.0 && 
            defined( $expect_threshold ) && 
            $expect_threshold > 0 &&
            defined( $body ) &&
            $body->length > $expect_threshold )
        {
            $headers->expect( '100-Continue' );
        }
        
        my $request = $req->start_line . $CRLF . $req->headers->as_string;
        $request .= $CRLF;
        my $bytes = $io->write_all( $request, $timeout );
        if( !defined( $bytes ) )
        {
            return( $self->pass_error( $io->error ) );
        }
        # Could not transmit the headers
        elsif( !$bytes )
        {
            return( $self->error({ code => 500, message => "Zero byte could actually be sent to the socket '", $io->filehandle, "'." }) );
        }
        $total_bytes_sent = $bytes;

        # If this is not an Expect query, we send the body now
        # otherwise if this is an Expect type of query, we would read the response header
        # and send the body
        if( !$headers->expect && defined( $body ) && $body )
        {
            my $bytes = $send_body->( $req->entity );
            return( $self->pass_error ) if( !defined( $bytes ) );
            $total_bytes_sent += $bytes;
        }
    }
    
    # read response
    my $buff = '';
    my $parser = HTTP::Promise::Parser->new;
    my $bufsize = $self->buffer_size;
    $io->max_read_buffer( $bufsize );
    $io->debug( $self->debug );
    
    # Maximum headers size is not oficial, but we definitely need to set some limit.
    # <https://security.stackexchange.com/questions/110565/large-over-sizesd-http-header-lengths-and-security-implications>
    my $max = $self->max_headers_size;
    my( $n, $def, $headers );
    $n = -1;
    LOOP: while(1)
    {
        $n = $io->read( $buff, 2048, length( $buff ) );
        if( !defined( $n ) || $n == 0 )
        {
            my $code = defined( $n ) ? '' : $io->error->code;
            if( $p->{in_keepalive} && 
                ( length( $buff ) // 0 ) == 0 &&
                !$opts->{total_attempts} &&
                ( defined( $n ) || $code == ECONNRESET || ( $IS_WIN32 && $code == ECONNABORTED ) ) )
            {
                # the server closed the connection (maybe because of keep-alive timeout)
                $opts->{total_attempts}++;
                return( $self->send( $req, %$opts ) );
            }
            elsif( !length( $buff ) )
            {
                return( $self->error({ code => HTTP_BAD_REQUEST, message => "Unexpected EOF while reading response from socket '", $io->filehandle, "'." }) );
            }
            elsif( !defined( $n ) )
            {
                return( $self->pass_error( $io->error ) );
            }
            else
            {
                return( $self->error({ code => HTTP_BAD_REQUEST, message => "No headers data could be retrieved in the first " . length( $buff ) . " bytes of data read." }) );
            }
        }
        
        $def = $parser->parse_response_headers( \$buff );
        if( !defined( $def ) )
        {
            # Is it an error 425 Too Early, it means we need more data.
            if( $parser->error->code == HTTP_TOO_EARLY )
            {
                next LOOP;
            }
            # 400 Bad request
            elsif( $parser->error->code == HTTP_BAD_REQUEST && length( $buff ) > $max )
            {
                return( $self->error({ code => HTTP_BAD_REQUEST, message => "Unable to find the response headers, within the first ${max} bytes of data. Do you need to increase the value for max_headers_size() ?" }) );
            }
            # For other errors, we stop and pass the error received
            return( $self->pass_error );
        }
        else
        {
            $headers = $def->{headers} ||
                return( $self->error( "No headers object set by \$parser->parse_headers_xs() !" ) );
            return( $self->error( "\$parser->parse_headers_xs() did not return the headers length as an integer ($def->{length})" ) ) if( !$self->_is_integer( $def->{length} ) );
            return( $self->error( "Headers length returned by \$parser->parse_headers_xs() ($def->{length}) is higher than our buffer size (", length( $buff ), ") !" ) ) if( $def->{length} > length( $buff ) );
            # succeeded
            substr( $buff, 0, $def->{length}, '' );
            $total_bytes_read += $def->{length};
            $io->unread( $buff ) if( length( $buff ) );
            # We need to consume the blank line separating the headers and the body, so it does 
            # not become part of the body, and because it does not belong anywhere
            my $trash = $io->read_until_in_memory( qr/${CRLF}/, include => 1 );
            return( $self->pass_error( $io->error ) ) if( !defined( $trash ) );
            if( $req->headers->exists( 'Expect' ) )
            {
                # If we initially sent an Expect request, i.e. without a body, we just got
                # The green light to proceed, so we remove the Expect: 100-Continue header and re-submit.
                # If we did not have that request header, we just read on as this is the final, albeit weird, response
                if( $def->{code} == HTTP_CONTINUE )
                {
                    # Read on to get the actual server response headers
                    my $bytes = $send_body->( $req->entity );
                    return( $self->pass_error ) if( !defined( $bytes ) );
                    $total_bytes_sent += $bytes;
                    # moving on to read the full response headers
                    # Something like this:

lib/HTTP/Promise.pm  view on Meta::CPAN

    $resp->entity( $ent );
    $ent->http_message( $resp );
    $resp->request( $req );
    my $body;
    
    my $max_redirect = 0;
    my $do_redirect = undef;
    if( $headers->exists( 'Location' ) )
    {
        $max_redirect = ( defined( $opts->{max_redirect} ) && $opts->{max_redirect} =~ /^\d+$/ )
            ? $opts->{max_redirect}
            : $self->max_redirect;
        $max_redirect //= 0;
        # Perform redirect for:
        # Moved Permanently (301),
        # Moved Temporarily (302)
        # See Other (303)
        # Temporary Redirect (307)
        # Permanent Redirect (308)
        $do_redirect = ( $max_redirect && $def->{code} =~ /^30[12378]$/ );
    }

    my $chunked = ( ( $headers->transfer_encoding // '' ) eq 'chunked' );
    my $content_length = $headers->content_length;
    if( defined( $content_length ) && 
        length( $content_length ) && 
        $content_length !~ /^\d+$/ )
    {
        # return( $self->error({ code => 500, message => "Bad Content-Length: ${content_length}" }) );
        warn( "Bad Content-Length '${content_length}' in server response.\n" ) if( $self->_warnings_is_enabled );
        undef( $content_length );
    }

    unless( $req->method eq 'HEAD'
            || ( $def->{code} >= 100 && $def->{code} < 200 )
            || $def->{code} == 204
            || $def->{code} == 302
            || $def->{code} == 304 )
    {
        if( $chunked )
        {
            $body = $self->_read_body_chunked(
                reader => $io,
                headers => $headers,
                entity => $ent,
            ) || return( $self->pass_error );
        }
        else
        {
            $body = $self->_read_body(
                reader => $io,
                headers => $headers,
                entity => $ent,
            ) || return( $self->pass_error );
        }
        return( $self->pass_error ) if( !defined( $body ) );
        $total_bytes_read += $body->length;
        $ent->body( $body );
    }

    # manage connection cache (i.e. keep-alive)
    if( defined( $connection_header ) && 
        lc( $connection_header ) eq 'keep-alive' )
    {
        my $connection = $headers->connection->lc // '';
        if( ( $def->{version} > 1.0
             ? $connection ne 'close'      # HTTP/1.1 can keep alive by default
             : $connection eq 'keep-alive' # HTTP/1.0 needs explicit keep-alive
            ) && ( defined( $content_length ) or $chunked ) )
        {
            my $sock = $io->filehandle;
            $self->_pool->push( $uri->host, $uri->port, $sock ) ||
                return( $self->pass_error );
        }
    }
    # explicitly close here, just after returning the socket to the pool,
    # since it might be reused in the upcoming recursive call
    # undef( $sock );
    $io->close;

    # Process 'Set-Cookie' header before redirect, because Cookies may have been set upon redirection.
    if( defined( $cookie_jar ) )
    {
        if( $self->_can( $cookie_jar => 'extract_cookies' ) )
        {
            $cookie_jar->extract_cookies( $resp ) ||
                return( $self->pass_error( $cookie_jar->error ) );
        }
        else
        {
            warn( "Cookie Jar object ", $self->_str_val( $cookie_jar ), " does not support the method 'extract_cookies'." );
        }
    }
    
    if( $do_redirect )
    {
        my $location = $headers->location;
        unless( $location =~ m{^[a-zA-Z][a-zA-Z0-9]+://} )
        {
            # RFC 2616 14.30 says Location header is absolute URI.
            # But, a lot of servers return relative URI.
            $location = URI->new_abs( $location => $uri );
        }
        # Note: RFC 1945 and RFC 2068 specify that the client is not allowed
        # to change the method on the redirected request. However, most
        # existing user agent implementations treat 302 as if it were a 303
        # response, performing a GET on the Location field-value regardless
        # of the original request method. The status codes 303 and 307 have
        # been added for servers that wish to make unambiguously clear which
        # kind of reaction is expected of the client. Also, 308 was introduced
        # to avoid the ambiguity of 301.
        # TODO: Create new object and add the old one as previous() to the new request.
        my $clone = $req->clone || return( $self->pass_error( $req->error ) );
        unless( $def->{code} =~ /^30[178]$/ )
        {
            $clone->method( 'GET' );
        }
        $clone->uri( $location );
        $max_redirect-- if( $max_redirect > 0 );
        $opts->{max_redirect} = $max_redirect;
        return( $self->send( $clone, $opts ) );
        my $resp2 = $self->send( $clone, $opts ) ||
            return( $self->pass_error );
        $resp2->previous( $resp );
        return( $resp2 );
    }

    my $type = $ent->mime_type;

lib/HTTP/Promise.pm  view on Meta::CPAN


=back

=head1 METHODS

The following methods are available. This interface provides similar interface as L<LWP::UserAgent> while providing more granular control.

=head2 accept_encoding

String. Sets or gets whether we should accept compressed data.

You can set it to C<none> to disable it. By default, this is C<auto>, and it will set the C<Accept-Encoding> C<HTTP> header to all the supported encoding based on the availability of associated modules.

You can also set this to a comma-separated list of known encoding, typically: C<bzip2,deflate,gzip,rawdeflate,brotli>

See L<HTTP::Promise::Stream> for more details.

Returns a L<scalar object|Module::Generic::Scalar> of the current value.

=head2 accept_language

An array of acceptable language. This will be used to set the C<Accept-Language> header.

See also L<HTTP::Promise::Headers::AcceptLanguage>

=head2 agent

This is a string.

Sets or gets the agent id used to identify when making the server connection.

It defaults to C<HTTP-Promise/v0.1.0>

    my $p = HTTP::Promise->new( agent => 'MyBot/1.0' );
    $p->agent( 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:99.0) Gecko/20100101 Firefox/99.0' );

The C<User-Agent> header field is only set to this provided value if it is not already set.

=head2 accept_language

Sets or gets an array of acceptable response content languages.

For example:

    $http->accept_language( [qw( fr-FR ja-JP en-GB en )] );

Would result into an C<Accept-Language> header set to C<fr-FR;q=0.9,ja-JP;q=0.8,en-GB;q=0.7,en;q=0.6>

The C<Accept-Language> header would only be set if it is not set already.

=head2 auto_switch_https

Boolean. If set to a true value, or if left to C<undef> (default value), this will set the C<Upgrade-Insecure-Requests> header field to C<1>

=head2 buffer_size

The size of the buffer to use when reading data from the filehandle or socket.

=head2 connection_header

Sets or gets the value for the header C<Connection>. It can be C<close> or C<keep-alive>

If it is let C<undef>, this module will try to guess the proper value based on the L<HTTP::Promise::Request/protocol> and L<HTTP::Promise::Request/version> used.

For protocol C<HTTP/1.0>, C<Connection> value would be C<close>, but above C<HTTP/1.1> the connection can be set to C<keep-alive> and thus be re-used.

=head2 cookie_jar

Sets or gets the Cookie jar class object to use. This is typically L<Cookie::Jar> or maybe L<HTTP::Cookies>

This defaults to L<Cookie::Jar>

    use Cookie::Jar;
    my $jar = Cookie::Jar->new;
    my $p = HTTP::Promise->new( cookie_jar => $jar );
    $p->cookie_jar( $jar );

=for Pod::Coverage decodable

=head2 decodable

This calls L<HTTP::Promise::Stream/decodable> passing it whatever arguments that were provided.

=head2 default_header

Sets one more default headers. This is a shortcut to C<< $p->default_headers->header >>

    $p->default_header( $field );
    $p->default_header( $field => $value );
    $p->default_header( 'Accept-Encoding' => scalar( HTTP::Promise->decodable ) );
    $p->default_header( 'Accept-Language' => 'fr, en, ja' );

=head2 default_headers

Sets or gets the L<default header object|HTTP::Promise::Headers>, which is set to C<undef> by default.

This can be either an L<HTTP::Promise::Headers> or L<HTTP::Headers> object.

    use HTTP::Promise::Headers;
    my $headers = HTTP::Promise::Headers->new(
        'Accept-Encoding' => scalar( HTTP::Promise->decodable ),
        'Accept-Language' => 'fr, en, ja',
    );
    my $p = HTTP::Promise->new( default_headers => $headers );

=head2 default_protocol

Sets or gets the default protocol to use. For example: C<HTTP/1.1>

=head2 delete

Provided with an C<uri> and an optional hash of header name/value pairs, and this will issue a C<DELETE> http request to the given C<uri>.

It returns a L<promise|Promise::Me>, which can be used to call one or more L<then|Promise::Me/then> and L<catch|Promise::Me/catch>

    # or $p->delete( $uri, $field1 => $value1, $field2 => $value2 )
    $p->delete( $uri )->then(sub
    {
        my( $resolve, $reject ) = @$_;
        # an HTTP::Promise::Response is returned
        my $resp = shift( @_ );
        # Do something with the $resp object
    })->catch(sub
    {
        my $ex = shift( @_ );



( run in 1.421 second using v1.01-cache-2.11-cpan-71847e10f99 )