line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package RestAPI; |
2
|
7
|
|
|
7
|
|
91918
|
use v5.18; |
|
7
|
|
|
|
|
29
|
|
3
|
|
|
|
|
|
|
our $VERSION = "0.12"; |
4
|
7
|
|
|
7
|
|
3662
|
use Moo; |
|
7
|
|
|
|
|
59140
|
|
|
7
|
|
|
|
|
31
|
|
5
|
7
|
|
|
7
|
|
9745
|
no warnings 'experimental'; |
|
7
|
|
|
|
|
14
|
|
|
7
|
|
|
|
|
221
|
|
6
|
7
|
|
|
7
|
|
3691
|
use Types::Standard qw( Any HashRef Bool Str Int ); |
|
7
|
|
|
|
|
629210
|
|
|
7
|
|
|
|
|
91
|
|
7
|
7
|
|
|
7
|
|
11151
|
use namespace::autoclean; |
|
7
|
|
|
|
|
74828
|
|
|
7
|
|
|
|
|
28
|
|
8
|
7
|
|
|
7
|
|
5471
|
use XML::Simple qw( XMLin XMLout ); |
|
7
|
|
|
|
|
70705
|
|
|
7
|
|
|
|
|
54
|
|
9
|
7
|
|
|
7
|
|
5692
|
use JSON::XS (); |
|
7
|
|
|
|
|
24223
|
|
|
7
|
|
|
|
|
147
|
|
10
|
7
|
|
|
7
|
|
1474
|
use LWP::UserAgent (); |
|
7
|
|
|
|
|
136374
|
|
|
7
|
|
|
|
|
160
|
|
11
|
7
|
|
|
7
|
|
3502
|
use Time::HiRes qw( gettimeofday tv_interval ); |
|
7
|
|
|
|
|
9012
|
|
|
7
|
|
|
|
|
32
|
|
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
# Basic construction params |
14
|
|
|
|
|
|
|
has 'server' => ( is => 'rw', isa => Str ); |
15
|
|
|
|
|
|
|
has 'port' => ( is => 'rw', isa => Int ); |
16
|
|
|
|
|
|
|
has 'ssl_opts' => ( is => 'rw', isa => HashRef ); |
17
|
|
|
|
|
|
|
has 'basicAuth' => ( is => 'rw', isa => Bool); |
18
|
|
|
|
|
|
|
has ['realm', 'username', 'password', 'scheme'] => ( is => 'rw' ); |
19
|
|
|
|
|
|
|
has 'timeout' => ( is => 'rw', isa => Int ); |
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
# Added construction params |
22
|
|
|
|
|
|
|
has 'headers' => ( is => 'rw', isa => HashRef, default => sub { {} } ); |
23
|
|
|
|
|
|
|
has 'query' => ( is => 'rw', isa => Str ); |
24
|
|
|
|
|
|
|
has 'path' => ( is => 'rw', isa => Str, trigger => \&_set_request ); |
25
|
|
|
|
|
|
|
has 'q_params' => ( is => 'rw', isa => HashRef, default => sub {{}}, trigger => \&_set_q_params ); |
26
|
|
|
|
|
|
|
has 'http_verb' => ( is => 'rw', isa => Str, default => 'GET' ); |
27
|
|
|
|
|
|
|
has 'payload' => ( is => 'rw', isa => Any, trigger => \&_set_payload ); |
28
|
|
|
|
|
|
|
has 'encoding' => ( is => 'rw', isa => Str ); |
29
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
# other objects |
31
|
|
|
|
|
|
|
has 'req' => ( is => 'ro', writer => '_set_req' ); |
32
|
|
|
|
|
|
|
has 'req_params' => ( is => 'ro', writer => '_set_req_params'); |
33
|
|
|
|
|
|
|
has 'ua' => ( is => 'rw', writer => '_set_ua' ); |
34
|
|
|
|
|
|
|
has 'jsonObj' => ( is => 'ro', default => sub { |
35
|
|
|
|
|
|
|
return JSON::XS->new |
36
|
|
|
|
|
|
|
->utf8 |
37
|
|
|
|
|
|
|
->allow_nonref |
38
|
|
|
|
|
|
|
->convert_blessed; |
39
|
|
|
|
|
|
|
} ); |
40
|
|
|
|
|
|
|
has 'raw' => ( is => 'ro', writer => '_set_raw' ); |
41
|
|
|
|
|
|
|
has 'response' => ( is => 'ro', writer => '_set_response' ); |
42
|
|
|
|
|
|
|
has 'metrics' => ( is => 'ro', isa => HashRef, default => sub { {} } ); |
43
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
# encodes the payload if not encoded already |
45
|
|
|
|
|
|
|
sub _set_payload { |
46
|
4
|
|
|
4
|
|
7481
|
my $self = shift; |
47
|
4
|
100
|
|
|
|
60
|
if ( ref $self->payload ) { |
48
|
2
|
|
|
|
|
17
|
my $str; |
49
|
2
|
|
|
|
|
29
|
for ( $self->encoding ) { |
50
|
2
|
|
|
|
|
16
|
when ( m|xml| ) { |
51
|
0
|
|
|
|
|
0
|
$str = XMLout( $self->payload ); |
52
|
|
|
|
|
|
|
} |
53
|
2
|
|
|
|
|
7
|
when ( m|json| ) { |
54
|
2
|
|
|
|
|
31
|
$str = $self->jsonObj->encode( $self->payload ); |
55
|
|
|
|
|
|
|
} |
56
|
|
|
|
|
|
|
} |
57
|
2
|
|
|
|
|
50
|
$self->payload( $str ); |
58
|
|
|
|
|
|
|
} |
59
|
|
|
|
|
|
|
} |
60
|
|
|
|
|
|
|
|
61
|
|
|
|
|
|
|
sub BUILD { |
62
|
7
|
|
|
7
|
0
|
364
|
my $self = shift; |
63
|
7
|
|
|
|
|
131
|
$self->_set_ua( LWP::UserAgent->new( |
64
|
|
|
|
|
|
|
ssl_opts => $self->ssl_opts, |
65
|
|
|
|
|
|
|
timeout => $self->timeout, |
66
|
|
|
|
|
|
|
agent => 'RestAPI/0.0.8', |
67
|
|
|
|
|
|
|
)); |
68
|
|
|
|
|
|
|
|
69
|
7
|
100
|
100
|
|
|
16525
|
$self->server( "$self->{server}:$self->{port}" ) if ( $self->{server} && $self->{port} ); |
70
|
|
|
|
|
|
|
|
71
|
7
|
50
|
|
|
|
208
|
if ( $self->basicAuth ) { |
72
|
0
|
|
|
|
|
0
|
$self->ua->credentials( |
73
|
|
|
|
|
|
|
$self->server, |
74
|
|
|
|
|
|
|
$self->realm, |
75
|
|
|
|
|
|
|
$self->username, |
76
|
|
|
|
|
|
|
$self->password |
77
|
|
|
|
|
|
|
); |
78
|
|
|
|
|
|
|
} |
79
|
|
|
|
|
|
|
|
80
|
7
|
100
|
|
|
|
96
|
if ( $self->scheme ) { |
81
|
6
|
|
|
|
|
107
|
$self->server($self->scheme . '://' . $self->server); |
82
|
|
|
|
|
|
|
} |
83
|
|
|
|
|
|
|
} |
84
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
sub _set_q_params { |
86
|
2
|
|
|
2
|
|
4058
|
my $self = shift; |
87
|
2
|
100
|
|
|
|
3
|
return unless keys %{$self->q_params}; |
|
2
|
|
|
|
|
34
|
|
88
|
1
|
|
|
|
|
7
|
my $q_params; |
89
|
1
|
|
|
|
|
2
|
while ( my ( $k, $v ) = each %{$self->q_params} ) { |
|
2
|
|
|
|
|
26
|
|
90
|
1
|
|
|
|
|
9
|
$q_params .= '&'."$k=$v"; |
91
|
|
|
|
|
|
|
} |
92
|
1
|
|
|
|
|
23
|
$self->_set_req_params( substr( $q_params, 1, length($q_params) - 1 ) ); |
93
|
|
|
|
|
|
|
} |
94
|
|
|
|
|
|
|
|
95
|
|
|
|
|
|
|
sub _set_request { |
96
|
10
|
|
|
10
|
|
337
|
my $self = shift; |
97
|
|
|
|
|
|
|
|
98
|
10
|
|
|
|
|
20
|
my $url; |
99
|
10
|
100
|
|
|
|
96
|
$url = $self->server if ( $self->{server} ); |
100
|
|
|
|
|
|
|
|
101
|
10
|
100
|
|
|
|
208
|
if ( $self->query ) { |
102
|
1
|
50
|
33
|
|
|
32
|
$self->{query} = '/'.$self->{query} if ( $url && $self->{query} !~ m|^/|); |
103
|
1
|
|
|
|
|
19
|
$url .= $self->query; |
104
|
|
|
|
|
|
|
} |
105
|
|
|
|
|
|
|
|
106
|
10
|
100
|
|
|
|
252
|
if ( $self->path ) { |
107
|
9
|
100
|
|
|
|
99
|
$self->{path} = '/'.$self->{path} unless ( $self->{path} =~ m|^/| ); |
108
|
9
|
|
|
|
|
122
|
$url .= $self->path; |
109
|
|
|
|
|
|
|
} |
110
|
|
|
|
|
|
|
|
111
|
10
|
50
|
|
|
|
88
|
$url .= '?'.$self->req_params if ($self->req_params); |
112
|
|
|
|
|
|
|
|
113
|
10
|
|
|
|
|
68
|
my $h = HTTP::Headers->new; |
114
|
10
|
100
|
|
|
|
235
|
$h->content_type($self->encoding) if ( $self->encoding ); |
115
|
|
|
|
|
|
|
|
116
|
10
|
|
|
|
|
209
|
while ( my ( $k, $v ) = each( %{$self->headers} ) ) { |
|
10
|
|
|
|
|
152
|
|
117
|
0
|
|
|
|
|
0
|
$h->header( $k, $v ); |
118
|
|
|
|
|
|
|
} |
119
|
|
|
|
|
|
|
|
120
|
10
|
|
|
|
|
247
|
$self->_set_req( HTTP::Request->new( $self->http_verb, $url, $h, $self->payload ) ); |
121
|
|
|
|
|
|
|
} |
122
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
sub do { |
124
|
4
|
|
|
4
|
0
|
32090
|
my $self = shift; |
125
|
|
|
|
|
|
|
|
126
|
4
|
|
|
|
|
19
|
$self->_set_request(); |
127
|
|
|
|
|
|
|
|
128
|
4
|
|
|
|
|
562
|
my %headers; |
129
|
4
|
|
|
|
|
88
|
my $t0 = [gettimeofday]; |
130
|
4
|
|
|
|
|
58
|
$self->_set_response( $self->ua->request( $self->req ) ); |
131
|
4
|
|
|
|
|
7723
|
$self->{metrics}->{'response_time'} = tv_interval( $t0, [gettimeofday] ); |
132
|
|
|
|
|
|
|
|
133
|
4
|
50
|
|
|
|
94
|
die "Error: ".$self->response->status_line |
134
|
|
|
|
|
|
|
unless ( $self->response->is_success ); |
135
|
|
|
|
|
|
|
|
136
|
4
|
|
|
|
|
125
|
%headers = $self->response->flatten(); |
137
|
4
|
|
|
|
|
497
|
$self->_set_raw( $self->response->decoded_content ); |
138
|
4
|
50
|
33
|
|
|
608
|
if ( exists $headers{'Content-Transfer-Encoding'} && |
139
|
|
|
|
|
|
|
$headers{'Content-Transfer-Encoding'} eq 'binary' ) { |
140
|
0
|
|
|
|
|
0
|
return $self->raw; |
141
|
|
|
|
|
|
|
} |
142
|
|
|
|
|
|
|
|
143
|
4
|
50
|
|
|
|
18
|
my $r_encoding = $self->response->header("Content_Type") |
144
|
|
|
|
|
|
|
or return $self->raw; |
145
|
|
|
|
|
|
|
|
146
|
4
|
|
|
|
|
142
|
my $outObj; |
147
|
4
|
|
|
|
|
11
|
for ( $r_encoding ) { |
148
|
4
|
|
|
|
|
12
|
when ( m|application/xml| ) { |
149
|
0
|
0
|
|
|
|
0
|
if ( $self->raw =~ /^<\?xml/ ) { |
150
|
0
|
|
|
|
|
0
|
$outObj = XMLin( $self->raw ); |
151
|
|
|
|
|
|
|
} else { |
152
|
0
|
|
|
|
|
0
|
$outObj = $self->raw; |
153
|
|
|
|
|
|
|
} |
154
|
|
|
|
|
|
|
} |
155
|
4
|
|
|
|
|
13
|
when ( m|application/json| ) { |
156
|
4
|
|
|
|
|
51
|
$outObj = $self->jsonObj->decode( $self->raw ); |
157
|
|
|
|
|
|
|
} |
158
|
0
|
|
|
|
|
0
|
when ( m|text| ) { |
159
|
0
|
|
|
|
|
0
|
$outObj = $self->raw; |
160
|
|
|
|
|
|
|
} |
161
|
|
|
|
|
|
|
} |
162
|
4
|
|
|
|
|
32
|
return $outObj; |
163
|
|
|
|
|
|
|
} |
164
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
__PACKAGE__->meta->make_immutable; |
166
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
__END__ |
168
|
|
|
|
|
|
|
|
169
|
|
|
|
|
|
|
#=============================================================================== |
170
|
|
|
|
|
|
|
|
171
|
|
|
|
|
|
|
=head1 NAME |
172
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
RestAPI - a base module to interact with a REST API interface |
174
|
|
|
|
|
|
|
|
175
|
|
|
|
|
|
|
=head1 VERSION |
176
|
|
|
|
|
|
|
|
177
|
|
|
|
|
|
|
Version 0.09 |
178
|
|
|
|
|
|
|
|
179
|
|
|
|
|
|
|
|
180
|
|
|
|
|
|
|
=head1 SYNOPSIS |
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
use RestAPI; |
183
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
# a REST GET request |
185
|
|
|
|
|
|
|
my $client = RestAPI->new( |
186
|
|
|
|
|
|
|
basicAuth => 1, |
187
|
|
|
|
|
|
|
realm => "Some Realm", |
188
|
|
|
|
|
|
|
ssl_opts => { verify_hostname => 0 }, |
189
|
|
|
|
|
|
|
username => "foo", |
190
|
|
|
|
|
|
|
password => "bar", |
191
|
|
|
|
|
|
|
timeout => 10, # in secs |
192
|
|
|
|
|
|
|
scheme => 'https', # if missing it is assumed comprised in the server or in the query |
193
|
|
|
|
|
|
|
server => '...', |
194
|
|
|
|
|
|
|
query => '...', # (maybe fixed) request part |
195
|
|
|
|
|
|
|
path => '...', # added alongside the request |
196
|
|
|
|
|
|
|
q_params => { foo => bar }, |
197
|
|
|
|
|
|
|
headers => { k => 'v' }, |
198
|
|
|
|
|
|
|
http_verb => 'GET', # any http verb... |
199
|
|
|
|
|
|
|
encoding => 'application/xml' # or whatever... |
200
|
|
|
|
|
|
|
); |
201
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
# a REST POST request |
203
|
|
|
|
|
|
|
my $client = RestAPI->new( |
204
|
|
|
|
|
|
|
basicAuth => 1, |
205
|
|
|
|
|
|
|
realm => "Some Realm", |
206
|
|
|
|
|
|
|
username => "foo", |
207
|
|
|
|
|
|
|
password => "bar", |
208
|
|
|
|
|
|
|
scheme => 'https', |
209
|
|
|
|
|
|
|
timeout => 10, # in secs |
210
|
|
|
|
|
|
|
server => '...', |
211
|
|
|
|
|
|
|
query => '...', |
212
|
|
|
|
|
|
|
path => '...', |
213
|
|
|
|
|
|
|
q_params => { foo => bar }, |
214
|
|
|
|
|
|
|
http_verb => 'POST', |
215
|
|
|
|
|
|
|
payload => '...', |
216
|
|
|
|
|
|
|
encoding => 'application/xml' |
217
|
|
|
|
|
|
|
); |
218
|
|
|
|
|
|
|
|
219
|
|
|
|
|
|
|
# a REST UPDATE request |
220
|
|
|
|
|
|
|
my $client = RestAPI->new( |
221
|
|
|
|
|
|
|
basicAuth => 1, |
222
|
|
|
|
|
|
|
realm => "Some Realm", |
223
|
|
|
|
|
|
|
username => "foo", |
224
|
|
|
|
|
|
|
password => "bar", |
225
|
|
|
|
|
|
|
scheme => 'https', |
226
|
|
|
|
|
|
|
timeout => 10, # in secs |
227
|
|
|
|
|
|
|
server => '...', |
228
|
|
|
|
|
|
|
query => '...', |
229
|
|
|
|
|
|
|
path => '...', |
230
|
|
|
|
|
|
|
q_params => { foo => bar }, |
231
|
|
|
|
|
|
|
http_verb => 'PUT', |
232
|
|
|
|
|
|
|
payload => '...', |
233
|
|
|
|
|
|
|
encoding => 'application/xml' |
234
|
|
|
|
|
|
|
); |
235
|
|
|
|
|
|
|
|
236
|
|
|
|
|
|
|
# a REST DELETE request |
237
|
|
|
|
|
|
|
my $client = RestAPI->new( |
238
|
|
|
|
|
|
|
basicAuth => 1, |
239
|
|
|
|
|
|
|
realm => "Some Realm", |
240
|
|
|
|
|
|
|
username => "foo", |
241
|
|
|
|
|
|
|
password => "bar", |
242
|
|
|
|
|
|
|
scheme => 'https', |
243
|
|
|
|
|
|
|
timeout => 10, # in secs |
244
|
|
|
|
|
|
|
server => '...', |
245
|
|
|
|
|
|
|
query => '...', |
246
|
|
|
|
|
|
|
path => '...', |
247
|
|
|
|
|
|
|
q_params => { foo => bar }, |
248
|
|
|
|
|
|
|
http_verb => 'DELETE', |
249
|
|
|
|
|
|
|
encoding => 'application/xml' |
250
|
|
|
|
|
|
|
); |
251
|
|
|
|
|
|
|
|
252
|
|
|
|
|
|
|
try { |
253
|
|
|
|
|
|
|
my $response_data = $client->do(); |
254
|
|
|
|
|
|
|
|
255
|
|
|
|
|
|
|
# $self->response is the HTTP::Response object |
256
|
|
|
|
|
|
|
# you get back from your request... |
257
|
|
|
|
|
|
|
my %response_headers = $client->response->flatten(); |
258
|
|
|
|
|
|
|
} catch { |
259
|
|
|
|
|
|
|
die "Error performing request, status line: $!\n"; |
260
|
|
|
|
|
|
|
} |
261
|
|
|
|
|
|
|
|
262
|
|
|
|
|
|
|
my $raw_response = $client->raw(); # the raw response. |
263
|
|
|
|
|
|
|
|
264
|
|
|
|
|
|
|
=head1 EXPORT |
265
|
|
|
|
|
|
|
|
266
|
|
|
|
|
|
|
None |
267
|
|
|
|
|
|
|
|
268
|
|
|
|
|
|
|
=head1 AUTHOR |
269
|
|
|
|
|
|
|
|
270
|
|
|
|
|
|
|
Marco Masetti, C<< <marco.masetti at sky.uk> >> |
271
|
|
|
|
|
|
|
|
272
|
|
|
|
|
|
|
=head1 SUPPORT |
273
|
|
|
|
|
|
|
|
274
|
|
|
|
|
|
|
You can find documentation for this module with the perldoc command. |
275
|
|
|
|
|
|
|
|
276
|
|
|
|
|
|
|
perldoc RestAPI |
277
|
|
|
|
|
|
|
|
278
|
|
|
|
|
|
|
|
279
|
|
|
|
|
|
|
=head1 LICENSE AND COPYRIGHT |
280
|
|
|
|
|
|
|
|
281
|
|
|
|
|
|
|
Copyright 2017 Marco Masetti. |
282
|
|
|
|
|
|
|
|
283
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or modify it |
284
|
|
|
|
|
|
|
under the terms of Perl itself. |
285
|
|
|
|
|
|
|
|
286
|
|
|
|
|
|
|
=cut |
287
|
|
|
|
|
|
|
|
288
|
|
|
|
|
|
|
#=============================================================================== |