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