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