The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Plack::App::Cascade;
use strict;
use base qw(Plack::Component);

use Plack::Util::Accessor qw(apps catch codes);

sub add {
    my $self = shift;
    $self->apps([]) unless $self->apps;
    push @{$self->apps}, @_;
}

sub prepare_app {
    my $self = shift;
    my %codes = map { $_ => 1 } @{ $self->catch || [ 404 ] };
    $self->codes(\%codes);
}

sub call {
    my($self, $env) = @_;

    my $res = [ 404, [ 'Content-Type' => 'text/html' ], [ '404 Not Found' ] ];

    for my $app (@{$self->apps || []}) {
        $res = $app->($env);
        last unless ref $res eq 'ARRAY' && $self->codes->{$res->[0]};
    }

    return $res;
}

1;

__END__

=head1 NAME

Plack::App::Cascade - Cascadable compound application

=head1 SYNOPSIS

  use Plack::App::Cascade;
  use Plack::App::URLMap;
  use Plack::App::File;

  # Serve static files from multiple search paths
  my $cascade = Plack::App::Cascade->new;
  $cascade->add( Plack::App::File->new(root => "/www/example.com/foo")->to_app );
  $cascade->add( Plack::App::File->new(root => "/www/example.com/bar")->to_app );

  my $app = Plack::App::URLMap->new;
  $app->map("/static", $cascade);
  $app->to_app;

=head1 DESCRIPTION

Plack::App::Cascade is a Plack middleware component that compounds
several apps and tries them to return the first response that is not
404.

Note that this application doesn't support I<psgi.streaming>
interface.

=head1 METHODS

=over 4

=item new

  $app = Plack::App::Cascade->new(apps => [ $app1, $app2 ]);

Creates a new Cascade application.

=item add

  $app->add($app1);
  $app->add($app2, $app3);

Appends a new application to the list of apps to try. You can pass the
multiple apps to the one C<add> call.

=item catch

  $app->catch([ 403, 404 ]);

Sets which error codes to catch and process onwards. Defaults to C<404>.

=back

=head1 AUTHOR

Tatsuhiko Miyagawa

=head1 SEE ALSO

L<Plack::App::URLMap> Rack::Cascade

=cut