File Coverage

blib/lib/URI/Fetch.pm
Criterion Covered Total %
statement 39 115 33.9
branch 0 42 0.0
condition 0 27 0.0
subroutine 14 15 93.3
pod 1 1 100.0
total 54 200 27.0


line stmt bran cond sub pod time code
1             package URI::Fetch;
2             $URI::Fetch::VERSION = '0.15';
3 1     1   866 use 5.008001;
  1         3  
4 1     1   13 use strict;
  1         2  
  1         20  
5 1     1   5 use warnings;
  1         2  
  1         27  
6              
7 1     1   5 use base qw( Class::ErrorHandler );
  1         2  
  1         561  
8              
9 1     1   1043 use LWP::UserAgent;
  1         46707  
  1         39  
10 1     1   7 use Carp qw( croak );
  1         2  
  1         49  
11 1     1   6 use URI;
  1         2  
  1         22  
12 1     1   505 use URI::Fetch::Response;
  1         2  
  1         51  
13              
14             our $HAS_ZLIB;
15             BEGIN {
16 1     1   51 $HAS_ZLIB = eval "use Compress::Zlib (); 1;";
  1     1   700  
  1         66268  
  1         12  
17             }
18              
19 1     1   8 use constant URI_OK => 200;
  1         2  
  1         59  
20 1     1   6 use constant URI_MOVED_PERMANENTLY => 301;
  1         2  
  1         50  
21 1     1   7 use constant URI_NOT_MODIFIED => 304;
  1         2  
  1         54  
22 1     1   12 use constant URI_GONE => 410;
  1         3  
  1         1195  
23              
24             sub fetch {
25 0     0 1   my $class = shift;
26 0           my($uri, %param) = @_;
27              
28             # get user parameters
29 0           my $cache = delete $param{Cache};
30 0           my $ua = delete $param{UserAgent};
31 0           my $p_etag = delete $param{ETag};
32 0           my $p_lastmod = delete $param{LastModified};
33 0           my $content_hook = delete $param{ContentAlterHook};
34 0           my $p_no_net = delete $param{NoNetwork};
35 0           my $p_cache_grep = delete $param{CacheEntryGrep};
36 0           my $freeze = delete $param{Freeze};
37 0           my $thaw = delete $param{Thaw};
38 0           my $force = delete $param{ForceResponse};
39 0 0         croak("Unknown parameters: " . join(", ", keys %param))
40             if %param;
41              
42 0           my $ref;
43 0 0         if ($cache) {
44 0 0 0       unless ($freeze && $thaw) {
45 0           require Storable;
46 0           $thaw = \&Storable::thaw;
47 0           $freeze = \&Storable::freeze;
48             }
49 0 0         if (my $blob = $cache->get($uri)) {
50 0           $ref = $thaw->($blob);
51             }
52             }
53              
54             # NoNetwork support (see pod docs below for logic clarification)
55 0 0         if ($p_no_net) {
56 0 0         croak("Invalid NoNetworkValue (negative)") if $p_no_net < 0;
57 0 0 0       if ($ref && ($p_no_net == 1 || $ref->{CacheTime} > time() - $p_no_net)) {
      0        
58              
59 0           my $fetch = URI::Fetch::Response->new;
60 0           $fetch->status(URI_OK);
61 0           $fetch->content($ref->{Content});
62 0           $fetch->etag($ref->{ETag});
63 0           $fetch->last_modified($ref->{LastModified});
64 0           $fetch->content_type($ref->{ContentType});
65 0           return $fetch;
66             }
67 0 0         return undef if $p_no_net == 1;
68             }
69              
70 0   0       $ua ||= do {
71 0           my $ua = LWP::UserAgent->new;
72 0           $ua->agent(join '/', $class, $class->VERSION);
73 0           $ua->env_proxy;
74 0           $ua;
75             };
76              
77 0           my $req = HTTP::Request->new(GET => $uri);
78 0 0         if ($HAS_ZLIB) {
79 0           $req->header('Accept-Encoding', 'gzip');
80             }
81 0 0 0       if (my $etag = ($p_etag || $ref->{ETag})) {
82 0           $req->header('If-None-Match', $etag);
83             }
84 0 0 0       if (my $ts = ($p_lastmod || $ref->{LastModified})) {
85 0           $req->if_modified_since($ts);
86             }
87              
88 0           my $res = $ua->request($req);
89 0           my $fetch = URI::Fetch::Response->new;
90 0           $fetch->uri($uri);
91 0           $fetch->http_status($res->code);
92 0           $fetch->http_response($res);
93 0           $fetch->content_type($res->header('Content-Type'));
94 0 0 0       if ($res->previous && $res->previous->code == HTTP::Status::RC_MOVED_PERMANENTLY()) {
    0          
    0          
    0          
95 0           $fetch->status(URI_MOVED_PERMANENTLY);
96 0           $fetch->uri($res->previous->header('Location'));
97             } elsif ($res->code == HTTP::Status::RC_GONE()) {
98 0           $fetch->status(URI_GONE);
99 0           $fetch->uri(undef);
100 0           return $fetch;
101             } elsif ($res->code == HTTP::Status::RC_NOT_MODIFIED()) {
102 0           $fetch->status(URI_NOT_MODIFIED);
103 0           $fetch->content($ref->{Content});
104 0           $fetch->etag($ref->{ETag});
105 0           $fetch->last_modified($ref->{LastModified});
106 0           $fetch->content_type($ref->{ContentType});
107 0           return $fetch;
108             } elsif (!$res->is_success) {
109 0 0         return $force ? $fetch : $class->error($res->message);
110            
111             } else {
112 0           $fetch->status(URI_OK);
113             }
114 0           $fetch->last_modified($res->last_modified);
115 0           $fetch->etag($res->header('ETag'));
116 0           my $content = $res->content;
117 0 0 0       if ($res->content_encoding && $res->content_encoding eq 'gzip') {
118 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 0 0         if ($content_hook) {
126 0 0         croak("ContentAlterHook is not a subref") unless ref $content_hook eq "CODE";
127 0           $content_hook->(\$content);
128             }
129              
130 0           $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 0 0 0       if ($cache &&
    0          
135             ($p_cache_grep ? $p_cache_grep->($fetch) : 1)) {
136              
137 0           $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 0           $fetch;
146             }
147              
148             1;
149             __END__