updated contributor list
[catagits/Web-Simple.git] / lib / Web / Simple.pm
CommitLineData
5c33dda5 1package Web::Simple;
2
8bd060f4 3use strictures 1;
8c4ffad3 4use 5.008;
8bd060f4 5use warnings::illegalproto ();
876e62e1 6use Moo ();
7use Web::Dispatch::Wrapper ();
8c4ffad3 8
b1217ca2 9our $VERSION = '0.009';
5c33dda5 10
44db8e76 11sub import {
5c33dda5 12 my ($class, $app_package) = @_;
876e62e1 13 $app_package ||= caller;
14 $class->_export_into($app_package);
15 eval "package $app_package; use Web::Dispatch::Wrapper; use Moo; 1"
16 or die "Failed to setup app package: $@";
445b3ea0 17 strictures->import;
8bd060f4 18 warnings::illegalproto->unimport;
5c33dda5 19}
20
21sub _export_into {
22 my ($class, $app_package) = @_;
23 {
24 no strict 'refs';
c7b1c57f 25 *{"${app_package}::PSGI_ENV"} = sub () { -1 };
5c33dda5 26 require Web::Simple::Application;
27 unshift(@{"${app_package}::ISA"}, 'Web::Simple::Application');
28 }
b7063124 29 (my $name = $app_package) =~ s/::/\//g;
30 $INC{"${name}.pm"} = 'Set by "use Web::Simple;" invocation';
5c33dda5 31}
32
7401408e 33=head1 NAME
34
35Web::Simple - A quick and easy way to build simple web applications
36
7401408e 37
38=head1 SYNOPSIS
39
05ad188d 40 #!/usr/bin/env perl
7401408e 41
42 use Web::Simple 'HelloWorld';
43
44 {
45 package HelloWorld;
46
445b3ea0 47 sub dispatch_request {
7401408e 48 sub (GET) {
49 [ 200, [ 'Content-type', 'text/plain' ], [ 'Hello world!' ] ]
50 },
51 sub () {
52 [ 405, [ 'Content-type', 'text/plain' ], [ 'Method not allowed' ] ]
53 }
445b3ea0 54 }
7401408e 55 }
56
57 HelloWorld->run_if_script;
58
05ad188d 59If you save this file into your cgi-bin as C<hello-world.cgi> and then visit:
7401408e 60
61 http://my.server.name/cgi-bin/hello-world.cgi/
62
63you'll get the "Hello world!" string output to your browser. For more complex
6a4808bf 64examples and non-CGI deployment, see below. To get help with L<Web::Simple>,
8c4ffad3 65please connect to the irc.perl.org IRC network and join #web-simple.
7401408e 66
fb771406 67=head1 DESCRIPTION
7401408e 68
6a4808bf 69The philosophy of L<Web::Simple> is to keep to an absolute bare minimum for
7401408e 70everything. It is not designed to be used for large scale applications;
71the L<Catalyst> web framework already works very nicely for that and is
72a far more mature, well supported piece of software.
73
74However, if you have an application that only does a couple of things, and
3895385d 75want to not have to think about complexities of deployment, then L<Web::Simple>
7401408e 76might be just the thing for you.
77
6a4808bf 78The only public interface the L<Web::Simple> module itself provides is an
79C<import> based one:
7401408e 80
81 use Web::Simple 'NameOfApplication';
82
6a4808bf 83This sets up your package (in this case "NameOfApplication" is your package)
3895385d 84so that it inherits from L<Web::Simple::Application> and imports L<strictures>,
85as well as installs a C<PSGI_ENV> constant for convenience, as well as some
86other subroutines.
87
6a4808bf 88Importing L<strictures> will automatically make your code use the C<strict> and
3895385d 89C<warnings> pragma, so you can skip the usual:
7401408e 90
91 use strict;
3895385d 92 use warnings FATAL => 'aa';
7401408e 93
94provided you 'use Web::Simple' at the top of the file. Note that we turn
95on *fatal* warnings so if you have any warnings at any point from the file
96that you did 'use Web::Simple' in, then your application will die. This is,
97so far, considered a feature.
98
a5006b25 99When we inherit from L<Web::Simple::Application> we also use L<Moo>, which is
3895385d 100the the equivalent of:
7401408e 101
102 {
103 package NameOfApplication;
445b3ea0 104 use Moo;
105 extends 'Web::Simple::Application';
7401408e 106 }
107
6a4808bf 108So you can use L<Moo> features in your application, such as creating attributes
109using the C<has> subroutine, etc. Please see the documentation for L<Moo> for
110more information.
111
445b3ea0 112It also exports the following subroutines for use in dispatchers:
7401408e 113
74afe4b7 114 response_filter { ... };
7401408e 115
116 redispatch_to '/somewhere';
117
b7063124 118Finally, import sets
119
120 $INC{"NameOfApplication.pm"} = 'Set by "use Web::Simple;" invocation';
121
122so that perl will not attempt to load the application again even if
123
124 require NameOfApplication;
125
126is encountered in other code.
127
3583ca04 128=head1 DISPATCH STRATEGY
129
6a4808bf 130L<Web::Simple> despite being straightforward to use, has a powerful system
3895385d 131for matching all sorts of incoming URLs to one or more subroutines. These
132subroutines can be simple actions to take for a given URL, or something
133more complicated, including entire L<Plack> applications, L<Plack::Middleware>
134and nested subdispatchers.
135
c21c9f07 136=head2 Examples
137
445b3ea0 138 sub dispatch_request {
c21c9f07 139 # matches: GET /user/1.htm?show_details=1
140 # GET /user/1.htm
141 sub (GET + /user/* + ?show_details~ + .htm|.html|.xhtml) {
c254b30e 142 my ($self, $user_id, $show_details) = @_;
c21c9f07 143 ...
144 },
145 # matches: POST /user?username=frew
146 # POST /user?username=mst&first_name=matt&last_name=trout
147 sub (POST + /user + ?username=&*) {
c254b30e 148 my ($self, $username, $misc_params) = @_;
c21c9f07 149 ...
150 },
151 # matches: DELETE /user/1/friend/2
152 sub (DELETE + /user/*/friend/*) {
c254b30e 153 my ($self, $user_id, $friend_id) = @_;
c21c9f07 154 ...
155 },
156 # matches: PUT /user/1?first_name=Matt&last_name=Trout
157 sub (PUT + /user/* + ?first_name~&last_name~) {
c254b30e 158 my ($self, $user_id, $first_name, $last_name) = @_;
c21c9f07 159 ...
160 },
161 sub (/user/*/...) {
445b3ea0 162 my $user_id = $_[1];
163 # matches: PUT /user/1/role/1
164 sub (PUT + /role/*) {
165 my $role_id = $_[1];
166 ...
167 },
168 # matches: DELETE /user/1/role/1
169 sub (DELETE + /role/*) {
170 my $role_id = $_[1];
171 ...
172 },
c21c9f07 173 },
174 }
175
3706e2a0 176=head2 The dispatch cycle
81a5b03e 177
3706e2a0 178At the beginning of a request, your app's dispatch_request method is called
179with the PSGI $env as an argument. You can handle the request entirely in
180here and return a PSGI response arrayref if you want:
81a5b03e 181
3706e2a0 182 sub dispatch_request {
183 my ($self, $env) = @_;
184 [ 404, [ 'Content-type' => 'text/plain' ], [ 'Amnesia == fail' ] ]
185 }
81a5b03e 186
3706e2a0 187However, generally, instead of that, you return a set of dispatch subs:
81a5b03e 188
3706e2a0 189 sub dispatch_request {
190 my $self = shift;
191 sub (/) { redispatch_to '/index.html' },
192 sub (/user/*) { $self->show_user($_[1]) },
193 ...
194 }
81a5b03e 195
3706e2a0 196If you return a subroutine with a prototype, the prototype is treated
197as a match specification - and if the test is passed, the body of the
198sub is called as a method any matched arguments (see below for more details).
81a5b03e 199
3706e2a0 200You can also return a plain subroutine which will be called with just $env
201- remember that in this case if you need $self you -must- close over it.
81a5b03e 202
3895385d 203If you return a normal object, L<Web::Simple> will simply return it upwards on
204the assumption that a response_filter (or some arbitrary L<Plack::Middleware>)
205somewhere will convert it to something useful. This allows:
81a5b03e 206
3706e2a0 207 sub dispatch_request {
208 my $self = shift;
209 sub (.html) { response_filter { $self->render_zoom($_[0]) } },
210 sub (/user/*) { $self->users->get($_[1]) },
211 }
81a5b03e 212
3895385d 213to render a user object to HTML, if there is an incoming URL such as:
214
215 http://myweb.org/user/111.html
216
217This works because as we descend down the dispachers, we first match
218C<sub (.html)>, which adds a C<response_filter> (basically a specialized routine
219that follows the L<Plack::Middleware> specification), and then later we also
220match C<sub (/user/*)> which gets a user and returns that as the response.
221This user object 'bubbles up' through all the wrapping middleware until it hits
222the C<response_filter> we defined, after which the return is converted to a
223true html response.
81a5b03e 224
3706e2a0 225However, two types of object are treated specially - a Plack::App object
3895385d 226will have its C<->to_app> method called and be used as a dispatcher:
81a5b03e 227
3706e2a0 228 sub dispatch_request {
229 my $self = shift;
230 sub (/static/...) { Plack::App::File->new(...) },
231 ...
81a5b03e 232 }
233
3706e2a0 234A Plack::Middleware object will be used as a filter for the rest of the
235dispatch being returned into:
81a5b03e 236
6af22ff2 237 ## responds to /admin/track_usage AND /admin/delete_accounts
238
3706e2a0 239 sub dispatch_request {
240 my $self = shift;
6af22ff2 241 sub (/admin/**) {
242 Plack::Middleware::Session->new(%opts);
243 },
244 sub (/admin/track_usage) {
245 ## something that needs a session
246 },
247 sub (/admin/delete_accounts) {
248 ## something else that needs a session
249 },
81a5b03e 250 }
251
3706e2a0 252Note that this is for the dispatch being -returned- to, so if you want to
253provide it inline you need to do:
81a5b03e 254
6af22ff2 255 ## ALSO responds to /admin/track_usage AND /admin/delete_accounts
256
3706e2a0 257 sub dispatch_request {
258 my $self = shift;
3706e2a0 259 sub (/admin/...) {
6af22ff2 260 sub {
261 Plack::Middleware::Session->new(%opts);
262 },
263 sub (/track_usage) {
264 ## something that needs a session
265 },
266 sub (/delete_accounts) {
267 ## something else that needs a session
268 },
3706e2a0 269 }
81a5b03e 270 }
271
3706e2a0 272And that's it - but remember that all this happens recursively - it's
3895385d 273dispatchers all the way down. A URL incoming pattern will run all matching
274dispatchers and then hit all added filters or L<Plack::Middleware>.
3706e2a0 275
81a5b03e 276=head2 Web::Simple match specifications
277
278=head3 Method matches
279
93e30ba3 280 sub (GET) {
15dfe701 281
282A match specification beginning with a capital letter matches HTTP requests
283with that request method.
284
81a5b03e 285=head3 Path matches
286
15dfe701 287 sub (/login) {
288
289A match specification beginning with a / is a path match. In the simplest
290case it matches a specific path. To match a path with a wildcard part, you
291can do:
292
293 sub (/user/*) {
294 $self->handle_user($_[1])
295
296This will match /user/<anything> where <anything> does not include a literal
297/ character. The matched part becomes part of the match arguments. You can
298also match more than one part:
299
300 sub (/user/*/*) {
301 my ($self, $user_1, $user_2) = @_;
302
303 sub (/domain/*/user/*) {
304 my ($self, $domain, $user) = @_;
305
306and so on. To match an arbitrary number of parts, use -
307
308 sub (/page/**) {
309
310This will result in an element per /-separated part so matched. Note that
311you can do
312
313 sub (/page/**/edit) {
314
315to match an arbitrary number of parts up to but not including some final
316part.
317
da8429c9 318Finally,
319
320 sub (/foo/...) {
321
6a4808bf 322Will match /foo/ on the beginning of the path -and- strip it. This is designed
323to be used to construct nested dispatch structures, but can also prove useful
324for having e.g. an optional language specification at the start of a path.
da8429c9 325
326Note that the '...' is a "maybe something here, maybe not" so the above
327specification will match like this:
328
329 /foo # no match
330 /foo/ # match and strip path to '/'
331 /foo/bar/baz # match and strip path to '/bar/baz'
332
15e679c1 333Note: Since Web::Simple handles a concept of file extensions, * and **
334matchers will not by default match things after a final dot, and this
335can be modified by using *.* and **.* in the final position, i.e.:
336
337 /one/* matches /one/two.three and captures "two"
338 /one/*.* matches /one/two.three and captures "two.three"
339 /** matches /one/two.three and captures "one/two"
340 /**.* matches /one/two.three and captures "one/two.three"
341
81a5b03e 342=head3 Extension matches
343
15dfe701 344 sub (.html) {
345
6a4808bf 346will match .html from the path (assuming the subroutine itself returns
347something, of course). This is normally used for rendering - e.g.
15dfe701 348
349 sub (.html) {
74afe4b7 350 response_filter { $self->render_html($_[1]) }
15dfe701 351 }
352
b8bd7bd1 353Additionally,
354
355 sub (.*) {
356
6a4808bf 357will match any extension and supplies the extension as a match argument.
b8bd7bd1 358
9b9866ae 359=head3 Query and body parameter matches
360
361Query and body parameters can be match via
362
363 sub (?<param spec>) { # match URI query
364 sub (%<param spec>) { # match body params
365
cb12d2a3 366The body spec will match if the request content is either
367application/x-www-form-urlencoded or multipart/form-data - the latter
368of which is required for uploads, which are now handled experimentally
369- see below.
9b9866ae 370
371The param spec is elements of one of the following forms -
372
373 param~ # optional parameter
374 param= # required parameter
375 @param~ # optional multiple parameter
376 @param= # required multiple parameter
eb9e0e25 377 :param~ # optional parameter in hashref
378 :param= # required parameter in hashref
379 :@param~ # optional multiple in hashref
380 :@param= # required multiple in hashref
381 * # include all other parameters in hashref
382 @* # include all other parameters as multiple in hashref
9b9866ae 383
eb9e0e25 384separated by the & character. The arguments added to the request are
385one per non-:/* parameter (scalar for normal, arrayref for multiple),
386plus if any :/* specs exist a hashref containing those values.
9b9866ae 387
3895385d 388Please note that if you specify a multiple type parameter match, you are
389ensured of getting an arrayref for the value, EVEN if the current incoming
390request has only one value. However if a parameter is specified as single
391and multiple values are found, the last one will be used.
392
393For example to match a page parameter with an optional order_by parameter one
9b9866ae 394would write:
395
396 sub (?page=&order_by~) {
eb9e0e25 397 my ($self, $page, $order_by) = @_;
398 return unless $page =~ /^\d+$/;
399 $page ||= 'id';
9b9866ae 400 response_filter {
401 $_[1]->search_rs({}, $p);
402 }
403 }
404
405to implement paging and ordering against a L<DBIx::Class::ResultSet> object.
406
3895385d 407Another Example: To get all parameters as a hashref of arrayrefs, write:
eb9e0e25 408
409 sub(?@*) {
410 my ($self, $params) = @_;
411 ...
412
8c4ffad3 413To get two parameters as a hashref, write:
414
415 sub(?:user~&:domain~) {
416 my ($self, $params) = @_; # params contains only 'user' and 'domain' keys
417
418You can also mix these, so:
419
420 sub (?foo=&@bar~&:coffee=&@*) {
421 my ($self, $foo, $bar, $params);
422
423where $bar is an arrayref (possibly an empty one), and $params contains
424arrayref values for all parameters -not- mentioned and a scalar value for
425the 'coffee' parameter.
426
3895385d 427Note, in the case where you combine arrayref, single parameter and named
428hashref style, the arrayref and single parameters will appear in C<@_> in the
429order you defined them in the protoype, but all hashrefs will merge into a
430single C<$params>, as in the example above.
431
05aafc1a 432=head3 Upload matches (EXPERIMENTAL)
433
434Note: This feature is experimental. This means that it may not remain
435100% in its current form. If we change it, notes on updating your code
436will be added to the L</CHANGES BETWEEN RELEASES> section below.
437
438 sub (*foo=) { # param specifier can be anything valid for query or body
439
440The upload match system functions exactly like a query/body match, except
441that the values returned (if any) are C<Web::Dispatch::Upload> objects.
442
443Note that this match type will succeed in two circumstances where you might
444not expect it to - first, when the field exists but is not an upload field
445and second, when the field exists but the form is not an upload form (i.e.
446content type "application/x-www-form-urlencoded" rather than
447"multipart/form-data"). In either of these cases, what you'll get back is
448a C<Web::Dispatch::NotAnUpload> object, which will C<die> with an error
449pointing out the problem if you try and use it. To be sure you have a real
450upload object, call
451
452 $upload->is_upload # returns 1 on a valid upload, 0 on a non-upload field
453
454and to get the reason why such an object is not an upload, call
455
456 $upload->reason # returns a reason or '' on a valid upload.
457
458Other than these two methods, the upload object provides the same interface
459as L<Plack::Request::Upload> with the addition of a stringify to the temporary
460filename to make copying it somewhere else easier to handle.
461
81a5b03e 462=head3 Combining matches
463
15dfe701 464Matches may be combined with the + character - e.g.
465
b8bd7bd1 466 sub (GET + /user/*) {
467
468to create an AND match. They may also be combined withe the | character - e.g.
469
470 sub (GET|POST) {
471
472to create an OR match. Matches can be nested with () - e.g.
473
474 sub ((GET|POST) + /user/*) {
475
476and negated with ! - e.g.
477
478 sub (!/user/foo + /user/*) {
479
480! binds to the immediate rightmost match specification, so if you want
481to negate a combination you will need to use
482
483 sub ( !(POST|PUT|DELETE) ) {
484
485and | binds tighter than +, so
486
487 sub ((GET|POST) + /user/*) {
488
489and
490
491 sub (GET|POST + /user/*) {
492
493are equivalent, but
494
1760e999 495 sub ((GET + /admin/...) | (POST + /admin/...)) {
b8bd7bd1 496
497and
498
1760e999 499 sub (GET + /admin/... | POST + /admin/...) {
b8bd7bd1 500
501are not - the latter is equivalent to
502
1760e999 503 sub (GET + (/admin/...|POST) + /admin/...) {
b8bd7bd1 504
3895385d 505which will never match!
b8bd7bd1 506
507=head3 Whitespace
15dfe701 508
509Note that for legibility you are permitted to use whitespace -
510
44db8e76 511 sub (GET + /user/*) {
15dfe701 512
b8bd7bd1 513but it will be ignored. This is because the perl parser strips whitespace
514from subroutine prototypes, so this is equivalent to
515
516 sub (GET+/user/*) {
15dfe701 517
24175cb5 518=head3 Accessing the PSGI env hash
519
3706e2a0 520In some cases you may wish to get the raw PSGI env hash - to do this,
521you can either use a plain sub -
522
523 sub {
524 my ($env) = @_;
525 ...
526 }
24175cb5 527
3706e2a0 528or use the PSGI_ENV constant exported to retrieve it:
c21c9f07 529
3706e2a0 530 sub (GET + /foo + ?some_param=) {
531 my $param = $_[1];
532 my $env = $_[PSGI_ENV];
533 }
c21c9f07 534
3706e2a0 535but note that if you're trying to add a middleware, you should simply use
536Web::Simple's direct support for doing so.
c21c9f07 537
445b3ea0 538=head1 EXPORTED SUBROUTINES
c21c9f07 539
540=head2 response_filter
541
542 response_filter {
543 # Hide errors from the user because we hates them, preciousss
445b3ea0 544 if (ref($_[0]) eq 'ARRAY' && $_[0]->[0] == 500) {
545 $_[0] = [ 200, @{$_[0]}[1..$#{$_[0]}] ];
c21c9f07 546 }
445b3ea0 547 return $_[0];
c21c9f07 548 };
549
550The response_filter subroutine is designed for use inside dispatch subroutines.
551
552It creates and returns a special dispatcher that always matches, and calls
553the block passed to it as a filter on the result of running the rest of the
554current dispatch chain.
555
556Thus the filter above runs further dispatch as normal, but if the result of
557dispatch is a 500 (Internal Server Error) response, changes this to a 200 (OK)
558response without altering the headers or body.
559
560=head2 redispatch_to
561
562 redispatch_to '/other/url';
563
564The redispatch_to subroutine is designed for use inside dispatch subroutines.
565
566It creates and returns a special dispatcher that always matches, and instead
567of continuing dispatch re-delegates it to the start of the dispatch process,
568but with the path of the request altered to the supplied URL.
569
950d8829 570Thus if you receive a POST to '/some/url' and return a redispatch to
c21c9f07 571'/other/url', the dispatch behaviour will be exactly as if the same POST
572request had been made to '/other/url' instead.
573
3895385d 574Note, this is not the same as returning an HTTP 3xx redirect as a response;
575rather it is a much more efficient internal process.
576
8c4ffad3 577=head1 CHANGES BETWEEN RELEASES
445b3ea0 578
579=head2 Changes between 0.004 and 0.005
580
581=over 4
582
583=item * dispatch {} replaced by declaring a dispatch_request method
584
585dispatch {} has gone away - instead, you write:
586
587 sub dispatch_request {
e4122532 588 my $self = shift;
445b3ea0 589 sub (GET /foo/) { ... },
590 ...
591 }
592
593Note that this method is still -returning- the dispatch code - just like
594dispatch did.
595
e4122532 596Also note that you need the 'my $self = shift' since the magic $self
597variable went away.
598
599=item * the magic $self variable went away.
600
601Just add 'my $self = shift;' while writing your 'sub dispatch_request {'
602like a normal perl method.
603
445b3ea0 604=item * subdispatch deleted - all dispatchers can now subdispatch
605
606In earlier releases you needed to write:
607
608 subdispatch sub (/foo/...) {
609 ...
610 [
611 sub (GET /bar/) { ... },
612 ...
613 ]
614 }
615
616As of 0.005, you can instead write simply:
617
618 sub (/foo/...) {
619 ...
620 (
621 sub (GET /bar/) { ... },
622 ...
623 )
624 }
8c4ffad3 625
c2150f7d 626=back
627
8c4ffad3 628=head2 Changes since Antiquated Perl
629
630=over 4
631
632=item * filter_response renamed to response_filter
633
634This is a pure rename; a global search and replace should fix it.
635
c21c9f07 636=item * dispatch [] changed to dispatch {}
8c4ffad3 637
638Simply changing
639
640 dispatch [ sub(...) { ... }, ... ];
641
642to
643
644 dispatch { sub(...) { ... }, ... };
645
646should work fine.
647
648=back
649
fb771406 650=head1 DEVELOPMENT HISTORY
651
652Web::Simple was originally written to form part of my Antiquated Perl talk for
653Italian Perl Workshop 2009, but in writing the bloggery example I realised
654that having a bare minimum system for writing web applications that doesn't
655drive me insane was rather nice and decided to spend my attempt at nanowrimo
656for 2009 improving and documenting it to the point where others could use it.
657
58fd1f7f 658The Antiquated Perl talk can be found at L<http://www.shadowcat.co.uk/archive/conference-video/> and the slides are reproduced in this distribution under
659L<Web::Simple::AntiquatedPerl>.
fb771406 660
8c4ffad3 661=head1 COMMUNITY AND SUPPORT
662
663=head2 IRC channel
664
665irc.perl.org #web-simple
666
667=head2 No mailing list yet
668
669Because mst's non-work email is a bombsite so he'd never read it anyway.
670
671=head2 Git repository
672
673Gitweb is on http://git.shadowcat.co.uk/ and the clone URL is:
674
675 git clone git://git.shadowcat.co.uk/catagits/Web-Simple.git
676
677=head1 AUTHOR
678
c2150f7d 679Matt S. Trout (mst) <mst@shadowcat.co.uk>
8c4ffad3 680
681=head1 CONTRIBUTORS
682
48904f80 683Devin Austin (dhoss) <dhoss@cpan.org>
684
685Arthur Axel 'fREW' Schmidt <frioux@gmail.com>
686
c2150f7d 687gregor herrmann (gregoa) <gregoa@debian.org>
8c4ffad3 688
48904f80 689John Napiorkowski (jnap) <jjn1056@yahoo.com>
690
691Josh McMichael <jmcmicha@linus222.gsc.wustl.edu>
692
693Justin Hunter <justin.d.hunter@gmail.com>
694
695Kjetil Kjernsmo <kjetil@kjernsmo.net>
696
697markie <markie@nulletch64.dreamhost.com>
698
699Christian Walde (Mithaldu) <walde.christian@googlemail.com>
700
701nperez <nperez@cpan.org>
702
703Robin Edwards <robin.ge@gmail.com>
704
8c4ffad3 705=head1 COPYRIGHT
706
6a4808bf 707Copyright (c) 2010 the Web::Simple L</AUTHOR> and L</CONTRIBUTORS>
8c4ffad3 708as listed above.
709
710=head1 LICENSE
711
712This library is free software and may be distributed under the same terms
713as perl itself.
714
3583ca04 715=cut
7401408e 716
5c33dda5 7171;