File Coverage

blib/lib/Net/Fastly/Client.pm
Criterion Covered Total %
statement 45 194 23.2
branch 4 58 6.9
condition 3 25 12.0
subroutine 12 34 35.2
pod 6 6 100.0
total 70 317 22.0


line stmt bran cond sub pod time code
1             package Net::Fastly::Client;
2              
3 4     4   15 use strict;
  4         5  
  4         84  
4 4     4   9 use warnings;
  4         5  
  4         67  
5 4     4   2027 use JSON::XS;
  4         23135  
  4         3499  
6              
7             =head1 NAME
8              
9             Net::Fastly::Client - communicate with the Fastly HTTP API
10              
11             =head1 SYNOPSIS
12              
13             =head1 PROXYING
14              
15             There are two ways to proxy:
16              
17             The first method is to pass a proxy option into the constructor
18              
19             my $client = Net::Fastly::Client->new(user => $username, password => $password, proxy => "http://localhost:8080");
20            
21             The second is to set your C environment variable. So, in Bash
22              
23             % export https_proxy=http://localhost:8080
24            
25             or in CSH or TCSH
26              
27             % setenv https_proxy=http://localhost:8080
28              
29             =head1 METHODS
30              
31             =cut
32              
33              
34             =head2 new
35              
36             Create a new Fastly user agent. Options are
37              
38             =over 4
39              
40             =item user
41              
42             The login to use
43              
44             =item password
45              
46             Your password
47              
48             =item api_key
49              
50             Alternatively use the API Key (only some commands are available)
51              
52             =item proxy
53              
54             Optionally pass in an https proxy to use.
55              
56             =back
57              
58              
59             =cut
60             sub new {
61 1     1 1 1 my $class = shift;
62 1         3 my %opts = @_;
63 1         2 my $self = bless \%opts, $class;
64            
65 1   50     6 my $base = $opts{base_url} ||= "api.fastly.com";
66 1   50     5 my $port = $opts{base_port} ||= 80;
67 1   33     8 $self->{user} ||= $self->{username};
68 1         8 $self->{_ua} = Net::Fastly::Client::UserAgent->new($base, $port, $opts{proxy});
69 1 50       4 return $self unless $self->fully_authed;
70              
71             # If we're fully authed (i.e username and password ) then we need to log in
72 0         0 my $res = $self->_ua->_post('/login', {}, user => $self->{user}, password => $self->{password});
73 0 0       0 unless ($res->is_success) {
74 0 0 0     0 die "You must have IO::Socket::SSL or Crypt::SSLeay installed in order to do SSL requests\n" if $res->code == 501 && $res->status_line =~ /Protocol scheme 'https' is not supported/;
75 0 0       0 die "Unauthorized" unless $res->is_success;
76             }
77 0         0 my $content = decode_json($res->decoded_content);
78 0         0 $self->{_cookie} = $res->header('set-cookie');
79 0 0       0 return wantarray ? ($self, $content->{user}, $content->{customer}) : $self;
80             }
81              
82 0     0   0 sub _ua { shift->{_ua} }
83              
84             =head2 authed
85              
86             Whether or not we're authed at all by either API key or username & password
87              
88             =cut
89             sub authed {
90 0     0 1 0 my $self = shift;
91 0 0       0 $self->key_authed || $self->fully_authed;
92             }
93              
94             =head2 key_authed
95              
96             Whether or not we're authed by API key
97              
98             =cut
99             sub key_authed {
100 0     0 1 0 my $self = shift;
101             defined $self->{api_key}
102 0         0 }
103              
104             =head2 fully_authed
105              
106             Whether or not we're authed by username & password
107              
108             =cut
109             sub fully_authed {
110 1     1 1 2 my $self = shift;
111 1 50       8 defined $self->{user} && defined $self->{password};
112             }
113              
114             =head2 set_customer
115              
116             Set the current customer to act as.
117              
118             B: this will only work if you're an admin
119              
120             =cut
121             sub set_customer {
122 0     0 1 0 my $self = shift;
123 0         0 my $id = shift;
124 0         0 $self->{explicit_customer} = $id;
125             }
126              
127             =head2 timeout
128              
129             Get or set the timeout value in seconds. The default value is 180 seconds.
130              
131             =cut
132              
133             sub timeout {
134 0     0 1 0 my $self = shift;
135 0         0 $self->_ua->_ua->timeout(@_);
136             }
137              
138             # Get stuff from the stats API
139             sub _get_stats {
140 0     0   0 my $self = shift;
141 0         0 my $content = $self->_get(@_);
142 0 0       0 die $content->{msg} unless $content->{status} eq 'success';
143 0         0 return $content->{data};
144             }
145              
146             sub _get {
147 0     0   0 my $self = shift;
148 0         0 my $path = shift;
149 0         0 my %opts = @_;
150 0   0     0 my $headers = delete $opts{headers} || {};
151              
152 0         0 my $res = $self->_ua->_get($path, $self->_headers($headers), %opts);
153 0 0       0 return undef if 404 == $res->code;
154 0 0       0 $self->_raise_error($res) unless $res->is_success;
155 0         0 my $content = decode_json($res->decoded_content);
156 0         0 return $content;
157             }
158              
159             sub _post {
160 0     0   0 my $self = shift;
161 0         0 my $path = shift;
162 0         0 my %params = @_;
163 0   0     0 my $headers = delete $params{headers} || {};
164              
165 0         0 my $res = $self->_ua->_post($path, $self->_headers($headers), %params);
166 0 0       0 $self->_raise_error($res) unless $res->is_success;
167 0         0 my $content = decode_json($res->decoded_content);
168 0         0 return $content;
169             }
170              
171             sub _purge {
172 0     0   0 my $self = shift;
173 0         0 my $url = shift;
174 0         0 my %params = @_;
175 0   0     0 my $headers = delete $params{headers} || {};
176              
177 0         0 my $method = "_purge";
178 0 0       0 if ($self->{use_old_purge_method}) {
179 0         0 $method = "_post";
180 0         0 $url = "/purge/$url";
181             }
182              
183 0         0 my $res = $self->_ua->$method($url, $self->_headers($headers), %params);
184 0 0       0 $self->_raise_error($res) unless $res->is_success;
185 0         0 my $content = decode_json($res->decoded_content);
186 0         0 return $content;
187             }
188              
189             sub _put {
190 0     0   0 my $self = shift;
191 0         0 my $path = shift;
192 0         0 my %params = @_;
193 0   0     0 my $headers = delete $params{headers} || {};
194              
195 0         0 my $res = $self->_ua->_put($path, $self->_headers($headers), %params);
196 0 0       0 $self->_raise_error($res) unless $res->is_success;
197 0         0 my $content = decode_json($res->decoded_content);
198 0         0 return $content;
199             }
200              
201             sub _delete {
202 0     0   0 my $self = shift;
203 0         0 my $path = shift;
204 0         0 my %params = @_;
205 0   0     0 my $headers = delete $params{headers} || {};
206              
207 0         0 my $res = $self->_ua->_delete($path, $self->_headers($headers));
208 0 0       0 $self->_raise_error($res) unless $res->is_success;
209 0         0 return 1;
210             }
211              
212             sub _headers {
213 0     0   0 my $self = shift;
214 0         0 my $extras = shift;
215 0 0       0 my $params = $self->fully_authed ? { 'Cookie' => $self->{_cookie} } : { 'Fastly-Key' => $self->{api_key} };
216 0 0       0 $params->{'Fastly-Explicit-Customer'} = $self->{explicit_customer} if defined $self->{explicit_customer};
217 0         0 $params->{'Content-Accept'} = 'application/json';
218 0         0 $params->{'User-Agent'} = "fastly-perl-v$Net::Fastly::VERSION";
219 0         0 while (my ($key, $value) = each %$extras) {
220 0 0       0 $params->{$key} = $value if defined $value;
221             }
222 0         0 return $params;
223             }
224              
225             sub _raise_error {
226 0     0   0 my $self = shift;
227 0         0 my $res = shift;
228              
229 0         0 my $content = eval { decode_json($res->decoded_content) };
  0         0  
230 0 0 0     0 my $message = $content ? $content->{detail} || $content->{msg} : $res->status_line." ".$res->decoded_content;
231 0         0 die "$message\n";
232             }
233              
234              
235             package Net::Fastly::Client::UserAgent;
236              
237 4     4   21 use strict;
  4         4  
  4         65  
