File Coverage

blib/lib/Flickr/API2/Raw.pm
Criterion Covered Total %
statement 82 85 96.4
branch 11 18 61.1
condition 6 15 40.0
subroutine 16 17 94.1
pod 5 5 100.0
total 120 140 85.7


line stmt bran cond sub pod time code
1             package Flickr::API2::Raw;
2 4     4   21 use strict;
  4         9  
  4         147  
3 4     4   21 use warnings;
  4         6  
  4         119  
4 4     4   1074 use JSON qw(decode_json);
  4         12728  
  4         35  
5 4     4   713 use Digest::MD5 qw(md5_hex);
  4         7  
  4         241  
6 4     4   5733 use Compress::Zlib;
  4         247275  
  4         1209  
7 4     4   6713 use LWP::UserAgent;
  4         65493  
  4         141  
8 4     4   3305 use Retry;
  4         243026  
  4         155  
9 4     4   5586 use Encode;
  4         30262  
  4         458  
10 4     4   33 use Carp qw(croak);
  4         7  
  4         196  
11 4     4   24 use parent qw(LWP::UserAgent);
  4         8  
  4         40  
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 9 my $class = shift;
42 4         7 my $options = shift;
43 4         39 my $self = LWP::UserAgent->new;
44 4         4292 $self->env_proxy; # Honour proxy settings in the environment.
45 4   50     5087 $self->timeout($options->{timeout} || 30); # Timeout after 30 seconds
46              
47 4         70 $self->{api_key} = $options->{key};
48 4         16 $self->{api_secret} = $options->{secret};
49 4   50     25 $self->{rest_uri} = $options->{rest_uri}
50             || 'http://api.flickr.com/services/rest/';
51 4   50     19 $self->{auth_uri} = $options->{auth_uri}
52             || 'http://api.flickr.com/services/auth/';
53              
54 4         16 $self->default_header( 'Accept-Encoding' => 'gzip' );
55 4         218 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 868 my ($self, $sig, $args) = @_;
66              
67 13         22 foreach my $key ( sort { $a cmp $b } keys %{$args} ) {
  85         128  
  13         90  
68 59 100       117 my $value = ( defined( $args->{$key} ) ) ? $args->{$key} : "";
69 59         118 $sig .= $key . $value;
70             }
71              
72 13         82 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 3 my $self = shift;
88 1         3 my $perms = shift;
89 1         3 my $frob = shift;
90              
91             return undef
92 1 50 33     18 unless defined $self->{api_secret} && length $self->{api_secret};
93              
94 1         5 my %args = (
95             'api_key' => $self->{api_key},
96             'perms' => $perms
97             );
98              
99 1 50       4 if ($frob) {
100 1         3 $args{frob} = $frob;
101             }
102              
103 1         4 $args{api_sig} = $self->sign_args( $self->{api_secret}, \%args );
104              
105 1         35 my $uri = URI->new( $self->{auth_uri} );
106 1         15497 $uri->query_form(%args);
107              
108 1         238 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 19 my ( $self, $request ) = @_;
120              
121 10         30 $request->{api_args}->{method} = $request->{api_method};
122 10         40 $request->{api_args}->{api_key} = $self->{api_key};
123              
124 10 50 33     71 if ( defined( $self->{api_secret} ) && length( $self->{api_secret} ) ) {
125 10         45 $request->{api_args}->{api_sig} =
126             $self->sign_args( $self->{api_secret}, $request->{api_args} );
127             }
128              
129 10         460 $request->encode_args();
130              
131 10         262 my $response = $self->do_request($request);
132              
133 10 50       44 croak("API call failed with HTTP status: " . $response->code)
134             unless $response->code == 200;
135              
136 10         178 my $content = $response->decoded_content;
137 10 50       26551 $content = $response->content() unless defined $content;
138              
139 10         30 my $json = eval { decode_json($content) };
  10         726  
140 10 50       45 if ($@) {
141 0         0 croak("Failed to parse API response as JSON. Error=$@\nContent=$content\n");
142             }
143              
144 10 100       48 if ( $json->{stat} eq 'ok' ) {
145 8         382 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 2         57 croak(sprintf("API call failed: \%s (\%s)\n",
151             $json->{message}, $json->{code})
152             );
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 18 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         375 );
172 10         5003 my $r;
173             $agent->retry(sub {
174 10     10   2353 $r = $self->request($request);
175 10 50 33     7802807 if (not $r->is_success and $r->status_line =~ /timeout/) {
176 0         0 croak("Connection timed out");
177             }
178 10         91 });
179 10         480 return $r;
180             }
181              
182             1;