Add a test and update docs on how to pass-through the Authorization header under...
[catagits/Catalyst-Runtime.git] / lib / Catalyst / Dispatcher.pm
CommitLineData
68a748b9 1package Catalyst::Dispatcher;
1abd6db7 2
3use strict;
fbcc39ad 4use base 'Class::Accessor::Fast';
a2f2cde9 5use Catalyst::Exception;
f05af9ba 6use Catalyst::Utils;
fbcc39ad 7use Catalyst::Action;
b7aebc12 8use Catalyst::ActionContainer;
b96f127f 9use Catalyst::DispatchType::Default;
bcccee4e 10use Catalyst::DispatchType::Index;
87b85407 11use Text::SimpleTable;
1abd6db7 12use Tree::Simple;
13use Tree::Simple::Visitor::FindByPath;
e72f8f51 14use Scalar::Util ();
1abd6db7 15
fbcc39ad 16# Stringify to class
17use overload '""' => sub { return ref shift }, fallback => 1;
18
49070d25 19__PACKAGE__->mk_accessors(
20 qw/tree dispatch_types registered_dispatch_types
e7bb8d33 21 method_action_class action_container_class
22 preload_dispatch_types postload_dispatch_types
a13e21ab 23 action_hash container_hash
9e81ba44 24 /
49070d25 25);
6d030e6f 26
27# Preload these action types
61a9002d 28our @PRELOAD = qw/Index Path Regex/;
1abd6db7 29
2d1d8f91 30# Postload these action types
61a9002d 31our @POSTLOAD = qw/Default/;
2d1d8f91 32
1abd6db7 33=head1 NAME
34
9c053379 35Catalyst::Dispatcher - The Catalyst Dispatcher
1abd6db7 36
37=head1 SYNOPSIS
38
39See L<Catalyst>.
40
41=head1 DESCRIPTION
42
4ab87e27 43This is the class that maps public urls to actions in your Catalyst
44application based on the attributes you set.
45
1abd6db7 46=head1 METHODS
47
76ddf86b 48=head2 new
4ab87e27 49
50Construct a new dispatcher.
51
e7bb8d33 52=cut
53
54sub new {
9e81ba44 55 my $self = shift;
e7bb8d33 56 my $class = ref($self) || $self;
9e81ba44 57
58 my $obj = $class->SUPER::new(@_);
59
e7bb8d33 60 # set the default pre- and and postloads
9e81ba44 61 $obj->preload_dispatch_types( \@PRELOAD );
e7bb8d33 62 $obj->postload_dispatch_types( \@POSTLOAD );
a13e21ab 63 $obj->action_hash( {} );
64 $obj->container_hash( {} );
65
66 # Create the root node of the tree
67 my $container =
68 Catalyst::ActionContainer->new( { part => '/', actions => {} } );
69 $obj->tree( Tree::Simple->new( $container, Tree::Simple->ROOT ) );
70
9e81ba44 71 return $obj;
e7bb8d33 72}
73
74=head2 $self->preload_dispatch_types
75
76An arrayref of pre-loaded dispatchtype classes
77
78Entries are considered to be available as C<Catalyst::DispatchType::CLASS>
79To use a custom class outside the regular C<Catalyst> namespace, prefix
80it with a C<+>, like so:
81
82 +My::Dispatch::Type
83
84=head2 $self->postload_dispatch_types
85
86An arrayref of post-loaded dispatchtype classes
87
88Entries are considered to be available as C<Catalyst::DispatchType::CLASS>
89To use a custom class outside the regular C<Catalyst> namespace, prefix
90it with a C<+>, like so:
91
92 +My::Dispatch::Type
93
b5ecfcf0 94=head2 $self->detach( $c, $command [, \@arguments ] )
6ef62eb2 95
4ab87e27 96Documented in L<Catalyst>
97
6ef62eb2 98=cut
99
100sub detach {
fbcc39ad 101 my ( $self, $c, $command, @args ) = @_;
bd7d2e94 102 $c->forward( $command, @args ) if $command;
fbcc39ad 103 die $Catalyst::DETACH;
6ef62eb2 104}
105
b5ecfcf0 106=head2 $self->dispatch($c)
1abd6db7 107
4ab87e27 108Delegate the dispatch to the action that matched the url, or return a
109message about unknown resource
110
111
1abd6db7 112=cut
113
114sub dispatch {
fbcc39ad 115 my ( $self, $c ) = @_;
66e28e3f 116 if ( $c->action ) {
28591cd7 117 $c->forward( join( '/', '', $c->action->namespace, '_DISPATCH' ) );
fbcc39ad 118 }
119
120 else {
1abd6db7 121 my $path = $c->req->path;
122 my $error = $path
123 ? qq/Unknown resource "$path"/
124 : "No default action defined";
125 $c->log->error($error) if $c->debug;
126 $c->error($error);
127 }
128}
129
b5ecfcf0 130=head2 $self->forward( $c, $command [, \@arguments ] )
1abd6db7 131
4ab87e27 132Documented in L<Catalyst>
133
1abd6db7 134=cut
135
136sub forward {
e72f8f51 137 my ( $self, $c, $command, @extra_params ) = @_;
99fe1710 138
1abd6db7 139 unless ($command) {
140 $c->log->debug('Nothing to forward to') if $c->debug;
141 return 0;
142 }
99fe1710 143
e72f8f51 144 my @args;
145
146 if ( ref( $extra_params[-1] ) eq 'ARRAY' ) {
147 @args = @{ pop @extra_params }
148 } else {
149 # this is a copy, it may take some abuse from ->_invoke_as_path if the path had trailing parts
150 @args = @{ $c->request->arguments };
151 }
152
153 my $action;
154
155 # forward to a string path ("/foo/bar/gorch") or action object which stringifies to that
156 $action = $self->_invoke_as_path( $c, "$command", \@args );
99fe1710 157
e72f8f51 158 # forward to a component ( "MyApp::*::Foo" or $c->component("...") - a path or an object)
159 unless ($action) {
160 my $method = @extra_params ? $extra_params[0] : "process";
161 $action = $self->_invoke_as_component( $c, $command, $method );
162 }
99fe1710 163
99fe1710 164
e540158b 165 unless ($action) {
166 my $error =
167 qq/Couldn't forward to command "$command": /
168 . qq/Invalid action or component./;
169 $c->error($error);
170 $c->log->debug($error) if $c->debug;
171 return 0;
172 }
bd7d2e94 173
e540158b 174 #push @$args, @_;
adb53907 175
e72f8f51 176 local $c->request->{arguments} = \@args;
b8f669f3 177 $action->dispatch( $c );
99fe1710 178
1abd6db7 179 return $c->state;
180}
181
adb53907 182sub _action_rel2abs {
e540158b 183 my ( $self, $c, $path ) = @_;
184
185 unless ( $path =~ m#^/# ) {
186 my $namespace = $c->stack->[-1]->namespace;
187 $path = "$namespace/$path";
188 }
189
190 $path =~ s#^/##;
191 return $path;
adb53907 192}
193
194sub _invoke_as_path {
e540158b 195 my ( $self, $c, $rel_path, $args ) = @_;
196
e540158b 197 my $path = $self->_action_rel2abs( $c, $rel_path );
198
199 my ( $tail, @extra_args );
200 while ( ( $path, $tail ) = ( $path =~ m#^(?:(.*)/)?(\w+)?$# ) )
201 { # allow $path to be empty
202 if ( my $action = $c->get_action( $tail, $path ) ) {
203 push @$args, @extra_args;
204 return $action;
205 }
206 else {
207 return
208 unless $path
209 ; # if a match on the global namespace failed then the whole lookup failed
210 }
211
212 unshift @extra_args, $tail;
213 }
adb53907 214}
215
216sub _find_component_class {
e540158b 217 my ( $self, $c, $component ) = @_;
adb53907 218
e540158b 219 return ref($component)
220 || ref( $c->component($component) )
221 || $c->component($component);
adb53907 222}
223
224sub _invoke_as_component {
e540158b 225 my ( $self, $c, $component, $method ) = @_;
226
227 my $class = $self->_find_component_class( $c, $component ) || return 0;
e540158b 228
229 if ( my $code = $class->can($method) ) {
230 return $self->method_action_class->new(
231 {
232 name => $method,
233 code => $code,
234 reverse => "$class->$method",
235 class => $class,
236 namespace => Catalyst::Utils::class2prefix(
237 $class, $c->config->{case_sensitive}
238 ),
239 }
240 );
241 }
242 else {
243 my $error =
244 qq/Couldn't forward to "$class". Does not implement "$method"/;
245 $c->error($error);
246 $c->log->debug($error)
247 if $c->debug;
248 return 0;
249 }
adb53907 250}
251
b5ecfcf0 252=head2 $self->prepare_action($c)
fbcc39ad 253
4ab87e27 254Find an dispatch type that matches $c->req->path, and set args from it.
255
fbcc39ad 256=cut
257
258sub prepare_action {
259 my ( $self, $c ) = @_;
260 my $path = $c->req->path;
261 my @path = split /\//, $c->req->path;
262 $c->req->args( \my @args );
263
61a9002d 264 unshift( @path, '' ); # Root action
78d760bb 265
b96f127f 266 DESCEND: while (@path) {
fbcc39ad 267 $path = join '/', @path;
61a9002d 268 $path =~ s#^/##;
fbcc39ad 269
61a9002d 270 $path = '' if $path eq '/'; # Root action
78d760bb 271
22f3a8dd 272 # Check out dispatch types to see if any will handle the path at
273 # this level
274
78d760bb 275 foreach my $type ( @{ $self->dispatch_types } ) {
2633d7dc 276 last DESCEND if $type->match( $c, $path );
66e28e3f 277 }
b96f127f 278
22f3a8dd 279 # If not, move the last part path to args
4082e678 280 my $arg = pop(@path);
281 $arg =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg;
282 unshift @args, $arg;
fbcc39ad 283 }
284
cccc8f68 285 s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg for grep { defined } @{$c->req->captures||[]};
66d7ad40 286
e3a13771 287 $c->log->debug( 'Path is "' . $c->req->match . '"' )
288 if ( $c->debug && $c->req->match );
289
fbcc39ad 290 $c->log->debug( 'Arguments are "' . join( '/', @args ) . '"' )
291 if ( $c->debug && @args );
292}
293
b5ecfcf0 294=head2 $self->get_action( $action, $namespace )
1abd6db7 295
4ab87e27 296returns a named action from a given namespace.
297
1abd6db7 298=cut
299
300sub get_action {
bcd1002b 301 my ( $self, $name, $namespace ) = @_;
79a3189a 302 return unless $name;
3d0d6d21 303
28928de9 304 $namespace = join( "/", grep { length } split '/', $namespace || "" );
99fe1710 305
a13e21ab 306 return $self->action_hash->{"$namespace/$name"};
1abd6db7 307}
308
34d28dfd 309=head2 $self->get_action_by_path( $path );
310
311Returns the named action by its full path.
3d0d6d21 312
34d28dfd 313=cut
3d0d6d21 314
315sub get_action_by_path {
316 my ( $self, $path ) = @_;
ea0e58d9 317 $path =~ s/^\///;
28928de9 318 $path = "/$path" unless $path =~ /\//;
3d0d6d21 319 $self->action_hash->{$path};
320}
321
b5ecfcf0 322=head2 $self->get_actions( $c, $action, $namespace )
a9dc674c 323
324=cut
325
326sub get_actions {
327 my ( $self, $c, $action, $namespace ) = @_;
328 return [] unless $action;
3d0d6d21 329
28928de9 330 $namespace = join( "/", grep { length } split '/', $namespace || "" );
a9dc674c 331
332 my @match = $self->get_containers($namespace);
333
684d10ed 334 return map { $_->get_action($action) } @match;
a9dc674c 335}
336
b5ecfcf0 337=head2 $self->get_containers( $namespace )
cfd04b0c 338
4ab87e27 339Return all the action containers for a given namespace, inclusive
340
cfd04b0c 341=cut
342
343sub get_containers {
344 my ( $self, $namespace ) = @_;
a13e21ab 345 $namespace ||= '';
346 $namespace = '' if $namespace eq '/';
cfd04b0c 347
a13e21ab 348 my @containers;
cfd04b0c 349
7f23827b 350 if ( length $namespace ) {
351 do {
352 push @containers, $self->container_hash->{$namespace};
353 } while ( $namespace =~ s#/[^/]+$## );
354 }
90ce41ba 355
a13e21ab 356 return reverse grep { defined } @containers, $self->container_hash->{''};
90ce41ba 357
a13e21ab 358 my @parts = split '/', $namespace;
cfd04b0c 359}
360
ea0e58d9 361=head2 $self->uri_for_action($action, \@captures)
362
363Takes a Catalyst::Action object and action parameters and returns a URI
364part such that if $c->req->path were this URI part, this action would be
365dispatched to with $c->req->captures set to the supplied arrayref.
366
367If the action object is not available for external dispatch or the dispatcher
368cannot determine an appropriate URI, this method will return undef.
369
370=cut
371
372sub uri_for_action {
373 my ( $self, $action, $captures) = @_;
374 $captures ||= [];
375 foreach my $dispatch_type ( @{ $self->dispatch_types } ) {
376 my $uri = $dispatch_type->uri_for_action( $action, $captures );
81e75875 377 return( $uri eq '' ? '/' : $uri )
378 if defined($uri);
ea0e58d9 379 }
380 return undef;
381}
382
b5ecfcf0 383=head2 $self->register( $c, $action )
aad72cc9 384
4ab87e27 385Make sure all required dispatch types for this action are loaded, then
386pass the action to our dispatch types so they can register it if required.
387Also, set up the tree with the action containers.
388
aad72cc9 389=cut
390
79a3189a 391sub register {
392 my ( $self, $c, $action ) = @_;
393
694d15f1 394 my $registered = $self->registered_dispatch_types;
395
396 my $priv = 0;
397 foreach my $key ( keys %{ $action->attributes } ) {
9a6ecf4f 398 next if $key eq 'Private';
694d15f1 399 my $class = "Catalyst::DispatchType::$key";
400 unless ( $registered->{$class} ) {
401 eval "require $class";
402 push( @{ $self->dispatch_types }, $class->new ) unless $@;
403 $registered->{$class} = 1;
404 }
405 }
406
407 # Pass the action to our dispatch types so they can register it if reqd.
01ce0928 408 foreach my $type ( @{ $self->dispatch_types } ) {
9a6ecf4f 409 $type->register( $c, $action );
694d15f1 410 }
411
79a3189a 412 my $namespace = $action->namespace;
a13e21ab 413 my $name = $action->name;
c7116517 414
ad5e4650 415 my $container = $self->_find_or_create_action_container($namespace);
15e9b5dd 416
417 # Set the method value
a13e21ab 418 $container->add_action($action);
c7116517 419
a13e21ab 420 $self->action_hash->{"$namespace/$name"} = $action;
421 $self->container_hash->{$namespace} = $container;
15e9b5dd 422}
423
ad5e4650 424sub _find_or_create_action_container {
a13e21ab 425 my ( $self, $namespace ) = @_;
426
427 my $tree ||= $self->tree;
99fe1710 428
a13e21ab 429 return $tree->getNodeValue unless $namespace;
78d760bb 430
a13e21ab 431 my @namespace = split '/', $namespace;
432 return $self->_find_or_create_namespace_node( $tree, @namespace )
433 ->getNodeValue;
8505565b 434}
90ce41ba 435
8505565b 436sub _find_or_create_namespace_node {
a13e21ab 437 my ( $self, $parent, $part, @namespace ) = @_;
78d760bb 438
a13e21ab 439 return $parent unless $part;
8505565b 440
a13e21ab 441 my $child =
442 ( grep { $_->getNodeValue->part eq $part } $parent->getAllChildren )[0];
8505565b 443
a13e21ab 444 unless ($child) {
445 my $container = Catalyst::ActionContainer->new($part);
446 $parent->addChild( $child = Tree::Simple->new($container) );
447 }
99fe1710 448
a13e21ab 449 $self->_find_or_create_namespace_node( $child, @namespace );
1abd6db7 450}
451
4ab87e27 452=head2 $self->setup_actions( $class, $context )
453
1abd6db7 454
455=cut
456
457sub setup_actions {
11bd4e3e 458 my ( $self, $c ) = @_;
99fe1710 459
6d030e6f 460 $self->dispatch_types( [] );
91d4abc5 461 $self->registered_dispatch_types( {} );
49070d25 462 $self->method_action_class('Catalyst::Action');
463 $self->action_container_class('Catalyst::ActionContainer');
12e28165 464
9e81ba44 465 my @classes =
ad5e4650 466 $self->_load_dispatch_types( @{ $self->preload_dispatch_types } );
9e81ba44 467 @{ $self->registered_dispatch_types }{@classes} = (1) x @classes;
b96f127f 468
49070d25 469 foreach my $comp ( values %{ $c->components } ) {
470 $comp->register_actions($c) if $comp->can('register_actions');
1abd6db7 471 }
e494bd6b 472
ad5e4650 473 $self->_load_dispatch_types( @{ $self->postload_dispatch_types } );
6d030e6f 474
11bd4e3e 475 return unless $c->debug;
99fe1710 476
684d10ed 477 my $privates = Text::SimpleTable->new(
dbf03873 478 [ 20, 'Private' ],
34d28dfd 479 [ 36, 'Class' ],
dbf03873 480 [ 12, 'Method' ]
684d10ed 481 );
99fe1710 482
87b85407 483 my $has_private = 0;
1abd6db7 484 my $walker = sub {
485 my ( $walker, $parent, $prefix ) = @_;
486 $prefix .= $parent->getNodeValue || '';
487 $prefix .= '/' unless $prefix =~ /\/$/;
b7aebc12 488 my $node = $parent->getNodeValue->actions;
99fe1710 489
78d760bb 490 for my $action ( keys %{$node} ) {
b7aebc12 491 my $action_obj = $node->{$action};
b0bb11ec 492 next
493 if ( ( $action =~ /^_.*/ )
494 && ( !$c->config->{show_internal_actions} ) );
684d10ed 495 $privates->row( "$prefix$action", $action_obj->class, $action );
87b85407 496 $has_private = 1;
1abd6db7 497 }
99fe1710 498
1abd6db7 499 $walker->( $walker, $_, $prefix ) for $parent->getAllChildren;
500 };
99fe1710 501
1abd6db7 502 $walker->( $walker, $self->tree, '' );
1cf0345b 503 $c->log->debug( "Loaded Private actions:\n" . $privates->draw . "\n" )
504 if $has_private;
99fe1710 505
a9cbd748 506 # List all public actions
11bd4e3e 507 $_->list($c) for @{ $self->dispatch_types };
1abd6db7 508}
509
ad5e4650 510sub _load_dispatch_types {
9e81ba44 511 my ( $self, @types ) = @_;
512
513 my @loaded;
514
515 # Preload action types
516 for my $type (@types) {
517 my $class =
518 ( $type =~ /^\+(.*)$/ ) ? $1 : "Catalyst::DispatchType::${type}";
519 eval "require $class";
520 Catalyst::Exception->throw( message => qq/Couldn't load "$class"/ )
521 if $@;
522 push @{ $self->dispatch_types }, $class->new;
523
524 push @loaded, $class;
525 }
526
a13e21ab 527 return @loaded;
9e81ba44 528}
529
1abd6db7 530=head1 AUTHOR
531
532Sebastian Riedel, C<sri@cpan.org>
158c88c0 533Matt S Trout, C<mst@shadowcatsystems.co.uk>
1abd6db7 534
535=head1 COPYRIGHT
536
537This program is free software, you can redistribute it and/or modify it under
538the same terms as Perl itself.
539
540=cut
541
5421;