merged conflicts
John Napiorkowski [Fri, 20 Sep 2013 18:19:26 +0000 (14:19 -0400)]
19 files changed:
Changes
MANIFEST.SKIP
Makefile.PL
lib/Catalyst.pm
lib/Catalyst/Request.pm
lib/Catalyst/Runtime.pm
lib/Catalyst/Utils.pm
t/author/spelling.t
t/data_handler.t [new file with mode: 0644]
t/lib/TestDataHandlers.pm [new file with mode: 0644]
t/lib/TestDataHandlers/Controller/Root.pm [new file with mode: 0644]
t/lib/TestMiddleware.pm [new file with mode: 0644]
t/lib/TestMiddleware/Controller/Root.pm [new file with mode: 0644]
t/lib/TestMiddleware/Custom.pm [new file with mode: 0644]
t/lib/TestMiddleware/share/static/forced.txt [new file with mode: 0644]
t/lib/TestMiddleware/share/static/message.txt [new file with mode: 0644]
t/lib/TestMiddleware/share/static2/message2.txt [new file with mode: 0644]
t/lib/TestMiddleware/share/static3/message3.txt [new file with mode: 0644]
t/plack-middleware.t [new file with mode: 0644]

diff --git a/Changes b/Changes
index 0a1b682..0922f90 100644 (file)
--- a/Changes
+++ b/Changes
@@ -1,5 +1,20 @@
 # This file documents the revision history for Perl extension Catalyst.
 
