# This file documents the revision history for Perl extension Catalyst.
+5.90007 - 2011-11-22 20:35:00
+
+ New features:
+ - Implement a match_captures hook which, if it exists on an action,
+ is called with the $ctx and \@captures and is expected to return
+ true to continue the chain matching and false to stop matching.
+ This can be used to implement action classes or roles which match
+ conditionally (for example only matching captures which are integers).
+
+ Bug fixes:
+ - Lighttpd script name fix is only applied for lighttpd versions
+ < 1.4.23. This should fix non-root installs of lighttpd in versions
+ over that.
+ - Prepare_action is now inside a try {} block, so that requests containing
+ bad unicode can be appropriately trapped by
+ Catalyst::Plugin::Unicode::Encoding
+
+5.90006 - 2011-10-25 09:18:00
+
+ New features:
+ - A new 'run_options' class data method has been added to Catalyst.pm
+ This is used to store all the options passed by scripts, allowing
+ application authors to add custom options to their scripts then
+ get them passed through to the application.
+
+ Doumentation:
+ - Clarify that if you manually write your own .psgi file, then optional
+ proxy support (via the using_frontend_proxy config value) will not be
+ enabled unless you explicitly apply the default middlewares from
+ Catalyst, or you apply the middleware manually.
+
+ Bug fixes:
+ - Fix issue due to perl internals bugs in 5.8 and 5.10 (not present in
+ other perl versions) require can pass the context inappropriately,
+ meaning that some methods of loading classes can fail due to void
+ context being passed throuh to make_immutable, causing it to not return
+ a value.
+ This bug caused loading Catalyst::Script::XXX to fail and is fixed
+ both by bumping the Class::Load dependency, and also adding an explicit
+ '1;' to the end of the classes, avoiding the context issue.
+
+ - Fix using_frontend_proxy support in mod_perl by using the psgi wrapped
+ in default middleware in mod_perl context, rather than the raw psgi.
+
5.90005 - 2011-10-22 13:35:00
New features:
requires 'namespace::clean' => '0.13';
requires 'B::Hooks::EndOfScope' => '0.08';
requires 'MooseX::Emulate::Class::Accessor::Fast' => '0.00903';
-requires 'Class::Load' => '0.08';
+requires 'Class::Load' => '0.12';
requires 'Class::MOP' => '0.95';
requires 'Data::OptList';
requires 'Moose' => '1.03';
__PACKAGE__->mk_classdata($_)
for qw/container arguments dispatcher engine log dispatcher_class
engine_loader context_class request_class response_class stats_class
- setup_finished _psgi_app loading_psgi_file/;
+ setup_finished _psgi_app loading_psgi_file run_options/;
__PACKAGE__->dispatcher_class('Catalyst::Dispatcher');
__PACKAGE__->request_class('Catalyst::Request');
# Remember to update this in Catalyst::Runtime as well!
-our $VERSION = '5.90005';
+our $VERSION = '5.90007';
sub import {
my ( $class, @arguments ) = @_;
=item @args?
-Optional list of extra arguments - can be supplied in the C<< \@captures_and_args? >>
-array ref, or here - whichever is easier for your code..
+Optional list of extra arguments - can be supplied in the
+C<< \@captures_and_args? >> array ref, or here - whichever is easier for your
+code.
-If your action may have a zero, a fixed or a variable number of args (e.g. C<< Args(1) >>
-for a fixed number or C<< Args() >> for a variable number)..
+Your action can have zero, a fixed or a variable number of args (e.g.
+C<< Args(1) >> for a fixed number or C<< Args() >> for a variable number)..
=item \%query_values?
EOF
}
+=head2 run_options
+
+Contains a hash of options passed from the application script, including
+the original ARGV the script received, the processed values from that
+ARGV and any extra arguments to the script which were not processed.
+
+This can be used to add custom options to your application's scripts
+and setup your application differently depending on the values of these
+options.
+
=head1 INTERNAL METHODS
These methods are not meant to be used by end users.
$c->prepare_body;
}
}
+ $c->prepare_action;
}
# VERY ugly and probably shouldn't rely on ->finalize actually working
catch {
$c->response->status(400);
$c->response->content_type('text/plain');
$c->response->body('Bad Request');
+ # Note we call finalize and then die here, which escapes
+ # finalize being called in the enclosing block..
+ # It in fact couldn't be called, as we don't return $c..
+ # This is a mess - but I'm unsure you can fix this without
+ # breaking compat for people doing crazy things (we should set
+ # the 400 and just return the ctx here IMO, letting finalize get called
+ # above...
$c->finalize;
die $_;
};
- my $method = $c->req->method || '';
- my $path = $c->req->path;
- $path = '/' unless length $path;
- my $address = $c->req->address || '';
-
$c->log_request;
- $c->prepare_action;
-
return $c;
}
$meta->add_method(handler => sub {
my $r = shift;
- my $psgi_app = $class->psgi_app;
+ my $psgi_app = $class->_finalized_psgi_app;
$apache->call_app($r, $psgi_app);
});
# If we're running under Lighttpd, swap PATH_INFO and SCRIPT_NAME
# http://lists.scsys.co.uk/pipermail/catalyst/2006-June/008361.html
- $psgi_app = Plack::Middleware::LighttpdScriptNameFix->wrap($psgi_app);
+ $psgi_app = Plack::Middleware::Conditional->wrap(
+ $psgi_app,
+ builder => sub { Plack::Middleware::LighttpdScriptNameFix->wrap($_[0]) },
+ condition => sub {
+ my ($env) = @_;
+ return unless $env->{SERVER_SOFTWARE} && $env->{SERVER_SOFTWARE} =~ m!lighttpd[-/]1\.(\d+\.\d+)!;
+ return unless $1 < 4.23;
+ 1;
+ },
+ );
# we're applying this unconditionally as the middleware itself already makes
# sure it doesn't fuck things up if it's not running under one of the right
If you do not wish to use the proxy support at all, you may set:
- MyApp->config(ignore_frontend_proxy => 1);
+ MyApp->config(ignore_frontend_proxy => 0);
+
+=head2 Note about psgi files
+
+Note that if you supply your own .psgi file, calling
+C<< MyApp->psgi_app(@_); >>, then B<this will not happen automatically>.
+
+You either need to apply L<Plack::Middleware::ReverseProxy> yourself
+in your psgi, for example:
+
+ builder {
+ enable "Plack::Middleware::ReverseProxy";
+ MyApp->psgi_app
+ };
+
+This will unconditionally add the ReverseProxy support, or you need to call
+C<< $app = MyApp->apply_default_middlewares($app) >> (to conditionally
+apply the support depending upon your config).
+
+See L<Catalyst::PSGI> for more information.
=head1 THREAD SAFETY
Provided by Moose.
+=head1 OPTIONAL METHODS
+
+=head2 match_captures
+
+Can be implemented by action class and action role authors. If the method
+exists, then it will be called with the request context and an array reference
+of the captures for this action.
+
+Returning true from this method causes the chain match to continue, returning
+makes the chain not match (and alternate, less preferred chains will be attempted).
+
=head1 AUTHORS
Catalyst Contributors, see Catalyst.pm
# strip CaptureArgs into list
push(@captures, splice(@parts, 0, $capture_attr->[0]));
+ # check if the action may fit, depending on a given test by the app
+ if ($action->can('match_captures')) { next TRY_ACTION unless $action->match_captures($c, \@captures) }
+
# try the remaining parts against children of this action
my ($actions, $captures, $action_parts, $n_pathparts) = $self->recurse_match(
$c, '/'.$action->reverse, \@parts
}
__PACKAGE__->meta->make_immutable;
+1;
=head1 USAGE
you C<detach> out of a chain, the rest of the chain will not get called
after the C<detach>.
+=head2 match_captures
+
+A method which can optionally be implemented by actions to
+stop chain matching.
+
+See L<Catalyst::Action> for further details.
+
=head1 AUTHORS
Catalyst Contributors, see Catalyst.pm
=head2 $self->prepare_request(@arguments)
-Populate the context object from the request object.
+Sets up the PSGI environment in the Engine.
=cut
# instead the $app->handle method is called per request.
$app->log->warn("Not supplied a Plack engine, falling back to engine auto-loader (are your scripts ancient?)")
}
+ $app->run_options($options);
$server->run($psgi, $options);
}
=head2 build_psgi_app ($app, @args)
-Builds and returns a PSGI application closure, wrapping it in the reverse proxy
-middleware if the using_frontend_proxy config setting is set.
+Builds and returns a PSGI application closure. (Raw, not wrapped in middleware)
=cut
=item L<Plack::Middleware::IIS6ScriptNameFix>
-=item nginx - local to Catalyst
-
=back
If you override the default by providing your own C<< .psgi >> file,
in the default middlewares if you want this behaviour and you are providing
your own .psgi file.
+This means that the auto-generated (no .psgi file) code looks something
+like this:
+
+ use strict;
+ use warnings;
+ use TestApp;
+
+ my $app = TestApp->apply_default_middlewares(TestApp->psgi_app(@_));
+
=head1 SEE ALSO
L<Catalyst::Upgrading>, L<Plack>, L<PSGI::FAQ>, L<PSGI>.
# Remember to update this in Catalyst as well!
-our $VERSION = '5.90005';
+our $VERSION = '5.90007';
=head1 NAME
with 'Catalyst::ScriptRole';
__PACKAGE__->meta->make_immutable;
+1;
=head1 NAME
}
__PACKAGE__->meta->make_immutable;
+1;
=head1 NAME
return %args;
}
-sub _application_args {
- my ($self) = shift;
+around _application_args => sub {
+ my ($orig, $self) = @_;
return (
$self->listen,
{
+ %{ $self->$orig },
nproc => $self->nproc,
pidfile => $self->pidfile,
manager => $self->manager,
proc_title => $self->proc_title,
}
);
-}
+};
__PACKAGE__->meta->make_immutable;
+1;
=head1 NAME
);
}
-sub _application_args {
- my ($self) = shift;
+around _application_args => sub {
+ my ($orig, $self) = @_;
return (
$self->port,
$self->host,
{
- argv => $self->ARGV,
+ %{ $self->$orig },
map { $_ => $self->$_ } qw/
fork
keepalive
pidfile
keepalive
follow_symlinks
+ port
+ host
/,
},
);
-}
+};
__PACKAGE__->meta->make_immutable;
-
1;
=head1 NAME
__PACKAGE__->meta->make_immutable;
+1;
=head1 NAME
}
sub _application_args {
- ()
+ my $self = shift;
+ return {
+ argv => $self->ARGV,
+ extra_argv => $self->extra_argv,
+ }
}
sub _plack_loader_args {
return (port => $app_args[0]);
}
+sub _plack_engine_name {}
+
sub _run_application {
my $self = shift;
my $app = $self->application_name;
Class::MOP::load_class($app);
my $server;
- if (my $e = $self->can('_plack_engine_name') ) {
- $server = $self->load_engine($self->$e, $self->_plack_loader_args);
+ if (my $e = $self->_plack_engine_name ) {
+ $server = $self->load_engine($e, $self->_plack_loader_args);
}
else {
$server = $self->autoload_engine($self->_plack_loader_args);
}
__PACKAGE__->meta->make_immutable;
+1;
=head1 NAME
ok( index($content, $path) > 1, 'uri can round trip through uri_for' )
or diag("Expected $path, got $content");
}
+
+ #
+ # match_captures
+ #
+ {
+
+ ok( my $response = request('http://localhost/chained/match_captures/foo/bar'), 'match_captures: falling through' );
+ is($response->header('X-TestAppActionTestMatchCaptures'), 'fallthrough', 'match_captures: fell through');
+
+ ok($response = request('http://localhost/chained/match_captures/force/bar'), 'match_captures: *not* falling through' );
+ is($response->header('X-TestAppActionTestMatchCaptures'), 'forcing', 'match_captures: forced');
+ is($response->header('X-TestAppActionTestMatchCapturesHasRan'), 'yes', 'match_captures: actually ran');
+ }
}
done_testing;
shift @TestAppToTestScripts::RUN_ARGS;
my $server = pop @TestAppToTestScripts::RUN_ARGS;
like ref($server), qr/^Plack::Handler/, 'Is a Plack::Handler';
-is_deeply \@TestAppToTestScripts::RUN_ARGS, [], "no args";
+is ref(delete($TestAppToTestScripts::RUN_ARGS[0]->{argv})), 'ARRAY';
+is ref(delete($TestAppToTestScripts::RUN_ARGS[0]->{extra_argv})), 'ARRAY';
+is_deeply \@TestAppToTestScripts::RUN_ARGS, [{}], "no args";
done_testing;
} "new_with_options";
# First element of RUN_ARGS will be the script name, which we don't care about
shift @TestAppToTestScripts::RUN_ARGS;
+
my $server = pop @TestAppToTestScripts::RUN_ARGS;
is $server, $fake_handler, 'Loaded Plack handler gets passed to the app';
+
+ if (scalar(@TestAppToTestScripts::RUN_ARGS) && ref($TestAppToTestScripts::RUN_ARGS[-1]) eq "HASH") {
+ is ref(delete($TestAppToTestScripts::RUN_ARGS[-1]->{argv})), 'ARRAY';
+ is ref(delete($TestAppToTestScripts::RUN_ARGS[-1]->{extra_argv})), 'ARRAY';
+ }
+
is_deeply \@TestAppToTestScripts::RUN_ARGS, $resultarray, "is_deeply comparison";
}
--- /dev/null
+use strict;
+use warnings;
+use Test::More;
+use FindBin qw/$Bin/;
+use IO::Handle;
+use Try::Tiny;
+use File::Temp qw/ tempfile /;
+use lib "$Bin/../lib";
+
+use_ok('Catalyst::ScriptRunner');
+use_ok('ScriptTestApp');
+
+is ScriptTestApp->run_options, undef;
+
+my ($fh, $fn) = tempfile();
+
+binmode( $fh );
+binmode( STDOUT );
+
+local @ARGV = ();
+local %ENV;
+
+my $saved;
+open( $saved, '>&'. STDOUT->fileno )
+ or croak("Can't dup stdout: $!");
+open( STDOUT, '>&='. $fh->fileno )
+ or croak("Can't open stdout: $!");
+local $SIG{__WARN__} = sub {}; # Shut up warnings...
+try { Catalyst::ScriptRunner->run('ScriptTestApp', 'CGI'); pass("Ran ok") }
+catch { fail "Failed to run $_" };
+
+STDOUT->flush
+ or croak("Can't flush stdout: $!");
+
+open( STDOUT, '>&'. fileno($saved) )
+ or croak("Can't restore stdout: $!");
+
+is_deeply ScriptTestApp->run_options, { argv => [], extra_argv => [] };
+
+done_testing;
# help -? -help --help -? --help
# debug -d -debug --debug -d --debug
# host -host --host --host
-testOption( [ qw/--host testhost/ ], ['3000', 'testhost', opthash()] );
-testOption( [ qw/-h testhost/ ], ['3000', 'testhost', opthash()] );
+testOption( [ qw/--host testhost/ ], ['3000', 'testhost', opthash(host => 'testhost')] );
+testOption( [ qw/-h testhost/ ], ['3000', 'testhost', opthash(host => 'testhost')] );
# port -p -port --port -l --listen
-testOption( [ qw/-p 3001/ ], ['3001', undef, opthash()] );
-testOption( [ qw/--port 3001/ ], ['3001', undef, opthash()] );
+testOption( [ qw/-p 3001/ ], ['3001', undef, opthash(port => 3001)] );
+testOption( [ qw/--port 3001/ ], ['3001', undef, opthash(port => 3001)] );
{
local $ENV{TESTAPPTOTESTSCRIPTS_PORT} = 5000;
- testOption( [ qw// ], [5000, undef, opthash()] );
+ testOption( [ qw// ], [5000, undef, opthash(port => 5000)] );
}
{
local $ENV{CATALYST_PORT} = 5000;
- testOption( [ qw// ], [5000, undef, opthash()] );
+ testOption( [ qw// ], [5000, undef, opthash(port => 5000)] );
}
if (try { require Starman; 1; }) {
$run_args[-1]->{pidfile} = $run_args[-1]->{pidfile}->file->stringify
if scalar(@run_args) && $run_args[-1]->{pidfile};
-
# Mangle argv into the options..
$resultarray->[-1]->{argv} = $argstring;
+ $resultarray->[-1]->{extra_argv} = [];
is_deeply \@run_args, $resultarray, "is_deeply comparison " . join(' ', @$argstring);
}
'follow_symlinks' => 0,
'background' => 0,
'keepalive' => 0,
+ port => 3000,
+ host => undef,
@_,
};
}
ctx _application MyApp restarter httponly Utils stash's unescapes
dispatchtype dispatchtypes redispatch redispatching
CaptureArgs ChainedParent PathPart PathPrefix
- BUILDARGS metaclass namespaces pre
+ BUILDARGS metaclass namespaces pre ARGV ReverseProxy
filename tempname request's subdirectory ini uninstalled uppercased
wiki bitmask uri url urls dir hostname proxied http https IP SSL
+ inline INLINE plugins
));
set_spell_cmd('aspell list -l en');
all_pod_files_spelling_ok();
--- /dev/null
+package ScriptTestApp;
+use Moose;
+
+extends 'Catalyst';
+
+__PACKAGE__->setup;
+1;
+
--- /dev/null
+package ScriptTestApp::Controller::Root;
+use Moose;
+use namespace::autoclean;
+
+BEGIN { extends 'Catalyst::Controller' }
+
+sub default : Chained('/') PathPart('') Args() {}
+
+1;
+
--- /dev/null
+package TestApp::Action::TestMatchCaptures;
+
+use Moose;
+
+extends 'Catalyst::Action';
+
+sub match_captures {
+ my ($self, $c, $cap) = @_;
+ if ($cap->[0] eq 'force') {
+ $c->res->header( 'X-TestAppActionTestMatchCaptures', 'forcing' );
+ return 1;
+ } else {
+ $c->res->header( 'X-TestAppActionTestMatchCaptures', 'fallthrough' );
+ return 0;
+ }
+}
+
+1;
\ No newline at end of file
$c->stash->{no_end} = 1;
}
+sub match_captures : Chained('/') PathPart('chained/match_captures') CaptureArgs(1) ActionClass('+TestApp::Action::TestMatchCaptures') {
+ my ($self, $c) = @_;
+ $c->res->header( 'X-TestAppActionTestMatchCapturesHasRan', 'yes');
+}
+
+sub match_captures_end : Chained('match_captures') PathPart('bar') Args(0) { }
+
sub end :Private {
my ($self, $c) = @_;
return if $c->stash->{no_end};