238 4     4   1715 use URI;
  4         12881  
  4         88  
239 4     4   2116 use LWP::UserAgent;
  4         113890  
  4         351  
240             BEGIN { # Compatibility fix for older versions of HTTP::Request::Common
241 4     4   1599 require HTTP::Request::Common;
242 4 50       6812 if (HTTP::Request::Common->can('DELETE')) {
    0          
243 4         2644 HTTP::Request::Common->import(qw(GET HEAD PUT POST DELETE));
244             } elsif (my $_simple_req = HTTP::Request::Common->can('_simple_req')) {
245 0         0 HTTP::Request::Common->import(qw(GET HEAD PUT POST));
246 0         0 *DELETE = sub { $_simple_req->('DELETE', @_) };
  0         0  
247             } else {
248 0         0 die << 'END'
249             "DELETE" is not exported by the HTTP::Request::Common module
250             and its underlying _simple_req() method is not available.
251             END
252             }
253             }
254              
255             sub new {
256 1     1   1 my $class = shift;
257 1         2 my $base = shift;
258 1         1 my $port = shift;
259 1         5 my $proxy = shift;
260 1         11 my $ua = Net::Fastly::UA->new;
261 1 50       1991 if ($proxy) {
262 0         0 $ua->proxy('https', $proxy);
263             } else {
264 1         7 $ua->env_proxy;
265             }
266 1         9419 return bless { _base => $base, _port => $port, _ua => $ua }, $class;
267            
268             }
269              
270 0     0     sub _ua { shift->{_ua} }
271              
272             sub _get {
273 0     0     my $self = shift;
274 0           my $path = shift;
275 0           my $headers = shift;
276 0           my %params = @_;
277 0           my $url = $self->_make_url($path, %params);
278 0           return $self->_ua->request(GET $url, %$headers);
279             }
280              
281             sub _post {
282 0     0     my $self = shift;
283 0           my $path = shift;
284 0           my $headers = shift;
285 0           my %params = @_;
286 0           my $url = $self->_make_url($path);
287 0           return $self->_ua->request(POST $url, [_make_params(%params)], %$headers);
288             }
289              
290             sub _purge {
291 0     0     my $self = shift;
292 0           my $url = shift;
293 0           my $headers = shift;
294 0           my %params = @_;
295 0           return $self->_ua->request(HTTP::Request->new("PURGE", $url, [%$headers]));
296             }
297              
298             sub _put {
299 0     0     my $self = shift;
300 0           my $path = shift;
301 0           my $headers = shift;
302 0           my %params = @_;
303 0           $headers->{content_type} = "application/x-www-form-urlencoded";
304 0           my $url = $self->_make_url($path);
305 0           my $uri = URI->new('http');
306 0           $uri->query_form(_make_params(%params));
307 0   0       return $self->_ua->request(PUT $url, %$headers, Content => $uri->query || "");
308             }
309              
310             sub _delete {
311 0     0     my $self = shift;
312 0           my $path = shift;
313 0           my $headers = shift;
314 0           my %params = @_;
315 0           my $url = $self->_make_url($path, %params);
316 0           return $self->_ua->request(DELETE $url, %$headers);
317             }
318              
319             sub _make_url {
320 0     0     my $self = shift;
321 0           my $base = $self->{_base};
322 0           my $port = $self->{_port};
323 0           my $path = shift;
324 0           my %params = @_;
325              
326 0           my $prot = "https:";
327 0 0         if ($base =~ s!^(https?:)//!!) {
328 0           $prot = $1;
329             }
330 0           my $url = URI->new($prot);
331 0           $url->host($base);
332 0 0         $url->port($port) if $port != 80;
333 0           $url->path($path);
334 0 0         $url->query_form(_make_params(%params)) if keys %params;
335 0           return $url;
336             }
337              
338             sub _make_params {
339 0     0     my %in = @_;
340 0           my %out;
341            
342 0           foreach my $key (keys %in) {
343 0           my $value = $in{$key};
344 0 0         next unless defined $value;
345 0 0         unless (ref($value) eq 'HASH') {
346 0           $out{$key} = $value;
347 0           next;
348             }
349 0           foreach my $sub_key (keys %$value) {
350 0           $out{$key."[".$sub_key."]"} = $value->{$sub_key};
351             }
352             }
353 0           return %out;
354             }
355              
356             package Net::Fastly::UA;
357              
358 4     4   20 use base qw(LWP::UserAgent);
  4         5  
  4         257  
359 4     4   1620 use LWP::Protocol::https;
  4         284040  
  4         472  
360             our $DEBUG=0;
361              
362             sub request {
363 0     0     my $self = shift;
364 0           my $req = shift;
365 0 0         print $req->as_string."\n------------------------\n\n" if $DEBUG;
366 0           my $res = $self->SUPER::request($req);
367 0 0         print $res->as_string."\n------------------------\n\n\n\n\n" if $DEBUG;
368 0           return $res;
369             }
370             1;