File Coverage

blib/lib/URI/Fetch.pm
Criterion Covered Total %
statement 114 115 99.1
branch 37 42 88.1
condition 17 27 62.9
subroutine 15 15 100.0
pod 1 1 100.0
total 184 200 92.0


line stmt bran cond sub pod time code
1             package URI::Fetch;
2             $URI::Fetch::VERSION = '0.13';
3 3     3   264813 use 5.008_001;
  3         693  
4 3     3   12 use strict;
  3         2  
  3         60  
5 3     3   17 use warnings;
  3         10  
  3         106  
6              
7 3     3   12 use base qw( Class::ErrorHandler );
  3         5  
  3         1250  
8              
9 3     3   2196 use LWP::UserAgent;
  3         91090  
  3         113  
10 3     3   18 use Carp qw( croak );
  3         4  
  3         124  
11 3     3   10 use URI;
  3         3  
  3         49  
12 3     3   1027 use URI::Fetch::Response;
  3         4  
  3         121  
13              
14             our $HAS_ZLIB;
15             BEGIN {
16 3     3   144 $HAS_ZLIB = eval "use Compress::Zlib (); 1;";
  3     3   1561  
  3         133071  
  3         28  
17             }
18              
19 3     3   17 use constant URI_OK => 200;
  3         3  
  3         130  
20 3     3   12 use constant URI_MOVED_PERMANENTLY => 301;
  3         4  
  3         102  
21 3     3   9 use constant URI_NOT_MODIFIED => 304;
  3         4  
  3         96  
22 3     3   8 use constant URI_GONE => 410;
  3         4  
  3         1648  
23              
24             sub fetch {
25 21     21 1 2688 my $class = shift;
26 21         70 my($uri, %param) = @_;
27              
28             # get user parameters
29 21         50 my $cache = delete $param{Cache};
30 21         46 my $ua = delete $param{UserAgent};
31 21         38 my $p_etag = delete $param{ETag};
32 21         34 my $p_lastmod = delete $param{LastModified};
33 21         56 my $content_hook = delete $param{ContentAlterHook};
34 21         30 my $p_no_net = delete $param{NoNetwork};
35 21         33 my $p_cache_grep = delete $param{CacheEntryGrep};
36 21         25 my $freeze = delete $param{Freeze};
37 21         28 my $thaw = delete $param{Thaw};
38 21         38 my $force = delete $param{ForceResponse};
39 21 50       84 croak("Unknown parameters: " . join(", ", keys %param))
40             if %param;
41              
42 21         32 my $ref;
43 21 100       62 if ($cache) {
44 13 100 66     62 unless ($freeze && $thaw) {
45 11         81 require Storable;
46 11         24 $thaw = \&Storable::thaw;
47 11         20 $freeze = \&Storable::freeze;
48             }
49 13 100       45 if (my $blob = $cache->get($uri)) {
50 6         41 $ref = $thaw->($blob);
51             }
52             }
53              
54             # NoNetwork support (see pod docs below for logic clarification)
55 21 100       315 if ($p_no_net) {
56 4 50       13 croak("Invalid NoNetworkValue (negative)") if $p_no_net < 0;
57 4 100 100     27 if ($ref && ($p_no_net == 1 || $ref->{CacheTime} > time() - $p_no_net)) {
      66        
58              
59 2         11 my $fetch = URI::Fetch::Response->new;
60 2         5 $fetch->status(URI_OK);
61 2         7 $fetch->content($ref->{Content});
62 2         6 $fetch->etag($ref->{ETag});
63 2         28 $fetch->last_modified($ref->{LastModified});
64 2         5 $fetch->content_type($ref->{ContentType});
65 2         8 return $fetch;
66             }
67 2 100       7 return undef if $p_no_net == 1;
68             }
69              
70 18   33     64 $ua ||= do {
71 18         137 my $ua = LWP::UserAgent->new;
72 18         7670 $ua->agent(join '/', $class, $class->VERSION);
73 18         715 $ua->env_proxy;
74 18         20193 $ua;
75             };
76              
77 18         119 my $req = HTTP::Request->new(GET => $uri);
78 18 50       12986 if ($HAS_ZLIB) {
79 18         77 $req->header('Accept-Encoding', 'gzip');
80             }
81 18 100 66     816 if (my $etag = ($p_etag || $ref->{ETag})) {
82 6         15 $req->header('If-None-Match', $etag);
83             }
84 18 100 66     216 if (my $ts = ($p_lastmod || $ref->{LastModified})) {
85 6         47 $req->if_modified_since($ts);
86             }
87              
88 18         389 my $res = $ua->request($req);
89 18         4843907 my $fetch = URI::Fetch::Response->new;
90 18         70 $fetch->uri($uri);
91 18         50 $fetch->http_status($res->code);
92 18         63 $fetch->http_response($res);
93 18         55 $fetch->content_type($res->header('Content-Type'));
94 18 100 66     44 if ($res->previous && $res->previous->code == HTTP::Status::RC_MOVED_PERMANENTLY()) {
    100          
    100          
    100          
95 1         53 $fetch->status(URI_MOVED_PERMANENTLY);
96 1         5 $fetch->uri($res->previous->header('Location'));
97             } elsif ($res->code == HTTP::Status::RC_GONE()) {
98 1         27 $fetch->status(URI_GONE);
99 1         3 $fetch->uri(undef);
100 1         27 return $fetch;
101             } elsif ($res->code == HTTP::Status::RC_NOT_MODIFIED()) {
102 7         159 $fetch->status(URI_NOT_MODIFIED);
103 7         30 $fetch->content($ref->{Content});
104 7         27 $fetch->etag($ref->{ETag});
105 7         28 $fetch->last_modified($ref->{LastModified});
106 7         23 $fetch->content_type($ref->{ContentType});
107 7         260 return $fetch;
108             } elsif (!$res->is_success) {
109 2 100       106 return $force ? $fetch : $class->error($res->message);
110            
111             } else {
112 7         310 $fetch->status(URI_OK);
113             }
114 8         83 $fetch->last_modified($res->last_modified);
115 8         32 $fetch->etag($res->header('ETag'));
116 8         38 my $content = $res->content;
117 8 50 33     110 if ($res->content_encoding && $res->content_encoding eq 'gzip') {
118 0         0 $content = Compress::Zlib::memGunzip($content);
119             }
120              
121             # let caller-defined transform hook modify the result that'll be
122             # cached. perhaps the caller only wants the section of
123             # HTML, or wants to change the content to a parsed datastructure
124             # already serialized with Storable.
125 8 100       232 if ($content_hook) {
126 1 50       4 croak("ContentAlterHook is not a subref") unless ref $content_hook eq "CODE";
127 1         5 $content_hook->(\$content);
128             }
129              
130 8         56 $fetch->content($content);
131              
132             # cache by default, if there's a cache. but let callers cancel
133             # the cache action by defining a cache grep hook
134 8 100 66     66 if ($cache &&
    100          
135             ($p_cache_grep ? $p_cache_grep->($fetch) : 1)) {
136              
137 5         25 $cache->set($uri, $freeze->({
138             ETag => $fetch->etag,
139             LastModified => $fetch->last_modified,
140             Content => $fetch->content,
141             CacheTime => time(),
142             ContentType => $fetch->content_type,
143             }));
144             }
145 8         685 $fetch;
146             }
147              
148             1;
149             __END__