HTTP-Promise

 view release on metacpan or  search on metacpan

README  view on Meta::CPAN

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

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

  connection_header
    Sets or gets the value for the header "Connection". It can be "close" or
    "keep-alive"

    If it is let "undef", this module will try to guess the proper value
    based on the "protocol" in HTTP::Promise::Request and "version" in
    HTTP::Promise::Request used.

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

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

    This defaults to Cookie::Jar

        use Cookie::Jar;
        my $jar = Cookie::Jar->new;

README.md  view on Meta::CPAN

## auto\_switch\_https

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

## buffer\_size

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

## connection\_header

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

If it is let `undef`, this module will try to guess the proper value based on the ["protocol" in HTTP::Promise::Request](https://metacpan.org/pod/HTTP%3A%3APromise%3A%3ARequest#protocol) and ["version" in HTTP::Promise::Request](https://metacpan.org/...

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

## cookie\_jar

Sets or gets the Cookie jar class object to use. This is typically [Cookie::Jar](https://metacpan.org/pod/Cookie%3A%3AJar) or maybe [HTTP::Cookies](https://metacpan.org/pod/HTTP%3A%3ACookies)

This defaults to [Cookie::Jar](https://metacpan.org/pod/Cookie%3A%3AJar)

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

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

    $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 ) ||

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

    {
        $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 ) )
            {

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

    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;

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

    # <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 ) );

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

                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 );

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

=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 );

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

    clearsitedata                    => 'HTTP::Promise::Headers::ClearSiteData',
    contentdisposition               => 'HTTP::Promise::Headers::ContentDisposition',
    contentrange                     => 'HTTP::Promise::Headers::ContentRange',
    contentsecuritypolicy            => 'HTTP::Promise::Headers::ContentSecurityPolicy',
    contentsecuritypolicyreportonly  => 'HTTP::Promise::Headers::ContentSecurityPolicyReportOnly',
    contenttype                      => 'HTTP::Promise::Headers::ContentType',
    cookie                           => 'HTTP::Promise::Headers::Cookie',
    expectct                         => 'HTTP::Promise::Headers::ExpectCT',
    forwarded                        => 'HTTP::Promise::Headers::Forwarded',
    generic                          => 'HTTP::Promise::Headers::Generic',
    keepalive                        => 'HTTP::Promise::Headers::KeepAlive',
    link                             => 'HTTP::Promise::Headers::Link',
    range                            => 'HTTP::Promise::Headers::Range',
    servertiming                     => 'HTTP::Promise::Headers::ServerTiming',
    stricttransportsecurity          => 'HTTP::Promise::Headers::StrictTransportSecurity',
    te                               => 'HTTP::Promise::Headers::TE',
    wantdigest                       => 'HTTP::Promise::Headers::WantDigest',
};

