File Coverage

blib/lib/CGI/Snapp/Dispatch.pm
Criterion Covered Total %
statement 223 232 96.1
branch 104 136 76.4
condition 28 46 60.8
subroutine 23 23 100.0
pod 4 5 80.0
total 382 442 86.4


line stmt bran cond sub pod time code
1             package CGI::Snapp::Dispatch;
2              
3 4     4   4237 use strict;
  4         6  
  4         90  
4 4     4   11 use warnings;
  4         4  
  4         71  
5              
6 4     4   11 use Carp;
  4         6  
  4         237  
7              
8 4     4   1606 use CGI::PSGI;
  4         104154  
  4         31  
9              
10 4     4   2385 use Class::Load ':all';
  4         59738  
  4         526  
11              
12 4     4   1569 use HTTP::Exception;
  4         14102  
  4         20  
13              
14 4     4   185628 use Log::Handler;
  4         112844  
  4         24  
15              
16 4     4   2160 use Moo;
  4         28423  
  4         35  
17              
18 4     4   6809 use Try::Tiny;
  4         6  
  4         16861  
19              
20             has logger =>
21             (
22             is => 'rw',
23             default => sub{return ''},
24             required => 0,
25             );
26              
27             has return_type =>
28             (
29             is => 'rw',
30             default => sub{return 0},
31             required => 0,
32             );
33              
34             our $VERSION = '2.00';
35              
36             # --------------------------------------------------
37              
38             sub as_psgi
39             {
40 2     2 1 4 my($self, @args) = @_;
41              
42 2         8 $self -> log(debug => 'as_psgi(...)');
43              
44 2 50 33     9 croak "Parameter \@args to dispatch() must be a hashref or a hash\n" if ( ($#args > 0) && ($#args % 2 != 1) );
45              
46 2 50       8 my($options) = ref $args[0] eq 'HASH' ? $args[0] : {@args};
47 2         9 my($args) = $self -> _merge_args($options);
48              
49 2 50 33     13 croak "Missing dispatch table, or it's not an arrayref\n" if (! $$args{table} || ref $$args{table} ne 'ARRAY');
50              
51 2         2 my($output);
52              
53             return
54             sub
55             {
56 2     2   716 my($env) = shift @_;
57 2         19 my($http_method) = $$env{REQUEST_METHOD};
58 2         10 my($named_args) = $self -> _parse_path($http_method, $self -> _clean_path($$env{PATH_INFO}, $args), $$args{table});
59              
60 2 50       7 HTTP::Exception -> throw(404, status_message => 'Not Found') if (! $$named_args{app});
61 2 50 33     6 HTTP::Exception -> throw(400, status_message => "Invalid characters in run mode name '$$named_args{rm}'") if ($$named_args{rm} && ($$named_args{rm} !~ m/^([a-zA-Z_][\w\']+)$/) );
62              
63             # If _prepare() croaks, error number is 404.
64             # If run() croaks, error number is 500,
65             # because the error message will not match /^\d+$/.
66              
67             try
68             {
69 2         96 my($module, $rm, $args_to_new) = $self -> _prepare($http_method, $args, $named_args);
70 2         4 $$args_to_new{_psgi} = 1; # Required.
71 2 50       14 $$args_to_new{QUERY} = CGI::PSGI -> new($env) if (! $$args_to_new{QUERY});
72 2         693 my($app) = $module -> new(%$args_to_new);
73              
74 2 50       6375 $app -> mode_param(sub {return $rm}) if ($rm);
  0         0  
75              
76 2         9 $output = $app -> run;
77             }
78             catch
79             {
80 0 0       0 my($error) = $_ =~ /^404/ ? 404 : 500;
81 0 0       0 my($message) = $error == 404 ? 'Not Found' : 'Internal Server Error';
82              
83 0         0 HTTP::Exception -> throw($error, status_message => $message);
84 2         20 };
85              
86 2         987 return $output;
87 2         12 };
88              
89             } # End of as_psgi.
90              
91             # --------------------------------------------------
92              
93             sub _clean_path
94             {
95 27     27   31 my($self, $path_info, $args) = @_;
96 27 100 66     108 $path_info = '' if (! defined $path_info || length $path_info == 0);
97              
98 27         68 $self -> log(debug => "_clean_path($path_info, ...)");
99              
100 27 100 66     102 $path_info = $$args{default} if (! defined $path_info || ($path_info eq '/') );
101 27 100 66     91 $path_info = '' if (! defined $path_info || length $path_info == 0);
102              
103             # Standardize the format of the path info, to simplify processing in _parse_path().
104              
105 27 100       55 $path_info = "/$path_info" if (index($path_info, '/') != 0);
106 27 100       68 $path_info = "$path_info/" if (substr($path_info, -1) ne '/');
107              
108 27         59 $self -> log(debug => "Path info '$path_info'");
109              
110 27         74 return $path_info;
111              
112             } # End of _clean_path.
113              
114             # --------------------------------------------------
115              
116             sub dispatch
117             {
118 28     28 1 154 my($self, @args) = @_;
119              
120 28         62 $self -> log(debug => 'dispatch(...)');
121              
122 28 50 66     111 croak "Parameter \@args to dispatch() must be a hashref or a hash\n" if ( ($#args > 0) && ($#args % 2 != 1) );
123              
124 28 50       80 my($options) = ref $args[0] eq 'HASH' ? $args[0] : {@args};
125 28         50 my($args) = $self -> _merge_args($options);
126              
127 28 50 33     116 croak "Missing dispatch table, or it's not an arrayref\n" if (! $$args{table} || ref $$args{table} ne 'ARRAY');
128              
129             # Return the args if the caller is testing.
130              
131 28 100       68 return $args if ($self -> return_type == 1);
132              
133 25         26 my($error);
134             my($output);
135              
136             try
137             {
138 25   33 25   720 my($http_method) = $ENV{HTTP_REQUEST_METHOD} || $ENV{REQUEST_METHOD};
139 25         60 my($named_args) = $self -> _parse_path($http_method, $self -> _clean_path($ENV{PATH_INFO}, $args), $$args{table});
140              
141 25 100       150 croak 404 if (! $$named_args{app});
142 24 100 100     234 croak 400 if ($$named_args{rm} && ($$named_args{rm} !~ m/^([a-zA-Z_][\w\']+)$/) );
143              
144 23 100       47 if ($self -> return_type == 2)
145             {
146             # Return the args if the caller is testing.
147             # Warning: You can't just return when within 'try',
148             # or your return value is discarded. Hence this 'if'.
149              
150 3         9 $output = $named_args;
151             }
152             else
153             {
154             # If run() croaks, _http_error() uses error number 500,
155             # because the error message will not match /^\d+$/.
156              
157 20         45 my($module, $rm, $args_to_new) = $self -> _prepare($http_method, $args, $named_args);
158 19         410 my($app) = $module -> new(%$args_to_new);
159              
160 19 100       13869 $app -> mode_param(sub {return $rm}) if ($rm);
  17         235  
161              
162 19         247 $output = $app -> run;
163             }
164             }
165             catch
166             {
167             # Remove any trailing text from error number, placed there by croak.
168              
169 6     6   801 ($error = $_) =~ s/^(\d+).+/$1/s;
170 6 50       22 $error = 500 if (! $error);
171 25         208 };
172              
173 25 100       10447 return $error ? $self -> _http_error($$args{error_document}, $error) : $output;
174              
175             } # End of dispatch.
176              
177             # --------------------------------------------------
178              
179             sub dispatch_args
180             {
181 10     10 1 15 my($self, $args) = @_;
182              
183             return
184             {
185 10         35 args_to_new => {},
186             default => '',
187             prefix => '',
188             table =>
189             [
190             ':app' => {},
191             ':app/:rm' => {},
192             ],
193             };
194              
195             } # End of dispatch_args.
196              
197             # --------------------------------------------------
198              
199             sub _http_error
200             {
201 6     6   10 my($self, $error_document, $error_number) = @_;
202              
203 6         18 $self -> log(debug => "_http_error(..., $error_number)");
204              
205 6   100     18 $error_document ||= '';
206 6 100       56 $error_number = 500 if ($error_number !~ /^\d+/);
207              
208 6         13 $self -> log(debug => "Processing HTTP error $error_number");
209              
210 6         6 my($output);
211             my($url);
212              
213 6 100       15 ($output, $url) = $self -> _parse_error_document($error_document, $error_number) if ($error_document);
214              
215             # Now process either the $output or the $url.
216              
217 6         14 my(%error_message) =
218             (
219             400 => 'Bad Request',
220             404 => 'Not Found',
221             500 => 'Internal Server Error',
222             );
223 6         10 my($message) = "$error_number $error_message{$error_number}";
224              
225 6 50       8 if ($url)
226             {
227             # Fabricate a somewhat malformed header. There'll be no error in the access log,
228             # but browers display the $url's document, and the old url in the address bar.
229              
230 0         0 $output = "HTTP/1.0 $message\nLocation: $url\n\n";
231             }
232             else
233             {
234             # Fabricate a HTML document if necessary.
235              
236 6         17 my($header) = '';
237 6   50     37 local $ENV{SERVER_ADMIN} ||= '';
238 6   50     21 local $ENV{SERVER_SIGNATURE} ||= '';
239              
240 6 100       27 $output = <
241            
242            
243            
244             $message
245            
246            
247             $header
248            

$message

249            

250            
251             $ENV{SERVER_ADMIN}
252            
253            

254            
255             $ENV{SERVER_SIGNATURE}
256            
257            
258             EOS
259 6 100       26 $header = "Status: $message\nContent-type: text/" .
260             ($output =~ /^(?:
261              
262             # Work around an IE bug. 'IE bug' is a tautology if I ever saw one...
263              
264 6 50       21 $output .= ' ' x (520 - length $output) if (length $output < 520);
265 6         40 $output = $header . $output;
266             }
267              
268 6         26 return $output;
269              
270             } # End of _http_error.
271              
272             # --------------------------------------------------
273              
274             sub log
275             {
276 627     627 0 559 my($self, $level, $s) = @_;
277              
278 627 50       813 croak "Error: No level defined in call to log()\n" if (! defined $level);
279              
280 627 50       1121 $self -> logger -> $level($s) if ($self -> logger);
281              
282             } # End of log.
283              
284             # --------------------------------------------------
285              
286             sub _merge_args
287             {
288 30     30   28 my($self, $args) = @_;
289              
290 30         42 $self -> log(debug => '_merge_args(...)');
291              
292 30         68 my($extra_args) = $self -> dispatch_args;
293 30         179 my($final_args) = {};
294              
295             # Process all args to dispatch().
296              
297 30         68 for my $key (keys %$args)
298             {
299             # Merge args_to_new because it's a hashref.
300              
301 36 100       49 if ($key eq 'args_to_new')
302             {
303 24         32 $$final_args{$key} = {};
304              
305             # Process all args to this key (args_to_new).
306              
307 24         21 for my $sub_key (keys %{$$args{$key} })
  24         45  
308             {
309             # If the sub key points to a hashref, merge data.
310              
311 27 100       43 if (ref $$args{$key}{$sub_key} eq 'HASH')
312             {
313             # But only merge if dispatch_args() returned data. Otherwise, overwrite.
314              
315 3 100       7 if (exists $$extra_args{$key}{$sub_key})
316             {
317 1         2 $$final_args{$key}{$sub_key} = {%{$$extra_args{$key}{$sub_key} }, %{$$args{$key}{$sub_key} } };
  1         2  
  1         18  
318             }
319             else
320             {
321 2         2 $$final_args{$key}{$sub_key} = {%{$$args{$key}{$sub_key} } };
  2         5  
322             }
323             }
324             else
325             {
326 24 50       69 $$final_args{$key}{$sub_key} = defined $$args{$key}{$sub_key} ? $$args{$key}{$sub_key} : $$extra_args{$key}{$sub_key};
327             }
328             }
329             }
330             else
331             {
332             # Overwrite when not a hashref.
333              
334 12         19 $$final_args{$key} = $$args{$key};
335             }
336             }
337              
338             # Now process args returned from dispatch_args() but not sent to this method.
339              
340 30         54 for my $key (keys %$extra_args)
341             {
342             # If the sub key points to a hashref, merge data.
343              
344 99 100       128 if (ref $$extra_args{$key} eq 'HASH')
345             {
346             # But only merge if $final_args contains data. Otherwise, overwrite.
347              
348 24 100       33 if (exists $$final_args{$key})
349             {
350             # But PARAMS itself is a hashref key.
351              
352 18 100       27 if ($$final_args{$key}{PARAMS})
353             {
354 3 100       6 if (exists $$extra_args{$key}{PARAMS})
355             {
356 1         1 $$final_args{$key}{PARAMS} = {%{$$final_args{$key}{PARAMS} }, %{$$extra_args{$key}{PARAMS} } };
  1         2  
  1         5  
357             }
358             }
359             else
360             {
361 15         12 $$final_args{$key}= {%{$$final_args{$key} }, %{$$extra_args{$key} } };
  15         25  
  15         47  
362             }
363             }
364             else
365             {
366 6         9 $$final_args{$key} = $$extra_args{$key};
367             }
368             }
369             else
370             {
371             # Overwrite since $$args{$key} does not exist.
372              
373 75 100       139 $$final_args{$key} = $$extra_args{$key} if (! exists $$args{$key});
374             }
375             }
376              
377 30         63 return $final_args;
378              
379             } # End of _merge_args.
380              
381             # --------------------------------------------------
382              
383             sub _parse_error_document
384             {
385 2     2   4 my($self, $error_document, $error_number) = @_;
386              
387 2         5 $self -> log(debug => "_parse_error_document(..., $error_number)");
388              
389             # Jam the error number into the document, if the latter contains %s.
390              
391 2         37 my($s) = sprintf($error_document, $error_number);
392              
393 2         4 my($output);
394             my($url);
395              
396 2 100       9 if (index($s, '"') == 0)
    50          
397             {
398             # It's a customised error string.
399             # Discard the leading " & use it as the output.
400              
401 1         2 $output = substr($s, 1);
402             }
403             elsif (index($s, '<') == 0)
404             {
405             # It's a local file, which is - hopefully - secure. Read it as the output.
406             # If we can't read it, $output will remain undef.
407              
408 1         7 require File::Spec;
409              
410 1         3 my($doc_root) = $ENV{DOCUMENT_ROOT};
411 1         2 $s = substr($s, 1);
412 1 50       3 $s = File::Spec -> catdir($doc_root, $s) if ($doc_root);
413              
414 1         5 $self -> log(debug => "Reading file $s");
415              
416 1 50 33     81 if (-f $s && open(INX, '<', $s) )
417             {
418 1         4 local $/ = undef;
419 1         21 $output = ;
420              
421 1         35 close INX;
422             }
423             else
424             {
425 0         0 carp "[Dispatch] Unable to open error_document file $s";
426             }
427             }
428             else
429             {
430             # It's neither customised error string nor file name.
431             # Assume it's a url. Keep it separate from $output for later.
432              
433 0         0 $url = $s;
434             }
435              
436 2 50       4 $self -> log(debug => "Redirecting HTTP error $error_number to $url") if ($url);
437 2 50       10 $self -> log(debug => "Displaying message for HTTP error $error_number") if ($output);
438              
439 2         3 return ($output, $url);
440              
441             } # End of _parse_error_document.
442              
443             # --------------------------------------------------
444              
445             sub _parse_path
446             {
447 25     25   32 my($self, $http_method, $path_info, $table) = @_;
448              
449 25         43 $self -> log(debug => "_parse_path($path_info, ...)");
450              
451             # Compare each rule in the table with the path_info, and process the 1st match.
452              
453 25         23 my($request_method_regexp, $rule);
454              
455 25         53 for (my $i = 0; $i < scalar @$table; $i += 2)
456             {
457 71         78 $rule = $$table[$i];
458              
459 71 50       100 next if (! defined $rule);
460              
461 71         118 $self -> log(debug => "Original rule '$rule'");
462              
463             # Firstly, look for a HTTP method name in the rule,
464             # as something like ':app/news[post]' => {rm => 'add_news'}.
465              
466 71         158 $request_method_regexp = qr/\[([^\]]+)\]$/;
467              
468 71 100       217 if ($rule =~ /$request_method_regexp/)
469             {
470             # If the method doesn't match the rule can't possibly match.
471              
472 3 100       13 next if (lc $http_method ne lc $1);
473              
474 2         5 $self -> log(debug => "Matched HTTP method '$http_method'");
475              
476             # Remove the method portion from the rule.
477              
478 2         9 $rule =~ s/$request_method_regexp//;
479             }
480              
481             # Standardize the format of the rule, to match the standardized path info.
482              
483 70 100       148 $rule = "/$rule" if (index($rule, '/') != 0);
484 70 100       136 $rule = "$rule/" if (substr($rule, -1) ne '/');
485              
486 70         114 $self -> log(debug => "Rule is now '$rule'");
487              
488             # Translate the rule into a regular expression, remembering where the named args are.
489             # '/:foo' will become '/([^\/]*)'
490             # and
491             # '/:bar?' will become '/?([^\/]*)?'
492             # and then remember which position it matches.
493              
494 70         54 my(@names);
495              
496 70         241 $rule =~
497             s{
498             (^|/) # Beginning, or a /.
499             (:([^/\?]+)(\?)?) # Stuff in between.
500             }{
501 83         124 push @names, $3;
502 83 100       268 $1 . ($4 ? '?([^/]*)?' : '([^/]*)')
503             }gxe;
504              
505             # '/*/' will become '/(.*)/$'.
506             # The final '/' has been added to the end of both $rule and $path_info already.
507              
508 70 100       126 if ($rule =~ m{/\*/$})
509             {
510 9         22 $rule =~ s{/\*/$}{/(.*)/\$};
511              
512 9         13 push @names, 'dispatch_url_remainder';
513             }
514              
515 70         123 $self -> log(debug => "Rule is now '$rule'");
516 70         142 $self -> log(debug => "Names in rule [" . join(', ', @names) . ']');
517 70         157 $self -> log(debug => "Trying to match path info '$path_info' against rule '$$table[$i]' using regexp '$rule'");
518              
519             # If we find a match, then run with it.
520              
521 70 100       1200 if (my @values = ($path_info =~ m#^$rule$#) )
522             {
523 25         38 $self -> log(debug => 'Matched!');
524              
525 25         18 my(%named_args) = %{$$table[++$i]};
  25         76  
526 25 100       73 @named_args{@names} = @values if @names;
527              
528 25         130 return {%named_args};
529             }
530             }
531              
532             # No rule matched the given path info.
533              
534 0         0 $self -> log(debug => 'Nothing matched');
535              
536 0         0 return {};
537              
538             } # End of _parse_path.
539              
540             # --------------------------------------------------
541              
542             sub _prepare
543             {
544 22     22   31 my($self, $http_method, $args, $named_args) = @_;
545 22   100     60 $http_method ||= '';
546              
547 22         48 $self -> log(debug => "_prepare($http_method, ...)");
548              
549 22         24 my($module, $prefix, $rm, $args_to_new) = delete @{$named_args}{qw(app prefix rm args_to_new)};
  22         53  
550 22 50       38 $module = '' if (! defined $module); # Stop uninit warning.
551 22 100       33 $rm = '' if (! defined $rm);
552              
553             # If another name for dispatch_url_remainder has been set, move the value to the requested name.
554              
555 22 100       39 if ($$named_args{'*'})
556             {
557 1         2 $$named_args{$$named_args{'*'} } = $$named_args{'dispatch_url_remainder'};
558              
559 1         2 delete $$named_args{'*'};
560 1         1 delete $$named_args{'dispatch_url_remainder'};
561             }
562              
563             # Warning: The following statement was copied from CGI::Application::Dispatch,
564             # but it does not do what you think, due to the way Perl equivalences hashrefs.
565             # The symptom is that up at line 62:
566             # $$args_to_new{QUERY} = CGI::PSGI -> new($env) if (! $$args_to_new{QUERY});
567             # it has the effect of setting $args{args_to_new}, and not just $args_to_new.
568             # That means the 'if (! $$args_to_new{QUERY})' stops a new CGI::PSGI being assigned
569             # during each call of the subref, so the initial CGI::PSGI object is preserved,
570             # and of course it has no CGI parameters, so no parameters are ever received :-(.
571              
572             #$args_to_new ||= $$args{args_to_new};
573              
574 22 50       31 if (! $args_to_new)
575             {
576 22         15 my(%new_args) = %{$$args{args_to_new} };
  22         62  
577 22         52 $args_to_new = {%new_args};
578             }
579              
580 22         35 @{$$args_to_new{PARAMS} }{keys %$named_args} = values %$named_args;
  22         35  
581 22 50       39 $args_to_new = {} if (! $args_to_new);
582 22         45 $module = $self -> translate_module_name($module);
583 22   100     84 $prefix ||= $$args{prefix} || '';
      100        
584 22 100       49 $module = $prefix . '::' . $module if ($prefix);
585 22 50       44 my($auto_rest) = defined $$named_args{auto_rest} ? $$named_args{auto_rest} : $$args{auto_rest};
586              
587 22 100       36 if ($auto_rest)
588             {
589 2 50       6 my($method_lc) = defined $$named_args{auto_rest_lc} ? $$named_args{auto_rest_lc} : $$args{auto_rest_lc};
590 2 100       5 $http_method = lc $http_method if ($method_lc);
591 2 50       5 $rm = length $rm ? "${rm}_$http_method" : $rm;
592             }
593              
594 22         57 $self -> log(debug => "Trying to load '$module'. Run method is '$rm'");
595              
596 22         61 try_load_class $module;
597              
598 22 100       39221 if (is_class_loaded $module)
599             {
600 21         631 $self -> log(debug => "Loaded '$module'");
601             }
602             else
603             {
604 1         177 croak 404;
605             }
606              
607 21         50 return ($module, $rm, $args_to_new);
608              
609             } # End of _prepare.
610              
611             # --------------------------------------------------
612              
613             sub translate_module_name
614             {
615 22     22 1 22 my($self, $name) = @_;
616              
617 22         45 $self -> log(debug => "translate_module_name($name)");
618              
619 22         54 $name = join('::', map{ucfirst $_} split(/_/, $name) );
  35         119  
620 22         42 $name = join('', map{ucfirst $_} split(/-/, $name) );
  24         41  
621              
622 22         32 return $name;
623              
624             } # End of translate_module_name.
625              
626             # --------------------------------------------------
627              
628             1;
629              
630             =pod
631              
632             =head1 NAME
633              
634             CGI::Snapp::Dispatch - Dispatch requests to CGI::Snapp-based objects
635              
636             =head1 Synopsis
637              
638             =head2 CGI Scripts
639              
640             Here is a minimal CGI instance script. I
641              
642             #!/usr/bin/env perl
643              
644             use CGI::Snapp::Dispatch;
645              
646             CGI::Snapp::Dispatch -> new -> dispatch;
647              
648             (The use of new() is discussed in detail under L, just below.)
649              
650             But, to override the default dispatch table, you probably want something like this:
651              
652             MyApp/Dispatch.pm:
653              
654             package MyApp::Dispatch;
655             parent 'CGI::Snapp::Dispatch';
656              
657             sub dispatch_args
658             {
659             my($self) = @_;
660              
661             return
662             {
663             prefix => 'MyApp',
664             table =>
665             [
666             '' => {app => 'Initialize', rm => 'start'},
667             ':app/:rm' => {},
668             'admin/:app/:rm' => {prefix => 'MyApp::Admin'},
669             ],
670             };
671             }
672              
673             And then you can write ... I
674              
675             #!/usr/bin/env perl
676              
677             use MyApp::Dispatch;
678              
679             MyApp::Dispatch -> new -> dispatch;
680              
681             =head2 PSGI Scripts
682              
683             Here is a PSGI script in production on my development machine. I
684              
685             #!/usr/bin/env perl
686             #
687             # Run with:
688             # starman -l 127.0.0.1:5020 --workers 1 httpd/cgi-bin/local/wines.psgi &
689             # or, for more debug output:
690             # plackup -l 127.0.0.1:5020 httpd/cgi-bin/local/wines.psgi &
691              
692             use strict;
693             use warnings;
694              
695             use CGI::Snapp::Dispatch;
696              
697             use Plack::Builder;
698              
699             # ---------------------
700              
701             my($app) = CGI::Snapp::Dispatch -> new -> as_psgi
702             (
703             prefix => 'Local::Wines::Controller', # A sub-class of CGI::Snapp.
704             table =>
705             [
706             '' => {app => 'Initialize', rm => 'display'},
707             ':app' => {rm => 'display'},
708             ':app/:rm/:id?' => {},
709             ],
710             );
711              
712             builder
713             {
714             enable "ContentLength";
715             enable "Static",
716             path => qr!^/(assets|favicon|yui)!,
717             root => '/dev/shm/html'; # /dev/shm/ is Debian's RAM disk.
718             $app;
719             };
720              
721             I The line my($app) = ... contains a call to L. This is definitely not the same as if you
722             were using L or L. They look like this:
723              
724             my($app) = CGI::Application::Dispatch -> as_psgi
725              
726             The lack of a call to new() there tells you I've implemented something very similar but different.
727             You have been warned...
728              
729             The point of this difference is that new() returns an object, and passing that into L as $self
730             allows the latter method to be much more sophisticated than it would otherwise be. Specifically, it can now share
731             a lot of code with L.
732              
733             Lastly, if you want to use regexps to match the path info, see L.
734              
735             =head1 Description
736              
737             This module provides a way to automatically look at the path info - $ENV{PATH_INFO} - of the incoming HTTP request,
738             and to process that path info like this:
739              
740             =over 4
741              
742             =item o Parse off a module name
743              
744             =item o Parse off a run mode
745              
746             =item o Create an instance of that module (i.e. load it)
747              
748             =item o Run that instance
749              
750             =item o Return the output of that run as the result of requsting that path info (i.e. module and run mode combo)
751              
752             =back
753              
754             Thus, it will translate a URI like this:
755              
756             /app/index.cgi/module_name/run_mode
757              
758             into something that is functionally equivalent to this:
759              
760             my($app) = Module::Name -> new(...);
761              
762             $app -> mode_param(sub {return 'run_mode'});
763              
764             return $app -> run;
765              
766             =head1 Distributions
767              
768             This module is available as a Unix-style distro (*.tgz).
769              
770             See L
771             for help on unpacking and installing distros.
772              
773             =head1 Installation
774              
775             Install L as you would for any C module:
776              
777             Run:
778              
779             cpanm CGI::Snapp::Dispatch
780              
781             or run:
782              
783             sudo cpan CGI::Snapp::Dispatch
784              
785             or unpack the distro, and then either:
786              
787             perl Build.PL
788             ./Build
789             ./Build test
790             sudo ./Build install
791              
792             or:
793              
794             perl Makefile.PL
795             make (or dmake or nmake)
796             make test
797             make install
798              
799             =head1 Constructor and Initialization
800              
801             C is called as C<< my($app) = CGI::Snapp::Dispatch -> new(k1 => v1, k2 => v2, ...) >>.
802              
803             It returns a new object of type C.
804              
805             Key-value pairs accepted in the parameter list (see corresponding methods for details
806             [e.g. L]):
807              
808             =over 4
809              
810             =item o logger => $aLoggerObject
811              
812             Specify a logger compatible with L.
813              
814             Note: This logs method calls etc inside CGI::Snapp::Dispatch.
815              
816             To log within L, see L.
817              
818             Default: '' (The empty string).
819              
820             To clarify: The built-in calls to log() all use a log level of 'debug', so if your logger has 'maxlevel' set
821             to anything less than 'debug', nothing nothing will get logged.
822              
823             'maxlevel' and 'minlevel' are discussed in L and L.
824              
825             =item o return_type => $integer
826              
827             Possible values for $integer:
828              
829             =over 4
830              
831             =item o 0 (zero)
832              
833             dispatch() returns the output of the run mode.
834              
835             This is the default.
836              
837             =item o 1 (one)
838              
839             dispatch() returns the hashref of args built from combining the output of dispatch_args() and the
840             args to dispatch().
841              
842             The requested module is I loaded and run. See t/args.t.
843              
844             =item o 2 (two)
845              
846             dispatch() returns the hashref of args build from parsing the path info.
847              
848             The requested module is I loaded and run. See t/args.t.
849              
850             =back
851              
852             Default: 0.
853              
854             Note: I is ignored by L.
855              
856             =back
857              
858             =head1 Methods
859              
860             =head2 as_psgi(@args)
861              
862             Returns a L-compatible coderef which, when called, runs your sub-class of L
863             as a L app.
864              
865             This works because the coderef actually calls L.
866              
867             See the next method, L, for a discussion of @args, which may be a hash or hashref.
868              
869             Lastly: as_psgi() does not support the I option the way dispatch({table => {error_document => ...} })
870             does. Rather, it throws errors of type L. Consider handling these errors with
871             L or similar.
872              
873             =head2 dispatch(@args)
874              
875             Returns the output generated by calling a L-based module.
876              
877             @args is a hash or hashref of options, which includes the all-important 'table' key, to define a dispatch table.
878             See L for details.
879              
880             The unfortunate mismatch between dispatch() taking a hash and dispatch_args() taking a hashref has been copied
881             from L. But, to clean things up, L allows dispatch() to accept
882             a hashref. You are encouraged to always use hashrefs, to avoid confusion.
883              
884             (Key => value) pairs which may appear in the hashref parameter ($args[0]):
885              
886             =over
887              
888             =item o args_to_new => $hashref
889              
890             This is a hashref of arguments that are passed into the constructor (C) of the application.
891              
892             If you wish to set parameters in your app which can be retrieved by the $self -> param($key) method, then use:
893              
894             my($app) = CGI::Snapp::Dispatch -> new;
895             my($output) = $app -> dispatch(args_to_new => {PARAMS => {key1 => 'value1'} });
896              
897             This means that inside your app, $self -> param('key1') will return 'value1'.
898              
899             See t/args.t's test_13(), which calls t/lib/CGI/Snapp/App1.pm's rm2().
900              
901             See also t/lib/CGI/Snapp/Dispatch/SubClass1.pm's dispatch_args() for how to pass in one or more such values via
902             your sub-class.
903              
904             =item o auto_rest => $Boolean
905              
906             If 1, this tells Dispatch that you are using REST by default and that you care about which HTTP method
907             is being used. Dispatch will append the HTTP method name (upper case by default) to
908             the run mode that is determined after finding the appropriate dispatch rule. So a GET request
909             that translates into C<< MyApp::Module -> foo >> will become C<< MyApp::Module -> foo_GET >>.
910              
911             This can be overridden on a per-rule basis in a derived class's dispatch table. See also the next option.
912              
913             Default: 0.
914              
915             See t/args.t test_27().
916              
917             =item o auto_rest_lc => $Boolean
918              
919             If 1, then in combination with I, this tells Dispatch that you prefer lower cased HTTP method names.
920             So instead of C and C you'll get C and C.
921              
922             See t/args.t test_28().
923              
924             =item o default
925              
926             Specify a value to use for the path info if one is not available.
927             This could be the case if the default page is selected (e.g.: '/cgi-bin/x.cgi' or perhaps '/cgi-bin/x.cgi/').
928              
929             =item o error_document
930              
931             Note: When using L, error_document makes no sense, and is ignored.
932             In that case, use L or similar.
933              
934             If this value is not provided, and something goes wrong, then Dispatch will return a '500 Internal Server Error',
935             using an internal HTML page. See t/args.t, test_25().
936              
937             Otherwise, the value should be one of the following:
938              
939             =over 4
940              
941             =item o A customised error string
942              
943             To use this, the string must start with a single double-quote (") character. This character
944             character will be trimmed from final output.
945              
946             =item o A file name
947              
948             To use this, the string must start with a less-than sign (<) character. This character
949             character will be trimmed from final output.
950              
951             $ENV{DOCUMENT_ROOT}, if not empty, will be prepended to this file name.
952              
953             The file will be read in and used as the error document.
954              
955             See t/args.t, test_26().
956              
957             =item o A URL to which the application will be redirected
958              
959             This happens when the I does not start with " or <.
960              
961             =back
962              
963             Note: In all 3 cases, the string may contain a '%s', which will be replaced with the error number (by sprintf).
964              
965             Currently CGI::Snapp::Dispatch uses three HTTP errors:
966              
967             =over 4
968              
969             =item o 400 Bad Request
970              
971             This is output if the run mode is not specified, or it contains an invalid character.
972              
973             =item o 404 Not Found
974              
975             This is output if the module name is not specified, or if there was no match with the dispatch table,
976             or the module could not be loaded by L.
977              
978             =item o 500 Internal Server Error
979              
980             This is output if the application dies.
981              
982             =back
983              
984             See t/args.t, test_24().
985              
986             =item o prefix
987              
988             This option will set the string to be prepended to the name of the application
989             module before it is loaded and created.
990              
991             For instance, consider /app/index.cgi/module_name/run_mode.
992              
993             This would, by default, load and create a module named 'Module::Name'. But let's say that you
994             have all of your application specific modules under the 'My' namespace. If you set this option
995             - C - to 'My' then it would instead load the 'My::Module::Name' application module instead.
996              
997             The algorithm for converting a path info into a module name is documented in L.
998              
999             =item o table
1000              
1001             In most cases, simply using Dispatch with the C and C is enough
1002             to simplify your application and your URLs, but there are many cases where you want
1003             more power. Enter the dispatch table (a hashref), specified here as the value of the C key.
1004              
1005             Since this table can be slightly complicated, a whole section exists on its use. Please see the L section.
1006              
1007             Examples are in the dispatch_args() method of both t/lib/CGI/Snapp/Dispatch/SubClass1.pm and
1008             t/lib/CGI/Snapp/Dispatch/SubClass2.pm.
1009              
1010             =back
1011              
1012             =head2 dispatch_args($args)
1013              
1014             Returns a hashref of args to be used by L.
1015              
1016             This hashref is a dispatch table. See L for details.
1017              
1018             L calls this method, passing in the hash/hashref which was passed in to L.
1019              
1020             Default output:
1021              
1022             {
1023             args_to_new => {},
1024             default => '',
1025             prefix => '',
1026             table =>
1027             [
1028             ':app' => {},
1029             ':app/:rm' => {},
1030             ],
1031             }
1032              
1033             This is the perfect method to override when creating a subclass to provide a richer L.
1034              
1035             See CGI::Snapp::Dispatch::SubClass1 and CGI::Snapp::Dispatch::SubClass2, both under t/lib/. These modules are
1036             exercised by t/args.t.
1037              
1038             =head2 new()
1039              
1040             See L for details on the parameters accepted by L.
1041              
1042             Returns an object of type L.
1043              
1044             =head2 translate_module_name($name)
1045              
1046             This method is used to control how the module name is translated from
1047             the matching section of the path. See L.
1048              
1049             The main reason that this method exists is so that it can be overridden if it doesn't do
1050             exactly what you want.
1051              
1052             The following transformations are performed on the input:
1053              
1054             =over 4
1055              
1056             =item o The text is split on '_'s (underscores)
1057              
1058             Next, each word has its first letter capitalized. The words are then joined
1059             back together using '::'.
1060              
1061             =item o The text is split on '-'s (hyphens)
1062              
1063             Next, each word has its first letter capitalized. The words are then joined
1064             back together without the '-'s.
1065              
1066             =back
1067              
1068             Examples:
1069              
1070             module_name => Module::Name
1071             module-name => ModuleName
1072             admin_top-scores => Admin::TopScores
1073              
1074             =head1 FAQ
1075              
1076             =head2 What is 'path info'?
1077              
1078             For a L script, it is just $ENV{PATH_INFO}. The value of $ENV{PATH_INFO} is normally set by the web server
1079             from the path info sent by the HTTP client.
1080              
1081             A request to /cgi-bin/x.cgi/path/info will set $ENV{PATH_INFO} to /path/info.
1082              
1083             For Apache, whether $ENV{PATH_INFO} is set or not depends on the setting of the
1084             L directive.
1085              
1086             For a L script, it is $$env{PATH_INFO}, within the $env hashref provided by PSGI.
1087              
1088             Path info is also discussed in L.
1089              
1090             Similar comments apply to the request method (GET, PUT etc) which may be used in rules.
1091              
1092             For CGI scripts, request method comes from $ENV{HTTP_REQUEST_METHOD} || $ENV{REQUEST_METHOD}, whereas for PSGI
1093             scripts it is just $$env{REQUEST_METHOD}.
1094              
1095             =head2 Is there any sample code?
1096              
1097             Yes. See t/args.t and t/lib/*.
1098              
1099             =head2 Why did you fork L?
1100              
1101             To be a companion module for L.
1102              
1103             =head2 What version of L did you fork?
1104              
1105             V 3.07.
1106              
1107             =head2 How does CGI::Snapp::Dispatch differ from CGI::Application::Dispatch?
1108              
1109             =head3 There is no module called CGI::Snapp::Dispatch::PSGI
1110              
1111             This just means the L-specific code is incorporated into CGI::Snapp::Dispatch.
1112             See L.
1113              
1114             =head3 Processing parameters to dispatch() and dispatch_args()
1115              
1116             The code which combines parameters to these 2 subs has been written from scratch. Obviously, the intention is that
1117             the new code behave in an identical fashion to the corresponding code in L.
1118              
1119             Also, the re-write allowed me to support a version of L which accepts a hashref, not just a hash.
1120             The same flexibility has been added to L.
1121              
1122             =head3 No special code for Apache, mod_perl or plugins
1123              
1124             I suggest that sort of stuff is best put in sub-classes.
1125              
1126             =head3 Unsupported features
1127              
1128             =over 4
1129              
1130             =item o dispatch_path()
1131              
1132             Method dispatch_path() is not provided. For L scripts, the code in dispatch() accesses $ENV{PATH_INFO} directly,
1133             whereas for L scripts, as_psgi() accesses the L environment
1134             hashref $$env{PATH_INFO}.
1135              
1136             =back
1137              
1138             =head3 Enhanced features
1139              
1140             L can take extra parameters:
1141              
1142             =over 4
1143              
1144             =item o return_type
1145              
1146             Note: I is ignored by L.
1147              
1148             =back
1149              
1150             =head3 This module uses Class::Load to try loading your application's module
1151              
1152             L uses:
1153              
1154             eval "require $module";
1155              
1156             whereas CGI::Snapp::Dispatch uses 2 methods from L:
1157              
1158             try_load_class $module;
1159             croak 404 if (! is_class_loaded $module);
1160              
1161             For L scripts, the 404 (and all other error numbers) is handled by sub _http_error(), whereas for
1162             L scripts, the code throws errors of type L.
1163              
1164             =head3 Reading an error document from a file
1165              
1166             L always prepends $ENV{DOCUMENT_ROOT} to the file name.
1167             Unfortunately, this means that when $ENV{DOCUMENT_ROOT} is not set, File::Spec prepends a '/' to the file name.
1168             So, an I of '
1169              
1170             This module only prepends $ENV{DOCUMENT_ROOT} if it is not empty. Hence, with an empty $ENV{DOCUMENT_ROOT},
1171             an I of '
1172              
1173             See sub _parse_error_document() and t/args.t test_26().
1174              
1175             =head3 Handling of exceptions
1176              
1177             L uses a combination of eval and L, together with L.
1178             Likewise, L uses the same combination, although without L.
1179              
1180             CGI::Snapp::Dispatch just uses L. This applies both to CGI scripts and PSGI scripts.
1181             For L scripts, errors are handled by sub _http_errror(). For L scripts, the code
1182             throws errors of type L.
1183              
1184             =head2 How does CGI::Snapp parse the path info?
1185              
1186             Firstly, the path info is split on '/' chars. Hence /module_name/mode1 gives us ('', 'module_name', 'mode1').
1187              
1188             The value 'module_name' is passed to L. In this case, the result is 'Module::Name'.
1189              
1190             You are free to override L to customize it.
1191              
1192             After that, the I option's value, if any, is added to the front of 'Module::Name'. See L for
1193             more about I.
1194              
1195             FInally, 'mode1' becomes the name of the run mode.
1196              
1197             Remember from the docs for L, that this is the I of the run mode, but is not necessarily the name
1198             of the method which will be run. The code in your sub-class of L can map run mode names to method
1199             names.
1200              
1201             For instance, a statement like:
1202              
1203             $self -> run_modes({rm_name_1 => 'rm_method_1', rm_name_2 => 'rm_method_2'});
1204              
1205             in (probably) sub setup(), shows how to separate run mode names from method names.
1206              
1207             =head2 What is the structure of the dispatch table?
1208              
1209             Sometimes it's easiest to explain with an example, so here you go:
1210              
1211             CGI::Snapp::Dispatch -> new -> dispatch # Note the new()!
1212             (
1213             args_to_new =>
1214             {
1215             PARAMS => {big => 'small'},
1216             },
1217             default => '/app',
1218             prefix => 'MyApp',
1219             table =>
1220             [
1221             '' => {app => 'Blog', rm => 'recent'},
1222             'posts/:category' => {app => 'Blog', rm => 'posts'},
1223             ':app/:rm/:id' => {app => 'Blog'},
1224             'date/:year/:month?/:day?' =>
1225             {
1226             app => 'Blog',
1227             rm => 'by_date',
1228             args_to_new => {PARAMS => {small => 'big'} },
1229             },
1230             ]
1231             );
1232              
1233             Firstly note, that besides passing this structure into L, you could sub-class L
1234             and design L to return exactly the same structure.
1235              
1236             OK. The components, all of which are optional, are:
1237              
1238             =over 4
1239              
1240             =item o args_to_new => $hashref
1241              
1242             This is how you specify a hashref of parameters to be passed to the constructor (new() ) of your sub-class of
1243             L.
1244              
1245             =item o default => $string
1246              
1247             This specifies a default for the path info in the case this code is called with an empty $ENV{PATH_INFO}.
1248              
1249             =item o prefix => $string
1250              
1251             This specifies a namespace to prepend to the class name derived by processing the path info.
1252              
1253             E.g. If path info was /module_name, then the above would produce 'MyApp::Module::Name'.
1254              
1255             =item o table => $arrayref
1256              
1257             This provides a set of rules, which are compared - 1 at a time, in the given order - with the path info, as the code tries to match the
1258             incoming path info to a rule you have provided.
1259              
1260             The first match wins.
1261              
1262             Each element of the array consists of a I and an I.
1263              
1264             Rules can be empty (see '' above), or they may be a combination of '/' chars and tokens. A token can be one of:
1265              
1266             =over 4
1267              
1268             =item o A literal
1269              
1270             Any token which does not start with a colon (:) is taken to be a literal string and must appear exactly as-is
1271             in the path info in order to match. In the rule 'posts/:category', posts is a literal.
1272              
1273             =item o A variable
1274              
1275             Any token which begins with a colon (:) is a variable token. These are simply wild-card place holders in the rule
1276             that will match anything - in the corresponding position - in the path info that isn't a slash.
1277              
1278             These variables can later be referred to in your application (sub-class of L) by using the
1279             $self -> param($name) mechanism. In the rule 'posts/:category', ':category' is a variable token.
1280              
1281             If the path info matched this rule, you could retrieve the value of that token from within your application
1282             like so: my($category) = $self -> param('category');.
1283              
1284             There are some variable tokens which are special. These can be used to further customize the dispatching.
1285              
1286             =over 4
1287              
1288             =item o :app
1289              
1290             This is the module name of the application. The value of this token will be sent to L
1291             and then prefixed with the prefix if there is one.
1292              
1293             =item o :rm
1294              
1295             This is the run mode of the application. The value of this token will be the actual name of the run mode used.
1296             As explained just above (L), this is not necessarily the name of the
1297             method within the module which will be run.
1298              
1299             =back
1300              
1301             =item o An optional variable
1302              
1303             Any token which begins with a colon (:) and ends with a question mark (?) is considered optional.
1304             If the rest of the path info matches the rest of the rule, then it doesn't matter whether it contains this token
1305             or not. It's best to only include optional variable tokens at the end of your rule. In the rule
1306             'date/:year/:month?/:day?', ':month?' and ':day?' are optional-variable tokens.
1307              
1308             Just as with variable tokens, optional-variable tokens' values can be retrieved by the application,
1309             if they existed in the path info. Try:
1310              
1311             if (defined $self -> param('month') )
1312             {
1313             ...
1314             }
1315              
1316             Lastly, $self -> param('month') will return undef if ':month?' does not match anything in the path info.
1317              
1318             =item o A wildcard
1319              
1320             The wildcard token '*' allows for partial matches. The token I appear at the end of the rule.
1321              
1322             E.g.: 'posts/list/*'. Given this rule, the 'dispatch_url_remainder' param is set to the remainder of the
1323             path info matched by the *. The name ('dispatch_url_remainder') of the param can be changed by setting '*'
1324             argument in the I. This example:
1325              
1326             'posts/list/*' => {'*' => 'post_list_filter'}
1327              
1328             specifies that $self -> param('post_list_filter') rather than $self -> param('dispatch_url_remainder') is to be
1329             used in your app, to retrieve the value which was passed in via the path info.
1330              
1331             See t/args.t, test_21() and test_22(), and the corresponding sub rm5() in t/lib/CGI/Snapp/App2.pm.
1332              
1333             =item o A HTTP method name
1334              
1335             You can also dispatch based on HTTP method. This is similar to using I but offers more fine-grained
1336             control. You include the (case insensitive) method name at the end of the rule and enclose it in square brackets.
1337             Samples:
1338              
1339             ':app/news[post]' => {rm => 'add_news' },
1340             ':app/news[get]' => {rm => 'news' },
1341             ':app/news[delete]' => {rm => 'delete_news'},
1342              
1343             The main reason that we don't use regular expressions for dispatch rules is that regular expressions did not provide
1344             for named back references (until recent versions of Perl), in the way variable tokens do.
1345              
1346             =back
1347              
1348             =back
1349              
1350             =head2 How do I use my own logger object?
1351              
1352             Study the sample code in L, which shows how to supply a L *.ini file to configure the logger via the wrapper class
1353             L.
1354              
1355             Also, see t/logs.t, t/log.a.pl and t/log.b.pl.
1356              
1357             See also L for important info and sample code.
1358              
1359             =head2 How do I sub-class CGI::Snapp::Dispatch?
1360              
1361             You do this the same way you sub-class L. See L.
1362              
1363             =head2 Are there any security implications from using this module?
1364              
1365             Yes. Since CGI::Snapp::Dispatch will dynamically choose which modules to use as content generators,
1366             it may give someone the ability to execute specially crafted modules on your system if those modules can be found
1367             in Perl's @INC path. This should only be a problem if you don't use a I.
1368              
1369             Of course those modules would have to behave like L based modules, but that still opens up the door
1370             more than most want.
1371              
1372             By using the I option you are only allowing Dispatch to pick modules from a pre-defined namespace.
1373              
1374             =head2 Why is CGI::PSGI required in Build.PL and Makefile.PL when it's sometimes not needed?
1375              
1376             It's a tradeoff. Leaving it out of those files is convenient for users who don't run under a PSGI environment,
1377             but it means users who do use PSGI must install L explicitly. And, worse, it means their code
1378             does not run by default, but only runs after manually installing that module.
1379              
1380             So, since L's only requirement is L, it's simpler to just always require it.
1381              
1382             =head1 Troubleshooting
1383              
1384             =head2 It doesn't work!
1385              
1386             Things to consider:
1387              
1388             =over 4
1389              
1390             =item o Run the *.cgi script from the command line
1391              
1392             shell> perl httpd/cgi-bin/cgi.snapp.one.cgi
1393              
1394             If that doesn't work, you're in b-i-g trouble. Keep reading for suggestions as to what to do next.
1395              
1396             =item o Did you try using a logger to trace the method calls?
1397              
1398             Pass a logger to your sub-class of L like this:
1399              
1400             my($logger) = Log::Handler -> new;
1401              
1402             $logger -> add
1403             (
1404             screen =>
1405             {
1406             maxlevel => 'debug',
1407             message_layout => '%m',
1408             minlevel => 'error',
1409             newline => 1, # When running from the command line.
1410             }
1411             );
1412             CGI::Snapp::Dispatch -> new -> as_psgi({args_to_new => {logger => $logger} }, ...);
1413              
1414             In addition, you can trace CGI::Snapp::Dispatch itself with the same (or a different) logger:
1415              
1416             CGI::Snapp::Dispatch -> new(logger => $logger) -> as_psgi({args_to_new => {logger => $logger} }, ...);
1417              
1418             The entry to each method in L and CGI::Snapp::Dispatch is logged using this technique,
1419             although only when maxlevel is 'debug'. Lower levels for maxlevel do not trigger logging.
1420             See the source for details. By 'this technique' I mean there is a statement like this at the entry of each method:
1421              
1422             $self -> log(debug => 'Entered x()');
1423              
1424             =item o Are you confused about combining parameters to dispatch() and dispatch_args()?
1425              
1426             I suggest you use the I option to L to capture output from the parameter merging code
1427             before trying to run your module. See t/args.t.
1428              
1429             =item o Are you confused about patterns in tables which do/don't use ':app' and ':rm'?
1430              
1431             The golden rule is:
1432              
1433             =over 4
1434              
1435             =item o If the rule uses 'app', then it is non-capturing
1436              
1437             This means the matching app name from $ENV{PATH_INFO} is I saved, so you must provide a modue name
1438             in the table's rule. E.g.: 'app/:rm' => {app => 'MyModule}, or perhaps use the I option to specify
1439             the complete module name.
1440              
1441             =item o If the rule uses ':app', then it is capturing
1442              
1443             This means the matching app name from $ENV{PATH_INFO} I saved, and it becomes the name of the module.
1444             Of course, I might come into play here, too.
1445              
1446             =back
1447              
1448             =item o Did you forget the leading < (read from file) in the customised error document file name?
1449              
1450             =item o Did you forget the leading " (double-quote) in the customised error document string?
1451              
1452             =item o Did you forget the embedded %s in the customised error document?
1453              
1454             This triggers the use of sprintf to merge the error number into the string.
1455              
1456             =item o Are you trying to use this module with an app non based on CGI::Snapp?
1457              
1458             Remember that L's new() takes a hash, not a hashref.
1459              
1460             =item o Did you get the mysterious error 'No such field "priority"'?
1461              
1462             You did this:
1463              
1464             as_psgi(args_to_new => $logger, ...)
1465              
1466             instead of this:
1467              
1468             as_psgi(args_to_new => {logger => $logger, ...}, ...)
1469              
1470             =item o The system Perl 'v' perlbrew
1471              
1472             Are you using perlbrew? If so, recall that your web server will use the first line of your L script to find a Perl,
1473             and that line probably says something like #!/usr/bin/env perl.
1474              
1475             So, perhaps you'd better turn perlbrew off and install L and this module under the system Perl, before trying again.
1476              
1477             =item o Generic advice
1478              
1479             L.
1480              
1481             =back
1482              
1483             =head1 See Also
1484              
1485             L - A almost back-compat fork of CGI::Application.
1486              
1487             As of V 1.01, L now supports L-style apps.
1488              
1489             And see L for another way of matching the path info.
1490              
1491             =head1 Machine-Readable Change Log
1492              
1493             The file Changes was converted into Changelog.ini by L.
1494              
1495             =head1 Version Numbers
1496              
1497             Version numbers < 1.00 represent development versions. From 1.00 up, they are production versions.
1498              
1499             =head1 Credits
1500              
1501             Please read L, since this module is a fork of the non-Apache
1502             components of L.
1503              
1504             =head1 Repository
1505              
1506             L
1507              
1508             =head1 Support
1509              
1510             Email the author, or log a bug on RT:
1511              
1512             L.
1513              
1514             =head1 Author
1515              
1516             L was written by Ron Savage Iron@savage.net.auE> in 2012.
1517              
1518             Home page: L.
1519              
1520             =head1 Copyright
1521              
1522             Australian copyright (c) 2012, Ron Savage.
1523              
1524             All Programs of mine are 'OSI Certified Open Source Software';
1525             you can redistribute them and/or modify them under the terms of
1526             The Artistic License, a copy of which is available at:
1527             http://www.opensource.org/licenses/index.html
1528              
1529             =cut