X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=catagits%2FCatalyst-Runtime.git;a=blobdiff_plain;f=lib%2FCatalyst%2FController.pm;h=466d4d91b860e97ad4c17cb3ca9f5d78d5d8274c;hp=b1c4c945da3282e02b79cf0323582e0f8d23b208;hb=82010ea176741c7a4f2baf3f6f27377b1d9f6b15;hpb=24d2dfaf82495fd2a914b02c748d73d323f12242 diff --git a/lib/Catalyst/Controller.pm b/lib/Catalyst/Controller.pm index b1c4c94..466d4d9 100644 --- a/lib/Catalyst/Controller.pm +++ b/lib/Catalyst/Controller.pm @@ -1,11 +1,18 @@ package Catalyst::Controller; use Moose; +use Class::MOP; +use Class::Load ':all'; +use String::RewritePrefix; use Moose::Util qw/find_meta/; +use List::Util qw/first/; use List::MoreUtils qw/uniq/; use namespace::clean -except => 'meta'; -BEGIN { extends qw/Catalyst::Component MooseX::MethodAttributes::Inheritable/; } +BEGIN { + extends qw/Catalyst::Component/; + with qw/MooseX::MethodAttributes::Role::AttrContainer::Inheritable/; +} use MooseX::MethodAttributes; use Catalyst::Exception; @@ -13,28 +20,57 @@ use Catalyst::Utils; with 'Catalyst::Component::ApplicationAttribute'; -has path_prefix => - ( - is => 'rw', - isa => 'Str', - init_arg => 'path', - predicate => 'has_path_prefix', - ); - -has action_namespace => - ( - is => 'rw', - isa => 'Str', - init_arg => 'namespace', - predicate => 'has_action_namespace', - ); - -has actions => - ( - accessor => '_controller_actions', - isa => 'HashRef', - init_arg => undef, - ); +has path_prefix => ( + is => 'rw', + isa => 'Str', + init_arg => 'path', + predicate => 'has_path_prefix', +); + +has action_namespace => ( + is => 'rw', + isa => 'Str', + init_arg => 'namespace', + predicate => 'has_action_namespace', +); + +has actions => ( + accessor => '_controller_actions', + isa => 'HashRef', + init_arg => undef, +); + +has _action_role_args => ( + traits => [qw(Array)], + isa => 'ArrayRef[Str]', + init_arg => 'action_roles', + default => sub { [] }, + handles => { + _action_role_args => 'elements', + }, +); + +has _action_roles => ( + traits => [qw(Array)], + isa => 'ArrayRef[RoleName]', + init_arg => undef, + lazy => 1, + builder => '_build__action_roles', + handles => { + _action_roles => 'elements', + }, +); + +has action_args => (is => 'ro'); + +# ->config(actions => { '*' => ... +has _all_actions_attributes => ( + is => 'ro', + isa => 'HashRef', + init_arg => undef, + lazy => 1, + builder => '_build__all_actions_attributes', +); sub BUILD { my ($self, $args) = @_; @@ -42,9 +78,23 @@ sub BUILD { my $actions = delete $args->{actions} || {}; my $attr_value = $self->merge_config_hashes($actions, $action); $self->_controller_actions($attr_value); + + # trigger lazy builder + $self->_all_actions_attributes; + $self->_action_roles; } +sub _build__action_roles { + my $self = shift; + my @roles = $self->_expand_role_shortname($self->_action_role_args); + load_class($_) for @roles; + return \@roles; +} +sub _build__all_actions_attributes { + my ($self) = @_; + delete $self->_controller_actions->{'*'} || {}; +} =head1 NAME @@ -70,11 +120,12 @@ for more info about how Catalyst dispatches to actions. =cut #I think both of these could be attributes. doesn't really seem like they need -#to ble class data. i think that attributes +default would work just fine -__PACKAGE__->mk_classdata($_) for qw/_dispatch_steps _action_class/; +#to be class data. i think that attributes +default would work just fine +__PACKAGE__->mk_classdata($_) for qw/_dispatch_steps _action_class _action_role_prefix/; __PACKAGE__->_dispatch_steps( [qw/_BEGIN _AUTO _ACTION/] ); __PACKAGE__->_action_class('Catalyst::Action'); +__PACKAGE__->_action_role_prefix([ 'Catalyst::ActionRole::' ]); sub _DISPATCH : Private { @@ -92,17 +143,27 @@ sub _BEGIN : Private { my $begin = ( $c->get_actions( 'begin', $c->namespace ) )[-1]; return 1 unless $begin; $begin->dispatch( $c ); - return !@{ $c->error }; + #If there is an error, all bets off + if( @{ $c->error }) { + return !@{ $c->error }; + } else { + return $c->state || 1; + } } sub _AUTO : Private { my ( $self, $c ) = @_; my @auto = $c->get_actions( 'auto', $c->namespace ); foreach my $auto (@auto) { + # We FORCE the auto action user to explicitly return + # true. We need to do this since there's some auto + # users (Catalyst::Authentication::Credential::HTTP) that + # actually do a detach instead. + $c->state(0); $auto->dispatch( $c ); return 0 unless $c->state; } - return 1; + return $c->state || 1; } sub _ACTION : Private { @@ -113,7 +174,12 @@ sub _ACTION : Private { { $c->action->dispatch( $c ); } - return !@{ $c->error }; + #If there is an error, all bets off + if( @{ $c->error }) { + return !@{ $c->error }; + } else { + return $c->state || 1; + } } sub _END : Private { @@ -182,11 +248,10 @@ around path_prefix => sub { sub get_action_methods { my $self = shift; my $meta = find_meta($self) || confess("No metaclass setup for $self"); - confess("Metaclass " - . ref($meta) . " for " - . $meta->name - . " cannot support register_actions." ) - unless $meta->can('get_nearest_methods_with_attributes'); + confess( + sprintf "Metaclass %s for %s cannot support register_actions.", + ref $meta, $meta->name, + ) unless $meta->can('get_nearest_methods_with_attributes'); my @methods = $meta->get_nearest_methods_with_attributes; # actions specified via config are also action_methods @@ -194,11 +259,9 @@ sub get_action_methods { @methods, map { $meta->find_method_by_name($_) - || confess( 'Action "' - . $_ - . '" is not available from controller ' - . ( ref $self ) ) - } keys %{ $self->_controller_actions } + || confess( sprintf 'Action "%s" is not available from controller %s', + $_, ref $self ) + } keys %{ $self->_controller_actions } ) if ( ref $self ); return uniq @methods; } @@ -230,7 +293,7 @@ sub register_action_methods { my $attributes = $method->can('attributes') ? $method->attributes : []; my $attrs = $self->_parse_attrs( $c, $name, @{ $attributes } ); if ( $attrs->{Private} && ( keys %$attrs > 1 ) ) { - $c->log->debug( 'Bad action definition "' + $c->log->warn( 'Bad action definition "' . join( ' ', @{ $attributes } ) . qq/" for "$class->$name"/ ) if $c->debug; @@ -250,6 +313,20 @@ sub register_action_methods { } } +sub _apply_action_class_roles { + my ($self, $class, @roles) = @_; + + load_class($_) for @roles; + my $meta = Moose::Meta::Class->initialize($class)->create_anon_class( + superclasses => [$class], + roles => \@roles, + cache => 1, + ); + $meta->add_method(meta => sub { $meta }); + + return $meta->name; +} + sub action_class { my $self = shift; my %args = @_; @@ -258,7 +335,7 @@ sub action_class { ? $args{attributes}{ActionClass}[0] : $self->_action_class); - Class::MOP::load_class($class); + load_class($class); return $class; } @@ -267,7 +344,23 @@ sub create_action { my %args = @_; my $class = $self->action_class(%args); - my $action_args = $self->config->{action_args}; + + load_class($class); + Moose->init_meta(for_class => $class) + unless Class::MOP::does_metaclass_exist($class); + + unless ($args{name} =~ /^_(DISPATCH|BEGIN|AUTO|ACTION|END)$/) { + my @roles = $self->gather_action_roles(%args); + push @roles, $self->gather_default_action_roles(%args); + + $class = $self->_apply_action_class_roles($class, @roles) if @roles; + } + + my $action_args = ( + ref($self) + ? $self->action_args + : $self->config->{action_args} + ); my %extra_args = ( %{ $action_args->{'*'} || {} }, @@ -277,6 +370,31 @@ sub create_action { return $class->new({ %extra_args, %args }); } +sub gather_action_roles { + my ($self, %args) = @_; + return ( + (blessed $self ? $self->_action_roles : ()), + @{ $args{attributes}->{Does} || [] }, + ); +} + +sub gather_default_action_roles { + my ($self, %args) = @_; + my @roles = (); + push @roles, 'Catalyst::ActionRole::HTTPMethods' + if $args{attributes}->{Method}; + + push @roles, 'Catalyst::ActionRole::ConsumesContent' + if $args{attributes}->{Consumes}; + + push @roles, 'Catalyst::ActionRole::Scheme' + if $args{attributes}->{Scheme}; + + push @roles, 'Catalyst::ActionRole::QueryMatching' + if $args{attributes}->{Query}; + return @roles; +} + sub _parse_attrs { my ( $self, $c, $name, @attrs ) = @_; @@ -286,7 +404,7 @@ sub _parse_attrs { # Parse out :Foo(bar) into Foo => bar etc (and arrayify) - if ( my ( $key, $value ) = ( $attr =~ /^(.*?)(?:\(\s*(.+?)\s*\))?$/ ) ) + if ( my ( $key, $value ) = ( $attr =~ /^(.*?)(?:\(\s*(.+?)?\s*\))?$/ ) ) { if ( defined $value ) { @@ -296,36 +414,59 @@ sub _parse_attrs { } } - #I know that the original behavior was to ignore action if actions was set - # but i actually think this may be a little more sane? we can always remove - # the merge behavior quite easily and go back to having actions have - # presedence over action by modifying the keys. i honestly think this is - # superior while mantaining really high degree of compat - my $actions; + my ($actions_config, $all_actions_config); if( ref($self) ) { - $actions = $self->_controller_actions; + $actions_config = $self->_controller_actions; + # No, you're not getting actions => { '*' => ... } with actions in MyApp. + $all_actions_config = $self->_all_actions_attributes; } else { my $cfg = $self->config; - $actions = $self->merge_config_hashes($cfg->{actions}, $cfg->{action}); + $actions_config = $self->merge_config_hashes($cfg->{actions}, $cfg->{action}); + $all_actions_config = {}; } - %raw_attributes = ((exists $actions->{'*'} ? %{$actions->{'*'}} : ()), - %raw_attributes, - (exists $actions->{$name} ? %{$actions->{$name}} : ())); + %raw_attributes = ( + %raw_attributes, + # Note we deep copy array refs here to stop crapping on config + # when attributes are parsed. RT#65463 + exists $actions_config->{$name} ? map { ref($_) eq 'ARRAY' ? [ @$_ ] : $_ } %{ $actions_config->{$name } } : (), + ); + # Private actions with additional attributes will raise a warning and then + # be ignored. Adding '*' arguments to the default _DISPATCH / etc. methods, + # which are Private, will prevent those from being registered. They should + # probably be turned into :Actions instead, or we might want to otherwise + # disambiguate between those built-in internal actions and user-level + # Private ones. + %raw_attributes = (%{ $all_actions_config }, %raw_attributes) + unless $raw_attributes{Private}; my %final_attributes; - foreach my $key (keys %raw_attributes) { + while (my ($key, $value) = each %raw_attributes){ + my $new_attrs = $self->_parse_attr($c, $name, $key => $value ); + push @{ $final_attributes{$_} }, @{ $new_attrs->{$_} } for keys %$new_attrs; + } - my $raw = $raw_attributes{$key}; + return \%final_attributes; +} - foreach my $value (ref($raw) eq 'ARRAY' ? @$raw : $raw) { +sub _parse_attr { + my ($self, $c, $name, $key, $values) = @_; - my $meth = "_parse_${key}_attr"; - if ( my $code = $self->can($meth) ) { - ( $key, $value ) = $self->$code( $c, $name, $value ); + my %final_attributes; + foreach my $value (ref($values) eq 'ARRAY' ? @$values : $values) { + my $meth = "_parse_${key}_attr"; + if ( my $code = $self->can($meth) ) { + my %new_attrs = $self->$code( $c, $name, $value ); + while (my ($new_key, $value) = each %new_attrs){ + my $new_attrs = $key eq $new_key ? + { $new_key => [$value] } : + $self->_parse_attr($c, $name, $new_key => $value ); + push @{ $final_attributes{$_} }, @{ $new_attrs->{$_} } for keys %$new_attrs; } + } + else { push( @{ $final_attributes{$key} }, $value ); } } @@ -335,14 +476,16 @@ sub _parse_attrs { sub _parse_Global_attr { my ( $self, $c, $name, $value ) = @_; - return $self->_parse_Path_attr( $c, $name, "/$name" ); + # _parse_attr will call _parse_Path_attr for us + return Path => "/$name"; } sub _parse_Absolute_attr { shift->_parse_Global_attr(@_); } sub _parse_Local_attr { my ( $self, $c, $name, $value ) = @_; - return $self->_parse_Path_attr( $c, $name, $name ); + # _parse_attr will call _parse_Path_attr for us + return Path => $name; } sub _parse_Relative_attr { shift->_parse_Local_attr(@_); } @@ -361,25 +504,6 @@ sub _parse_Path_attr { } } -sub _parse_Regex_attr { - my ( $self, $c, $name, $value ) = @_; - return ( 'Regex', $value ); -} - -sub _parse_Regexp_attr { shift->_parse_Regex_attr(@_); } - -sub _parse_LocalRegex_attr { - my ( $self, $c, $name, $value ) = @_; - unless ( $value =~ s/^\^// ) { $value = "(?:.*?)$value"; } - - my $prefix = $self->path_prefix( $c ); - $prefix .= '/' if length( $prefix ); - - return ( 'Regex', "^${prefix}${value}" ); -} - -sub _parse_LocalRegexp_attr { shift->_parse_LocalRegex_attr(@_); } - sub _parse_Chained_attr { my ($self, $c, $name, $value) = @_; @@ -428,11 +552,45 @@ sub _parse_MyAction_attr { my ( $self, $c, $name, $value ) = @_; my $appclass = Catalyst::Utils::class2appclass($self); - $value = "${appclass}::Action::${value}"; + $value = "+${appclass}::Action::${value}"; return ( 'ActionClass', $value ); } +sub _parse_Does_attr { + my ($self, $app, $name, $value) = @_; + return Does => $self->_expand_role_shortname($value); +} + +sub _parse_GET_attr { Method => 'GET' } +sub _parse_POST_attr { Method => 'POST' } +sub _parse_PUT_attr { Method => 'PUT' } +sub _parse_DELETE_attr { Method => 'DELETE' } +sub _parse_OPTIONS_attr { Method => 'OPTIONS' } +sub _parse_HEAD_attr { Method => 'HEAD' } +sub _parse_PATCH_attr { Method => 'PATCH' } + +sub _expand_role_shortname { + my ($self, @shortnames) = @_; + my $app = $self->_application; + + my $prefix = $self->can('_action_role_prefix') ? $self->_action_role_prefix : ['Catalyst::ActionRole::']; + my @prefixes = (qq{${app}::ActionRole::}, @$prefix); + + return String::RewritePrefix->rewrite( + { '' => sub { + my $loaded = load_first_existing_class( + map { "$_$_[0]" } @prefixes + ); + return first { $loaded =~ /^$_/ } + sort { length $b <=> length $a } @prefixes; + }, + '~' => $prefixes[0], + '+' => '' }, + @shortnames, + ); +} + __PACKAGE__->meta->make_immutable; 1; @@ -461,7 +619,7 @@ Sets 'path_prefix', as described below. Allows you to set the attributes that the dispatcher creates actions out of. This allows you to do 'rails style routes', or override some of the -attribute defintions of actions composed from Roles. +attribute definitions of actions composed from Roles. You can set arguments globally (for all actions of the controller) and specifically (for a single action). @@ -504,10 +662,25 @@ arguments, when it is instantiated: From L, stashes the application instance as $self->_application. -=head2 $self->action_for('name') +=head2 $self->action_for($action_name) -Returns the Catalyst::Action object (if any) for a given method name -in this component. +Returns the Catalyst::Action object (if any) for a given action in this +controller or relative to it. You may refer to actions in controllers +nested under the current controllers namespace, or in controllers 'up' +from the current controller namespace. For example: + + package MyApp::Controller::One::Two; + use base 'Catalyst::Controller'; + + sub foo :Local { + my ($self, $c) = @_; + $self->action_for('foo'); # action 'foo' in Controller 'One::Two' + $self->action_for('three/bar'); # action 'bar' in Controller 'One::Two::Three' + $self->action_for('../boo'); # action 'boo' in Controller 'One' + } + +This returns 'undef' if there is no action matching the requested action +name (after any path normalization) so you should check for this as needed. =head2 $self->action_namespace($c) @@ -519,7 +692,7 @@ overridden from the "namespace" config key. =head2 $self->path_prefix($c) -Returns the default path prefix for :PathPrefix, :Local, :LocalRegex and +Returns the default path prefix for :PathPrefix, :Local and relative :Path actions in this component. Defaults to the action_namespace or can be overridden from the "path" config key. @@ -550,12 +723,338 @@ action class to use. Called with a hash of data to be use for construction of a new Catalyst::Action (or appropriate sub/alternative class) object. +=head2 $self->gather_action_roles(\%action_args) + +Gathers the list of roles to apply to an action with the given %action_args. + +=head2 $self->gather_default_action_roles(\%action_args) + +returns a list of action roles to be applied based on core, builtin rules. +Currently only the L role is applied +this way. + =head2 $self->_application =head2 $self->_app Returns the application instance stored by C +=head1 ACTION SUBROUTINE ATTRIBUTES + +Please see L for more details + +Think of action attributes as a sort of way to record metadata about an action, +similar to how annotations work in other languages you might have heard of. +Generally L uses these to influence how the dispatcher sees your +action and when it will run it in response to an incoming request. They can +also be used for other things. Here's a summary, but you should refer to the +linked manual page for additional help. + +=head2 Global + + sub homepage :Global { ... } + +A global action defined in any controller always runs relative to your root. +So the above is the same as: + + sub myaction :Path("/homepage") { ... } + +=head2 Absolute + +Status: Deprecated alias to L. + +=head2 Local + +Alias to "Path("$action_name"). The following two actions are the same: + + sub myaction :Local { ... } + sub myaction :Path('myaction') { ... } + +=head2 Relative + +Status: Deprecated alias to L + +=head2 Path + +Handle various types of paths: + + package MyApp::Controller::Baz { + + ... + + sub myaction1 :Path { ... } # -> /baz + sub myaction2 :Path('foo') { ... } # -> /baz/foo + sub myaction2 :Path('/bar') { ... } # -> /bar + } + +This is a general toolbox for attaching your action to a given path. + + +=head2 Regex + +=head2 Regexp + +B Use Chained methods or other techniques. +If you really depend on this, install the standalone +L distribution. + +A global way to match a give regular expression in the incoming request path. + +=head2 LocalRegex + +=head2 LocalRegexp + +B Use Chained methods or other techniques. +If you really depend on this, install the standalone +L distribution. + +Like L but scoped under the namespace of the containing controller + +=head2 Chained + +=head2 ChainedParent + +=head2 PathPrefix + +=head2 PathPart + +=head2 CaptureArgs + +Allowed values for CaptureArgs is a single integer (CaptureArgs(2), meaning two +allowed) or you can declare a L, L or L +named constraint such as CaptureArgs(Int,Str) would require two args with +the first being a Integer and the second a string. You may declare your own +custom type constraints and import them into the controller namespace: + + package MyApp::Controller::Root; + + use Moose; + use MooseX::MethodAttributes; + use MyApp::Types qw/Int/; + + extends 'Catalyst::Controller'; + + sub chain_base :Chained(/) CaptureArgs(1) { } + + sub any_priority_chain :Chained(chain_base) PathPart('') Args(1) { } + + sub int_priority_chain :Chained(chain_base) PathPart('') Args(Int) { } + +See L for more. + +Please see L for more. + +=head2 ActionClass + +Set the base class for the action, defaults to L. It is now +preferred to use L. + +=head2 MyAction + +Set the ActionClass using a custom Action in your project namespace. + +The following is exactly the same: + + sub foo_action1 : Local ActionClass('+MyApp::Action::Bar') { ... } + sub foo_action2 : Local MyAction('Bar') { ... } + +=head2 Does + + package MyApp::Controller::Zoo; + + sub foo : Local Does('Buzz') { ... } # Catalyst::ActionRole:: + sub bar : Local Does('~Buzz') { ... } # MyApp::ActionRole::Buzz + sub baz : Local Does('+MyApp::ActionRole::Buzz') { ... } + +=head2 GET + +=head2 POST + +=head2 PUT + +=head2 DELETE + +=head2 OPTION + +=head2 HEAD + +=head2 PATCH + +=head2 Method('...') + +Sets the give action path to match the specified HTTP method, or via one of the +broadly accepted methods of overriding the 'true' method (see +L). + +=head2 Args + +When used with L indicates the number of arguments expected in +the path. However if no Args value is set, assumed to 'slurp' all +remaining path pars under this namespace. + +Allowed values for Args is a single integer (Args(2), meaning two allowed) or you +can declare a L, L or L named constraint such +as Args(Int,Str) would require two args with the first being a Integer and the +second a string. You may declare your own custom type constraints and import +them into the controller namespace: + + package MyApp::Controller::Root; + + use Moose; + use MooseX::MethodAttributes; + use MyApp::Types qw/Tuple Int Str StrMatch UserId/; + + extends 'Catalyst::Controller'; + + sub user :Local Args(UserId) { + my ($self, $c, $int) = @_; + } + + sub an_int :Local Args(Int) { + my ($self, $c, $int) = @_; + } + + sub many_ints :Local Args(ArrayRef[Int]) { + my ($self, $c, @ints) = @_; + } + + sub match :Local Args(StrMatch[qr{\d\d-\d\d-\d\d}]) { + my ($self, $c, $int) = @_; + } + +If you choose not to use imported type constraints (like L, or +you may use L 'stringy' types however just like when you use these types in your +declared attributes you must quote them: + + sub my_moose_type :Local Args('Int') { ... } + +If you use 'reference' type constraints (such as ArrayRef[Int]) that have an unknown +number of allowed matches, we set this the same way "Args" is. Please keep in mind +that actions with an undetermined number of args match at lower precedence than those +with a fixed number. You may use reference types such as Tuple from L +that allows you to fix the number of allowed args. For example Args(Tuple[Int,Int]) +would be determined to be two args (or really the same as Args(Int,Int).) You may +find this useful for creating custom subtypes with complex matching rules that you +wish to reuse over many actions. + +See L for more. + +B: It is highly recommended to use L for your type constraints over +other options. L exposed a better meta data interface which allows us to +do more and better types of introspection driving tests and debugging. + +=head2 Consumes('...') + +Matches the current action against the content-type of the request. Typically +this is used when the request is a POST or PUT and you want to restrict the +submitted content type. For example, you might have an HTML for that either +returns classic url encoded form data, or JSON when Javascript is enabled. In +this case you may wish to match either incoming type to one of two different +actions, for properly processing. + +Examples: + + sub is_json : Chained('start') Consumes('application/json') { ... } + sub is_urlencoded : Chained('start') Consumes('application/x-www-form-urlencoded') { ... } + sub is_multipart : Chained('start') Consumes('multipart/form-data') { ... } + +To reduce boilerplate, we include the following content type shortcuts: + +Examples + + sub is_json : Chained('start') Consume(JSON) { ... } + sub is_urlencoded : Chained('start') Consumes(UrlEncoded) { ... } + sub is_multipart : Chained('start') Consumes(Multipart) { ... } + +You may specify more than one match: + + sub is_more_than_one + : Chained('start') + : Consumes('application/x-www-form-urlencoded') + : Consumes('multipart/form-data') + + sub is_more_than_one + : Chained('start') + : Consumes(UrlEncoded) + : Consumes(Multipart) + +Since it is a common case the shortcut C matches both +'application/x-www-form-urlencoded' and 'multipart/form-data'. Here's the full +list of available shortcuts: + + JSON => 'application/json', + JS => 'application/javascript', + PERL => 'application/perl', + HTML => 'text/html', + XML => 'text/XML', + Plain => 'text/plain', + UrlEncoded => 'application/x-www-form-urlencoded', + Multipart => 'multipart/form-data', + HTMLForm => ['application/x-www-form-urlencoded','multipart/form-data'], + +Please keep in mind that when dispatching, L will match the first most +relevant case, so if you use the C attribute, you should place your +most accurate matches early in the Chain, and your 'catchall' actions last. + +See L for more. + +=head2 Scheme(...) + +Allows you to specify a URI scheme for the action or action chain. For example +you can required that a given path be C or that it is a websocket endpoint +C or C. For an action chain you may currently only have one defined +Scheme. + + package MyApp::Controller::Root; + + use base 'Catalyst::Controller'; + + sub is_http :Path(scheme) Scheme(http) Args(0) { + my ($self, $c) = @_; + $c->response->body("is_http"); + } + + sub is_https :Path(scheme) Scheme(https) Args(0) { + my ($self, $c) = @_; + $c->response->body("is_https"); + } + +In the above example http://localhost/root/scheme would match the first +action (is_http) but https://localhost/root/scheme would match the second. + +As an added benefit, if an action or action chain defines a Scheme, when using +$c->uri_for the scheme of the generated URL will use what you define in the action +or action chain (the current behavior is to set the scheme based on the current +incoming request). This makes it easier to use uri_for on websites where some +paths are secure and others are not. You may also use this to other schemes +like websockets. + +See L for more. + +=head1 OPTIONAL METHODS + +=head2 _parse_[$name]_attr + +Allows you to customize parsing of subroutine attributes. + + sub myaction1 :Path TwoArgs { ... } + + sub _parse_TwoArgs_attr { + my ( $self, $c, $name, $value ) = @_; + # $self -> controller instance + # + return(Args => 2); + } + +Please note that this feature does not let you actually assign new functions +to actions via subroutine attributes, but is really more for creating useful +aliases to existing core and extended attributes, and transforms based on +existing information (like from configuration). Code for actually doing +something meaningful with the subroutine attributes will be located in the +L classes (or your subclasses), L and +in subclasses of L. Remember these methods only get +called basically once when the application is starting, not per request! + =head1 AUTHORS Catalyst Contributors, see Catalyst.pm