merged master changes
John Napiorkowski [Fri, 3 Oct 2014 21:29:05 +0000 (16:29 -0500)]
Changes
lib/Catalyst.pm
lib/Catalyst/Middleware/Stash.pm
lib/Catalyst/Response.pm
lib/Catalyst/Runtime.pm
t/aggregate/to_app.t [new file with mode: 0644]
t/middleware-stash.t [new file with mode: 0644]
t/psgi_utils.t

diff --git a/Changes b/Changes
index 3028b3f..f4326ce 100644 (file)
--- a/Changes
+++ b/Changes
@@ -1,5 +1,17 @@
 # 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++)
 
index 9abd86b..dbab5af 100644 (file)
@@ -127,7 +127,7 @@ __PACKAGE__->stats_class('Catalyst::Stats');
 __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 ) = @_;
@@ -495,6 +495,18 @@ Catalyst).
     # 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 {
@@ -2993,7 +3005,9 @@ sub apply_default_middlewares {
     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
@@ -3004,6 +3018,8 @@ reference of your Catalyst application for use in F<.psgi> files.
 
 =cut
 
+*to_app = \&psgi_app;
+
 sub psgi_app {
     my ($app) = @_;
     my $psgi = $app->engine->build_psgi_app($app);
@@ -3095,9 +3111,7 @@ sub _handle_param_unicode_decoding {
 
     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({
index 170fa11..e99285c 100644 (file)
@@ -9,12 +9,12 @@ use Carp 'croak';
 
 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 {
@@ -38,16 +38,13 @@ sub _create_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
@@ -63,6 +60,15 @@ alone distribution
 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.
@@ -104,7 +110,7 @@ clients.  Stash key / value are stored in memory.
         ["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
 
index f049ebf..709f0ad 100644 (file)
@@ -4,6 +4,7 @@ use Moose;
 use HTTP::Headers;
 use Moose::Util::TypeConstraints;
 use namespace::autoclean;
+use Scalar::Util 'blessed';
 
 with 'MooseX::Emulate::Class::Accessor::Fast';
 
@@ -116,6 +117,9 @@ sub finalize_headers {
 
 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);
@@ -430,6 +434,8 @@ a $responder) set the response from it.
 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;
index fb39f90..a8da289 100644 (file)
@@ -7,7 +7,7 @@ BEGIN { require 5.008003; }
 
 # Remember to update this in Catalyst as well!
 
-our $VERSION = '5.90074';
+our $VERSION = '5.90080_001';
 
 =head1 NAME
 
diff --git a/t/aggregate/to_app.t b/t/aggregate/to_app.t
new file mode 100644 (file)
index 0000000..7bcb497
--- /dev/null
@@ -0,0 +1,11 @@
+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;
diff --git a/t/middleware-stash.t b/t/middleware-stash.t
new file mode 100644 (file)
index 0000000..e35e9ef
--- /dev/null
@@ -0,0 +1,52 @@
+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;
index 078dd82..9c05559 100644 (file)
@@ -9,6 +9,12 @@ my $psgi_app = sub {
 };
 
 {
+  package MyApp::PSGIObject;
+
+  sub as_psgi {
+    return [200, ['Content-Type' => 'text/plain'], ['as_psgi']];
+  };
+
   package MyApp::Controller::Docs;
   $INC{'MyApp/Controller/Docs.pm'} = __FILE__;
 
@@ -16,6 +22,12 @@ my $psgi_app = sub {
   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;
@@ -122,6 +134,11 @@ use Test::More;
 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';
@@ -367,32 +384,3 @@ use Catalyst::Test 'MyApp';
 }
 
 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];
-