+5.90049_002 - 2013-08-20
+  - Fixed loading middleware from project directory
+  - Fixed some pointless warnings when middleware class lacked VERSION
+  - NEW FEATURE: Declare global 'data_handlers' for parsing HTTP POST/PUT
+    alternative content, and created default JSON handler.  Yes, now Catalyst
+    handles JSON request content out of the box!  More docs eventually but
+    for now see the DATA HANDLERS section in Catalyst.pm (or review the test
+    case t/data_handler.t
+
+5.90049_001 - 2013-07-26
+  - Declare PSGI compliant Middleware as part of your Catalyst Application via
+    a new configuration key, "psgi_middleware".
+  - Increased lowest allowed module version for Module::Pluggable to be 4.7 (up
+    from 3.4) to solve the fact this is no longer bundled with Perl in v5.18.
+
 5.90042 - 2013-06-14
   - Removed more places where an optional dependency shows up in the test
     suite. Hopefully really fixed the unicode regression introduced in 5.90040
index 876969a..b9ad8e0 100644 (file)
@@ -1,2 +1,2 @@
-^(?!script/\w+\.pl$|TODO$|lib/.+(?<!ROADMAP)\.p(m|od)$|inc/|t/a(uthor|ggregate)/.*\.t$|t/([^/]+|.{1,2}|[^t][^m][^p].*)\.(gif|yml|pl|t)$|t/lib/.*\.pm$|t/something/(Makefile.PL|script/foo/bar/for_dist)$|t/conf/extra.conf.in$|Makefile.PL$|README$|MANIFEST$|Changes$|META.yml$)
+^(?!script/\w+\.pl$|TODO$|lib/.+(?<!ROADMAP)\.p(m|od)$|inc/|t/a(uthor|ggregate)/.*\.t$|t/([^/]+|.{1,2}|[^t][^m][^p].*)\.(gif|yml|pl|t)$|t/lib/.*\.pm$|t/something/(Makefile.PL|script/foo/bar/for_dist)$|t/conf/extra.conf.in$|Makefile.PL$|README$|MANIFEST$|Changes$|META.yml$|.+testappencodingsetinconfig.json|.+TestMiddleware/share.*)
 /cpanfile
index 7ff59dc..79b8380 100644 (file)
@@ -47,7 +47,7 @@ requires 'HTTP::Headers' => '1.64';
 requires 'HTTP::Request' => '5.814';
 requires 'HTTP::Response' => '5.813';
 requires 'HTTP::Request::AsCGI' => '1.0';
-requires 'Module::Pluggable' => '3.9';
+requires 'Module::Pluggable' => '4.7';
 requires 'Path::Class' => '0.09';
 requires 'Scalar::Util';
 requires 'Sub::Exporter';
@@ -70,6 +70,7 @@ requires 'Class::Data::Inheritable';
 requires 'Encode' => '2.49';
 requires 'LWP' => '5.837'; # LWP had unicode fail in 5.8.26
 requires 'URI' => '1.36';
+requires 'JSON::MaybeXS' => '1.000000',
 
 # Install the standalone Regex dispatch modules in order to ease the
 # deprecation transition
@@ -81,6 +82,7 @@ test_requires 'Data::Dump';
 test_requires 'HTTP::Request::Common';
 test_requires 'IO::Scalar';
 test_requires 'HTTP::Status';
+test_requires 'JSON::MaybeXS';
 
 # see also cpanfile for authordeps -- install via
 # cpanm --installdeps --with-develop .
index 1c7d830..0e2fdbd 100644 (file)
@@ -41,6 +41,7 @@ use Plack::Middleware::ReverseProxy;
 use Plack::Middleware::IIS6ScriptNameFix;
 use Plack::Middleware::IIS7KeepAliveFix;
 use Plack::Middleware::LighttpdScriptNameFix;
+use Plack::Util;
 use Class::Load 'load_class';
 
 BEGIN { require 5.008003; }
@@ -63,6 +64,7 @@ sub _build_request_constructor_args {
     my $self = shift;
     my %p = ( _log => $self->log );
     $p{_uploadtmp} = $self->_uploadtmp if $self->_has_uploadtmp;
+    $p{data_handlers} = {$self->registered_data_handlers};
     \%p;
 }
 
@@ -106,7 +108,8 @@ our $GO        = Catalyst::Exception::Go->new;
 __PACKAGE__->mk_classdata($_)
   for qw/components arguments dispatcher engine log dispatcher_class
   engine_loader context_class request_class response_class stats_class
-  setup_finished _psgi_app loading_psgi_file run_options/;
+  setup_finished _psgi_app loading_psgi_file run_options _psgi_middleware
+  _data_handlers/;
 
 __PACKAGE__->dispatcher_class('Catalyst::Dispatcher');
 __PACKAGE__->request_class('Catalyst::Request');
@@ -115,7 +118,7 @@ __PACKAGE__->stats_class('Catalyst::Stats');
 
 # Remember to update this in Catalyst::Runtime as well!
 
-our $VERSION = '5.90042';
+our $VERSION = '5.90049_002';
 
 sub import {
     my ( $class, @arguments ) = @_;
@@ -1121,6 +1124,8 @@ sub setup {
 
     $class->setup_log( delete $flags->{log} );
     $class->setup_plugins( delete $flags->{plugins} );
+    $class->setup_middleware();
+    $class->setup_data_handlers();
     $class->setup_dispatcher( delete $flags->{dispatcher} );
     if (my $engine = delete $flags->{engine}) {
         $class->log->warn("Specifying the engine in ->setup is no longer supported, see Catalyst::Upgrading");
@@ -1162,6 +1167,27 @@ EOF
             $class->log->debug( "Loaded plugins:\n" . $t->draw . "\n" );
         }
 
+        my @middleware = map {
+          ref $_ eq 'CODE' ? 
+            "Inline Coderef" : 
+              (ref($_) .'  '. ($_->can('VERSION') ? $_->VERSION || '' : '') 
+                || '')  } $class->registered_middlewares;
+
+        if (@middleware) {
+            my $column_width = Catalyst::Utils::term_width() - 6;
+            my $t = Text::SimpleTable->new($column_width);
+            $t->row($_) for @middleware;
+            $class->log->debug( "Loaded PSGI Middleware:\n" . $t->draw . "\n" );
+        }
+
+        my %dh = $class->registered_data_handlers;
+        if (my @data_handlers = keys %dh) {
+            my $column_width = Catalyst::Utils::term_width() - 6;
+            my $t = Text::SimpleTable->new($column_width);
+            $t->row($_) for @data_handlers;
+            $class->log->debug( "Loaded Request Data Handlers:\n" . $t->draw . "\n" );
+        }
+
         my $dispatcher = $class->dispatcher;
         my $engine     = $class->engine;
         my $home       = $class->config->{home};
@@ -2739,6 +2765,11 @@ sub setup_engine {
     return;
 }
 
+## This exists just to supply a prebuild psgi app for mod_perl and for the 
+## build in server support (back compat support for pre psgi port behavior).
+## This is so that we don't build a new psgi app for each request when using
+## the mod_perl handler or the built in servers (http and fcgi, etc).
+
 sub _finalized_psgi_app {
     my ($app) = @_;
 
@@ -2750,6 +2781,12 @@ sub _finalized_psgi_app {
     return $app->_psgi_app;
 }
 
+## Look for a psgi file like 'myapp_web.psgi' (if the app is MyApp::Web) in the
+## home directory and load that and return it (just assume it is doing the 
+## right thing :) ).  If that does not exist, call $app->psgi_app, wrap that
+## in default_middleware and return it ( this is for backward compatibility
+## with pre psgi port behavior ).
+
 sub _setup_psgi_app {
     my ($app) = @_;
 
@@ -2860,7 +2897,8 @@ reference of your Catalyst application for use in F<.psgi> files.
 
 sub psgi_app {
     my ($app) = @_;
-    return $app->engine->build_psgi_app($app);
+    my $psgi = $app->engine->build_psgi_app($app);
+    return $app->Catalyst::Utils::apply_registered_middleware($psgi);
 }
 
 =head2 $c->setup_home
@@ -3044,6 +3082,128 @@ the plugin name does not begin with C<Catalyst::Plugin::>.
             $class => @roles
         ) if @roles;
     }
+}    
+
+=head2 registered_middlewares
+
+Read only accessor that returns an array of all the middleware in the order
+that they were added (which is the REVERSE of the order they will be applied).
+
+The values returned will be either instances of L<Plack::Middleware> or of a
+compatible interface, or a coderef, which is assumed to be inlined middleware
+
+=head2 setup_middleware (?@middleware)
+
+Read configuration information stored in configuration key C<psgi_middleware> or
+from passed @args.
+
+See under L</CONFIGURATION> information regarding C<psgi_middleware> and how
+to use it to enable L<Plack::Middleware>
+
+This method is automatically called during 'setup' of your application, so
+you really don't need to invoke it.
+
+When we read middleware definitions from configuration, we reverse the list
+which sounds odd but is likely how you expect it to work if you have prior
+experience with L<Plack::Builder> or if you previously used the plugin
+L<Catalyst::Plugin::EnableMiddleware> (which is now considered deprecated)
+
+=cut
+
+sub registered_middlewares {
+    my $class = shift;
+    if(my $middleware = $class->_psgi_middleware) {
+        return @$middleware;
+    } else {
+        die "You cannot call ->registered_middlewares until middleware has been setup";
+    }
+}
+
+sub setup_middleware {
+    my ($class, @middleware_definitions) = @_;
+    push @middleware_definitions, reverse(
+      @{$class->config->{'psgi_middleware'}||[]});
+
+    my @middleware = ();
+    while(my $next = shift(@middleware_definitions)) {
+        if(ref $next) {
+            if(Scalar::Util::blessed $next && $next->can('wrap')) {
+                push @middleware, $next;
+            } elsif(ref $next eq 'CODE') {
+                push @middleware, $next;
+            } elsif(ref $next eq 'HASH') {
+                my $namespace = shift @middleware_definitions;
+                my $mw = $class->Catalyst::Utils::build_middleware($namespace, %$next);
+                push @middleware, $mw;
+            } else {
+              die "I can't handle middleware definition ${\ref $next}";
+            }
+        } else {
+          my $mw = $class->Catalyst::Utils::build_middleware($next);
+          push @middleware, $mw;
+        }
+    }
+
+    $class->_psgi_middleware(\@middleware);
+}
+
+=head2 registered_data_handlers
+
+A read only copy of registered Data Handlers returned as a Hash, where each key
+is a content type and each value is a subref that attempts to decode that content
+type.
+
+=head2 setup_data_handlers (?@data_handler)
+
+Read configuration information stored in configuration key C<data_handlers> or
+from passed @args.
+
+See under L</CONFIGURATION> information regarding C<data_handlers>.
+
+This method is automatically called during 'setup' of your application, so
+you really don't need to invoke it.
+
+=head2 default_data_handlers
+
+Default Data Handler that come bundled with L<Catalyst>.  Currently there is
+only one default data handler, for 'application/json'.  This uses L<JSON::MaybeXS>
+which uses the dependency free L<JSON::PP> OR L<Cpanel::JSON::XS> if you have
+installed it.  If you don't mind the XS dependency, you should add the faster
+L<Cpanel::JSON::XS> to you dependency list (in your Makefile.PL or dist.ini, or
+cpanfile, etc.)
+
+L<JSON::MaybeXS> is loaded the first time you ask for it (so if you never ask
+for it, its never used).
+
+=cut
+
+sub registered_data_handlers {
+    my $class = shift;
+    if(my $data_handlers = $class->_data_handlers) {
+        return %$data_handlers;
+    } else {
+        die "You cannot call ->registered_data_handlers until data_handers has been setup";
+    }
+}
+
+sub setup_data_handlers {
+    my ($class, %data_handler_callbacks) = @_;
+    %data_handler_callbacks = (
+      %{$class->default_data_handlers},
+      %{$class->config->{'data_handlers'}||+{}},
+      %data_handler_callbacks);
+
+    $class->_data_handlers(\%data_handler_callbacks);
+}
+
+sub default_data_handlers {
+    my ($class) = @_;
+    return +{
+      'application/json' => sub {
+          local $/;
+          Class::Load::load_class("JSON::MaybeXS");
+          JSON::MaybeXS::decode_json $_->getline },
+    };
 }
 
 =head2 $c->stack
@@ -3233,6 +3393,14 @@ use like:
 
 In the future this might become the default behavior.
 
+=item *
+
+C<psgi_middleware> - See L<PSGI MIDDLEWARE>.
+
+=item *
+
+C<data_handlers> - See L<DATA HANDLERS>.
+
 =back
 
 =head1 INTERNAL ACTIONS
@@ -3324,6 +3492,171 @@ If you plan to operate in a threaded environment, remember that all other
 modules you are using must also be thread-safe. Some modules, most notably
 L<DBD::SQLite>, are not thread-safe.
 
+=head1 DATA HANDLERS
+
+The L<Catalyst::Request> object uses L<HTTP::Body> to populate 'classic' HTML
+form parameters and URL search query fields.  However it has become common
+for various alternative content types to be PUT or POSTed to your controllers
+and actions.  People working on RESTful APIs, or using AJAX often use JSON,
+XML and other content types when communicating with an application server.  In
+order to better support this use case, L<Catalyst> defines a global configuration
+option, C<data_handlers>, which lets you associate a content type with a coderef
+that parses that content type into something Perl can readily access.
+
+    package MyApp::Web;
+    use Catalyst;
+    use JSON::Maybe;
+    __PACKAGE__->config(
+      data_handlers => {
+        'application/json' => sub { local $/; decode_json $_->getline },
+      },
+      ## Any other configuration.
+    );
+    __PACKAGE__->setup;
+
+By default L<Catalyst> comes with a generic JSON data handler similar to the
+example given above, which uses L<JSON::Maybe> to provide either L<JSON::PP>
+(a pure Perl, dependency free JSON parser) or L<Cpanel::JSON::XS> if you have
+it installed (if you want the faster XS parser, add it to you project Makefile.PL
+or dist.ini, cpanfile, etc.)
+
+The C<data_handlers> configuation is a hashref whose keys are HTTP Content-Types
+(matched against the incoming request type using a regexp such as to be case
+insensitive) and whose values are coderefs that receive a localized version of
+C<$_> which is a filehandle object pointing to received body.
+
+This feature is considered an early access release and we reserve the right
+to alter the interface in order to provide a performant and secure solution to
+alternative request body content.  Your reports welcomed!
+
+=head1 PSGI MIDDLEWARE
+
+You can define middleware, defined as L<Plack::Middleware> or a compatible
+interface in configuration.  Your middleware definitions are in the form of an
+arrayref under the configuration key C<psgi_middleware>.  Here's an example
+with details to follow:
+
+    package MyApp::Web;
+    use Catalyst;
+    use Plack::Middleware::StackTrace;
+    my $stacktrace_middleware = Plack::Middleware::StackTrace->new;
+    __PACKAGE__->config(
+      'psgi_middleware', [
+        'Debug',
+        '+MyApp::Custom',
+        $stacktrace_middleware,
+        'Session' => {store => 'File'},
+        sub {
+          my $app = shift;
+          return sub {
+            my $env = shift;
+            $env->{myapp.customkey} = 'helloworld';
+            $app->($env);
+          },
+        },
+      ],
+    );
+    __PACKAGE__->setup;
+
+So the general form is:
+
+    __PACKAGE__->config(psgi_middleware => \@middleware_definitions);
+
+Where C<@middleware> is one or more of the following, applied in the REVERSE of
+the order listed (to make it function similarly to L<Plack::Builder>:
+=over 4
+=item Middleware Object
+An already initialized object that conforms to the L<Plack::Middleware>
+specification:
+    my $stacktrace_middleware = Plack::Middleware::StackTrace->new;
+    __PACKAGE__->config(
+      'psgi_middleware', [
+        $stacktrace_middleware,
+      ]);
+=item coderef
+A coderef that is an inlined middleware:
+    __PACKAGE__->config(
+      'psgi_middleware', [
+        sub {
+          my $app = shift;
+          return sub {
+            my $env = shift;
+            if($env->{PATH_INFO} =~m/forced/) {
+              Plack::App::File
+                ->new(file=>TestApp->path_to(qw/share static forced.txt/))
+                ->call($env);
+            } else {
+              return $app->($env);
+            }
+         },
+      },
+    ]);
+=item a scalar
+We assume the scalar refers to a namespace after normalizing it using the
+following rules:
+
+(1) If the scalar is prefixed with a "+" (as in C<+MyApp::Foo>) then the full string
+is assumed to be 'as is', and we just install and use the middleware.
+
+(2) If the scalar begins with "Plack::Middleware" or your application namespace
+(the package name of your Catalyst application subclass), we also assume then
+that it is a full namespace, and use it.
+
+(3) Lastly, we then assume that the scalar is a partial namespace, and attempt to
+resolve it first by looking for it under your application namespace (for example
+if you application is "MyApp::Web" and the scalar is "MyMiddleware", we'd look
+under "MyApp::Web::Middleware::MyMiddleware") and if we don't find it there, we
+will then look under the regular L<Plack::Middleware> namespace (i.e. for the
+previous we'd try "Plack::Middleware::MyMiddleware").  We look under your application
+namespace first to let you 'override' common L<Plack::Middleware> locally, should
+you find that a good idea.
+
+Examples:
+
+    package MyApp::Web;
+
+    __PACKAGE__->config(
+      'psgi_middleware', [
+        'Debug',  ## MyAppWeb::Middleware::Debug->wrap or Plack::Middleware::Debug->wrap
+        'Plack::Middleware::Stacktrace', ## Plack::Middleware::Stacktrace->wrap
+        '+MyApp::Custom',  ## MyApp::Custom->wrap
+      ],
+    );
+=item a scalar followed by a hashref
+Just like the previous, except the following C<HashRef> is used as arguments
+to initialize the middleware object.
+    __PACKAGE__->config(
+      'psgi_middleware', [
+         'Session' => {store => 'File'},
+    ]);
+
+=back
+
+Please see L<PSGI> for more on middleware.
+
 =head1 ENCODING
 
 On request, decodes all params from encoding into a sequence of
index f75319b..ab87e17 100644 (file)
@@ -92,10 +92,10 @@ has _log => (
 );
 
 has io_fh => (
-  is=>'ro',
-  predicate=>'has_io_fh',
-  lazy=>1,
-  builder=>'_build_io_fh');
+    is=>'ro',
+    predicate=>'has_io_fh',
+    lazy=>1,
+    builder=>'_build_io_fh');
 
 sub _build_io_fh {
     my $self = shift;
@@ -103,16 +103,27 @@ sub _build_io_fh {
       || die "Your Server does not support psgix.io";
 };
 
-has body_fh => (
-  is=>'ro',
-  predicate=>'has_body_fh',
-  lazy=>1,
-  builder=>'_build_body_fh');
+has data_handlers => ( is=>'ro', isa=>'HashRef', default=>sub { +{} } );
 
-sub _build_body_fh {
-    (my $input_fh = shift->env->{'psgi.input'})->seek(0, 0);
-    return $input_fh;
-};
+has body_data => (
+    is=>'ro',
+    lazy=>1,
+    builder=>'_build_body_data');
+
+sub _build_body_data {
+    my ($self) = @_;
+    my $content_type = $self->content_type;
+    my ($match) = grep { $content_type =~/$_/i }
+      keys(%{$self->data_handlers});
+
+    if($match) {
+      my $fh = $self->body;
+      local $_ = $fh;
+      return $self->data_handlers->{$match}->($fh, $self);
+    } else { 
+      return undef;
+    }
+}
 
 # Amount of data to read from input on each pass
 our $CHUNKSIZE = 64 * 1024;
@@ -182,8 +193,6 @@ sub prepare_parameters {
     return $self->parameters;
 }
 
-
-
 sub _build_parameters {
     my ( $self ) = @_;
     my $parameters = {};
@@ -342,6 +351,7 @@ Catalyst::Request - provides information about the current client request
     $req->args;
     $req->base;
     $req->body;
+    $req->body_data;
     $req->body_parameters;
     $req->content_encoding;
     $req->content_length;
@@ -424,6 +434,14 @@ Returns the message body of the request, as returned by L<HTTP::Body>: a string,
 unless Content-Type is C<application/x-www-form-urlencoded>, C<text/xml>, or
 C<multipart/form-data>, in which case a L<File::Temp> object is returned.
 
+=head2 $req->body_data
+
+Returns a Perl representation of POST/PUT body data that is not classic HTML
+form data, such as JSON, XML, etc.  By default, Catalyst will parse incoming
+data of the type 'application/json' and return access to that data via this
+method.  You may define addition data_handlers via a global configuration
+setting.  See L<Catalyst\DATA HANDLERS> for more information.
+
 =head2 $req->body_parameters
 
 Returns a reference to a hash containing body (POST) parameters. Values can
index 867ecf8..ebca126 100644 (file)
@@ -7,7 +7,7 @@ BEGIN { require 5.008003; }
 
 # Remember to update this in Catalyst as well!
 
-our $VERSION = '5.90042';
+our $VERSION = '5.90049_002';
 
 =head1 NAME
 
index 56c2a80..31773b7 100644 (file)
@@ -9,6 +9,7 @@ use Carp qw/croak/;
 use Cwd;
 use Class::Load 'is_class_loaded';
 use String::RewritePrefix;
+use Class::Load ();
 
 use namespace::clean;
 
@@ -432,6 +433,56 @@ sub resolve_namespace {
     }, @classes);
 }
 
+=head2 build_middleware (@args)
+
+Internal application that converts a single middleware definition (see
+L<Catalyst/psgi_middleware>) into an actual instance of middleware.
+
+=cut
+
+sub build_middleware {
+    my ($class, $namespace, @init_args) = @_;
+
+    if(
+      $namespace =~s/^\+// ||
+      $namespace =~/^Plack::Middleware/ ||
+      $namespace =~/^$class/
+    ) {  ## the string is a full namespace
+        return Class::Load::try_load_class($namespace) ?
+          $namespace->new(@init_args) :
+            die "Can't load class $namespace";
+    } else { ## the string is a partial namespace
+      if(Class::Load::try_load_class($class .'::Middleware::'. $namespace)) { ## Load Middleware from Project namespace
+          my $ns = $class .'::Middleware::'. $namespace;
+          return $ns->new(@init_args);
+        } elsif(Class::Load::try_load_class("Plack::Middleware::$namespace")) { ## Act like Plack::Builder
+          return "Plack::Middleware::$namespace"->new(@init_args);
+        }
+    }
+
+    return; ## be sure we can count on a proper return when valid
+}
+
+=head2 apply_registered_middleware ($psgi)
+
+Given a $psgi reference, wrap all the L<Catalyst/registered_middlewares>
+around it and return the wrapped version.
+
+This exists to deal with the fact Catalyst registered middleware can be
+either an object with a wrap method or a coderef.
+
+=cut
+
+sub apply_registered_middleware {
+    my ($class, $psgi) = @_;
+    my $new_psgi = $psgi;
+    foreach my $middleware ($class->registered_middlewares) {
+        $new_psgi = Scalar::Util::blessed $middleware ?
+          $middleware->wrap($new_psgi) :
+            $middleware->($new_psgi);
+    }
+    return $new_psgi;
+}
 
 =head1 AUTHORS
 
index 37cea27..7a57be7 100644 (file)
@@ -19,7 +19,8 @@ add_stopwords(qw(
     inline INLINE plugins cpanfile
     FastCGI Stringifies Rethrows DispatchType Wishlist Refactor ROADMAP HTTPS Unescapes Restarter Nginx Refactored
     ActionClass LocalRegex LocalRegexp MyAction metadata cometd io psgix websockets
-    UTF async codebase dev filenames params
+    UTF async codebase dev filenames params MyMiddleware
+    JSON POSTed RESTful configuation performant subref
     Andreas
     Ashton
     Axel
diff --git a/t/data_handler.t b/t/data_handler.t
new file mode 100644 (file)
index 0000000..26ce2f9
--- /dev/null
@@ -0,0 +1,24 @@
+#!/usr/bin/env perl
+
+use warnings;
+use strict;
+
+use FindBin;
+use Test::More;
+use HTTP::Request::Common;
+use JSON::MaybeXS;
+
+use lib "$FindBin::Bin/lib";
+use Catalyst::Test 'TestDataHandlers';
+
+ok my($res, $c) = ctx_request('/');
+ok my $message = 'helloworld';
+ok my $post = encode_json +{message=>$message};
+ok my $req = POST $c->uri_for_action('/test_json'),
+   Content_Type => 'application/json',
+   Content => $post;
+
+ok my $response = request $req, 'got a response from a catalyst controller';
+is $response->content, $message, 'expected content body';
+
+done_testing;
diff --git a/t/lib/TestDataHandlers.pm b/t/lib/TestDataHandlers.pm
new file mode 100644 (file)
index 0000000..0c52778
--- /dev/null
@@ -0,0 +1,9 @@
+package TestDataHandlers;
+
+use Catalyst;
+
+__PACKAGE__->config(
+  'Controller::Root', { namespace => '' }
+);
+
+__PACKAGE__->setup;
diff --git a/t/lib/TestDataHandlers/Controller/Root.pm b/t/lib/TestDataHandlers/Controller/Root.pm
new file mode 100644 (file)
index 0000000..42bf85b
--- /dev/null
@@ -0,0 +1,10 @@
+package TestDataHandlers::Controller::Root;
+
+use base 'Catalyst::Controller';
+
+sub test_json :Local {
+    my ($self, $c) = @_;
+    $c->res->body($c->req->body_data->{message});
+}
+
+1;
diff --git a/t/lib/TestMiddleware.pm b/t/lib/TestMiddleware.pm
new file mode 100644 (file)
index 0000000..326f1be
--- /dev/null
@@ -0,0 +1,38 @@
+package TestMiddleware;
+
+use Moose;
+use Plack::Middleware::Static;
+use Plack::App::File;
+use Catalyst;
+
+extends 'Catalyst';
+
+my $static = Plack::Middleware::Static->new(
+  path => qr{^/static/}, root => TestMiddleware->path_to('share'));
+
+__PACKAGE__->config(
+  'Controller::Root', { namespace => '' },
+  'psgi_middleware', [
+    'Head',
+    $static,
+    'Static', { path => qr{^/static2/}, root => TestMiddleware->path_to('share') },
+    'Runtime',
+    '+TestMiddleware::Custom', { path => qr{^/static3/}, root => TestMiddleware->path_to('share') },
+    sub {
+      my $app = shift;
+      return sub {
+        my $env = shift;
+        if($env->{PATH_INFO} =~m/forced/) {
+          Plack::App::File->new(file=>TestMiddleware->path_to(qw/share static forced.txt/))
+            ->call($env);
+        } else {
+          return $app->($env);
+        }
+      },
+    },
+
+  ],
+);
+
+__PACKAGE__->setup;
+
diff --git a/t/lib/TestMiddleware/Controller/Root.pm b/t/lib/TestMiddleware/Controller/Root.pm
new file mode 100644 (file)
index 0000000..7c116a5
--- /dev/null
@@ -0,0 +1,13 @@
+package TestMiddleware::Controller::Root;
+
+use Moose;
+use MooseX::MethodAttributes;
+
+extends 'Catalyst::Controller';
+
+sub default : Path { }
+sub welcome : Path(welcome) {
+  pop->res->body('Welcome to Catalyst');
+}
+
+__PACKAGE__->meta->make_immutable;
diff --git a/t/lib/TestMiddleware/Custom.pm b/t/lib/TestMiddleware/Custom.pm
new file mode 100644 (file)
index 0000000..580a66e
--- /dev/null
@@ -0,0 +1,8 @@
+package TestMiddleware::Custom;
+
+use strict;
+use warnings;
+
+use parent qw/Plack::Middleware::Static/;
+
+1;
diff --git a/t/lib/TestMiddleware/share/static/forced.txt b/t/lib/TestMiddleware/share/static/forced.txt
new file mode 100644 (file)
index 0000000..47b6118
--- /dev/null
@@ -0,0 +1 @@
+forced message
diff --git a/t/lib/TestMiddleware/share/static/message.txt b/t/lib/TestMiddleware/share/static/message.txt
new file mode 100644 (file)
index 0000000..9bdf9d1
--- /dev/null
@@ -0,0 +1 @@
+static message
diff --git a/t/lib/TestMiddleware/share/static2/message2.txt b/t/lib/TestMiddleware/share/static2/message2.txt
new file mode 100644 (file)
index 0000000..9bdf9d1
--- /dev/null
@@ -0,0 +1 @@
+static message
diff --git a/t/lib/TestMiddleware/share/static3/message3.txt b/t/lib/TestMiddleware/share/static3/message3.txt
new file mode 100644 (file)
index 0000000..9bdf9d1
--- /dev/null
@@ -0,0 +1 @@
+static message
diff --git a/t/plack-middleware.t b/t/plack-middleware.t
new file mode 100644 (file)
index 0000000..4cc3e72
--- /dev/null
@@ -0,0 +1,57 @@
+#!/usr/bin/env perl
+
+use warnings;
+use strict;
+
+use FindBin;
+use Test::More;
+use HTTP::Request::Common;
+
+use lib "$FindBin::Bin/lib";
+use Catalyst::Test 'TestMiddleware';
+
+ok my($res, $c) = ctx_request('/');
+
+{
+  ok my $response = request GET $c->uri_for_action('/welcome'),
+    'got welcome from a catalyst controller';
+
+  is $response->content, 'Welcome to Catalyst',
+    'expected content body';
+}
+
+{
+  ok my $response = request GET $c->uri_for('/static/message.txt'),
+    'got welcome from a catalyst controller';
+
+  like $response->content, qr'static message',
+    'expected content body';
+}
+
+{
+  ok my $response = request GET $c->uri_for('/static2/message2.txt'),
+    'got welcome from a catalyst controller';
+
+  like $response->content, qr'static message',
+    'expected content body';
+}
+
+{
+  ok my $response = request GET $c->uri_for('/static3/message3.txt'),
+    'got welcome from a catalyst controller';
+
+  like $response->content, qr'static message',
+    'expected content body';
+}
+
+{
+  ok my $response = request GET $c->uri_for('/forced'),
+    'got welcome from a catalyst controller';
+
+  like $response->content, qr'forced message',
+    'expected content body';
+
+  ok $response->headers->{"x-runtime"}, "Got value for expected middleware";
+}
+
+done_testing;