File Coverage

blib/lib/RestAPI.pm
Criterion Covered Total %
statement 80 89 89.8
branch 24 32 75.0
condition 5 9 55.5
subroutine 14 14 100.0
pod 0 2 0.0
total 123 146 84.2


line stmt bran cond sub pod time code
1             package RestAPI;
2 7     7   105921 use v5.14;
  7         32  
3             our $VERSION = "0.10";
4 7     7   3712 use Moo;
  7         54343  
  7         33  
5 7     7   10171 no warnings 'experimental';
  7         17  
  7         269  
6 7     7   3991 use Types::Standard qw( Any HashRef Bool Str Int );
  7         672357  
  7         109  
7 7     7   13355 use namespace::autoclean;
  7         38482  
  7         28  
8 7     7   5760 use XML::Simple qw( XMLin XMLout );
  7         62840  
  7         58  
9 7     7   5732 use JSON::XS ();
  7         25770  
  7         158  
10 7     7   1414 use LWP::UserAgent ();
  7         90419  
  7         171  
11 7     7   3616 use Time::HiRes qw( gettimeofday tv_interval );
  7         9250  
  7         45  
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   8692 my $self = shift;
47 4 100       69 if ( ref $self->payload ) {
48 2         20 my $str;
49 2         32 for ( $self->encoding ) {
50 2         18 when ( m|xml| ) {
51 0         0 $str = XMLout( $self->payload );
52             }
53 2         7 when ( m|json| ) {
54 2         35 $str = $self->jsonObj->encode( $self->payload );
55             }
56             }
57 2         56 $self->payload( $str );
58             }
59             }
60              
61             sub BUILD {
62 7     7 0 357 my $self = shift;
63 7         148 $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     18420 $self->server( "$self->{server}:$self->{port}" ) if ( $self->{server} && $self->{port} );
70              
71 7 50       239 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       103 if ( $self->scheme ) {
81 6         123 $self->server($self->scheme . '://' . $self->server);
82             }
83             }
84              
85             sub _set_q_params {
86 2     2   5046 my $self = shift;
87 2 100       5 return unless keys %{$self->q_params};
  2         38  
88 1         10 my $q_params;
89 1         3 while ( my ( $k, $v ) = each %{$self->q_params} ) {
  2         33  
90 1         13 $q_params .= '&'."$k=$v";
91             }
92 1         28 $self->_set_req_params( substr( $q_params, 1, length($q_params) - 1 ) );
93             }
94              
95             sub _set_request {
96 10     10   234 my $self = shift;
97              
98 10         23 my $url;
99 10 100       115 $url = $self->server if ( $self->{server} );
100              
101 10 100       215 if ( $self->query ) {
102 1 50 33     16 $self->{query} = '/'.$self->{query} if ( $url && $self->{query} !~ m|^/|);
103 1         19 $url .= $self->query;
104             }
105              
106 10 100       239 if ( $self->path ) {
107 9 100       108 $self->{path} = '/'.$self->{path} unless ( $self->{path} =~ m|^/| );
108 9         136 $url .= $self->path;
109             }
110              
111 10 50       97 $url .= '?'.$self->req_params if ($self->req_params);
112              
113 10         73 my $h = HTTP::Headers->new;
114 10 100       243 $h->content_type($self->encoding) if ( $self->encoding );
115              
116 10         233 while ( my ( $k, $v ) = each( %{$self->headers} ) ) {
  10         168  
117 0         0 $h->header( $k, $v );
118             }
119              
120 10         280 $self->_set_req( HTTP::Request->new( $self->http_verb, $url, $h, $self->payload ) );
121             }
122              
123             sub do {
124 4     4 0 21568 my $self = shift;
125              
126 4         20 $self->_set_request();
127              
128 4         618 my %headers;
129 4         30 my $t0 = [gettimeofday];
130 4         52 $self->_set_response( $self->ua->request( $self->req ) );
131 4         6204 $self->{metrics}->{'response_time'} = tv_interval( $t0, [gettimeofday] );
132              
133 4 50       163 die "Error: ".$self->response->status_line
134             unless ( $self->response->is_success );
135              
136 4         88 %headers = $self->response->flatten();
137 4         596 $self->_set_raw( $self->response->decoded_content );
138 4 50 33     687 if ( exists $headers{'Content-Transfer-Encoding'} &&
139             $headers{'Content-Transfer-Encoding'} eq 'binary' ) {
140 0         0 return $self->raw;
141             }
142            
143 4 50       22 my $r_encoding = $self->response->header("Content_Type")
144             or return $self->raw;
145              
146 4         166 my $outObj;
147 4         11 for ( $r_encoding ) {
148 4         14 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         15 when ( m|application/json| ) {
156 4         59 $outObj = $self->jsonObj->decode( $self->raw );
157             }
158 0         0 when ( m|text| ) {
159 0         0 $outObj = $self->raw;
160             }
161             }
162 4         31 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             #===============================================================================