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