Fork me on GitHub

Day 18: Load middleware conditionally

I've introduced a couple of middleware components. Some of them are useful and could be enabled globally, while others might be better enabled on certain conditions. Today we'll talk about a solution to this.

Load middleware conditionally

Conditional middleware is a super (or meta) middleware that takes one middleware and enable that middleware based on a runtime condition. Let's take some examples:

  • You want to enable JSONP middleware only if the path begins with /public
  • You don't want to enable Basic Auth if the request comes from local IP

We investigated how they deal with situations like this in WSGI and Rack, but couldn't find a generic solution, and they mostly just implement options to individual component, which did not look cool for me.

Middleware::Conditional

The Conditional middleware is an ultimate flexible solution to this:

 use Plack::Builder;

 builder {
     enable_if { $_[0]->{REMOTE_ADDR} !~ /^192\.168\.0\./ }
         "Auth::Basic", authenticator => ...;
     $app;
 };

We added a new keyword to Plack::Builder enable_if, which takes a block that gets evaluated in the request time ($_[0] there is the $env hash) and if the block returns true, run the wrapped application but otherwise pass through.

This example code examines if the request comes from a local network and runs a basic authentication otherwise.

Conditional is implemented as a normal piece of middleware, and internally this is equivalent to:

 use Plack::Middleware::Conditional;
 use Plack::Middleware::Auth::Basic;

 my $app = sub { ... };

 $app = Plack::Middleware::Conditional->wrap($app,
     builder => sub {
         Plack::Middleware::Auth::Basic->wrap(
             $_[0], authenticator => ...,
         );
     },
     condition => sub {
         my $env = shift;
         $env->{REMOTE_ADDR} !~ /^192\.168\.0\./;
     },
 );

But it's a little boring to write, so we added a DSL version, which I recommend to use :)