File Coverage

blib/lib/CGI/Application/Dispatch.pm
Criterion Covered Total %
statement 152 225 67.5
branch 73 152 48.0
condition 40 66 60.6
subroutine 21 24 87.5
pod 6 7 85.7
total 292 474 61.6


line stmt bran cond sub pod time code
1             package CGI::Application::Dispatch;
2 1     1   1621 use strict;
  1         3  
  1         51  
3 1     1   7 use warnings;
  1         3  
  1         39  
4 1     1   18 use Carp 'carp';
  1         2  
  1         74  
5 1     1   880 use Try::Tiny;
  1         1611  
  1         100  
6              
7             our $VERSION = '3.12';
8             our $DEBUG = 0;
9              
10             BEGIN {
11 1     1   6 use constant IS_MODPERL => exists($ENV{MOD_PERL});
  1         2  
  1         73  
12 1         185 use constant IS_MODPERL2 =>
13 1     1   5 (IS_MODPERL() and exists $ENV{MOD_PERL_API_VERSION} and $ENV{MOD_PERL_API_VERSION} == 2);
  1         2  
14              
15 1     1   58 if(IS_MODPERL2()) {
16             require Apache2::RequestUtil;
17             require Apache2::RequestRec;
18             require APR::Table;
19             require Apache2::Const;
20             Apache2::Const->import(qw(OK SERVER_ERROR HTTP_BAD_REQUEST NOT_FOUND REDIRECT));
21 0         0 } elsif(IS_MODPERL()) {
22             require Apache::Constants;
23             Apache::Constants->import(qw(OK SERVER_ERROR BAD_REQUEST NOT_FOUND REDIRECT));
24             }
25             }
26              
27             # these return values have different values used in different ENV
28             use Exception::Class (
29 1         15 'CGI::Application::Dispatch::Exception',
30             'CGI::Application::Dispatch::ERROR' => {
31             isa => 'CGI::Application::Dispatch::Exception',
32             alias => 'throw_error',
33             description => 500,
34             },
35             'CGI::Application::Dispatch::NOT_FOUND' => {
36             isa => 'CGI::Application::Dispatch::Exception',
37             alias => 'throw_not_found',
38             description => 404,
39             },
40             'CGI::Application::Dispatch::BAD_REQUEST' => {
41             isa => 'CGI::Application::Dispatch::Exception',
42             alias => 'throw_bad_request',
43             description => 400,
44             },
45 1     1   966 );
  1         17597  
