File Coverage

blib/lib/HTTP/Simple.pm
Criterion Covered Total %
statement 96 97 98.9
branch 41 46 89.1
condition 18 18 100.0
subroutine 27 27 100.0
pod 15 15 100.0
total 197 203 97.0


line stmt bran cond sub pod time code
1             package HTTP::Simple;
2              
3 2     2   134436 use strict;
  2         21  
  2         56  
4 2     2   9 use warnings;
  2         9  
  2         60  
5 2     2   9 use Carp 'croak';
  2         4  
  2         84  
6 2     2   10 use Exporter 'import';
  2         3  
  2         75  
7 2     2   11 use File::Basename 'dirname';
  2         21  
  2         167  
8 2     2   1596 use File::Temp;
  2         43530  
  2         136  
9 2     2   1563 use HTTP::Tiny;
  2         82822  
  2         81  
10 2     2   1471 use JSON::PP;
  2         27797  
  2         3092  
11              
12             our $VERSION = '0.003';
13              
14             my @request_functions = qw(get getjson head getprint getstore mirror postform postjson postfile);
15             my @status_functions = qw(is_info is_success is_redirect is_error is_client_error is_server_error);
16             our @EXPORT = (@request_functions, @status_functions);
17             our %EXPORT_TAGS = (
18             all => [@request_functions, @status_functions],
19             request => \@request_functions,
20             status => \@status_functions,
21             );
22              
23             our $UA = HTTP::Tiny->new(agent => "HTTP::Simple/$VERSION");
24             our $JSON = JSON::PP->new->utf8->canonical->allow_nonref->convert_blessed;
25              
26             sub get {
27 3     3 1 2328 my ($url) = @_;
28 3         9 my $res = $UA->get($url);
29 3 100       35 return $res->{content} if $res->{success};
30 2 100       106 croak $res->{content} if $res->{status} == 599;
31 1         214 croak "$res->{status} $res->{reason}";
32             }
33              
34             sub getjson {
35 7     7 1 517 my ($url) = @_;
36 7         24 my $res = $UA->get($url);
37 7 100       58 return ref($JSON) ? $JSON->decode($res->{content}) : _load_function($JSON, 'decode_json')->($res->{content}) if $res->{success};
    100          
38 2 100       107 croak $res->{content} if $res->{status} == 599;
39 1         94 croak "$res->{status} $res->{reason}";
40             }
41              
42             sub head {
43 3     3 1 1059 my ($url) = @_;
44 3         10 my $res = $UA->head($url);
45 3 100       35 return $res->{headers} if $res->{success};
46 2 100       93 croak $res->{content} if $res->{status} == 599;
47 1         94 croak "$res->{status} $res->{reason}";
48             }
49              
50             sub getprint {
51 3     3 1 2607 my ($url) = @_;
52 3     2   27 my $res = $UA->get($url, {data_callback => sub { print $_[0] }});
  2         16  
53 3 100       130 croak $res->{content} if $res->{status} == 599;
54 2         11 return $res->{status};
55             }
56              
57             sub getstore {
58 2     2 1 3137 my ($url, $file) = @_;
59 2         153 my $temp = File::Temp->new(DIR => dirname $file);
60 2     2   860 my $res = $UA->get($url, {data_callback => sub { print {$temp} $_[0] }});
  2         7  
  2         177  
61 2 50       24 croak $res->{content} if $res->{status} == 599;
62 2 50       69 close $temp or croak "Failed to close $temp: $!";
63 2 50       12 rename $temp->filename, $file or croak "Failed to rename $temp to $file: $!";
64 2         237 $temp->unlink_on_destroy(0);
65 2         35 return $res->{status};
66             }
67              
68             sub mirror {
69 3     3 1 12 my ($url, $file) = @_;
70 3         12 my $res = $UA->mirror($url, $file);
71 3 100       28 return $res->{status} if $res->{success};
72 2 100       99 croak $res->{content} if $res->{status} == 599;
73 1         97 croak "$res->{status} $res->{reason}";
74             }
75              
76             sub postform {
77 3     3 1 10 my ($url, $form) = @_;
78 3         10 my $res = $UA->post_form($url, $form);
79 3 100       29 return $res->{content} if $res->{success};
80 2 100       104 croak $res->{content} if $res->{status} == 599;
81 1         101 croak "$res->{status} $res->{reason}";
82             }
83              
84             sub postjson {
85 4     4 1 1001 my ($url, $data) = @_;
86 4         8 my %options;
87 4         14 $options{headers} = {'Content-Type' => 'application/json; charset=UTF-8'};
88 4 100       19 $options{content} = ref($JSON) ? $JSON->encode($data) : _load_function($JSON, 'encode_json')->($data);
89 4         471 my $res = $UA->post($url, \%options);
90 4 100       38 return $res->{content} if $res->{success};
91 2 100       106 croak $res->{content} if $res->{status} == 599;
92 1         98 croak "$res->{status} $res->{reason}";
93             }
94              
95             sub postfile {
96 5     5 1 474 my ($url, $file, $content_type) = @_;
97 5 50       217 open my $fh, '<:raw', $file or croak "Failed to open $file: $!";
98 5         15 my %options;
99 5 50       20 $options{headers} = {'Content-Type' => $content_type} if defined $content_type;
100 5         13 my $chunk = 131072;
101 5     13   36 $options{content} = sub { my $buffer; sysread $fh, $buffer, $chunk; $buffer };
  13         89  
  13         575  
  13         64  
102 5         28 my $res = $UA->post($url, \%options);
103 5 100       135 return $res->{content} if $res->{success};
104 2 100       125 croak $res->{content} if $res->{status} == 599;
105 1         137 croak "$res->{status} $res->{reason}";
106             }
107              
108 104   100 104 1 28676 sub is_info { !!($_[0] >= 100 && $_[0] < 200) }
109 104   100 104 1 28715 sub is_success { !!($_[0] >= 200 && $_[0] < 300) }
110 104   100 104 1 28909 sub is_redirect { !!($_[0] >= 300 && $_[0] < 400) }
111 205   100 205 1 56332 sub is_error { !!($_[0] >= 400 && $_[0] < 600) }
112 104   100 104 1 28689 sub is_client_error { !!($_[0] >= 400 && $_[0] < 500) }
113 104   100 104 1 28947 sub is_server_error { !!($_[0] >= 500 && $_[0] < 600) }
114              
115             sub _load_function {
116 5     5   10 my ($module, $function) = @_;
117 5         43 my $code = $module->can($function);
118 5 100       20 return $code if defined $code;
119 2         12 (my $path = $module) =~ s{::}{/}g;
120 2         377 require "$path.pm";
121 0           return $module->can($function);
122             }
123              
124             1;
125              
126             =head1 NAME
127              
128             HTTP::Simple - Simple procedural interface to HTTP::Tiny
129              
130             =head1 SYNOPSIS
131              
132             perl -MHTTP::Simple -e'getprint(shift)' 'https://example.com'
133              
134             use HTTP::Simple;
135              
136             my $content = get 'https://example.com';
137              
138             if (mirror('https://example.com', '/path/to/file.html') == 304) { ... }
139              
140             if (is_success(getprint 'https://example.com')) { ... }
141              
142             postform('https://example.com', {foo => ['bar', 'baz']});
143              
144             postjson('https://example.com', [{bar => 'baz'}]);
145              
146             postfile('https://example.com', '/path/to/file.png');
147              
148             =head1 DESCRIPTION
149              
150             This module is a wrapper of L that provides simplified functions
151             for performing HTTP requests in a similar manner to L, but with
152             slightly more useful error handling. For full control of the request process
153             and response handling, use L directly.
154              
155             L is required for HTTPS requests with L.
156              
157             Request methods that return the body content of the response will return a byte
158             string suitable for directly printing, but that may need to be
159             L for text operations.
160              
161             The L object used by these functions to make requests can be
162             accessed as C<$HTTP::Simple::UA> (for example, to configure the timeout, or
163             replace it with a compatible object like L).
164              
165             The JSON encoder used by the JSON functions defaults to a L instance,
166             and can be accessed as C<$HTTP::Simple::JSON>. If replaced with a new object,
167             it should have UTF-8 encoding/decoding enabled (usually the C option). If
168             it is set to a string, it will be used as a module name that is expected to
169             have C and C functions.
170              
171             =head1 FUNCTIONS
172              
173             All functions are exported by default. Functions can also be requested
174             individually or with the tags C<:request>, C<:status>, or C<:all>.
175              
176             =head2 get
177              
178             my $contents = get($url);
179              
180             Retrieves the document at the given URL with a GET request and returns it as a
181             byte string. Throws an exception on connection or HTTP errors.
182              
183             =head2 getjson
184              
185             my $data = getjson($url);
186              
187             Retrieves the JSON document at the given URL with a GET request and decodes it
188             from JSON to a Perl structure. Throws an exception on connection, HTTP, or JSON
189             errors.
190              
191             =head2 head
192              
193             my $headers = head($url);
194              
195             Retrieves the headers at the given URL with a HEAD request and returns them as
196             a hash reference. Header field names are normalized to lower case, and values
197             may be an array reference if the header is repeated. Throws an exception on
198             connection or HTTP errors.
199              
200             =head2 getprint
201              
202             my $status = getprint($url);
203              
204             Retrieves the document at the given URL with a GET request and prints it as it
205             is received. Returns the HTTP status code. Throws an exception on connection
206             errors.
207              
208             =head2 getstore
209              
210             my $status = getstore($url, $path);
211              
212             Retrieves the document at the given URL with a GET request and stores it to the
213             given file path. Returns the HTTP status code. Throws an exception on
214             connection or filesystem errors.
215              
216             =head2 mirror
217              
218             my $status = mirror($url, $path);
219              
220             Retrieves the document at the given URL with a GET request and mirrors it to
221             the given file path, using the C headers to short-circuit if
222             the file exists and is new enough, and the C header to set its
223             modification time. Returns the HTTP status code. Throws an exception on
224             connection, HTTP, or filesystem errors.
225              
226             =head2 postform
227              
228             my $contents = postform($url, $form);
229              
230             Sends a POST request to the given URL with the given hash or array reference of
231             form data serialized to C. Returns the
232             response body as a byte string. Throws an exception on connection or HTTP
233             errors.
234              
235             =head2 postjson
236              
237             my $contents = postjson($url, $data);
238              
239             Sends a POST request to the given URL with the given data structure encoded to
240             JSON. Returns the response body as a byte string. Throws an exception on
241             connection, HTTP, or JSON errors.
242              
243             =head2 postfile
244              
245             my $contents = postfile($url, $path);
246             my $contents = postfile($url, $path, $content_type);
247              
248             Sends a POST request to the given URL, streaming the contents of the given
249             file. The content type is passed as C if not
250             specified. Returns the response body as a byte string. Throws an exception on
251             connection, HTTP, or filesystem errors.
252              
253             =head2 is_info
254              
255             my $bool = is_info($status);
256              
257             Returns true if the status code indicates an informational response (C<1xx>).
258              
259             =head2 is_success
260              
261             my $bool = is_success($status);
262              
263             Returns true if the status code indicates a successful response (C<2xx>).
264              
265             =head2 is_redirect
266              
267             my $bool = is_redirect($status);
268              
269             Returns true if the status code indicates a redirection response (C<3xx>).
270              
271             =head2 is_error
272              
273             my $bool = is_error($status);
274              
275             Returns true if the status code indicates an error response (C<4xx> or C<5xx>).
276              
277             =head2 is_client_error
278              
279             my $bool = is_client_error($status);
280              
281             Returns true if the status code indicates a client error response (C<4xx>).
282              
283             =head2 is_server_error
284              
285             my $bool = is_server_error($status);
286              
287             Returns true if the status code indicates a server error response (C<5xx>).
288              
289             =head1 BUGS
290              
291             Report any issues on the public bugtracker.
292              
293             =head1 AUTHOR
294              
295             Dan Book
296              
297             =head1 COPYRIGHT AND LICENSE
298              
299             This software is Copyright (c) 2019 by Dan Book.
300              
301             This is free software, licensed under:
302              
303             The Artistic License 2.0 (GPL Compatible)
304              
305             =head1 SEE ALSO
306              
307             L, L, L