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