rename things in an attempt to gain slightly more clarity
[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
2de44d53 9our $VERSION = '0.008';
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
366The body is only matched if the content type is
367application/x-www-form-urlencoded (note this means that Web::Simple does
368not yet handle uploads; this will be addressed in a later release).
369
370The param spec is elements of one of the following forms -
371
372 param~ # optional parameter
373 param= # required parameter
374 @param~ # optional multiple parameter
375 @param= # required multiple parameter
eb9e0e25 376 :param~ # optional parameter in hashref
377 :param= # required parameter in hashref
378 :@param~ # optional multiple in hashref
379 :@param= # required multiple in hashref
380 * # include all other parameters in hashref
381 @* # include all other parameters as multiple in hashref
9b9866ae 382
eb9e0e25 383separated by the & character. The arguments added to the request are
384one per non-:/* parameter (scalar for normal, arrayref for multiple),
385plus if any :/* specs exist a hashref containing those values.
9b9866ae 386
3895385d 387Please note that if you specify a multiple type parameter match, you are
388ensured of getting an arrayref for the value, EVEN if the current incoming
389request has only one value. However if a parameter is specified as single
390and multiple values are found, the last one will be used.
391
392For example to match a page parameter with an optional order_by parameter one
9b9866ae 393would write:
394
395 sub (?page=&order_by~) {
eb9e0e25 396 my ($self, $page, $order_by) = @_;
397 return unless $page =~ /^\d+$/;
398 $page ||= 'id';
9b9866ae 399 response_filter {
400 $_[1]->search_rs({}, $p);
401 }
402 }
403
404to implement paging and ordering against a L<DBIx::Class::ResultSet> object.
405
3895385d 406Another Example: To get all parameters as a hashref of arrayrefs, write:
eb9e0e25 407
408 sub(?@*) {
409 my ($self, $params) = @_;
410 ...
411
8c4ffad3 412To get two parameters as a hashref, write:
413
414 sub(?:user~&:domain~) {
415 my ($self, $params) = @_; # params contains only 'user' and 'domain' keys
416
417You can also mix these, so:
418
419 sub (?foo=&@bar~&:coffee=&@*) {
420 my ($self, $foo, $bar, $params);
421
422where $bar is an arrayref (possibly an empty one), and $params contains
423arrayref values for all parameters -not- mentioned and a scalar value for
424the 'coffee' parameter.
425
3895385d 426Note, in the case where you combine arrayref, single parameter and named
427hashref style, the arrayref and single parameters will appear in C<@_> in the
428order you defined them in the protoype, but all hashrefs will merge into a
429single C<$params>, as in the example above.
430
05aafc1a 431=head3 Upload matches (EXPERIMENTAL)
432
433Note: This feature is experimental. This means that it may not remain
434100% in its current form. If we change it, notes on updating your code
435will be added to the L</CHANGES BETWEEN RELEASES> section below.
436
437 sub (*foo=) { # param specifier can be anything valid for query or body
438
439The upload match system functions exactly like a query/body match, except
440that the values returned (if any) are C<Web::Dispatch::Upload> objects.
441
442Note that this match type will succeed in two circumstances where you might
443not expect it to - first, when the field exists but is not an upload field
444and second, when the field exists but the form is not an upload form (i.e.
445content type "application/x-www-form-urlencoded" rather than
446"multipart/form-data"). In either of these cases, what you'll get back is
447a C<Web::Dispatch::NotAnUpload> object, which will C<die> with an error
448pointing out the problem if you try and use it. To be sure you have a real
449upload object, call
450
451 $upload->is_upload # returns 1 on a valid upload, 0 on a non-upload field
452
453and to get the reason why such an object is not an upload, call
454
455 $upload->reason # returns a reason or '' on a valid upload.
456
457Other than these two methods, the upload object provides the same interface
458as L<Plack::Request::Upload> with the addition of a stringify to the temporary
459filename to make copying it somewhere else easier to handle.
460
81a5b03e 461=head3 Combining matches
462
15dfe701 463Matches may be combined with the + character - e.g.
464
b8bd7bd1 465 sub (GET + /user/*) {
466
467to create an AND match. They may also be combined withe the | character - e.g.
468
469 sub (GET|POST) {
470
471to create an OR match. Matches can be nested with () - e.g.
472
473 sub ((GET|POST) + /user/*) {
474
475and negated with ! - e.g.
476
477 sub (!/user/foo + /user/*) {
478
479! binds to the immediate rightmost match specification, so if you want
480to negate a combination you will need to use
481
482 sub ( !(POST|PUT|DELETE) ) {
483
484and | binds tighter than +, so
485
486 sub ((GET|POST) + /user/*) {
487
488and
489
490 sub (GET|POST + /user/*) {
491
492are equivalent, but
493
1760e999 494 sub ((GET + /admin/...) | (POST + /admin/...)) {
b8bd7bd1 495
496and
497
1760e999 498 sub (GET + /admin/... | POST + /admin/...) {
b8bd7bd1 499
500are not - the latter is equivalent to
501
1760e999 502 sub (GET + (/admin/...|POST) + /admin/...) {
b8bd7bd1 503
3895385d 504which will never match!
b8bd7bd1 505
506=head3 Whitespace
15dfe701 507
508Note that for legibility you are permitted to use whitespace -
509
44db8e76 510 sub (GET + /user/*) {
15dfe701 511
b8bd7bd1 512but it will be ignored. This is because the perl parser strips whitespace
513from subroutine prototypes, so this is equivalent to
514
515 sub (GET+/user/*) {
15dfe701 516
24175cb5 517=head3 Accessing the PSGI env hash
518
3706e2a0 519In some cases you may wish to get the raw PSGI env hash - to do this,
520you can either use a plain sub -
521
522 sub {
523 my ($env) = @_;
524 ...
525 }
24175cb5 526
3706e2a0 527or use the PSGI_ENV constant exported to retrieve it:
c21c9f07 528
3706e2a0 529 sub (GET + /foo + ?some_param=) {
530 my $param = $_[1];
531 my $env = $_[PSGI_ENV];
532 }
c21c9f07 533
3706e2a0 534but note that if you're trying to add a middleware, you should simply use
535Web::Simple's direct support for doing so.
c21c9f07 536
445b3ea0 537=head1 EXPORTED SUBROUTINES
c21c9f07 538
539=head2 response_filter
540
541 response_filter {
542 # Hide errors from the user because we hates them, preciousss
445b3ea0 543 if (ref($_[0]) eq 'ARRAY' && $_[0]->[0] == 500) {
544 $_[0] = [ 200, @{$_[0]}[1..$#{$_[0]}] ];
c21c9f07 545 }
445b3ea0 546 return $_[0];
c21c9f07 547 };
548
549The response_filter subroutine is designed for use inside dispatch subroutines.
550
551It creates and returns a special dispatcher that always matches, and calls
552the block passed to it as a filter on the result of running the rest of the
553current dispatch chain.
554
555Thus the filter above runs further dispatch as normal, but if the result of
556dispatch is a 500 (Internal Server Error) response, changes this to a 200 (OK)
557response without altering the headers or body.
558
559=head2 redispatch_to
560
561 redispatch_to '/other/url';
562
563The redispatch_to subroutine is designed for use inside dispatch subroutines.
564
565It creates and returns a special dispatcher that always matches, and instead
566of continuing dispatch re-delegates it to the start of the dispatch process,
567but with the path of the request altered to the supplied URL.
568
950d8829 569Thus if you receive a POST to '/some/url' and return a redispatch to
c21c9f07 570'/other/url', the dispatch behaviour will be exactly as if the same POST
571request had been made to '/other/url' instead.
572
3895385d 573Note, this is not the same as returning an HTTP 3xx redirect as a response;
574rather it is a much more efficient internal process.
575
8c4ffad3 576=head1 CHANGES BETWEEN RELEASES
445b3ea0 577
578=head2 Changes between 0.004 and 0.005
579
580=over 4
581
582=item * dispatch {} replaced by declaring a dispatch_request method
583
584dispatch {} has gone away - instead, you write:
585
586 sub dispatch_request {
e4122532 587 my $self = shift;
445b3ea0 588 sub (GET /foo/) { ... },
589 ...
590 }
591
592Note that this method is still -returning- the dispatch code - just like
593dispatch did.
594
e4122532 595Also note that you need the 'my $self = shift' since the magic $self
596variable went away.
597
598=item * the magic $self variable went away.
599
600Just add 'my $self = shift;' while writing your 'sub dispatch_request {'
601like a normal perl method.
602
445b3ea0 603=item * subdispatch deleted - all dispatchers can now subdispatch
604
605In earlier releases you needed to write:
606
607 subdispatch sub (/foo/...) {
608 ...
609 [
610 sub (GET /bar/) { ... },
611 ...
612 ]
613 }
614
615As of 0.005, you can instead write simply:
616
617 sub (/foo/...) {
618 ...
619 (
620 sub (GET /bar/) { ... },
621 ...
622 )
623 }
8c4ffad3 624
625=head2 Changes since Antiquated Perl
626
627=over 4
628
629=item * filter_response renamed to response_filter
630
631This is a pure rename; a global search and replace should fix it.
632
c21c9f07 633=item * dispatch [] changed to dispatch {}
8c4ffad3 634
635Simply changing
636
637 dispatch [ sub(...) { ... }, ... ];
638
639to
640
641 dispatch { sub(...) { ... }, ... };
642
643should work fine.
644
645=back
646
fb771406 647=head1 DEVELOPMENT HISTORY
648
649Web::Simple was originally written to form part of my Antiquated Perl talk for
650Italian Perl Workshop 2009, but in writing the bloggery example I realised
651that having a bare minimum system for writing web applications that doesn't
652drive me insane was rather nice and decided to spend my attempt at nanowrimo
653for 2009 improving and documenting it to the point where others could use it.
654
58fd1f7f 655The 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
656L<Web::Simple::AntiquatedPerl>.
fb771406 657
8c4ffad3 658=head1 COMMUNITY AND SUPPORT
659
660=head2 IRC channel
661
662irc.perl.org #web-simple
663
664=head2 No mailing list yet
665
666Because mst's non-work email is a bombsite so he'd never read it anyway.
667
668=head2 Git repository
669
670Gitweb is on http://git.shadowcat.co.uk/ and the clone URL is:
671
672 git clone git://git.shadowcat.co.uk/catagits/Web-Simple.git
673
674=head1 AUTHOR
675
676Matt S. Trout <mst@shadowcat.co.uk>
677
678=head1 CONTRIBUTORS
679
680None required yet. Maybe this module is perfect (hahahahaha ...).
681
682=head1 COPYRIGHT
683
6a4808bf 684Copyright (c) 2010 the Web::Simple L</AUTHOR> and L</CONTRIBUTORS>
8c4ffad3 685as listed above.
686
687=head1 LICENSE
688
689This library is free software and may be distributed under the same terms
690as perl itself.
691
3583ca04 692=cut
7401408e 693
5c33dda5 6941;