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