corrected filter example
[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
9ddb5734 9our $VERSION = '0.004';
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
37=head1 WARNING
38
8c4ffad3 39This is really quite new. If you're reading this on CPAN, it means the stuff
40that's here we're probably happy with. But only probably. So we may have to
41change stuff. And if you're reading this from git, come check with irc.perl.org
42#web-simple that we're actually sure we're going to keep anything that's
43different from the CPAN version.
7401408e 44
8c4ffad3 45If we do find we have to change stuff we'll add to the
46L<CHANGES BETWEEN RELEASES> section explaining how to switch your code across
47to the new version, and we'll do our best to make it as painless as possible
48because we've got Web::Simple applications too. But we can't promise not to
49change things at all. Not yet. Sorry.
7401408e 50
51=head1 SYNOPSIS
52
53 #!/usr/bin/perl
54
55 use Web::Simple 'HelloWorld';
56
57 {
58 package HelloWorld;
59
445b3ea0 60 sub dispatch_request {
7401408e 61 sub (GET) {
62 [ 200, [ 'Content-type', 'text/plain' ], [ 'Hello world!' ] ]
63 },
64 sub () {
65 [ 405, [ 'Content-type', 'text/plain' ], [ 'Method not allowed' ] ]
66 }
445b3ea0 67 }
7401408e 68 }
69
70 HelloWorld->run_if_script;
71
72If you save this file into your cgi-bin as hello-world.cgi and then visit
73
74 http://my.server.name/cgi-bin/hello-world.cgi/
75
76you'll get the "Hello world!" string output to your browser. For more complex
8c4ffad3 77examples and non-CGI deployment, see below. To get help with Web::Simple,
78please connect to the irc.perl.org IRC network and join #web-simple.
7401408e 79
80=head1 WHY?
81
da8429c9 82Web::Simple was originally written to form part of my Antiquated Perl talk for
83Italian Perl Workshop 2009, but in writing the bloggery example I realised
84that having a bare minimum system for writing web applications that doesn't
85drive me insane was rather nice and decided to spend my attempt at nanowrimo
86for 2009 improving and documenting it to the point where others could use it.
7401408e 87
88The philosophy of Web::Simple is to keep to an absolute bare minimum, for
89everything. It is not designed to be used for large scale applications;
90the L<Catalyst> web framework already works very nicely for that and is
91a far more mature, well supported piece of software.
92
93However, if you have an application that only does a couple of things, and
94want to not have to think about complexities of deployment, then Web::Simple
95might be just the thing for you.
96
97The Antiquated Perl talk can be found at L<http://www.shadowcat.co.uk/archive/conference-video/>.
98
99=head1 DESCRIPTION
100
101The only public interface the Web::Simple module itself provides is an
102import based one -
103
104 use Web::Simple 'NameOfApplication';
105
106This imports 'strict' and 'warnings FATAL => "all"' into your code as well,
107so you can skip the usual
108
109 use strict;
110 use warnings;
111
112provided you 'use Web::Simple' at the top of the file. Note that we turn
113on *fatal* warnings so if you have any warnings at any point from the file
114that you did 'use Web::Simple' in, then your application will die. This is,
115so far, considered a feature.
116
117Calling the import also makes NameOfApplication isa Web::Simple::Application
445b3ea0 118and sets your app class up as a L<Moo> class- i.e. does the equivalent of
7401408e 119
120 {
121 package NameOfApplication;
445b3ea0 122 use Moo;
123 extends 'Web::Simple::Application';
7401408e 124 }
125
445b3ea0 126It also exports the following subroutines for use in dispatchers:
7401408e 127
74afe4b7 128 response_filter { ... };
7401408e 129
130 redispatch_to '/somewhere';
131
b7063124 132Finally, import sets
133
134 $INC{"NameOfApplication.pm"} = 'Set by "use Web::Simple;" invocation';
135
136so that perl will not attempt to load the application again even if
137
138 require NameOfApplication;
139
140is encountered in other code.
141
3583ca04 142=head1 DISPATCH STRATEGY
143
c21c9f07 144=head2 Examples
145
445b3ea0 146 sub dispatch_request {
c21c9f07 147 # matches: GET /user/1.htm?show_details=1
148 # GET /user/1.htm
149 sub (GET + /user/* + ?show_details~ + .htm|.html|.xhtml) {
c254b30e 150 my ($self, $user_id, $show_details) = @_;
c21c9f07 151 ...
152 },
153 # matches: POST /user?username=frew
154 # POST /user?username=mst&first_name=matt&last_name=trout
155 sub (POST + /user + ?username=&*) {
c254b30e 156 my ($self, $username, $misc_params) = @_;
c21c9f07 157 ...
158 },
159 # matches: DELETE /user/1/friend/2
160 sub (DELETE + /user/*/friend/*) {
c254b30e 161 my ($self, $user_id, $friend_id) = @_;
c21c9f07 162 ...
163 },
164 # matches: PUT /user/1?first_name=Matt&last_name=Trout
165 sub (PUT + /user/* + ?first_name~&last_name~) {
c254b30e 166 my ($self, $user_id, $first_name, $last_name) = @_;
c21c9f07 167 ...
168 },
169 sub (/user/*/...) {
445b3ea0 170 my $user_id = $_[1];
171 # matches: PUT /user/1/role/1
172 sub (PUT + /role/*) {
173 my $role_id = $_[1];
174 ...
175 },
176 # matches: DELETE /user/1/role/1
177 sub (DELETE + /role/*) {
178 my $role_id = $_[1];
179 ...
180 },
c21c9f07 181 },
182 }
183
3706e2a0 184=head2 The dispatch cycle
81a5b03e 185
3706e2a0 186At the beginning of a request, your app's dispatch_request method is called
187with the PSGI $env as an argument. You can handle the request entirely in
188here and return a PSGI response arrayref if you want:
81a5b03e 189
3706e2a0 190 sub dispatch_request {
191 my ($self, $env) = @_;
192 [ 404, [ 'Content-type' => 'text/plain' ], [ 'Amnesia == fail' ] ]
193 }
81a5b03e 194
3706e2a0 195However, generally, instead of that, you return a set of dispatch subs:
81a5b03e 196
3706e2a0 197 sub dispatch_request {
198 my $self = shift;
199 sub (/) { redispatch_to '/index.html' },
200 sub (/user/*) { $self->show_user($_[1]) },
201 ...
202 }
81a5b03e 203
3706e2a0 204If you return a subroutine with a prototype, the prototype is treated
205as a match specification - and if the test is passed, the body of the
206sub is called as a method any matched arguments (see below for more details).
81a5b03e 207
3706e2a0 208You can also return a plain subroutine which will be called with just $env
209- remember that in this case if you need $self you -must- close over it.
81a5b03e 210
3706e2a0 211If you return a normal object, Web::Simple will simply return it upwards on
212the assumption that a response_filter somewhere will convert it to something
213useful - this allows:
81a5b03e 214
3706e2a0 215 sub dispatch_request {
216 my $self = shift;
217 sub (.html) { response_filter { $self->render_zoom($_[0]) } },
218 sub (/user/*) { $self->users->get($_[1]) },
219 }
81a5b03e 220
3706e2a0 221to render a user object to HTML, for example.
81a5b03e 222
3706e2a0 223However, two types of object are treated specially - a Plack::App object
224will have its ->to_app method called and be used as a dispatcher:
81a5b03e 225
3706e2a0 226 sub dispatch_request {
227 my $self = shift;
228 sub (/static/...) { Plack::App::File->new(...) },
229 ...
81a5b03e 230 }
231
3706e2a0 232A Plack::Middleware object will be used as a filter for the rest of the
233dispatch being returned into:
81a5b03e 234
6af22ff2 235 ## responds to /admin/track_usage AND /admin/delete_accounts
236
3706e2a0 237 sub dispatch_request {
238 my $self = shift;
6af22ff2 239 sub (/admin/**) {
240 Plack::Middleware::Session->new(%opts);
241 },
242 sub (/admin/track_usage) {
243 ## something that needs a session
244 },
245 sub (/admin/delete_accounts) {
246 ## something else that needs a session
247 },
81a5b03e 248 }
249
3706e2a0 250Note that this is for the dispatch being -returned- to, so if you want to
251provide it inline you need to do:
81a5b03e 252
6af22ff2 253 ## ALSO responds to /admin/track_usage AND /admin/delete_accounts
254
3706e2a0 255 sub dispatch_request {
256 my $self = shift;
3706e2a0 257 sub (/admin/...) {
6af22ff2 258 sub {
259 Plack::Middleware::Session->new(%opts);
260 },
261 sub (/track_usage) {
262 ## something that needs a session
263 },
264 sub (/delete_accounts) {
265 ## something else that needs a session
266 },
3706e2a0 267 }
81a5b03e 268 }
269
3706e2a0 270And that's it - but remember that all this happens recursively - it's
271dispatchers all the way down.
272
81a5b03e 273=head2 Web::Simple match specifications
274
275=head3 Method matches
276
93e30ba3 277 sub (GET) {
15dfe701 278
279A match specification beginning with a capital letter matches HTTP requests
280with that request method.
281
81a5b03e 282=head3 Path matches
283
15dfe701 284 sub (/login) {
285
286A match specification beginning with a / is a path match. In the simplest
287case it matches a specific path. To match a path with a wildcard part, you
288can do:
289
290 sub (/user/*) {
291 $self->handle_user($_[1])
292
293This will match /user/<anything> where <anything> does not include a literal
294/ character. The matched part becomes part of the match arguments. You can
295also match more than one part:
296
297 sub (/user/*/*) {
298 my ($self, $user_1, $user_2) = @_;
299
300 sub (/domain/*/user/*) {
301 my ($self, $domain, $user) = @_;
302
303and so on. To match an arbitrary number of parts, use -
304
305 sub (/page/**) {
306
307This will result in an element per /-separated part so matched. Note that
308you can do
309
310 sub (/page/**/edit) {
311
312to match an arbitrary number of parts up to but not including some final
313part.
314
da8429c9 315Finally,
316
317 sub (/foo/...) {
318
319will match /foo/ on the beginning of the path -and- strip it, much like
320.html strips the extension. This is designed to be used to construct
321nested dispatch structures, but can also prove useful for having e.g. an
322optional language specification at the start of a path.
323
324Note that the '...' is a "maybe something here, maybe not" so the above
325specification will match like this:
326
327 /foo # no match
328 /foo/ # match and strip path to '/'
329 /foo/bar/baz # match and strip path to '/bar/baz'
330
81a5b03e 331=head3 Extension matches
332
15dfe701 333 sub (.html) {
334
335will match and strip .html from the path (assuming the subroutine itself
336returns something, of course). This is normally used for rendering - e.g.
337
338 sub (.html) {
74afe4b7 339 response_filter { $self->render_html($_[1]) }
15dfe701 340 }
341
b8bd7bd1 342Additionally,
343
344 sub (.*) {
345
346will match any extension and supplies the stripped extension as a match
347argument.
348
9b9866ae 349=head3 Query and body parameter matches
350
351Query and body parameters can be match via
352
353 sub (?<param spec>) { # match URI query
354 sub (%<param spec>) { # match body params
355
356The body is only matched if the content type is
357application/x-www-form-urlencoded (note this means that Web::Simple does
358not yet handle uploads; this will be addressed in a later release).
359
360The param spec is elements of one of the following forms -
361
362 param~ # optional parameter
363 param= # required parameter
364 @param~ # optional multiple parameter
365 @param= # required multiple parameter
eb9e0e25 366 :param~ # optional parameter in hashref
367 :param= # required parameter in hashref
368 :@param~ # optional multiple in hashref
369 :@param= # required multiple in hashref
370 * # include all other parameters in hashref
371 @* # include all other parameters as multiple in hashref
9b9866ae 372
eb9e0e25 373separated by the & character. The arguments added to the request are
374one per non-:/* parameter (scalar for normal, arrayref for multiple),
375plus if any :/* specs exist a hashref containing those values.
9b9866ae 376
377So, to match a page parameter with an optional order_by parameter one
378would write:
379
380 sub (?page=&order_by~) {
eb9e0e25 381 my ($self, $page, $order_by) = @_;
382 return unless $page =~ /^\d+$/;
383 $page ||= 'id';
9b9866ae 384 response_filter {
385 $_[1]->search_rs({}, $p);
386 }
387 }
388
389to implement paging and ordering against a L<DBIx::Class::ResultSet> object.
390
8c4ffad3 391Note that if a parameter is specified as single and multiple values are found,
392the last one will be used.
393
eb9e0e25 394To get all parameters as a hashref of arrayrefs, write:
395
396 sub(?@*) {
397 my ($self, $params) = @_;
398 ...
399
8c4ffad3 400To get two parameters as a hashref, write:
401
402 sub(?:user~&:domain~) {
403 my ($self, $params) = @_; # params contains only 'user' and 'domain' keys
404
405You can also mix these, so:
406
407 sub (?foo=&@bar~&:coffee=&@*) {
408 my ($self, $foo, $bar, $params);
409
410where $bar is an arrayref (possibly an empty one), and $params contains
411arrayref values for all parameters -not- mentioned and a scalar value for
412the 'coffee' parameter.
413
81a5b03e 414=head3 Combining matches
415
15dfe701 416Matches may be combined with the + character - e.g.
417
b8bd7bd1 418 sub (GET + /user/*) {
419
420to create an AND match. They may also be combined withe the | character - e.g.
421
422 sub (GET|POST) {
423
424to create an OR match. Matches can be nested with () - e.g.
425
426 sub ((GET|POST) + /user/*) {
427
428and negated with ! - e.g.
429
430 sub (!/user/foo + /user/*) {
431
432! binds to the immediate rightmost match specification, so if you want
433to negate a combination you will need to use
434
435 sub ( !(POST|PUT|DELETE) ) {
436
437and | binds tighter than +, so
438
439 sub ((GET|POST) + /user/*) {
440
441and
442
443 sub (GET|POST + /user/*) {
444
445are equivalent, but
446
447 sub ((GET + .html) | (POST + .html)) {
448
449and
450
451 sub (GET + .html | POST + .html) {
452
453are not - the latter is equivalent to
454
455 sub (GET + (.html|POST) + .html) {
456
457which will never match.
458
459=head3 Whitespace
15dfe701 460
461Note that for legibility you are permitted to use whitespace -
462
44db8e76 463 sub (GET + /user/*) {
15dfe701 464
b8bd7bd1 465but it will be ignored. This is because the perl parser strips whitespace
466from subroutine prototypes, so this is equivalent to
467
468 sub (GET+/user/*) {
15dfe701 469
24175cb5 470=head3 Accessing the PSGI env hash
471
3706e2a0 472In some cases you may wish to get the raw PSGI env hash - to do this,
473you can either use a plain sub -
474
475 sub {
476 my ($env) = @_;
477 ...
478 }
24175cb5 479
3706e2a0 480or use the PSGI_ENV constant exported to retrieve it:
c21c9f07 481
3706e2a0 482 sub (GET + /foo + ?some_param=) {
483 my $param = $_[1];
484 my $env = $_[PSGI_ENV];
485 }
c21c9f07 486
3706e2a0 487but note that if you're trying to add a middleware, you should simply use
488Web::Simple's direct support for doing so.
c21c9f07 489
445b3ea0 490=head1 EXPORTED SUBROUTINES
c21c9f07 491
492=head2 response_filter
493
494 response_filter {
495 # Hide errors from the user because we hates them, preciousss
445b3ea0 496 if (ref($_[0]) eq 'ARRAY' && $_[0]->[0] == 500) {
497 $_[0] = [ 200, @{$_[0]}[1..$#{$_[0]}] ];
c21c9f07 498 }
445b3ea0 499 return $_[0];
c21c9f07 500 };
501
502The response_filter subroutine is designed for use inside dispatch subroutines.
503
504It creates and returns a special dispatcher that always matches, and calls
505the block passed to it as a filter on the result of running the rest of the
506current dispatch chain.
507
508Thus the filter above runs further dispatch as normal, but if the result of
509dispatch is a 500 (Internal Server Error) response, changes this to a 200 (OK)
510response without altering the headers or body.
511
512=head2 redispatch_to
513
514 redispatch_to '/other/url';
515
516The redispatch_to subroutine is designed for use inside dispatch subroutines.
517
518It creates and returns a special dispatcher that always matches, and instead
519of continuing dispatch re-delegates it to the start of the dispatch process,
520but with the path of the request altered to the supplied URL.
521
950d8829 522Thus if you receive a POST to '/some/url' and return a redispatch to
c21c9f07 523'/other/url', the dispatch behaviour will be exactly as if the same POST
524request had been made to '/other/url' instead.
525
8c4ffad3 526=head1 CHANGES BETWEEN RELEASES
445b3ea0 527
528=head2 Changes between 0.004 and 0.005
529
530=over 4
531
532=item * dispatch {} replaced by declaring a dispatch_request method
533
534dispatch {} has gone away - instead, you write:
535
536 sub dispatch_request {
e4122532 537 my $self = shift;
445b3ea0 538 sub (GET /foo/) { ... },
539 ...
540 }
541
542Note that this method is still -returning- the dispatch code - just like
543dispatch did.
544
e4122532 545Also note that you need the 'my $self = shift' since the magic $self
546variable went away.
547
548=item * the magic $self variable went away.
549
550Just add 'my $self = shift;' while writing your 'sub dispatch_request {'
551like a normal perl method.
552
445b3ea0 553=item * subdispatch deleted - all dispatchers can now subdispatch
554
555In earlier releases you needed to write:
556
557 subdispatch sub (/foo/...) {
558 ...
559 [
560 sub (GET /bar/) { ... },
561 ...
562 ]
563 }
564
565As of 0.005, you can instead write simply:
566
567 sub (/foo/...) {
568 ...
569 (
570 sub (GET /bar/) { ... },
571 ...
572 )
573 }
8c4ffad3 574
575=head2 Changes since Antiquated Perl
576
577=over 4
578
579=item * filter_response renamed to response_filter
580
581This is a pure rename; a global search and replace should fix it.
582
c21c9f07 583=item * dispatch [] changed to dispatch {}
8c4ffad3 584
585Simply changing
586
587 dispatch [ sub(...) { ... }, ... ];
588
589to
590
591 dispatch { sub(...) { ... }, ... };
592
593should work fine.
594
595=back
596
597=head1 COMMUNITY AND SUPPORT
598
599=head2 IRC channel
600
601irc.perl.org #web-simple
602
603=head2 No mailing list yet
604
605Because mst's non-work email is a bombsite so he'd never read it anyway.
606
607=head2 Git repository
608
609Gitweb is on http://git.shadowcat.co.uk/ and the clone URL is:
610
611 git clone git://git.shadowcat.co.uk/catagits/Web-Simple.git
612
613=head1 AUTHOR
614
615Matt S. Trout <mst@shadowcat.co.uk>
616
617=head1 CONTRIBUTORS
618
619None required yet. Maybe this module is perfect (hahahahaha ...).
620
621=head1 COPYRIGHT
622
623Copyright (c) 2009 the Web::Simple L</AUTHOR> and L</CONTRIBUTORS>
624as listed above.
625
626=head1 LICENSE
627
628This library is free software and may be distributed under the same terms
629as perl itself.
630
3583ca04 631=cut
7401408e 632
5c33dda5 6331;