# This file documents the revision history for Perl extension Catalyst.
+5.90080_001 - TBD
+ - MyApp->to_app is now an alias for MyApp->psgi_app in order to better support
+ existing Plack conventions.
+ - Modify Catayst::Response->from_psgi_response to allow the first argument to
+ be an object that does ->as_psgi.
+ - Modified Catayst::Middleware::Stash to be a shallow copy in $env. Added some
+ docs. Added a test case to make sure stash keys added in a child application
+ don't bubble back up to the main application.
+ - We no longer use Encode::is_utf8 since it doesn't work the way we think it
+ does... This required some UTF-8 changes. If your application is UTF-8 aware
+ I highly suggest you test this release.
+
5.90074 - 2014-10-01
- Specify Carp minimum version to avoid pointless test fails (valy++)
__PACKAGE__->_encode_check(Encode::FB_CROAK | Encode::LEAVE_SRC);
# Remember to update this in Catalyst::Runtime as well!
-our $VERSION = '5.90074';
+our $VERSION = '5.90080_001';
sub import {
my ( $class, @arguments ) = @_;
# stash is automatically passed to the view for use in a template
$c->forward( 'MyApp::View::TT' );
+The stash hash is currently stored in the PSGI C<$env> and is managed by
+L<Catalyst::Middleware::Stash>. Since it's part of the C<$env> items in
+the stash can be accessed in sub applications mounted under your main
+L<Catalyst> application. For example if you delegate the response of an
+action to another L<Catalyst> application, that sub application will have
+access to all the stash keys of the main one, and if can of course add
+more keys of its own. However those new keys will not 'bubble' back up
+to the main application.
+
+For more information the best thing to do is to review the test case:
+t/middleware-stash.t in the distribution /t directory.
+
=cut
sub stash {
return $psgi_app;
}
-=head2 $c->psgi_app
+=head2 App->psgi_app
+
+=head2 App->to_app
Returns a PSGI application code reference for the catalyst application
C<$c>. This is the bare application without any middlewares
=cut
+*to_app = \&psgi_app;
+
sub psgi_app {
my ($app) = @_;
my $psgi = $app->engine->build_psgi_app($app);
my $enc = $self->encoding;
return try {
- Encode::is_utf8( $value ) ?
- $value
- : $enc->decode( $value, $self->_encode_check );
+ $enc->decode( $value, $self->_encode_check );
}
catch {
$self->handle_unicode_encoding_exception({
our @EXPORT_OK = qw(stash get_stash);
-sub PSGI_KEY { 'Catalyst.Stash.v1' };
+sub PSGI_KEY () { 'Catalyst.Stash.v1' }
sub get_stash {
my $env = shift;
- return $env->{&PSGI_KEY} ||
- _init_stash_in($env);
+ return $env->{+PSGI_KEY} ||
+ croak "You requested a stash, but one does not exist.";
}
sub stash {
};
}
-sub _init_stash_in {
- my ($env) = @_;
- return $env->{&PSGI_KEY} ||=
- _create_stash;
-}
-
sub call {
my ($self, $env) = @_;
- _init_stash_in($env);
- return $self->app->($env);
+ my $new_env = +{ %$env };
+ my %stash = %{ ($env->{+PSGI_KEY} || sub {})->() || +{} };
+
+ $new_env->{+PSGI_KEY} = _create_stash( \%stash );
+ return $self->app->($new_env);
}
=head1 TITLE
We store a coderef under the C<PSGI_KEY> which can be dereferenced with
key values or nothing to access the underly hashref.
+The stash middleware is designed so that you can 'nest' applications that
+use it. If for example you have a L<Catalyst> application that is called
+by a controller under a parent L<Catalyst> application, the child application
+will inherit the full stash of the parent BUT any new keys added by the child
+will NOT bubble back up to the parent. However, children of children will.
+
+For more information the current test case t/middleware-stash.t is the best
+documentation.
+
=head1 SUBROUTINES
This class defines the following subroutines.
["I found $stashed in the stash!"]];
};
-If the stash does not yet exist, we initialize one and return that.
+If the stash does not yet exist, an exception is thrown.
=head1 METHODS
use HTTP::Headers;
use Moose::Util::TypeConstraints;
use namespace::autoclean;
+use Scalar::Util 'blessed';
with 'MooseX::Emulate::Class::Accessor::Fast';
sub from_psgi_response {
my ($self, $psgi_res) = @_;
+ if(blessed($psgi_res) && $psgi_res->can('as_psgi')) {
+ $psgi_res = $psgi_res->as_psgi;
+ }
if(ref $psgi_res eq 'ARRAY') {
my ($status, $headers, $body) = @$psgi_res;
$self->status($status);
Properly supports streaming and delayed response and / or async IO if running
under an expected event loop.
+If passed an object, will expect that object to do a method C<as_psgi>.
+
Example:
package MyApp::Web::Controller::Test;
# Remember to update this in Catalyst as well!
-our $VERSION = '5.90074';
+our $VERSION = '5.90080_001';
=head1 NAME
--- /dev/null
+use strict;
+use warnings;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use TestApp;
+use Test::More;
+
+ok(TestApp->can('to_app'));
+is(ref(TestApp->to_app), 'CODE');
+
+done_testing;
--- /dev/null
+use warnings;
+use strict;
+
+{
+
+ package MyAppChild::Controller::User;
+ $INC{'MyAppChild/Controller/User.pm'} = __FILE__;
+
+ use base 'Catalyst::Controller';
+ use Test::More;
+
+ sub stash :Local {
+ my ($self, $c) = @_;
+ $c->stash->{inner} = "inner";
+ $c->res->body( "inner: ${\$c->stash->{inner}}, outer: ${\$c->stash->{outer}}");
+
+ is_deeply [sort {$a cmp $b} keys($c->stash)], ['inner','outer'], 'both keys in stash';
+ }
+
+ package MyAppChild;
+ $INC{'MyAppChild.pm'} = __FILE__;
+
+ use Catalyst;
+ MyAppChild->setup;
+
+ package MyAppParent::Controller::User;
+ $INC{'MyAppParent/Controller/User.pm'} = __FILE__;
+
+ use base 'Catalyst::Controller';
+ use Test::More;
+
+ sub stash :Local {
+ my ($self, $c) = @_;
+ $c->stash->{outer} = "outer";
+ $c->res->from_psgi_response( MyAppChild->to_app->($c->req->env) );
+
+ is_deeply [keys($c->stash)], ['outer'], 'only one key in stash';
+ }
+
+ package MyAppParent;
+ use Catalyst;
+ MyAppParent->setup;
+
+}
+
+use Test::More;
+use Catalyst::Test 'MyAppParent';
+
+my $res = request '/user/stash';
+is $res->content, 'inner: inner, outer: outer', 'got expected response';
+
+done_testing;
};
{
+ package MyApp::PSGIObject;
+
+ sub as_psgi {
+ return [200, ['Content-Type' => 'text/plain'], ['as_psgi']];
+ };
+
package MyApp::Controller::Docs;
$INC{'MyApp/Controller/Docs.pm'} = __FILE__;
use Plack::Request;
use Catalyst::Utils;
+ sub as_psgi :Local {
+ my ($self, $c) = @_;
+ my $as_psgi = bless +{}, 'MyApp::PSGIObject';
+ $c->res->from_psgi_response($as_psgi);
+ }
+
sub name :Local {
my ($self, $c) = @_;
my $env = $c->Catalyst::Utils::env_at_action;
use Catalyst::Test 'MyApp';
{
+ my ($res, $c) = ctx_request('/docs/as_psgi');
+ is $res->content, 'as_psgi';
+}
+
+{
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';
}
done_testing();
-
-__END__
-
-
-use Plack::App::URLMap;
-use HTTP::Request::Common;
-use HTTP::Message::PSGI;
-
-my $urlmap = Plack::App::URLMap->new;
-
-my $app1 = sub {
- my $env = shift;
- return [200, [], [
- "REQUEST_URI: $env->{REQUEST_URI}, FROM: $env->{MAP_TO}, PATH_INFO: $env->{PATH_INFO}, SCRIPT_NAME $env->{SCRIPT_NAME}"]];
-};
-
-$urlmap->map("/" => sub { my $env = shift; $env->{MAP_TO} = '/'; $app1->($env)});
-$urlmap->map("/foo" => sub { my $env = shift; $env->{MAP_TO} = '/foo'; $app1->($env)});
-$urlmap->map("/bar/baz" => sub { my $env = shift; $env->{MAP_TO} = '/foo/bar'; $app1->($env)});
-
-my $app = $urlmap->to_app;
-
-warn $app->(req_to_psgi(GET '/'))->[2]->[0];
-warn $app->(req_to_psgi(GET '/111'))->[2]->[0];
-warn $app->(req_to_psgi(GET '/foo'))->[2]->[0];
-warn $app->(req_to_psgi(GET '/foo/222'))->[2]->[0];
-warn $app->(req_to_psgi(GET '/bar/baz'))->[2]->[0];
-warn $app->(req_to_psgi(GET '/bar/baz/333'))->[2]->[0];
-