File Coverage

blib/lib/Web/Request.pm
Criterion Covered Total %
statement 100 105 95.2
branch 16 22 72.7
condition 5 6 83.3
subroutine 27 28 96.4
pod 13 14 92.8
total 161 175 92.0


line stmt bran cond sub pod time code
1             package Web::Request;
2             BEGIN {
3 23     23   684556 $Web::Request::AUTHORITY = 'cpan:DOY';
4             }
5             {
6             $Web::Request::VERSION = '0.11';
7             }
8 23     23   10271 use Moose;
  23         9492605  
  23         165  
9             # ABSTRACT: common request class for web frameworks
10              
11 23     23   170255 use Encode ();
  23         179460  
  23         494  
12 23     23   8300 use HTTP::Body ();
  23         651497  
  23         546  
13 23     23   173 use HTTP::Headers ();
  23         52  
  23         338  
14 23     23   8545 use HTTP::Message::PSGI ();
  23         156845  
  23         469  
15 23     23   196 use Module::Runtime ();
  23         53  
  23         305  
16 23     23   9214 use Stream::Buffered ();
  23         77233  
  23         1310  
17 23     23   284 use URI ();
  23         45  
  23         261  
18 23     23   95 use URI::Escape ();
  23         42  
  23         50029  
19              
20              
21             has env => (
22             traits => ['Hash'],
23             is => 'ro',
24             isa => 'HashRef',
25             required => 1,
26             handles => {
27             address => [ get => 'REMOTE_ADDR' ],
28             remote_host => [ get => 'REMOTE_HOST' ],
29             protocol => [ get => 'SERVER_PROTOCOL' ],
30             method => [ get => 'REQUEST_METHOD' ],
31             port => [ get => 'SERVER_PORT' ],
32             request_uri => [ get => 'REQUEST_URI' ],
33             path_info => [ get => 'PATH_INFO' ],
34             script_name => [ get => 'SCRIPT_NAME' ],
35             scheme => [ get => 'psgi.url_scheme' ],
36             _input => [ get => 'psgi.input' ],
37             content_length => [ get => 'CONTENT_LENGTH' ],
38             content_type => [ get => 'CONTENT_TYPE' ],
39             session => [ get => 'psgix.session' ],
40             session_options => [ get => 'psgix.session.options' ],
41             logger => [ get => 'psgix.logger' ],
42             },
43             );
44              
45             has _base_uri => (
46             is => 'ro',
47             isa => 'Str',
48             lazy => 1,
49             default => sub {
50             my $self = shift;
51              
52             my $env = $self->env;
53              
54             my $scheme = $self->scheme || "http";
55             my $server = $self->host;
56             my $path = $self->script_name || '/';
57              
58             return "${scheme}://${server}${path}";
59             },
60             );
61              
62             has base_uri => (
63             is => 'ro',
64             isa => 'URI',
65             lazy => 1,
66             default => sub { URI->new(shift->_base_uri)->canonical },
67             );
68              
69             has uri => (
70             is => 'ro',
71             isa => 'URI',
72             lazy => 1,
73             default => sub {
74             my $self = shift;
75              
76             my $base = $self->_base_uri;
77              
78             # We have to escape back PATH_INFO in case they include stuff
79             # like ? or # so that the URI parser won't be tricked. However
80             # we should preserve '/' since encoding them into %2f doesn't
81             # make sense. This means when a request like /foo%2fbar comes
82             # in, we recognize it as /foo/bar which is not ideal, but that's
83             # how the PSGI PATH_INFO spec goes and we can't do anything
84             # about it. See PSGI::FAQ for details.
85             my $path_escape_class = q{^/;:@&=A-Za-z0-9\$_.+!*'(),-};
86              
87             my $path = URI::Escape::uri_escape(
88             $self->path_info || '',
89             $path_escape_class
90             );
91             $path .= '?' . $self->env->{QUERY_STRING}
92             if defined $self->env->{QUERY_STRING}
93             && $self->env->{QUERY_STRING} ne '';
94              
95             $base =~ s!/$!! if $path =~ m!^/!;
96              
97             return URI->new($base . $path)->canonical;
98             },
99             );
100              
101             has headers => (
102             is => 'ro',
103             isa => 'HTTP::Headers',
104             lazy => 1,
105             default => sub {
106             my $self = shift;
107             my $env = $self->env;
108             return HTTP::Headers->new(
109             map {
110             (my $field = $_) =~ s/^HTTPS?_//;
111             $field => $env->{$_}
112             } grep {
113             /^(?:HTTP|CONTENT)/i
114             } keys %$env
115             );
116             },
117             handles => ['header', 'content_encoding', 'referer', 'user_agent'],
118             );
119              
120             has cookies => (
121             is => 'ro',
122             isa => 'HashRef',
123             lazy => 1,
124             default => sub {
125             my $self = shift;
126              
127             my $cookie_str = $self->env->{HTTP_COOKIE};
128             return {} unless defined $cookie_str;
129              
130             my %results;
131             for my $pair (grep { /=/ } split /[;,] ?/, $cookie_str) {
132             $pair =~ s/^\s+|\s+$//g;
133             my ($key, $value) = map {
134             URI::Escape::uri_unescape($_)
135             } split(/=/, $pair, 2);
136             # XXX $self->decode too?
137             $results{$key} = $value unless exists $results{$key};
138             }
139              
140             return \%results;
141             },
142             );
143              
144             has _http_body => (
145             is => 'rw',
146             isa => 'HTTP::Body',
147             );
148              
149             has _parsed_body => (
150             traits => ['Hash'],
151             is => 'ro',
152             isa => 'HashRef',
153             lazy => 1,
154             default => sub {
155             my $self = shift;
156              
157             my $ct = $self->content_type;
158             my $cl = $self->content_length;
159             if (!$ct && !$cl) {
160             if (!$self->env->{'psgix.input.buffered'}) {
161             $self->env->{'psgix.input.buffered'} = 1;
162             $self->env->{'psgi.input'} = Stream::Buffered->new(0)->rewind;
163             }
164             return {
165             body => {},
166             uploads => {},
167             };
168             }
169              
170             my $body = HTTP::Body->new($ct, $cl);
171             # automatically clean up, but wait until the request object is gone
172             $body->cleanup(1);
173             $self->_http_body($body);
174              
175             my $input = $self->_input;
176              
177             my $buffer;
178             if ($self->env->{'psgix.input.buffered'}) {
179             $input->seek(0, 0);
180             }
181             else {
182             $buffer = Stream::Buffered->new($cl);
183             }
184              
185             my $spin = 0;
186             while ($cl) {
187             $input->read(my $chunk, $cl < 8192 ? $cl : 8192);
188             my $read = length($chunk);
189             $cl -= $read;
190             $body->add($chunk);
191             $buffer->print($chunk) if $buffer;
192              
193             if ($read == 0 && $spin++ > 2000) {
194             confess "Bad Content-Length ($cl bytes remaining)";
195             }
196             }
197              
198             if ($buffer) {
199             $self->env->{'psgix.input.buffered'} = 1;
200             $self->env->{'psgi.input'} = $buffer->rewind;
201             }
202             else {
203             $input->seek(0, 0);
204             }
205              
206             return {
207             body => $body->param,
208             uploads => $body->upload,
209             }
210             },
211             handles => {
212             _body => [ get => 'body' ],
213             _uploads => [ get => 'uploads' ],
214             },
215             );
216              
217             has query_parameters => (
218             is => 'ro',
219             isa => 'HashRef[Str]',
220             lazy => 1,
221             clearer => '_clear_query_parameters',
222             default => sub {
223             my $self = shift;
224              
225             my %params = (
226             $self->uri->query_form,
227             (map { $_ => '' } $self->uri->query_keywords),
228             );
229             return {
230             map { $self->_decode($_) } map { $_ => $params{$_} } keys %params
231             };
232             },
233             );
234              
235             has all_query_parameters => (
236             is => 'ro',
237             isa => 'HashRef[ArrayRef[Str]]',
238             lazy => 1,
239             clearer => '_clear_all_query_parameters',
240             default => sub {
241             my $self = shift;
242              
243             my @params = $self->uri->query_form;
244             my $ret = {};
245              
246             while (my ($k, $v) = splice @params, 0, 2) {
247             $k = $self->_decode($k);
248             push @{ $ret->{$k} ||= [] }, $self->_decode($v);
249             }
250              
251             return $ret;
252             },
253             );
254              
255             has body_parameters => (
256             is => 'ro',
257             isa => 'HashRef[Str]',
258             lazy => 1,
259             clearer => '_clear_body_parameters',
260             default => sub {
261             my $self = shift;
262              
263             my $body = $self->_body;
264              
265             my $ret = {};
266             for my $key (keys %$body) {
267             my $val = $body->{$key};
268             $key = $self->_decode($key);
269             $ret->{$key} = $self->_decode(ref($val) ? $val->[-1] : $val);
270             }
271              
272             return $ret;
273             },
274             );
275              
276             has all_body_parameters => (
277             is => 'ro',
278             isa => 'HashRef[ArrayRef[Str]]',
279             lazy => 1,
280             clearer => '_clear_all_body_parameters',
281             default => sub {
282             my $self = shift;
283              
284             my $body = $self->_body;
285              
286             my $ret = {};
287             for my $key (keys %$body) {
288             my $val = $body->{$key};
289             $key = $self->_decode($key);
290             $ret->{$key} = ref($val)
291             ? [ map { $self->_decode($_) } @$val ]
292             : [ $self->_decode($val) ];
293             }
294              
295             return $ret;
296             },
297             );
298              
299             has uploads => (
300             is => 'ro',
301             isa => 'HashRef[Web::Request::Upload]',
302             lazy => 1,
303             default => sub {
304             my $self = shift;
305              
306             my $uploads = $self->_uploads;
307              
308             my $ret = {};
309             for my $key (keys %$uploads) {
310             my $val = $uploads->{$key};
311             $ret->{$key} = ref($val) eq 'ARRAY'
312             ? $self->_new_upload($val->[-1])
313             : $self->_new_upload($val);
314             }
315              
316             return $ret;
317             },
318             );
319              
320             has all_uploads => (
321             is => 'ro',
322             isa => 'HashRef[ArrayRef[Web::Request::Upload]]',
323             lazy => 1,
324             default => sub {
325             my $self = shift;
326              
327             my $uploads = $self->_uploads;
328              
329             my $ret = {};
330             for my $key (keys %$uploads) {
331             my $val = $uploads->{$key};
332             $ret->{$key} = ref($val) eq 'ARRAY'
333             ? [ map { $self->_new_upload($_) } @$val ]
334             : [ $self->_new_upload($val) ];
335             }
336              
337             return $ret;
338             },
339             );
340              
341             has _encoding_obj => (
342             is => 'rw',
343             isa => 'Object', # no idea what this should be
344             clearer => '_clear_encoding_obj',
345             predicate => 'has_encoding',
346             );
347              
348             sub BUILD {
349 71     71 0 145 my $self = shift;
350 71         157 my ($params) = @_;
351 71 50       209 if (defined $params->{encoding}) {
352 0         0 $self->encoding($params->{encoding});
353             }
354             else {
355 71         224 $self->encoding($self->default_encoding);
356             }
357             }
358              
359             sub new_from_env {
360 71     71 1 512810 my $class = shift;
361 71         181 my ($env) = @_;
362              
363 71         1996 return $class->new(env => $env);
364             }
365              
366             sub new_from_request {
367 1     1 1 6054 my $class = shift;
368 1         4 my ($req) = @_;
369              
370 1         14 return $class->new_from_env(HTTP::Message::PSGI::req_to_psgi($req));
371             }
372              
373             sub new_response {
374 32     32 1 3236 my $self = shift;
375              
376 32         99 Module::Runtime::use_package_optimistically($self->response_class);
377 32         1352 my $res = $self->response_class->new(@_);
378 32 100       852 $res->_encoding_obj($self->_encoding_obj)
379             if $self->has_encoding;
380 32         128 return $res;
381             }
382              
383             sub _new_upload {
384 13     13   24 my $self = shift;
385              
386 13         35 Module::Runtime::use_package_optimistically($self->upload_class);
387 13         521 $self->upload_class->new(@_);
388             }
389              
390             sub host {
391 44     44 1 76 my $self = shift;
392              
393 44         906 my $env = $self->env;
394 44         86 my $host = $env->{HTTP_HOST};
395             $host = ($env->{SERVER_NAME} || '') . ':'
396 44 100 100     160 . ($env->{SERVER_PORT} || 80)
      100        
397             unless defined $host;
398              
399 44         95 return $host;
400             }
401              
402             sub path {
403 0     0 1 0 my $self = shift;
404              
405 0         0 my $path = $self->path_info;
406 0 0       0 return $path if length($path);
407 0         0 return '/';
408             }
409              
410             sub parameters {
411 14     14 1 573 my $self = shift;
412              
413             return {
414 14         398 %{ $self->query_parameters },
415 14         55 %{ $self->body_parameters },
  14         325  
416             };
417             }
418              
419             sub all_parameters {
420 3     3 1 6 my $self = shift;
421              
422 3         6 my $ret = { %{ $self->all_query_parameters } };
  3         79  
423 3         91 my $body_parameters = $self->all_body_parameters;
424              
425 3         8 for my $key (keys %$body_parameters) {
426 2   50     5 push @{ $ret->{$key} ||= [] }, @{ $body_parameters->{$key} };
  2         9  
  2         6  
427             }
428              
429 3         18 return $ret;
430             }
431              
432             sub param {
433 3     3 1 6 my $self = shift;
434 3         8 my ($key) = @_;
435              
436 3         8 $self->parameters->{$key};
437             }
438              
439             sub content {
440 11     11 1 334 my $self = shift;
441              
442 11 100       245 unless ($self->env->{'psgix.input.buffered'}) {
443             # the builder for this attribute also sets up psgi.input
444 5         115 $self->_parsed_body;
445             }
446              
447 11 50       267 my $fh = $self->_input or return '';
448 11 100       297 my $cl = $self->content_length or return '';
449              
450 9         46 $fh->seek(0, 0); # just in case middleware/apps read it without seeking back
451              
452 9         95 $fh->read(my $content, $cl, 0);
453 9         73 $fh->seek(0, 0);
454              
455 9         47 return $self->_decode($content);
456             }
457              
458             sub _decode {
459 84     84   141 my $self = shift;
460 84         157 my ($content) = @_;
461 84 50       2090 return $content unless $self->has_encoding;
462 84         1746 return $self->_encoding_obj->decode($content);
463             }
464              
465             sub encoding {
466 87     87 1 12150 my $self = shift;
467              
468 87 50       261 if (@_ > 0) {
469 87         183 my ($encoding) = @_;
470 87         269 $self->_clear_encoded_data;
471 87 100       240 if (defined($encoding)) {
472 86         379 $self->_encoding_obj(Encode::find_encoding($encoding));
473             }
474             else {
475 1         24 $self->_clear_encoding_obj;
476             }
477             }
478              
479 87 100       1918 return $self->_encoding_obj ? $self->_encoding_obj->name : undef;
480             }
481              
482             sub _clear_encoded_data {
483 87     87   140 my $self = shift;
484 87         2598 $self->_clear_encoding_obj;
485 87         2318 $self->_clear_query_parameters;
486 87         2347 $self->_clear_all_query_parameters;
487 87         2316 $self->_clear_body_parameters;
488 87         2423 $self->_clear_all_body_parameters;
489             }
490              
491 64     64 1 1028 sub response_class { 'Web::Response' }
492 26     26 1 372 sub upload_class { 'Web::Request::Upload' }
493 71     71 1 242 sub default_encoding { 'iso8859-1' }
494              
495             __PACKAGE__->meta->make_immutable;
496 23     23   202 no Moose;
  23         46  
  23         2145  
497              
498              
499              
500             1;
501              
502             __END__
503              
504             =pod
505              
506             =head1 NAME
507              
508             Web::Request - common request class for web frameworks
509              
510             =head1 VERSION
511              
512             version 0.11
513              
514             =head1 SYNOPSIS
515              
516             use Web::Request;
517              
518             my $app = sub {
519             my ($env) = @_;
520             my $req = Web::Request->new_from_env($env);
521             # ...
522             };
523              
524             =head1 DESCRIPTION
525              
526             Web::Request is a request class for L<PSGI> applications. It provides access to
527             all of the information received in a request, generated from the PSGI
528             environment. The available methods are listed below.
529              
530             Note that Web::Request objects are intended to be (almost) entirely read-only -
531             although some methods (C<headers>, C<uri>, etc) may return mutable objects,
532             changing those objects will have no effect on the actual environment, or the
533             return values of any of the other methods. Doing this is entirely unsupported.
534             In addition, the return values of most methods that aren't direct accesses to
535             C<env> are cached, so if you do modify the actual environment hashref, you
536             should create a new Web::Request object for it.
537              
538             The one exception is the C<encoding> attribute, which is allowed to be
539             modified. Changing the encoding will change the return value of any subsequent
540             calls to C<content>, C<query_parameters>, C<all_query_parameters>,
541             C<body_parameters>, and C<all_body_parameters>.
542              
543             Web::Request is based heavily on L<Plack::Request>, but with the intention of
544             growing to become more generally useful to end users (rather than just
545             framework and middleware developers). In the future, it is expected to grow in
546             functionality to support a lot more convenient functionality, while
547             Plack::Request has a more minimalist goal.
548              
549             =head1 METHODS
550              
551             =head2 address
552              
553             Returns the IP address of the remote client.
554              
555             =head2 remote_host
556              
557             Returns the hostname of the remote client. May be empty.
558              
559             =head2 protocol
560              
561             Returns the protocol (HTTP/1.0, HTTP/1.1, etc.) used in the current request.
562              
563             =head2 method
564              
565             Returns the HTTP method (GET, POST, etc.) used in the current request.
566              
567             =head2 port
568              
569             Returns the local port that this request was made on.
570              
571             =head2 host
572              
573             Returns the contents of the HTTP C<Host> header. If it doesn't exist, falls
574             back to recreating the host from the C<SERVER_NAME> and C<SERVER_PORT>
575             variables.
576              
577             =head2 path
578              
579             Returns the request path for the current request. Unlike C<path_info>, this
580             will never be empty, it will always start with C</>. This is most likely what
581             you want to use to dispatch on.
582              
583             =head2 path_info
584              
585             Returns the request path for the current request. This can be C<''> if
586             C<script_name> ends in a C</>. This can be appended to C<script_name> to get
587             the full (absolute) path that was requested from the server.
588              
589             =head2 script_name
590              
591             Returns the absolute path where your application is mounted. It may be C<''>
592             (in which case, C<path_info> will start with a C</>).
593              
594             =head2 request_uri
595              
596             Returns the raw, undecoded URI path (the literal path provided in the request,
597             so C</foo%20bar> in C<GET /foo%20bar HTTP/1.1>). You most likely want to use
598             C<path>, C<path_info>, or C<script_name> instead.
599              
600             =head2 scheme
601              
602             Returns C<http> or C<https> depending on the scheme used in the request.
603              
604             =head2 session
605              
606             Returns the session object, if a middleware is used which provides one. See
607             L<PSGI::Extensions>.
608              
609             =head2 session_options
610              
611             Returns the session options hashref, if a middleware is used which provides
612             one. See L<PSGI::Extensions>.
613              
614             =head2 logger
615              
616             Returns the logger object, if a middleware is used which provides one. See
617             L<PSGI::Extensions>.
618              
619             =head2 uri
620              
621             Returns the full URI used in the current request, as a L<URI> object.
622              
623             =head2 base_uri
624              
625             Returns the base URI for the current request (only the components up through
626             C<script_name>) as a L<URI> object.
627              
628             =head2 headers
629              
630             Returns a L<HTTP::Headers> object containing the headers for the current
631             request.
632              
633             =head2 content_length
634              
635             The length of the content, in bytes. Corresponds to the C<Content-Length>
636             header.
637              
638             =head2 content_type
639              
640             The MIME type of the content. Corresponds to the C<Content-Type> header.
641              
642             =head2 content_encoding
643              
644             The encoding of the content. Corresponds to the C<Content-Encoding> header.
645              
646             =head2 referer
647              
648             Returns the value of the C<Referer> header.
649              
650             =head2 user_agent
651              
652             Returns the value of the C<User-Agent> header.
653              
654             =head2 header($name)
655              
656             Shortcut for C<< $req->headers->header($name) >>.
657              
658             =head2 cookies
659              
660             Returns a hashref of cookies received in this request. The values are URI
661             decoded.
662              
663             =head2 content
664              
665             Returns the content received in this request, decoded based on the value of
666             C<encoding>.
667              
668             =head2 param($param)
669              
670             Returns the parameter value for the parameter named C<$param>. Returns the last
671             parameter given if more than one are passed.
672              
673             =head2 parameters
674              
675             Returns a hashref of parameter names to values. If a name is given more than
676             once, the last value is provided.
677              
678             =head2 all_parameters
679              
680             Returns a hashref where the keys are parameter names and the values are
681             arrayrefs holding every value given for that parameter name. All parameters are
682             stored in an arrayref, even if there is only a single value.
683              
684             =head2 query_parameters
685              
686             Like C<parameters>, but only return the parameters that were given in the query
687             string.
688              
689             =head2 all_query_parameters
690              
691             Like C<all_parameters>, but only return the parameters that were given in the
692             query string.
693              
694             =head2 body_parameters
695              
696             Like C<parameters>, but only return the parameters that were given in the
697             request body.
698              
699             =head2 all_body_parameters
700              
701             Like C<all_parameters>, but only return the parameters that were given in the
702             request body.
703              
704             =head2 uploads
705              
706             Returns a hashref of upload objects (instances of C<upload_class>). If more
707             than one upload is provided with a given name, returns the last one given.
708              
709             =head2 all_uploads
710              
711             Returns a hashref where the keys are upload names and the values are arrayrefs
712             holding an upload object (instance of C<upload_class>) for every upload given
713             for that name. All uploads are stored in an arrayref, even if there is only a
714             single value.
715              
716             =head2 new_response(@params)
717              
718             Returns a new response object, passing C<@params> to its constructor.
719              
720             =head2 env
721              
722             Returns the L<PSGI> environment that was provided in the constructor (or
723             generated from the L<HTTP::Request>, if C<new_from_request> was used).
724              
725             =head2 encoding($enc)
726              
727             Returns the encoding that was provided in the constructor. You can also pass an
728             encoding name to this method to set the encoding that will be used to decode
729             the content and encode the response. For instance, you can set the encoding to
730             UTF-8 in order to read the body content and parameters, and then set the
731             encoding to C<undef> at the end of the handler in order to indicate that the
732             response should not be encoded (for instance, if it is a binary file).
733              
734             =head2 response_class
735              
736             Returns the name of the class to use when creating a new response object via
737             C<new_response>. Defaults to L<Web::Response>. This can be overridden in
738             a subclass.
739              
740             =head2 upload_class
741              
742             Returns the name of the class to use when creating a new upload object for
743             C<uploads> or C<all_uploads>. Defaults to L<Web::Request::Upload>. This can be
744             overridden in a subclass.
745              
746             =head2 default_encoding
747              
748             Returns the name of the default encoding to use for decoding. Defaults to
749             iso8859-1. This can be overridden in a subclass.
750              
751             =head1 CONSTRUCTORS
752              
753             =head2 new_from_env($env)
754              
755             Create a new Web::Request object from a L<PSGI> environment hashref.
756              
757             =head2 new_from_request($request)
758              
759             Create a new Web::Request object from a L<HTTP::Request> object.
760              
761             =head2 new(%params)
762              
763             Create a new Web::Request object with named parameters. Valid parameters are:
764              
765             =over 4
766              
767             =item env
768              
769             A L<PSGI> environment hashref. Required.
770              
771             =item encoding
772              
773             The encoding to use for decoding all input in the request and encoding all
774             output in the response. Defaults to the value of C<default_encoding>. If
775             C<undef> is passed, no encoding or decoding will be done.
776              
777             =back
778              
779             =head1 BUGS
780              
781             No known bugs.
782              
783             Please report any bugs through RT: email
784             C<bug-web-request at rt.cpan.org>, or browse to
785             L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Web-Request>.
786              
787             =head1 SEE ALSO
788              
789             L<Plack::Request> - Much of this module's API and implementation were taken
790             from Plack::Request.
791              
792             =head1 SUPPORT
793              
794             You can find this documentation for this module with the perldoc command.
795              
796             perldoc Web::Request
797              
798             You can also look for information at:
799              
800             =over 4
801              
802             =item * MetaCPAN
803              
804             L<https://metacpan.org/release/Web-Request>
805              
806             =item * Github
807              
808             L<https://github.com/doy/web-request>
809              
810             =item * RT: CPAN's request tracker
811              
812             L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Web-Request>
813              
814             =item * CPAN Ratings
815              
816             L<http://cpanratings.perl.org/d/Web-Request>
817              
818             =back
819              
820             =for Pod::Coverage BUILD
821              
822             =head1 AUTHOR
823              
824             Jesse Luehrs <doy@tozt.net>
825              
826             =head1 COPYRIGHT AND LICENSE
827              
828             This software is copyright (c) 2013 by Jesse Luehrs.
829              
830             This is free software; you can redistribute it and/or modify it under
831             the same terms as the Perl 5 programming language system itself.
832              
833             =cut