File Coverage

blib/lib/Flickr/API2/Raw.pm
Criterion Covered Total %
statement 81 84 96.4
branch 11 18 61.1
condition 6 15 40.0
subroutine 16 17 94.1
pod 5 5 100.0
total 119 139 85.6


line stmt bran cond sub pod time code
1             package Flickr::API2::Raw;
2 4     4   28 use 5.12.0;
  4         10  
3 4     4   14 use warnings;
  4         6  
  4         110  
4 4     4   670 use JSON qw(decode_json);
  4         9260  
  4         24  
5 4     4   499 use Digest::MD5 qw(md5_hex);
  4         3  
  4         168  
6 4     4   2211 use Compress::Zlib;
  4         119897  
  4         800  
7 4     4   1228 use LWP::UserAgent;
  4         31244  
  4         120  
8 4     4   1725 use Retry;
  4         26576  
  4         121  
9 4     4   1103 use Encode;
  4         13726  
  4         263  
10 4     4   121 use Carp qw(croak);
  4         6  
  4         150  
11 4     4   15 use parent qw(LWP::UserAgent);
  4         5  
  4         24  
12              
13             =head1 NAME
14              
15             Flickr::API2::Raw
16              
17             =head1 DESCRIPTION
18              
19             This module encapsulates the raw interactions with Flickr's API - the creation
20             of an HTTP request, signing of arguments, checking of response codes, and so
21             forth.
22              
23             End users shouldn't need to use this module - instead, use the Flickr::API2
24             object, and call 'execute_method' on it.
25              
26             =head1 METHODS
27              
28             =cut
29              
30             =head2 new
31              
32             Constructor - takes arguments of:
33             key (api key)
34             secret (api key's secret)
35             rest_uri (which URL at flickr to use - defaults to the correct value)
36             auth_uri (which URL at flickr for authentication - defaults to correct value)
37              
38             =cut
39              
40             sub new {
41 4     4 1 4 my $class = shift;
42 4         5 my $options = shift;
43 4         20 my $self = LWP::UserAgent->new;
44 4         2570 $self->env_proxy; # Honour proxy settings in the environment.
45 4   50     3086 $self->timeout($options->{timeout} || 30); # Timeout after 30 seconds
46              
47 4         52 $self->{api_key} = $options->{key};
48 4         13 $self->{api_secret} = $options->{secret};
49             $self->{rest_uri} = $options->{rest_uri}
50 4   50     20 || 'http://api.flickr.com/services/rest/';
51             $self->{auth_uri} = $options->{auth_uri}
52 4   50     18 || 'http://api.flickr.com/services/auth/';
53              
54 4         11 $self->default_header( 'Accept-Encoding' => 'gzip' );
55 4         158 bless $self, $class;
56             }
57              
58             =head2 sign_args ($secret, \%args)
59              
60             Signs the given arguments with the given secret key.
61              
62             =cut
63              
64             sub sign_args {
65 13     13 1 449 my ($self, $sig, $args) = @_;
66              
67 13         17 foreach my $key ( sort { $a cmp $b } keys %{$args} ) {
  87         87  
  13         69  
68 59 100       83 my $value = ( defined( $args->{$key} ) ) ? $args->{$key} : "";
69 59         86 $sig .= $key . $value;
70             }
71              
72 13         61 return md5_hex(encode('utf8', $sig));
73             }
74              
75             =head2 request_auth_url ($perms, $frob)
76              
77             Returns a C object representing the URL that an application must redirect a user to for approving
78             an authentication token.
79              
80             For web-based applications I<$frob> is an optional parameter.
81              
82             Returns undef if a secret was not specified when creating the C object.
83              
84             =cut
85              
86             sub request_auth_url {
87 1     1 1 1 my $self = shift;
88 1         4 my $perms = shift;
89 1         1 my $frob = shift;
90              
91             return undef
92 1 50 33     9 unless defined $self->{api_secret} && length $self->{api_secret};
93              
94             my %args = (
95             'api_key' => $self->{api_key},
96 1         3 'perms' => $perms
97             );
98              
99 1 50       2 if ($frob) {
100 1         1 $args{frob} = $frob;
101             }
102              
103 1         2 $args{api_sig} = $self->sign_args( $self->{api_secret}, \%args );
104              
105 1         25 my $uri = URI->new( $self->{auth_uri} );
106 1         5132 $uri->query_form(%args);
107              
108 1         122 return $uri;
109             }
110              
111             =head2 execute_request
112              
113             Called from execute_method() to kick off the API query process.
114             Either dies with an exception, or returns a hash-reference of the results.
115              
116             =cut
117              
118             sub execute_request {
119 10     10 1 17 my ( $self, $request ) = @_;
120              
121 10         22 $request->{api_args}->{method} = $request->{api_method};
122 10         33 $request->{api_args}->{api_key} = $self->{api_key};
123              
124 10 50 33     63 if ( defined( $self->{api_secret} ) && length( $self->{api_secret} ) ) {
125             $request->{api_args}->{api_sig} =
126 10         35 $self->sign_args( $self->{api_secret}, $request->{api_args} );
127             }
128              
129 10         477 $request->encode_args();
130              
131 10         212 my $response = $self->do_request($request);
132              
133 10 50       32 croak("API call failed with HTTP status: " . $response->code)
134             unless $response->code == 200;
135              
136 10         111 my $content = $response->decoded_content;
137 10 50       16543 $content = $response->content() unless defined $content;
138              
139 10         20 my $json = eval { decode_json($content) };
  10         467  
140 10 50       31 if ($@) {
141 0         0 croak("Failed to parse API response as JSON. Error=$@\nContent=$content\n");
142             }
143              
144 10 100       38 if ( $json->{stat} eq 'ok' ) {
145 8         162 return $json;
146             # Do we still care about returning the $response somehow?
147             # It doesn't have much of interest at this stage, I think.
148             }
149              
150             croak(sprintf("API call failed: \%s (\%s)\n",
151             $json->{message}, $json->{code})
152 2         38 );
153             }
154              
155             =head2 do_request
156              
157             Calls LWP::UserAgent's ->request method, but does so within the Retry system,
158             in order to catch and retry timeouts.
159              
160             Added by request.
161              
162             =cut
163              
164             sub do_request {
165 10     10 1 17 my ($self, $request) = @_;
166              
167             my $agent = Retry->new(
168             failure_callback => sub {
169 0     0   0 warn "API request failed (will retry): " . $_[0] . "\n"
170             }
171 10         280 );
172 10         3664 my $r;
173             $agent->retry(sub {
174 10     10   1913 $r = $self->request($request);
175 10 50 33     2708380 if (not $r->is_success and $r->status_line =~ /timeout/) {
176 0         0 croak("Connection timed out");
177             }
178 10         71 });
179 10         332 return $r;
180             }
181              
182             1;