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 )