File Coverage

blib/lib/Net/HTTP2/Client.pm
Criterion Covered Total %
statement 65 76 85.5
branch 12 22 54.5
condition 2 6 33.3
subroutine 14 14 100.0
pod 0 2 0.0
total 93 120 77.5


line stmt bran cond sub pod time code
1             package Net::HTTP2::Client;
2              
3 1     1   359 use strict;
  1         2  
  1         22  
4 1     1   4 use warnings;
  1         1  
  1         20  
5              
6             =head1 NAME
7              
8             Net::HTTP2::Client - HTTP/2 client base class
9              
10             =head1 SYNOPSIS
11              
12             use Net::HTTP2::Client::Mojo;
13              
14             Net::HTTP2::Client::Mojo->new()->request(
15             GET => 'https://perl.com',
16             )->then(
17             sub ($response) {
18             # Handle the Net::HTTP2::Response
19             }
20             )->wait();
21              
22             L and L
23             exist as well for L and L, respectively.
24              
25             =head1 DESCRIPTION
26              
27             This class implements base functionality for an HTTP/2 client in Perl.
28              
29             =cut
30              
31             # perl -I ../p5-X-Tiny/lib -MData::Dumper -MAnyEvent -I ../p5-IO-SigGuard/lib -I ../p5-Promise-ES6/lib -Ilib -MNet::HTTP2::Client::Mojo -e'my $h2 = Net::HTTP2::Client::Mojo->new(); $h2->request("GET", "https://google.com")->then( sub { print Dumper shift } )->wait();'
32              
33             #----------------------------------------------------------------------
34              
35 1     1   3 use Carp ();
  1         1  
  1         20  
36 1     1   329 use URI::Split ();
  1         2311  
  1         17  
37              
38 1     1   299 use Net::HTTP2::Constants ();
  1         2  
  1         15  
39 1     1   312 use Net::HTTP2::Client::ConnectionPool ();
  1         2  
  1         20  
40              
41 1         47 use constant _SIMPLE_REDIRECTS => (
42             301, 308,
43             302, 307,
44 1     1   4 );
  1         1  
45              
46 1     1   5 use constant _FORBIDDEN => ('port');
  1         1  
  1         590  
47              
48             #----------------------------------------------------------------------
49              
50             sub new {
51 1     1 0 10 my ($class, %opts) = @_;
52              
53 1         2 my @bad = grep { defined $opts{$_} } _FORBIDDEN;
  1         3  
54 1 50       3 Carp::croak "Forbidden: @bad" if @bad;
55              
56 1         2 delete @opts{ _FORBIDDEN() };
57              
58 1         8 return bless {
59             host_port_conn => { },
60             conn_pool => Net::HTTP2::Client::ConnectionPool->new(
61             $class->_CLIENT_IO(),
62             \%opts,
63             ),
64             }, $class;
65             }
66              
67             sub _split_uri_auth {
68 2     2   3 my $auth = shift;
69              
70 2 50       7 if ( $auth =~ m<\A([^:]+):(.+)> ) {
71 0         0 return ($1, $2);
72             }
73              
74 2         6 return ($auth, Net::HTTP2::Constants::HTTPS_PORT);
75             }
76              
77             sub request {
78 1     1 0 3548 my ($self, $method, $url, @opts_kv) = @_;
79              
80             # Omit the fragment:
81 1         4 my ($scheme, $auth, $path, $query) = URI::Split::uri_split($url);
82              
83 1 50       9 if (!$scheme) {
84 0         0 Carp::croak "Need absolute URL, not “$url”";
85             }
86              
87 1 50       4 if ($scheme ne 'https') {
88 0         0 Carp::croak "https only, not $scheme!";
89             }
90              
91 1         3 my ($host, $port) = _split_uri_auth($auth);
92              
93 1         4 my $host_port_conn_hr = $self->{'host_port_conn'};
94              
95 1         2 my $path_and_query = $path;
96 1 50 33     3 if (defined $query && length $query) {
97 0         0 $path_and_query .= "?$query";
98             }
99              
100             return _request_recurse(
101 1         3 $self->{'conn_pool'},
102             $method,
103             $host,
104             $port,
105             $path_and_query,
106             @opts_kv,
107             );
108             }
109              
110             sub _request_recurse {
111 2     2   6 my ($conn_pool, $method, $host, $port, $path_and_query, @opts_kv) = @_;
112              
113 2         8 my $conn = $conn_pool->get_connection($host, $port);
114              
115             return $conn->request($method, $path_and_query, @opts_kv)->then(
116             sub {
117 2     2   82 my $resp = shift;
118              
119 2         8 my $status = $resp->status();
120 2         6 my $redirect_yn = grep { $_ == $status } _SIMPLE_REDIRECTS;
  8         15  
121              
122 2 50       7 if ($status == 303) {
123 0         0 $redirect_yn = 1;
124              
125 0         0 $method = 'GET';
126 0         0 push @opts_kv, body => q<>;
127             }
128              
129 2 100       4 if ($redirect_yn) {
130             my ($new_host, $new_port, $path_and_query) = _consume_location(
131 1         3 $resp->headers()->{'location'},
132             $host, $port, $path_and_query,
133             );
134              
135 1         2 $host = $new_host;
136 1         1 $port = $new_port;
137              
138 1         3 return _request_recurse( $conn_pool, $method, $host, $port, $path_and_query, @opts_kv );
139             }
140              
141 1         3 return $resp;
142             }
143 2         9 );
144             }
145              
146             sub _consume_location {
147 1     1   3 my ($location, $host, $port, $old_path) = @_;
148              
149 1         5 my ($scheme, $auth, $path, $query) = URI::Split::uri_split($location);
150              
151 1         13 my $path_and_query = $path;
152 1 50 33     3 if (defined $query && length $query) {
153 0         0 $path_and_query .= "?$query";
154             }
155              
156 1 50       3 if ($scheme) {
157 1 50       2 if ($scheme ne 'https') {
158 0         0 Carp::croak "Invalid scheme in redirect: $location";
159             }
160              
161 1         5 ($host, $port) = _split_uri_auth($auth);
162             }
163              
164 1 50       4 if (rindex($path, '/', 0) != 0) {
165 0         0 $old_path =~ s<(.*)/><$1>;
166 0         0 substr( $path_and_query, 0, 0, "$old_path/" );
167             }
168              
169 1         4 return ($host, $port, $path_and_query);
170             }
171              
172             1;