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