# This file documents the revision history for Perl extension Catalyst.
+5.90069_002
+ - Catalyst stash functionality has been moved to Middleware. It should
+ work entirely the same when used as a context method, please report
+ questions or problems!
+ - Removed code related to supporting the long deprecated stand alone
+ PSGI Engine. If you are still using this you code is now broken.
+ Luckily you can just stop using it and likely everything will work
+ under the new PSGI support built into Catalyst for several years.
+ - 'abort_chain_on_error_fix' now defaults to true. If this behavior
+ causes you issues, you can explicitly turn it off by setting it to a
+ non true defined value (0 is a good option here).
+ - When throwing an http style exception, make sure we properly flush the
+ existing log and report other errors in the error stack.
+
+5.90069_001
+ - Set encoding on STDERR when encoding is set in config
+ - documentation and test fixes
+
5.90065 - 2014-06-04
- The Catalyst::Log object now has 'autoflush' (which defaults to true) and
causes log messages to be written out in real-time. This is helpful for the
use Plack::Middleware::FixMissingBodyInRedirect;
use Plack::Middleware::MethodOverride;
use Plack::Middleware::RemoveRedundantBody;
+use Catalyst::Middleware::Stash;
use Plack::Util;
use Class::Load 'load_class';
BEGIN { require 5.008003; }
has stack => (is => 'ro', default => sub { [] });
-has stash => (is => 'rw', default => sub { {} });
has state => (is => 'rw', default => 0);
has stats => (is => 'rw');
has action => (is => 'rw');
# Remember to update this in Catalyst::Runtime as well!
-our $VERSION = '5.90065';
+our $VERSION = '5.90069_002';
sub import {
my ( $class, @arguments ) = @_;
=cut
-around stash => sub {
- my $orig = shift;
- my $c = shift;
- my $stash = $orig->($c);
- if (@_) {
- my $new_stash = @_ > 1 ? {@_} : $_[0];
- croak('stash takes a hash or hashref') unless ref $new_stash;
- foreach my $key ( keys %$new_stash ) {
- $stash->{$key} = $new_stash->{$key};
- }
+sub stash {
+ my $c = shift;
+ my $stash = Catalyst::Middleware::Stash->get($c->req->env);
+ if(@_) {
+ my $new_stash = @_ > 1 ? {@_} : $_[0];
+ croak('stash takes a hash or hashref') unless ref $new_stash;
+ foreach my $key ( keys %$new_stash ) {
+ $stash->{$key} = $new_stash->{$key};
}
-
- return $stash;
-};
-
+ }
+ return $stash;
+}
=head2 $c->error
$c->error(0);
}
+=head2 $c->has_errors
+
+Returns true if you have errors
+
+=cut
+
+sub has_errors { scalar(@{shift->error}) ? 1:0 }
+
sub _comp_search_prefixes {
my $c = shift;
return map $c->components->{ $_ }, $c->_comp_names_search_prefixes(@_);
if ( my $error = $@ ) {
#rethow if this can be handled by middleware
if(blessed $error && ($error->can('as_psgi') || $error->can('code'))) {
+ foreach my $err (@{$c->error}) {
+ $c->log->error($err);
+ }
+ $c->clear_errors;
+ $c->log->_flush if $c->log->can('_flush');
+
$error->can('rethrow') ? $error->rethrow : croak $error;
}
if ( blessed($error) and $error->isa('Catalyst::Exception::Detach') ) {
# In the case where the error 'knows what it wants', becauses its PSGI
# aware, just rethow and let middleware catch it
$error->can('rethrow') ? $error->rethrow : croak $error;
- croak $error;
} else {
$c->engine->finalize_error( $c, @_ )
}
my $class = shift;
my $config = shift;
- my @paths = qw( ::Controller ::C ::Model ::M ::View ::V );
+ my @paths = qw( ::M ::Model ::V ::View ::C ::Controller );
my $extra = delete $config->{ search_extra } || [];
- push @paths, @$extra;
-
- my $locator = Module::Pluggable::Object->new(
- search_path => [ map { s/^(?=::)/$class/; $_; } @paths ],
- %$config
- );
+ unshift @paths, @$extra;
- # XXX think about ditching this sort entirely
- my @comps = sort { length $a <=> length $b } $locator->plugins;
+ my @comps = map { sort { length($a) <=> length($b) } Module::Pluggable::Object->new(
+ search_path => [ map { s/^(?=::)/$class/; $_; } ($_) ],
+ %$config
+ )->plugins } @paths;
return @comps;
}
my $class = shift;
if(my $middleware = $class->_psgi_middleware) {
return (
+ Catalyst::Middleware::Stash->new,
Plack::Middleware::HTTPExceptions->new,
Plack::Middleware::RemoveRedundantBody->new,
Plack::Middleware::FixMissingBodyInRedirect->new,
if(my $data_handlers = $class->_data_handlers) {
return %$data_handlers;
} else {
- die "You cannot call ->registered_data_handlers until data_handers has been setup";
+ $class->setup_data_handlers;
+ return $class->registered_data_handlers;
}
}
local $c->request->{arguments} = \@args;
$action->dispatch( $c );
- # break the chain if exception occurs in the middle of chain
- return if (@{$c->error} && $c->config->{abort_chain_on_error_fix});
+ # break the chain if exception occurs in the middle of chain. We
+ # check the global config flag 'abort_chain_on_error_fix', but this
+ # is now considered true by default, so unless someone explictly sets
+ # it to false we default it to true (if its not defined).
+ my $abort = defined($c->config->{abort_chain_on_error_fix}) ?
+ $c->config->{abort_chain_on_error_fix} : 1;
+ return if ($c->has_errors && $abort);
}
$last->dispatch( $c );
}
# Amount of data to read from input on each pass
our $CHUNKSIZE = 64 * 1024;
-# XXX - this is only here for compat, do not use!
-has env => ( is => 'rw', writer => '_set_env' );
-my $WARN_ABOUT_ENV = 0;
-around env => sub {
- my ($orig, $self, @args) = @_;
- if(@args) {
- warn "env as a writer is deprecated, you probably need to upgrade Catalyst::Engine::PSGI"
- unless $WARN_ABOUT_ENV++;
- return $self->_set_env(@args);
- }
- return $self->$orig;
-};
-
# XXX - Only here for Engine::PSGI compat
sub prepare_connection {
my ($self, $ctx) = @_;
my ($self, $ctx, %args) = @_;
$ctx->log->psgienv($args{env}) if $ctx->log->can('psgienv');
$ctx->request->_set_env($args{env});
- $self->_set_env($args{env}); # Nasty back compat!
$ctx->response->_set_response_cb($args{response_cb});
}
--- /dev/null
+package ## Hide from pause
+ Catalyst::Middleware::Stash;
+
+# Please don't use this, this is likely to go away before stable version is
+# released. Ideally this could be a stand alone distribution.
+#
+
+use strict;
+use warnings;
+use base 'Plack::Middleware';
+
+sub PSGI_KEY { 'Catalyst.Stash.v1' };
+
+sub _init_stash {
+ my ($self, $env) = @_;
+ $env->{&PSGI_KEY} = bless +{}, 'Catalyst::Stash';
+}
+
+sub get {
+ my ($class, $env) = @_;
+ return $env->{&PSGI_KEY};
+}
+
+sub call {
+ my ($self, $env) = @_;
+ $self->_init_stash($env);
+ return $self->app->($env);
+}
+
+=head1 TITLE
+
+Catalyst::Middleware::Stash - The Catalyst stash - in middleware
+
+=head1 DESCRIPTION
+
+We've moved the L<Catalyst> stash to middleware. Please don't use this
+directly since it is likely to move off the Catalyst namespace into a stand
+alone distribution
+
+=head1 METHODS
+
+This class defines the following methods
+
+=head2 PSGI_KEY
+
+Returns the hash key where we store the stash
+
+=head2 get
+
+Get the stash out of the C<$env>
+
+=head2 call
+
+Used by plack to call the middleware
+
+=cut
+
+1;
if (my $wanted = shift) {
$encoding = Encode::find_encoding($wanted)
or Carp::croak( qq/Unknown encoding '$wanted'/ );
+ binmode(STDERR, ':encoding(' . $encoding->name . ')');
+ }
+ else {
+ binmode(STDERR);
}
$encoding = ref $c
# Remember to update this in Catalyst as well!
-our $VERSION = '5.90065';
+our $VERSION = '5.90069_002';
=head1 NAME
ok( $warnings, 'view() w/o a default is random, warnings thrown' );
}
-is ( bless ({stash=>{current_view=>'V'}}, 'MyMVCTestApp')->view , 'MyMVCTestApp::View::V', 'current_view ok');
+#is ( bless ({stash=>{current_view=>'V'}}, 'MyMVCTestApp')->view , 'MyMVCTestApp::View::V', 'current_view ok');
my $view = bless {} , 'MyMVCTestApp::View::V';
-is ( bless ({stash=>{current_view_instance=> $view }}, 'MyMVCTestApp')->view , $view, 'current_view_instance ok');
+#is ( bless ({stash=>{current_view_instance=> $view }}, 'MyMVCTestApp')->view , $view, 'current_view_instance ok');
-is ( bless ({stash=>{current_view_instance=> $view, current_view=>'MyMVCTestApp::V::View' }}, 'MyMVCTestApp')->view , $view,
- 'current_view_instance precedes current_view ok');
+#is ( bless ({stash=>{current_view_instance=> $view, current_view=>'MyMVCTestApp::V::View' }}, 'MyMVCTestApp')->view , $view,
+# 'current_view_instance precedes current_view ok');
{
my $warnings = 0;
ok( $warnings, 'model() w/o a default is random, warnings thrown' );
}
-is ( bless ({stash=>{current_model=>'M'}}, 'MyMVCTestApp')->model , 'MyMVCTestApp::Model::M', 'current_model ok');
+#is ( bless ({stash=>{current_model=>'M'}}, 'MyMVCTestApp')->model , 'MyMVCTestApp::Model::M', 'current_model ok');
my $model = bless {} , 'MyMVCTestApp::Model::M';
-is ( bless ({stash=>{current_model_instance=> $model }}, 'MyMVCTestApp')->model , $model, 'current_model_instance ok');
+#is ( bless ({stash=>{current_model_instance=> $model }}, 'MyMVCTestApp')->model , $model, 'current_model_instance ok');
-is ( bless ({stash=>{current_model_instance=> $model, current_model=>'MyMVCTestApp::M::Model' }}, 'MyMVCTestApp')->model , $model,
- 'current_model_instance precedes current_model ok');
+#is ( bless ({stash=>{current_model_instance=> $model, current_model=>'MyMVCTestApp::M::Model' }}, 'MyMVCTestApp')->model , $model,
+# 'current_model_instance precedes current_model ok');
MyMVCTestApp->config->{default_view} = 'V';
-is ( bless ({stash=>{}}, 'MyMVCTestApp')->view , 'MyMVCTestApp::View::V', 'default_view ok');
+#is ( bless ({stash=>{}}, 'MyMVCTestApp')->view , 'MyMVCTestApp::View::V', 'default_view ok');
is ( MyMVCTestApp->view , 'MyMVCTestApp::View::V', 'default_view in class method ok');
MyMVCTestApp->config->{default_model} = 'M';
-is ( bless ({stash=>{}}, 'MyMVCTestApp')->model , 'MyMVCTestApp::Model::M', 'default_model ok');
+#is ( bless ({stash=>{}}, 'MyMVCTestApp')->model , 'MyMVCTestApp::Model::M', 'default_model ok');
is ( MyMVCTestApp->model , 'MyMVCTestApp::Model::M', 'default_model in class method ok');
# regexp behavior tests
$ctx->response->body('stashed context closure');
}
+sub non_closure : Local {
+ my ($self, $ctx) = @_;
+ $ctx->stash(no_closure => "not a closure");
+}
+
__PACKAGE__->meta->make_immutable;
1;
sub env_on_engine : Action Relative {
my ( $self, $c ) = @_;
- $c->stash(env => $c->engine->env);
+ # JNAP - I changed this to req since the engine no longer
+ # has the env but the tests here are useful.
+ $c->stash(env => $c->req->env);
$c->forward('TestApp::View::Dump::Env');
}
return $self->SUPER::process($c, {
map { ($_ => $env->{$_}) }
grep { $_ ne 'psgi.input' }
+ grep { $_ !~/^Catalyst/ }
keys %{ $env },
});
}
plan skip_all => 'CatalystX::LeakChecker 0.05 required for this test';
}
- plan tests => 4;
+ plan tests => 6;
}
use FindBin;
ok($resp->is_success);
is($ctx->count_leaks, 0);
}
+
+{
+ my ($resp, $ctx) = ctx_request('/contextclosure/non_closure');
+ ok($resp->is_success);
+ is($ctx->count_leaks, 0);
+}
+++ /dev/null
-use strict;
-use warnings;
-use FindBin qw/$Bin/;
-use lib "$Bin/lib";
-
-use Test::More;
-use Test::Fatal;
-use Plack::Test;
-use TestApp;
-use HTTP::Request::Common;
-
-plan skip_all => "Catalyst::Engine::PSGI required for this test"
- unless eval { local $SIG{__WARN__} = sub{}; require Catalyst::Engine::PSGI; 1; };
-
-my $warning;
-local $SIG{__WARN__} = sub { $warning = $_[0] };
-
-TestApp->setup_engine('PSGI');
-my $app = sub { TestApp->run(@_) };
-
-like $warning, qr/You are running Catalyst\:\:Engine\:\:PSGI/,
- 'got deprecation alert warning';
-
-test_psgi $app, sub {
- my $cb = shift;
- is exception {
- my $TIMEOUT_IN_SECONDS = 5;
- local $SIG{ALRM} = sub { die "alarm\n" };
- alarm($TIMEOUT_IN_SECONDS);
-
- my $res = $cb->(GET "/");
- is $res->content, "root index", 'got expected content';
- like $warning, qr/env as a writer/, 'got deprecation alert warning';
-
- alarm(0);
- 1
- }, undef, q{app didn't die or timeout};
-};
-
-done_testing;
-
+++ /dev/null
-use strict;
-use warnings;
-no warnings 'once';
-use FindBin qw/$Bin/;
-use lib "$Bin/lib";
-
-use Test::More;
-
-use File::Spec;
-use File::Temp qw/ tempdir /;
-
-my $temp;
-BEGIN {
- $temp = tempdir( CLEANUP => 1 );
-
- $ENV{CATALYST_HOME} = $temp;
- open(my $psgi, '>', File::Spec->catfile($temp, 'testapp.psgi')) or die;
- print $psgi q{
- use strict;
- use TestApp;
-
- $main::have_loaded_psgi = 1;
- TestApp->setup_engine('PSGI');
- my $app = sub { TestApp->run(@_) };
- };
- close($psgi);
-}
-use Catalyst::Test qw/ TestApp /;
-
-ok !$main::have_loaded_psgi, 'legacy psgi file got ignored';
-
-like do {
- my $warning;
- local $SIG{__WARN__} = sub { $warning = $_[0] };
- ok request('/');
- $warning;
-}, qr/ignored/, 'legacy psgi files raise a warning';
-
-done_testing;
-
$c->res->from_psgi_response(
$psgi_app->($env));
}
+
+ sub mounted :Local Args(1) {
+ my ($self, $c, $arg) = @_;
+ our $app ||= ref($c)->psgi_app;
+ my $env = $self->get_env($c);
+ $c->res->from_psgi_response(
+ $app->($env));
+ }
+
+ sub mount_arg :Path(/mounted) Arg(1) {
+ my ($self, $c, $arg) = @_;
+ my $uri = $c->uri_for( $self->action_for('local_example_args1'),$arg);
+ $c->res->body("$uri");
+ }
+
+ sub mount_noarg :Path(/mounted_no_arg) {
+ my ($self, $c) = @_;
+ my $uri = $c->uri_for( $self->action_for('local_example_args1'),444);
+ $c->res->body("$uri");
+ }
+
sub get_env {
my ($self, $c) = @_;
use Test::More;
use Catalyst::Test 'MyApp';
+{
+ my ($res, $c) = ctx_request('/user/mounted/111?path_prefix=1');
+ is $c->action, 'user/mounted';
+ is $res->content, 'http://localhost/user/user/local_example_args1/111';
+ is_deeply $c->req->args, [111];
+}
+
+{
+ my ($res, $c) = ctx_request('/user/mounted/mounted_no_arg?env_path=1');
+ is $c->action, 'user/mounted';
+ is $res->content, 'http://localhost/user/mounted/user/local_example_args1/444';
+ is_deeply $c->req->args, ['mounted_no_arg'];
+}
+
# BEGIN [user/local_example]
{
my ($res, $c) = ctx_request('/user/local_example');