sub new
{

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

# <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-Range>
sub if_range { return( shift->_set_get_one( 'If-Range', @_ ) ); }

# NOTE: if_unmodified_since() is inherited
# <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-Unmodified-Since>
sub if_unmodified_since { return( shift->_date_header( 'If-Unmodified-Since', @_ ) ); }

# NOTE: init_header() is inherited

# <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Keep-Alive>
sub keep_alive { return( shift->_set_get_one( 'Keep-Alive', @_ ) ); }

# NOTE: last_modified() is inherited
# <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Last-Modified>
sub last_modified { return( shift->_date_header( 'Last-Modified', @_ ) ); }

# <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Link>
sub link { return( shift->_set_get_multi( 'Link', @_ ) ); }

# <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Location>
sub location { return( shift->_set_get_one( 'Location', @_ ) ); }

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

    $h->clear_site_data( [qw( cache cookies storage executionContexts )] );

The Clear-Site-Data header accepts one or more directives. If all types of data should be cleared, the wildcard directive ("*") can be used.

See also L<HTTP::Promise::Headers::ClearSiteData> to have a more granular control.

See L<Mozilla documentation|https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Clear-Site-Data>

=head2 connection

    # Connection: keep-alive
    # Connection: close

Sets or gets the C<Connection> header field value. It takes a string value.

See L<rfc7230, section 6.1|https://tools.ietf.org/html/rfc7230#section-6.1> and L<Mozilla documentation|https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Connection>

=head2 content_disposition

    # Content-Disposition: inline
    # Content-Disposition: attachment

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

This sets or gets the C<If-Unmodified-Since> header value. It takes a date string value, a unix timestamp or a L<DateTime::Lite>, or a L<DateTime> value.

If no value is provided, it returns the current value of the C<Date> header field as a L<DateTime::Lite> object.

For example:

    If-Unmodified-Since: Wed, 21 Oct 2015 07:28:00 GMT

See also L<rfc7232, section 3.4|https://tools.ietf.org/html/rfc7232#section-3.4> and L<Mozilla documentation|https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-Unmodified-Since>

=head2 keep_alive

This sets or gets the C<Keep-Alive> header value. It takes either a string or an array or array reference of properly formatted values.

See also L<HTTP::Promise::Headers::KeepAlive> to have a more granular control.

Example response containing a Keep-Alive header:

    HTTP/1.1 200 OK
    Connection: Keep-Alive
    Content-Encoding: gzip

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

    # 417 Expectation failed
    # 422 Unprocessable entity
    # 425 Too early
    elsif( $len == -2 )
    {
        return( $self->error({ code => 425, message => 'Incomplete request, call again when there is more data.', class => $EXCEPTION_CLASS }) );
    }
    # response headers:
    # {
    #   "_content_length" => 15,
    #   "_keepalive"      => 0,
    #   "_message"        => "OK",
    #   "_protocol"       => "HTTP/1.0",
    #   "_status"         => 200,
    #   "content-length"  => [15],
    #   "content-type"    => ["text/plain"],
    #   "host"            => ["example.com"],
    #   "user-agent"      => ["hoge"],
    # }
    # request headers:
    # {
    #   "_content_length" => 27,
    #   "_keepalive"      => 1,
    #   "_method"         => "POST",
    #   "_protocol"       => "HTTP/1.1",
    #   "_query_string"   => "",
    #   "_request_uri"    => "/test",
    #   "_uri"            => "/test",
    #   "content-length"  => [27],
    #   "content-type"    => ["application/x-www-form-urlencoded"],
    #   "host"            => ["foo.example"],
    # }
    $r->{_protocol} = "HTTP/${bkp_version}" if( defined( $bkp_version ) );

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


See L<rfc7231, section 6.3.1|https://datatracker.ietf.org/doc/html/rfc7231#section-6.3.1>

This is returned to inform the request has succeeded. It can also alternatively be C<204 No Content> when there is no response body.

For example:

    HTTP/1.1 200 OK
    Content-Type: text/html; charset=utf-8
    Content-Length: 184
    Connection: keep-alive
    Cache-Control: s-maxage=300, public, max-age=0
    Content-Language: en-US
    Date: Mon, 18 Apr 2022 17:37:18 GMT
    ETag: "2e77ad1dc6ab0b53a2996dfd4653c1c3"
    Server: Apache/2.4
    Strict-Transport-Security: max-age=63072000
    X-Content-Type-Options: nosniff
    X-Frame-Options: DENY
    X-XSS-Protection: 1; mode=block
    Vary: Accept-Encoding,Cookie

t/03.headers.t  view on Meta::CPAN

can_ok( $h => 'expires' );
can_ok( $h => 'expose_headers' );
can_ok( $h => 'forwarded' );
can_ok( $h => 'from' );
can_ok( $h => 'host' );
can_ok( $h => 'if_match' );
can_ok( $h => 'if_modified_since' );
can_ok( $h => 'if_none_match' );
can_ok( $h => 'if_range' );
can_ok( $h => 'if_unmodified_since' );
can_ok( $h => 'keep_alive' );
can_ok( $h => 'last_modified' );
can_ok( $h => 'link' );
can_ok( $h => 'location' );
can_ok( $h => 'max_age' );
can_ok( $h => 'nel' );
can_ok( $h => 'origin' );
can_ok( $h => 'proxy' );
can_ok( $h => 'proxy_authenticate' );
can_ok( $h => 'proxy_authorization' );
can_ok( $h => 'range' );

t/13.header_fields.t  view on Meta::CPAN

{
    use ok( 'HTTP::Promise::Headers::Forwarded' );
    my $str = q{for=192.0.2.60; proto=http; by=203.0.113.43};
    my $h = HTTP::Promise::Headers::Forwarded->new( $str );
    is( "$h", $str );
    is( $h->for, '192.0.2.60' );
    is( $h->proto, 'http' );
    is( $h->by, '203.0.113.43' );
};

subtest 'keep-alive' => sub
{
    use ok( 'HTTP::Promise::Headers::KeepAlive' );
    my $str = q{timeout=5, max=1000};
    my $h = HTTP::Promise::Headers::KeepAlive->new( $str );
    is( "$h", $str );
    is( $h->timeout, 5 );
    is( $h->max, 1000 );
};

subtest 'link' => sub

t/13.header_fields.t  view on Meta::CPAN

        clear_site_data     => 'HTTP::Promise::Headers::ClearSiteData',
        content_disposition => 'HTTP::Promise::Headers::ContentDisposition',
        content_range       => 'HTTP::Promise::Headers::ContentRange',
        content_securit_ypolicy => 'HTTP::Promise::Headers::ContentSecurityPolicy',
        content_security_policy_report_only => 'HTTP::Promise::Headers::ContentSecurityPolicyReportOnly',
        content_type        => 'HTTP::Promise::Headers::ContentType',
        cookie              => 'HTTP::Promise::Headers::Cookie',
        expectct            => 'HTTP::Promise::Headers::ExpectCT',
        forwarded           => 'HTTP::Promise::Headers::Forwarded',
        generic             => 'HTTP::Promise::Headers::Generic',
        keepalive           => 'HTTP::Promise::Headers::KeepAlive',
        link                => 'HTTP::Promise::Headers::Link',
        range               => 'HTTP::Promise::Headers::Range',
        server_timing       => 'HTTP::Promise::Headers::ServerTiming',
        strict_transport_security => 'HTTP::Promise::Headers::StrictTransportSecurity',
        te                  => 'HTTP::Promise::Headers::TE',
        wantdigest          => 'HTTP::Promise::Headers::WantDigest',
    );
    foreach( sort( keys( %tests ) ) )
    {
        my $f = $h->new_field( $_ ) || do

t/14.reader.t  view on Meta::CPAN

POST /test HTTP/1.1
Host: www.csm-testcenter.org
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:100.0) Gecko/20100101 Firefox/100.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: fr-FR,en-GB;q=0.8,fr;q=0.6,en;q=0.4,ja;q=0.2
Accept-Encoding: gzip, deflate, br
Content-Type: multipart/form-data; boundary=---------------------------40260073931083483614643569375
Content-Length: 79784
Origin: http://www.csm-testcenter.org
DNT: 1
Connection: keep-alive
Referer: http://www.csm-testcenter.org/
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: cross-site
Sec-Fetch-User: ?1

EOT
$io->seek(0,0);
$reader->buffer->reset;

t/14.reader.t  view on Meta::CPAN

is( $data, q{POST /test HTTP/1.1
Host: www.csm-testcenter.org
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:100.0) Gecko/20100101 Firefox/100.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: fr-FR,en-GB;q=0.8,fr;q=0.6,en;q=0.4,ja;q=0.2
Accept-Encoding: gzip, deflate, br
Content-Type: multipart/form-data; boundary=---------------------------40260073931083483614643569375
Content-Length: 79784
Origin: http://www.csm-testcenter.org
DNT: 1
Connection: keep-alive
Referer: http://www.csm-testcenter.org/
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: cross-site
Sec-Fetch-User: ?1} );
use ok( 'HTTP::Promise::Parser' );
my $p = HTTP::Promise::Parser->new( debug => $DEBUG );
my $def = $p->parse_request_headers( "$data\n\n" ) || bail_out( $p->error );
my $headers = $def->{headers};



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