File Coverage

blib/lib/Net/HTTP2/Client.pm
Criterion Covered Total %
statement 65 76 85.5
branch 13 24 54.1
condition 5 14 35.7
subroutine 15 15 100.0
pod 0 2 0.0
total 98 131 74.8


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