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