fix config handling, finish porting bloggery, safer exporting
[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
3706e2a0 235 sub dispatch_request {
236 my $self = shift;
237 ...
238 sub (/admin) { Plack::Middleware::Session->new(...) },
239 ... # dispatchers needing a session go here
81a5b03e 240 }
241
3706e2a0 242Note that this is for the dispatch being -returned- to, so if you want to
243provide it inline you need to do:
81a5b03e 244
3706e2a0 245 sub dispatch_request {
246 my $self = shift;
81a5b03e 247 ...
3706e2a0 248 sub (/admin/...) {
249 sub { Plack::Middleware::Session->new(...) },
250 ... # dispatchers under /admin
251 }
81a5b03e 252 }
253
3706e2a0 254And that's it - but remember that all this happens recursively - it's
255dispatchers all the way down.
256
81a5b03e 257=head2 Web::Simple match specifications
258
259=head3 Method matches
260
93e30ba3 261 sub (GET) {
15dfe701 262
263A match specification beginning with a capital letter matches HTTP requests
264with that request method.
265
81a5b03e 266=head3 Path matches
267
15dfe701 268 sub (/login) {
269
270A match specification beginning with a / is a path match. In the simplest
271case it matches a specific path. To match a path with a wildcard part, you
272can do:
273
274 sub (/user/*) {
275 $self->handle_user($_[1])
276
277This will match /user/<anything> where <anything> does not include a literal
278/ character. The matched part becomes part of the match arguments. You can
279also match more than one part:
280
281 sub (/user/*/*) {
282 my ($self, $user_1, $user_2) = @_;
283
284 sub (/domain/*/user/*) {
285 my ($self, $domain, $user) = @_;
286
287and so on. To match an arbitrary number of parts, use -
288
289 sub (/page/**) {
290
291This will result in an element per /-separated part so matched. Note that
292you can do
293
294 sub (/page/**/edit) {
295
296to match an arbitrary number of parts up to but not including some final
297part.
298
da8429c9 299Finally,
300
301 sub (/foo/...) {
302
303will match /foo/ on the beginning of the path -and- strip it, much like
304.html strips the extension. This is designed to be used to construct
305nested dispatch structures, but can also prove useful for having e.g. an
306optional language specification at the start of a path.
307
308Note that the '...' is a "maybe something here, maybe not" so the above
309specification will match like this:
310
311 /foo # no match
312 /foo/ # match and strip path to '/'
313 /foo/bar/baz # match and strip path to '/bar/baz'
314
81a5b03e 315=head3 Extension matches
316
15dfe701 317 sub (.html) {
318
319will match and strip .html from the path (assuming the subroutine itself
320returns something, of course). This is normally used for rendering - e.g.
321
322 sub (.html) {
74afe4b7 323 response_filter { $self->render_html($_[1]) }
15dfe701 324 }
325
b8bd7bd1 326Additionally,
327
328 sub (.*) {
329
330will match any extension and supplies the stripped extension as a match
331argument.
332
9b9866ae 333=head3 Query and body parameter matches
334
335Query and body parameters can be match via
336
337 sub (?<param spec>) { # match URI query
338 sub (%<param spec>) { # match body params
339
340The body is only matched if the content type is
341application/x-www-form-urlencoded (note this means that Web::Simple does
342not yet handle uploads; this will be addressed in a later release).
343
344The param spec is elements of one of the following forms -
345
346 param~ # optional parameter
347 param= # required parameter
348 @param~ # optional multiple parameter
349 @param= # required multiple parameter
eb9e0e25 350 :param~ # optional parameter in hashref
351 :param= # required parameter in hashref
352 :@param~ # optional multiple in hashref
353 :@param= # required multiple in hashref
354 * # include all other parameters in hashref
355 @* # include all other parameters as multiple in hashref
9b9866ae 356
eb9e0e25 357separated by the & character. The arguments added to the request are
358one per non-:/* parameter (scalar for normal, arrayref for multiple),
359plus if any :/* specs exist a hashref containing those values.
9b9866ae 360
361So, to match a page parameter with an optional order_by parameter one
362would write:
363
364 sub (?page=&order_by~) {
eb9e0e25 365 my ($self, $page, $order_by) = @_;
366 return unless $page =~ /^\d+$/;
367 $page ||= 'id';
9b9866ae 368 response_filter {
369 $_[1]->search_rs({}, $p);
370 }
371 }
372
373to implement paging and ordering against a L<DBIx::Class::ResultSet> object.
374
8c4ffad3 375Note that if a parameter is specified as single and multiple values are found,
376the last one will be used.
377
eb9e0e25 378To get all parameters as a hashref of arrayrefs, write:
379
380 sub(?@*) {
381 my ($self, $params) = @_;
382 ...
383
8c4ffad3 384To get two parameters as a hashref, write:
385
386 sub(?:user~&:domain~) {
387 my ($self, $params) = @_; # params contains only 'user' and 'domain' keys
388
389You can also mix these, so:
390
391 sub (?foo=&@bar~&:coffee=&@*) {
392 my ($self, $foo, $bar, $params);
393
394where $bar is an arrayref (possibly an empty one), and $params contains
395arrayref values for all parameters -not- mentioned and a scalar value for
396the 'coffee' parameter.
397
81a5b03e 398=head3 Combining matches
399
15dfe701 400Matches may be combined with the + character - e.g.
401
b8bd7bd1 402 sub (GET + /user/*) {
403
404to create an AND match. They may also be combined withe the | character - e.g.
405
406 sub (GET|POST) {
407
408to create an OR match. Matches can be nested with () - e.g.
409
410 sub ((GET|POST) + /user/*) {
411
412and negated with ! - e.g.
413
414 sub (!/user/foo + /user/*) {
415
416! binds to the immediate rightmost match specification, so if you want
417to negate a combination you will need to use
418
419 sub ( !(POST|PUT|DELETE) ) {
420
421and | binds tighter than +, so
422
423 sub ((GET|POST) + /user/*) {
424
425and
426
427 sub (GET|POST + /user/*) {
428
429are equivalent, but
430
431 sub ((GET + .html) | (POST + .html)) {
432
433and
434
435 sub (GET + .html | POST + .html) {
436
437are not - the latter is equivalent to
438
439 sub (GET + (.html|POST) + .html) {
440
441which will never match.
442
443=head3 Whitespace
15dfe701 444
445Note that for legibility you are permitted to use whitespace -
446
44db8e76 447 sub (GET + /user/*) {
15dfe701 448
b8bd7bd1 449but it will be ignored. This is because the perl parser strips whitespace
450from subroutine prototypes, so this is equivalent to
451
452 sub (GET+/user/*) {
15dfe701 453
24175cb5 454=head3 Accessing the PSGI env hash
455
3706e2a0 456In some cases you may wish to get the raw PSGI env hash - to do this,
457you can either use a plain sub -
458
459 sub {
460 my ($env) = @_;
461 ...
462 }
24175cb5 463
3706e2a0 464or use the PSGI_ENV constant exported to retrieve it:
c21c9f07 465
3706e2a0 466 sub (GET + /foo + ?some_param=) {
467 my $param = $_[1];
468 my $env = $_[PSGI_ENV];
469 }
c21c9f07 470
3706e2a0 471but note that if you're trying to add a middleware, you should simply use
472Web::Simple's direct support for doing so.
c21c9f07 473
445b3ea0 474=head1 EXPORTED SUBROUTINES
c21c9f07 475
476=head2 response_filter
477
478 response_filter {
479 # Hide errors from the user because we hates them, preciousss
445b3ea0 480 if (ref($_[0]) eq 'ARRAY' && $_[0]->[0] == 500) {
481 $_[0] = [ 200, @{$_[0]}[1..$#{$_[0]}] ];
c21c9f07 482 }
445b3ea0 483 return $_[0];
c21c9f07 484 };
485
486The response_filter subroutine is designed for use inside dispatch subroutines.
487
488It creates and returns a special dispatcher that always matches, and calls
489the block passed to it as a filter on the result of running the rest of the
490current dispatch chain.
491
492Thus the filter above runs further dispatch as normal, but if the result of
493dispatch is a 500 (Internal Server Error) response, changes this to a 200 (OK)
494response without altering the headers or body.
495
496=head2 redispatch_to
497
498 redispatch_to '/other/url';
499
500The redispatch_to subroutine is designed for use inside dispatch subroutines.
501
502It creates and returns a special dispatcher that always matches, and instead
503of continuing dispatch re-delegates it to the start of the dispatch process,
504but with the path of the request altered to the supplied URL.
505
950d8829 506Thus if you receive a POST to '/some/url' and return a redispatch to
c21c9f07 507'/other/url', the dispatch behaviour will be exactly as if the same POST
508request had been made to '/other/url' instead.
509
8c4ffad3 510=head1 CHANGES BETWEEN RELEASES
445b3ea0 511
512=head2 Changes between 0.004 and 0.005
513
514=over 4
515
516=item * dispatch {} replaced by declaring a dispatch_request method
517
518dispatch {} has gone away - instead, you write:
519
520 sub dispatch_request {
e4122532 521 my $self = shift;
445b3ea0 522 sub (GET /foo/) { ... },
523 ...
524 }
525
526Note that this method is still -returning- the dispatch code - just like
527dispatch did.
528
e4122532 529Also note that you need the 'my $self = shift' since the magic $self
530variable went away.
531
532=item * the magic $self variable went away.
533
534Just add 'my $self = shift;' while writing your 'sub dispatch_request {'
535like a normal perl method.
536
445b3ea0 537=item * subdispatch deleted - all dispatchers can now subdispatch
538
539In earlier releases you needed to write:
540
541 subdispatch sub (/foo/...) {
542 ...
543 [
544 sub (GET /bar/) { ... },
545 ...
546 ]
547 }
548
549As of 0.005, you can instead write simply:
550
551 sub (/foo/...) {
552 ...
553 (
554 sub (GET /bar/) { ... },
555 ...
556 )
557 }
8c4ffad3 558
559=head2 Changes since Antiquated Perl
560
561=over 4
562
563=item * filter_response renamed to response_filter
564
565This is a pure rename; a global search and replace should fix it.
566
c21c9f07 567=item * dispatch [] changed to dispatch {}
8c4ffad3 568
569Simply changing
570
571 dispatch [ sub(...) { ... }, ... ];
572
573to
574
575 dispatch { sub(...) { ... }, ... };
576
577should work fine.
578
579=back
580
581=head1 COMMUNITY AND SUPPORT
582
583=head2 IRC channel
584
585irc.perl.org #web-simple
586
587=head2 No mailing list yet
588
589Because mst's non-work email is a bombsite so he'd never read it anyway.
590
591=head2 Git repository
592
593Gitweb is on http://git.shadowcat.co.uk/ and the clone URL is:
594
595 git clone git://git.shadowcat.co.uk/catagits/Web-Simple.git
596
597=head1 AUTHOR
598
599Matt S. Trout <mst@shadowcat.co.uk>
600
601=head1 CONTRIBUTORS
602
603None required yet. Maybe this module is perfect (hahahahaha ...).
604
605=head1 COPYRIGHT
606
607Copyright (c) 2009 the Web::Simple L</AUTHOR> and L</CONTRIBUTORS>
608as listed above.
609
610=head1 LICENSE
611
612This library is free software and may be distributed under the same terms
613as perl itself.
614
3583ca04 615=cut
7401408e 616
5c33dda5 6171;