46              
47             =pod
48              
49             =head1 NAME
50              
51             CGI::Application::Dispatch - Dispatch requests to CGI::Application based objects
52              
53             =head1 SYNOPSIS
54              
55             =head2 Out of Box
56              
57             Under mod_perl:
58              
59            
60             SetHandler perl-script
61             PerlHandler CGI::Application::Dispatch
62            
63              
64             Under normal cgi:
65              
66             This would be the instance script for your application, such
67             as /cgi-bin/dispatch.cgi:
68              
69             #!/usr/bin/perl
70             use FindBin::Real 'Bin';
71             use lib Bin() . '/../../rel/path/to/my/perllib';
72             use CGI::Application::Dispatch;
73             CGI::Application::Dispatch->dispatch();
74              
75             =head2 With a dispatch table
76              
77             package MyApp::Dispatch;
78             use base 'CGI::Application::Dispatch';
79              
80             sub dispatch_args {
81             return {
82             prefix => 'MyApp',
83             table => [
84             '' => { app => 'Welcome', rm => 'start' },
85             ':app/:rm' => { },
86             'admin/:app/:rm' => { prefix => 'MyApp::Admin' },
87             ],
88             };
89             }
90              
91             Under mod_perl:
92              
93            
94             SetHandler perl-script
95             PerlHandler MyApp::Dispatch
96            
97              
98             Under normal cgi:
99              
100             This would be the instance script for your application, such
101             as /cgi-bin/dispatch.cgi:
102              
103             #!/usr/bin/perl
104             use FindBin::Real 'Bin';
105             use lib Bin() . '/../../rel/path/to/my/perllib';
106             use MyApp::Dispatch;
107             MyApp::Dispatch->dispatch();
108              
109             =head1 DESCRIPTION
110              
111             This module provides a way (as a mod_perl handler or running under
112             vanilla CGI) to look at the path (as returned by L) of
113             the incoming request, parse off the desired module and its run mode,
114             create an instance of that module and run it.
115              
116             It currently supports both generations of mod_perl (1.x and
117             2.x). Although, for simplicity, all examples involving Apache
118             configuration and mod_perl code will be shown using mod_perl 1.x.
119             This may change as mp2 usage increases.
120              
121             It will translate a URI like this (under mod_perl):
122              
123             /app/module_name/run_mode
124              
125             or this (vanilla cgi)
126              
127             /app/index.cgi/module_name/run_mode
128              
129             into something that will be functionally similar to this
130              
131             my $app = Module::Name->new(..);
132             $app->mode_param(sub {'run_mode'}); #this will set the run mode
133              
134             =head1 METHODS
135              
136             =head2 dispatch(%args)
137              
138             This is the primary method used during dispatch. Even under mod_perl,
139             the L method uses this under the hood.
140              
141             #!/usr/bin/perl
142             use strict;
143             use CGI::Application::Dispatch;
144              
145             CGI::Application::Dispatch->dispatch(
146             prefix => 'MyApp',
147             default => 'module_name',
148             );
149              
150             This method accepts the following name value pairs:
151              
152             =over
153              
154             =item default
155              
156             Specify a value to use for the path if one is not available.
157             This could be the case if the default page is selected (eg: "/" ).
158              
159             =item prefix
160              
161             This option will set the string that will be prepended to the name of
162             the application module before it is loaded and created. So to use our
163             previous example request of
164              
165             /app/index.cgi/module_name/run_mode
166              
167             This would by default load and create a module named
168             'Module::Name'. But let's say that you have all of your application
169             specific modules under the 'My' namespace. If you set this option to
170             'My' then it would instead load the 'My::Module::Name' application
171             module instead.
172              
173             =item args_to_new
174              
175             This is a hash of arguments that are passed into the C
176             constructor of the application.
177              
178             =item table
179              
180             In most cases, simply using Dispatch with the C and C
181             is enough to simplify your application and your URLs, but there are
182             many cases where you want more power. Enter the dispatch table. Since
183             this table can be slightly complicated, a whole section exists on its
184             use. Please see the L section.
185              
186             =item debug
187              
188             Set to a true value to send debugging output for this module to
189             STDERR. Off by default.
190              
191             =item error_document
192              
193             This string is similar to Apache ErrorDocument directive. If this value is not
194             present, then Dispatch will return a NOT FOUND error either to the browser with
195             simple hardcoded message (under CGI) or to Apache (under mod_perl).
196              
197             This value can be one of the following:
198              
199             B
200             - if it starts with a single double-quote character (C<">). This double-quote
201             character will be trimmed from final output.
202              
203             B
204             - if it starts with less-than sign (C<<>). First character will be excluded
205             as well. Path of this file should be relative to server DOCUMENT_ROOT.
206              
207             B - if no leading C<"> or
208             C<<> will be found.
209              
210             Custom messages will be displayed I. (Under
211             mod_perl, please use ErrorDocument directive in Apache configuration files.)
212             This value can contain C<%s> placeholder for L Perl function. This
213             placeholder will be replaced with numeric HTTP error code. Currently
214             CGI::Application::Dispatch uses three HTTP errors:
215              
216             B<400 Bad Request>
217             - If there are invalid characters in module name (parameter :app) or
218             runmode name (parameter :rm).
219              
220             B<404 Not Found>
221             - When the path does not match anything in the L,
222             or module could not be found in @INC, or run mode did not exist.
223              
224             B<500 Internal Server Error>
225             - If application error occurs.
226              
227             Examples of using error_document (assume error 404 have been returned):
228              
229             # return in browser 'Opss... HTTP Error #404'
230             error_document => '"Opss... HTTP Error #%s'
231              
232             # return contents of file $ENV{DOCUMENT_ROOT}/errors/error404.html
233             error_document => '
234              
235             # internal redirect to /errors/error404.html
236             error_document => '/errors/error%s.html'
237              
238             # external redirect to
239             # http://host.domain/cgi-bin/errors.cgi?error=404
240             error_document => 'http://host.domain/cgi-bin/errors.cgi?error=%s'
241              
242             =item auto_rest
243              
244             This tells Dispatch that you are using REST by default and that you
245             care about which HTTP method is being used. Dispatch will append the
246             HTTP method name (upper case by default) to the run mode that is
247             determined after finding the appropriate dispatch rule. So a GET
248             request that translates into C<< MyApp::Module->foo >> will become
249             C<< MyApp::Module->foo_GET >>.
250              
251             This can be overridden on a per-rule basis in a custom dispatch table.
252              
253             =item auto_rest_lc
254              
255             In combinaion with L this tells Dispatch that you prefer
256             lower cased HTTP method names. So instead of C and
257             C you'll have C and C.
258              
259             =back
260              
261             =cut
262              
263             sub dispatch {
264 22     22 1 11593 my ($self, %args) = @_;
265              
266             # merge dispatch_args() and %args with %args taking precendence
267 22         95 my $dispatch_args = $self->dispatch_args(\%args);
268 22         229 for my $arg (keys %$dispatch_args) {
269              
270             # args_to_new should be merged
271 79 100       143 if($arg eq 'args_to_new') {
272 22   100     93 $args{args_to_new} ||= {};
273              
274             # merge the PARAMS hash
275 22 100       64 if($dispatch_args->{args_to_new}->{PARAMS}) {
276              
277             # merge the hashes
278 10         23 $args{args_to_new}->{PARAMS} = {
279 10 100       60 %{$dispatch_args->{args_to_new}->{PARAMS}},
280 10         16 %{$args{args_to_new}->{PARAMS} || {}},
281             };
282             }
283              
284             # combine any TMPL_PATHs
285 22 50       61 if($dispatch_args->{args_to_new}->{TMPL_PATH}) {
286              
287             # make sure the orginial is an array ref
288 0 0       0 if($args{args_to_new}->{TMPL_PATH}) {
289 0 0       0 if(!ref $args{args_to_new}->{TMPL_PATH}) {
290 0         0 $args{args_to_new}->{TMPL_PATH} = [$args{args_to_new}->{TMPL_PATH}];
291             }
292             } else {
293 0         0 $args{args_to_new}->{TMPL_PATH} = [];
294             }
295              
296             # now add the rest to the end
297 0 0       0 if(ref $dispatch_args->{args_to_new}->{TMPL_PATH}) {
298 0         0 push(
299 0         0 @{$args{args_to_new}->{TMPL_PATH}},
300 0         0 @{$dispatch_args->{args_to_new}->{TMPL_PATH}},
301             );
302             } else {
303 0         0 push(
304 0         0 @{$args{args_to_new}->{TMPL_PATH}},
305             $dispatch_args->{args_to_new}->{TMPL_PATH},
306             );
307             }
308             }
309              
310             # now merge the args_to_new hashes
311 22         29 $args{args_to_new} = {%{$dispatch_args->{args_to_new}}, %{$args{args_to_new}},};
  22         48  
  22         89  
312             } else {
313              
314             # anything else should override
315 57 100       186 $args{$arg} = $dispatch_args->{$arg} unless exists $args{$arg};
316             }
317             }
318              
319 22 50       62 $DEBUG = $args{debug} ? 1 : 0;
320              
321             # check for extra args (for backwards compatibility)
322 22         55 for (keys %args) {
323             next
324 80 50 100     673 if( $_ eq 'prefix'
      66        
      66        
      66        
      100        
      66        
      66        
      33        
      33        
325             or $_ eq 'default'
326             or $_ eq 'debug'
327             or $_ eq 'rm'
328             or $_ eq 'args_to_new'
329             or $_ eq 'table'
330             or $_ eq 'auto_rest'
331             or $_ eq 'auto_rest_lc'
332             or $_ eq 'not_found'
333             or $_ eq 'error_document');
334 1         185 carp "Passing extra args ('$_') to dispatch() is deprecated! Please use 'args_to_new'";
335 1         33 $args{args_to_new}->{$_} = delete $args{$_};
336             }
337              
338             # TODO: delete this block some time later
339 22 50       70 if(exists $args{not_found}) {
340 0         0 carp 'Passing not_found to dispatch() is deprecated! Please use error_document instead';
341 0 0       0 $args{error_document} = delete($args{not_found})
342             unless exists($args{error_document});
343             }
344              
345 22         49 %args = map { lc $_ => $args{$_} } keys %args; # lc for backwards
  79         241  
346             # compatability
347              
348             # get the PATH_INFO
349 22         90 my $path_info = $self->dispatch_path();
350              
351             # use the 'default' if we need to
352 22 100 50     113 $path_info = $args{default} || '' if(!$path_info || $path_info eq '/');
      100        
353              
354             # make sure they all start and end with a '/', to correspond with
355             # the RE we'll make
356 22 100       59 $path_info = "/$path_info" unless(index($path_info, '/') == 0);
357 22 100       66 $path_info = "$path_info/" unless(substr($path_info, -1) eq '/');
358              
359 22         27 my ($module, $rm, $local_prefix, $local_args_to_new, $output);
360              
361             # take args from path
362 0         0 my $named_args;
363             try {
364 22 100   22   670 $named_args = $self->_parse_path($path_info, $args{table})
365             or throw_not_found("Resource not found");
366             } catch {
367 1     1   383 $output = $self->http_error($_, $args{error_document});
368 22         158 };
369 22 100       317 return $output if $output;
370              
371 21 50       50 if($DEBUG) {
372 0         0 require Data::Dumper;
373 0         0 warn "[Dispatch] Named args from match: " . Data::Dumper::Dumper($named_args) . "\n";
374             }
375              
376             # eval and catch any exceptions that might be thrown
377             try {
378 21 50 33 21   615 if(exists($named_args->{PARAMS}) || exists($named_args->{TMPL_PATH})) {
379 0         0 carp "PARAMS and TMPL_PATH are not allowed here. Did you mean to use args_to_new?";
380 0         0 throw_error("PARAMS and TMPL_PATH not allowed");
381             }
382              
383 21         82 ($module, $local_prefix, $rm, $local_args_to_new) =
384 21         25 delete @{$named_args}{qw(app prefix rm args_to_new)};
385              
386             # If another name for dispatch_url_remainder has been set move
387             # the value to the requested name
388 21 100       67 if($$named_args{'*'}) {
389 1         4 $$named_args{$$named_args{'*'}} = $$named_args{'dispatch_url_remainder'};
390 1         2 delete $$named_args{'*'};
391 1         2 delete $$named_args{'dispatch_url_remainder'};
392             }
393              
394 21 100       46 $module or throw_error("App not defined");
395 20         64 $module = $self->translate_module_name($module);
396              
397 20   100     82 $local_prefix ||= $args{prefix};
398 20 100       55 $module = $local_prefix . '::' . $module if($local_prefix);
399              
400 20   66     70 $local_args_to_new ||= $args{args_to_new};
401              
402             # add the rest of the named_args to PARAMS
403 20         38 @{$local_args_to_new->{PARAMS}}{keys %$named_args} = values %$named_args;
  20         46  
404              
405 20 50       48 my $auto_rest =
406             defined $named_args->{auto_rest} ? $named_args->{auto_rest} : $args{auto_rest};
407 20 0 33     49 if($auto_rest && defined $rm && length $rm) {
      33        
408 0 0       0 my $method_lc =
409             defined $named_args->{auto_rest_lc}
410             ? $named_args->{auto_rest_lc}
411             : $args{auto_rest_lc};
412 0         0 my $http_method = $self->_http_method;
413 0 0       0 $http_method = lc $http_method if $method_lc;
414 0         0 $rm .= "_$http_method";
415             }
416              
417             # load and run the module
418 20         60 $self->require_module($module);
419 19         63 $output = $self->_run_app($module, $rm, $local_args_to_new);
420             } catch {
421 2     2   1242 my $e = $_;
422 2 50 33     18 unless ( ref $e && $e->isa('Exception::Class::Base') ) {
423 0         0 $e = Exception::Class::Base->new($e);
424             }
425 2         9 $output = $self->http_error($e, $args{error_document});
426 21         150 };
427 21         646 return $output;
428             }
429              
430              
431             =pod
432              
433             =head2 dispatch_path()
434              
435             This method returns the path that is to be processed.
436              
437             By default it returns the value of C<$ENV{PATH_INFO}>
438             (or C<< $r->path_info >> under mod_perl) which should work for
439             most cases. It allows the ability for subclasses to override the value if
440             they need to do something more specific.
441              
442             =cut
443              
444             sub dispatch_path {
445 23     23 1 103625 return $ENV{PATH_INFO};
446             }
447              
448             sub http_error {
449 3     3 0 7 my ($self, $e, $errdoc) = @_;
450              
451 3 50       25 warn '[Dispatch] ERROR'
452             . ($ENV{REQUEST_URI} ? " for request '$ENV{REQUEST_URI}': " : ': ')
453             . $e->error . "\n";
454              
455 3 50       102 my $errno =
456             $e->isa('CGI::Application::Dispatch::Exception')
457             ? $e->description
458             : 500;
459              
460 3         8 my ($url, $output);
461              
462 3 50       11 if($errdoc) {
463 0         0 my $str = sprintf($errdoc, $errno);
464 0 0       0 if(IS_MODPERL) { #compile out all other stuff
    0          
465             $url = $str; # no messages, please
466 0         0 } elsif(index($str, '"') == 0) { # Error message
467 0         0 $output = substr($str, 1);
468             } elsif(index($str, '<') == 0) { # Local file
469             # Is it secure?
470 0         0 require File::Spec;
471 0         0 $str = File::Spec->catdir($ENV{DOCUMENT_ROOT}, substr($str, 1));
472 0         0 local *FH;
473 0 0 0     0 if(-f $str && open(FH, '<', $str)) {
474 0         0 local $/ = undef;
475 0         0 $output = ;
476 0         0 close FH;
477             } else {
478 0         0 warn "[Dispatch] Error opening error document '$str'.\n";
479             }
480             } else { # Last case is url
481 0         0 $url = $str;
482             }
483              
484 0 0       0 if($DEBUG) {
485 0 0       0 warn "[Dispatch] Redirection for HTTP error #$errno to $url\n"
486             if $url;
487 0 0       0 warn "[Dispatch] Displaying message for HTTP error #$errno\n"
488             if $output;
489             }
490              
491             }
492              
493             # if we're under mod_perl
494 3         4 if(IS_MODPERL) {
495             my $r = $self->_r;
496             $r->status($errno);
497              
498             # if we just want to redirect
499             $r->headers_out->{'Location'} = $url if $url;
500             return '';
501             } else { # else print the HTTP stuff ourselves
502              
503             # stolen from http_protocol.c in Apache sources
504             # we don't actually use anything other than 200, 307, 400, 404 and 500
505              
506 3         19 my %status_lines = (
507              
508             # 100 => 'Continue',
509             # 101 => 'Switching Protocols',
510             # 102 => 'Processing',
511             200 => 'OK',
512              
513             # 201 => 'Created',
514             # 202 => 'Accepted',
515             # 203 => 'Non-Authoritative Information',
516             # 204 => 'No Content',
517             # 205 => 'Reset Content',
518             # 206 => 'Partial Content',
519             # 207 => 'Multi-Status',
520             # 300 => 'Multiple Choices',
521             # 301 => 'Moved Permanently',
522             # 302 => 'Found',
523             # 303 => 'See Other',
524             # 304 => 'Not Modified',
525             # 305 => 'Use Proxy',
526             307 => 'Temporary Redirect',
527             400 => 'Bad Request',
528              
529             # 401 => 'Authorization Required',
530             # 402 => 'Payment Required',
531             # 403 => 'Forbidden',
532             404 => 'Not Found',
533              
534             # 405 => 'Method Not Allowed',
535             # 406 => 'Not Acceptable',
536             # 407 => 'Proxy Authentication Required',
537             # 408 => 'Request Time-out',
538             # 409 => 'Conflict',
539             # 410 => 'Gone',
540             # 411 => 'Length Required',
541             # 412 => 'Precondition Failed',
542             # 413 => 'Request Entity Too Large',
543             # 414 => 'Request-URI Too Large',
544             # 415 => 'Unsupported Media Type',
545             # 416 => 'Requested Range Not Satisfiable',
546             # 417 => 'Expectation Failed',
547             # 422 => 'Unprocessable Entity',
548             # 423 => 'Locked',
549             # 424 => 'Failed Dependency',
550             500 => 'Internal Server Error',
551              
552             # 501 => 'Method Not Implemented',
553             # 502 => 'Bad Gateway',
554             # 503 => 'Service Temporarily Unavailable',
555             # 504 => 'Gateway Time-out',
556             # 505 => 'HTTP Version Not Supported',
557             # 506 => 'Variant Also Negotiates',
558             # 507 => 'Insufficient Storage',
559             # 510 => 'Not Extended',
560             );
561              
562 3 50       10 $errno = 500 if(!exists $status_lines{$errno});
563              
564 3 50       7 if($url) {
565              
566             # somewhat mailformed header, no errors in access.log, but browsers
567             # display contents of $url document and old URI in address bar.
568 0         0 $output = "HTTP/1.0 $errno $status_lines{$errno}\n";
569 0         0 $output .= "Location: $url\n\n";
570             } else {
571              
572 3 50       8 unless($output) {
573              
574             # TODO: possibly provide more feedback in a way that
575             # is XSS safe. (I'm not sure that passing through the
576             # raw ENV variable directly is safe.)
577             #

We tried: $ENV{REQUEST_URI}

";
578 3 50 50     39 $output = qq(
    50          
579            
580            
581             $errno $status_lines{$errno}
582             )
583             . (
584             $DEBUG
585             ? '

' . __PACKAGE__ . ' error!

'
586             : ''
587             )
588             . qq(

$status_lines{$errno}

589            

)
590             . ($ENV{SERVER_ADMIN} ? "($ENV{SERVER_ADMIN})" : '') . qq(

591            
)
592             . ($ENV{SERVER_SIGNATURE} || '') . qq();
593             }
594              
595             # Apache will report $errno in access.log
596 3         8 my $header .= "Status: $errno $status_lines{$errno}\n";
597              
598             # try to guess, what a crap we get here
599 3 50       15 $header .=
600             $output =~ /
601             ? "Content-type: text/html\n\n"
602             : "Content-type: text/plain\n\n";
603              
604             # Workaround for IE error document 512 byte size "feature"
605 3 50       16 $output .= ' ' x (520 - length($output))
606             if(length($output) < 520);
607              
608 3         10 $output = $header . $output;
609             }
610              
611             # Send output to browser (unless we're in serious debug mode!)
612 3 50       10 print $output unless $ENV{CGI_APP_RETURN_ONLY};
613              
614 3         73 return $output;
615             }
616             }
617              
618             # protected method - designed to be used by sub classes, not by end users
619             sub _parse_path {
620 22     22   38 my ($self, $path, $table) = @_;
621              
622             # get the module name from the table
623 22 50       48 return unless defined($path);
624              
625 22 50       59 unless(ref($table) eq 'ARRAY') {
626 0         0 warn "[Dispatch] Invalid or no dispatch table!\n";
627 0         0 return;
628             }
629              
630             # look at each rule and stop when we get a match
631 22         53 for(my $i = 0 ; $i < scalar(@$table) ; $i += 2) {
632              
633 60         100 my $rule = $table->[$i];
634              
635             # are we trying to dispatch based on HTTP_METHOD?
636 60         197 my $http_method_regex = qr/\[([^\]]+)\]$/;
637 60 50       231 if($rule =~ /$http_method_regex/) {
638 0         0 my $http_method = $1;
639              
640             # go ahead to the next rule
641 0 0       0 next unless lc($1) eq lc($self->_http_method);
642              
643             # remove the method portion from the rule
644 0         0 $rule =~ s/$http_method_regex//;
645             }
646              
647             # make sure they start and end with a '/' to match how
648             # PATH_INFO is formatted
649 60 50       173 $rule = "/$rule" unless(index($rule, '/') == 0);
650 60 50       150 $rule = "$rule/" if(substr($rule, -1) ne '/');
651              
652 60         91 my @names = ();
653              
654             # translate the rule into a regular expression, but remember
655             # where the named args are
656             # '/:foo' will become '/([^\/]*)'
657             # and
658             # '/:bar?' will become '/?([^\/]*)?'
659             # and then remember which position it matches
660              
661 60         279 $rule =~ s{
662             (^|/) # beginning or a /
663             (:([^/\?]+)(\?)?) # stuff in between
664             }{
665 84         162 push(@names, $3);
666 84 100       367 $1 . ($4 ? '?([^/]*)?' : '([^/]*)')
667             }gxe;
668              
669             # '/*/' will become '/(.*)/$' the end / is added to the end of
670             # both $rule and $path elsewhere
671 60 100       157 if($rule =~ m{/\*/$}) {
672 9         33 $rule =~ s{/\*/$}{/(.*)/\$};
673 9         14 push(@names, 'dispatch_url_remainder');
674             }
675              
676             warn
677 60 50       100 "[Dispatch] Trying to match '${path}' against rule '$table->[$i]' using regex '${rule}'\n"
678             if $DEBUG;
679              
680             # if we found a match, then run with it
681 60 100       1436 if(my @values = ($path =~ m#^$rule$#)) {
682              
683 21 50       49 warn "[Dispatch] Matched!\n" if $DEBUG;
684              
685 21         24 my %named_args = %{$table->[++$i]};
  21         168  
686 21 100       87 @named_args{@names} = @values if @names;
687              
688 21         140 return \%named_args;
689             }
690             }
691              
692 1         7 return;
693             }
694              
695             sub _http_method {
696 0 0   0   0 IS_MODPERL ? shift->_r->method : ($ENV{HTTP_REQUEST_METHOD} || $ENV{REQUEST_METHOD});
697             }
698              
699 0     0   0 sub _r { IS_MODPERL2 ? Apache2::RequestUtil->request: Apache->request; }
700              
701             sub _run_app {
702 19     19   37 my ($self, $module, $rm, $args) = @_;
703              
704 19 50       41 if($DEBUG) {
705 0         0 require Data::Dumper;
706 0         0 warn "[Dispatch] Final args to pass to new(): " . Data::Dumper::Dumper($args) . "\n";
707             }
708              
709 19 100       37 if($rm) {
710              
711             # check runmode name
712 17         67 ($rm) = ($rm =~ /^([a-zA-Z_][\w']+)$/);
713 17 50       46 throw_bad_request("Invalid characters in runmode name") unless $rm;
714             }
715              
716             # now create and run then application object
717 19 50       41 warn "[Dispatch] creating instance of $module\n" if($DEBUG);
718              
719 19         25 my $output;
720 19         27 eval {
721 19 50       110 my $app = ref($args) eq 'HASH' ? $module->new($args) : $module->new();
722 19 100   17   3843 $app->mode_param(sub { return $rm }) if($rm);
  17         31720  
723 19         234 $output = $app->run();
724             };
725              
726 19 50       10060 if($@) {
727              
728             # catch invalid run-mode stuff
729 0 0 0     0 if(not ref $@ and $@ =~ /No such run mode/) {
730 0         0 throw_not_found("RM '$rm' not found")
731              
732             # otherwise, just pass it up the chain
733             } else {
734 0         0 die $@;
735             }
736             }
737              
738 19         64 return $output;
739             }
740              
741             =head2 handler()
742              
743             This method is used so that this module can be run as a mod_perl handler.
744             When it creates the application module it passes the $r argument into the PARAMS
745             hash of new()
746              
747            
748             SetHandler perl-script
749             PerlHandler CGI::Application::Dispatch
750             PerlSetVar CGIAPP_DISPATCH_PREFIX MyApp
751             PerlSetVar CGIAPP_DISPATCH_DEFAULT /module_name
752            
753              
754             The above example would tell apache that any url beginning with /app
755             will be handled by CGI::Application::Dispatch. It also sets the prefix
756             used to create the application module to 'MyApp' and it tells
757             CGI::Application::Dispatch that it shouldn't set the run mode but that
758             it will be determined by the application module as usual (through the
759             query string). It also sets a default application module to be used if
760             there is no path. So, a url of C would create an
761             instance of C.
762              
763             Using this method will add the Crequest> object to your
764             application's C as 'r'.
765              
766             # inside your app
767             my $request = $self->param('r');
768              
769             If you need more customization than can be accomplished with just
770             L and L, then it would be best to just subclass
771             CGI::Application::Dispatch and override L since
772             C uses L to do the heavy lifting.
773              
774             package MyApp::Dispatch;
775             use base 'CGI::Application::Dispatch';
776              
777             sub dispatch_args {
778             return {
779             prefix => 'MyApp',
780             table => [
781             '' => { app => 'Welcome', rm => 'start' },
782             ':app/:rm' => { },
783             'admin/:app/:rm' => { prefix => 'MyApp::Admin' },
784             ],
785             args_to_new => {
786             PARAMS => {
787             foo => 'bar',
788             baz => 'bam',
789             },
790             }
791             };
792             }
793              
794             1;
795              
796             And then in your httpd.conf
797              
798            
799             SetHandler perl-script
800             PerlHandler MyApp::Dispatch
801            
802              
803             =cut
804              
805             sub handler : method {
806 0     0 1 0 my ($self, $r) = @_;
807              
808             # set the PATH_INFO
809 0         0 $ENV{PATH_INFO} = $r->path_info();
810              
811             # setup our args to dispatch()
812 0         0 my %args;
813 0         0 my $config_args = $r->dir_config();
814 0         0 for my $var (qw(DEFAULT PREFIX ERROR_DOCUMENT)) {
815 0         0 my $dir_var = "CGIAPP_DISPATCH_$var";
816 0 0       0 $args{lc($var)} = $config_args->{$dir_var}
817             if($config_args->{$dir_var});
818             }
819              
820             # add $r to the args_to_new's PARAMS
821 0         0 $args{args_to_new}->{PARAMS}->{r} = $r;
822              
823             # set debug if we need to
824 0 0       0 $DEBUG = 1 if($config_args->{CGIAPP_DISPATCH_DEBUG});
825 0 0       0 if($DEBUG) {
826 0         0 require Data::Dumper;
827 0         0 warn "[Dispatch] Calling dispatch() with the following arguments: "
828             . Data::Dumper::Dumper(\%args) . "\n";
829             }
830              
831 0         0 $self->dispatch(%args);
832              
833 0 0       0 if($r->status == 404) {
    0          
    0          
834 0         0 return NOT_FOUND();
835             } elsif($r->status == 500) {
836 0         0 return SERVER_ERROR();
837             } elsif($r->status == 400) {
838 0         0 return IS_MODPERL2() ? HTTP_BAD_REQUEST() : BAD_REQUEST();
839             } else {
840 0         0 return OK();
841             }
842             }
843              
844             =head2 dispatch_args()
845              
846             Returns a hashref of args that will be passed to L(). It
847             will return the following structure by default.
848              
849             {
850             prefix => '',
851             args_to_new => {},
852             table => [
853             ':app' => {},
854             ':app/:rm' => {},
855             ],
856             }
857              
858             This is the perfect place to override when creating a subclass to
859             provide a richer dispatch L.
860              
861             When called, it receives 1 argument, which is a reference to the hash
862             of args passed into L.
863              
864             =cut
865              
866             sub dispatch_args {
867 13     13 1 23 my ($self, $args) = @_;
868             return {
869 13   100     202 default => ($args->{default} || ''),
      100        
      100        
870             prefix => ($args->{prefix} || ''),
871             args_to_new => ($args->{args_to_new} || {}),
872             table => [
873             ':app' => {},
874             ':app/:rm' => {},
875             ],
876             };
877             }
878              
879             =head2 translate_module_name($input)
880              
881             This method is used to control how the module name is translated from
882             the matching section of the path (see L<"Path Parsing">).
883             The main
884             reason that this method exists is so that it can be overridden if it
885             doesn't do exactly what you want.
886              
887             The following transformations are performed on the input:
888              
889             =over
890              
891             =item The text is split on '_'s (underscores)
892             and each word has its first letter capitalized. The words are then joined
893             back together and each instance of an underscore is replaced by '::'.
894              
895              
896             =item The text is split on '-'s (hyphens)
897             and each word has its first letter capitalized. The words are then joined
898             back together and each instance of a hyphen removed.
899              
900             =back
901              
902             Here are some examples to make it even clearer:
903              
904             module_name => Module::Name
905             module-name => ModuleName
906             admin_top-scores => Admin::TopScores
907              
908             =cut
909              
910             sub translate_module_name {
911 19     19 1 105 my ($self, $input) = @_;
912              
913 19         54 $input = join('::', map { ucfirst($_) } split(/_/, $input));
  36         87  
914 19         48 $input = join('', map { ucfirst($_) } split(/-/, $input));
  19         38  
915              
916 19         51 return $input;
917             }
918              
919             =head2 require_module($module_name)
920              
921             This class method is used internally by CGI::Application::Dispatch to
922             take a module name (supplied by L) and require it in
923             a secure fashion. It is provided as a public class method so that if
924             you override other functionality of this module, you can still safely
925             require user specified modules. If there are any problems requiring
926             the named module, then we will C.
927              
928             CGI::Application::Dispatch->require_module('MyApp::Module::Name');
929              
930             =cut
931              
932             sub require_module {
933 20     20 1 32 my ($self, $module) = @_;
934              
935 20 50       38 $module or throw_not_found("Can't define module name");
936              
937             #untaint the module name
938 20         90 ($module) = ($module =~ /^([A-Za-z][A-Za-z0-9_\-\:\']+)$/);
939              
940 20 50       42 unless($module) {
941 0         0 throw_bad_request("Invalid characters in module name");
942             }
943              
944 20 50       43 warn "[Dispatch] loading module $module\n" if($DEBUG);
945 20         1178 eval "require $module";
946 20 100       87 return unless $@;
947              
948 1         3 my $module_path = $module;
949 1         3 $module_path =~ s/::/\//g;
950              
951 1 50       16 if($@ =~ /Can't locate $module_path.pm/) {
952 1         9 throw_not_found("Can't find module $module");
953             } else {
954 0           throw_error("Unable to load module '$module': $@");
955             }
956             }
957              
958             1;
959              
960             __END__