Middleware
Middleware is a concept in PSGI (as always, stolen from Python's WSGI and Ruby's Rack) where we define components that plays the both side of a server and an application.
This picture illustrates the middleware concept very well. The PSGI application is in the core of the Onion layers, and middleware components wrap the original application in return, and they preprocess as a request comes in (outer to inner) and then postprocess the response as a response goes out (inner to outer).
Lots of functionalities can be added to the PSGI application by wrapping it with a middleware component, from HTTP authentication, capturing errors to logging output or wrapping JSON output with JSONP.
Plack::Middleware
Plack::Middleware is a base class for middleware components and it allows you to write middleware really simply but in a reusable fashion.
Using Middleware components written with Plack::Middleware is easy, just wrap the original application with wrap
method:
my $app = sub { [ 200, ... ] };
use Plack::Middleware::StackTrace;
$app = Plack::Middleware::StackTrace->wrap($app);
This example wraps the original application with StackTrace middleware (which is actually enabled by default using plackup) with the wrap
method. So when the wrapped application throws an error, the middleware component catches the error to display a beautiful HTML page using Devel::StackTrace::AsHTML.
Some other middleware components take parameters, in which case you can pass the parameters as a hash after $app
, like:
my $app = sub { ... };
use Plack::Middleware::MethodOverride;
$app = Plack::Middleware::MethodOverride->wrap($app, header => 'X-Method');
Installing multiple middleware components is tedious especially since you need to use
those modules first, and we have a quick solution for that using a DSL style syntax.
use Plack::Builder;
my $app = sub { ... };
builder {
enable "StackTrace";
enable "MethodOverride", header => 'X-Method';
enable "Deflater";
$app;
};
We'll see more about Plack::Builder tomorrow.
Middleware and Frameworks
The beauty of Middleware is that it can wrap any PSGI application. It might not be obvious from the code examples, but the wrapped application can be anything, which means you can run your existing web application in the PSGI mode and apply middleware components to it. For instance, with CGI::Application:
use CGI::Application::PSGI;
use WebApp;
my $app = sub {
my $env = shift;
my $app = WebApp->new({ QUERY => CGI::PSGI->new($env) });
CGI::Application::PSGI->run($app);
};
use Plack::Builder;
builder {
enable "Auth::Basic", authenticator => sub { $_[1] eq 'foobar' };
$app;
};
This will enable the Basic authentication middleware to CGI::Application based application. You can do the same with any other frameworks that supports PSGI.
The onion shows a "Session Middleware". Will there be a session handling for Plack?
Posted by: Uwe | 12/11/2009 at 02:04 AM
Uwe: the picture is taken from Pylons (http://pylonshq.com/). We're currently working on Plack session middleware and about to ship the first version pretty soon. It's rather really simple port of HTTP::Session module from CPAN.
Posted by: miyagawa | 12/11/2009 at 10:03 AM
I guess for me the thing that is most confusing is the overlap in functionality between some of the larger web application frameworks, such as Catalyst, that has relatively mature versions of some of the things going into middleware, such as sessioning, or the stacktrace you pointed out, or the http basic authentication.
I can definitely see the advantage of moving all those bits 'up the stack' a bit, since plack middleware is going to be more broadly useful than say stuff that's specific to a particular framework. I just wonder what will be the effort involved in making sure all the bits work down to the application level.
For example, in Catalyst using the Catalyst session plugin, I configure the plugin via the standard Catalyst configuration method (typically using ConfigLoader Plugin to hold session configuration stuff, like expiration time, what the session storage type will be, etc.) Then I can just do "$c->session..." in my controllers. I imagine plack session middleware will be configured via the .psgi file? I'd worry a bit about possible fragmentation of configuration. Or maybe we will have some sort of lazy way to activate middleware layers so that configuration info can be supplied from deeper in the stack. Then, from say something like Catalyst, I image we'd need some sort of wrapper to expose that session object to the app framework. I wonder if you have any thoughts on the matter.
Maybe over time we will see a migration of efforts from Application specific to middleware and then some of these issue will be solved.
Thank you for putting so much time into helping eveyone understand how psgi is going to rock the Perl Web development world!
Posted by: john | 12/12/2009 at 10:45 AM
john: your concern is valid. Things like StackTrace is no brainer but Authentication or Session is something developers need to configure so it will play nicely with the configuration of framework, say Catalyst.
But hey, this is a solved problem in Ruby/Rack world, precisely the way you proposed. Rails originally started as using cgi.rb and mod_ruby etc. and its own plugin system and has moved to Rack, allowing developers to use Rack middleware components in config/initializers/stack.rb.
http://guides.rubyonrails.org/rails_on_rack.html
I guess this could be added to Catalyst as a Catalyst::Config* plugin (or something equivalent), so the end users can configure Catalyst core and PSGI middleware using the same semantics or syntax.
Posted by: miyagawa | 12/12/2009 at 10:51 AM
Thanks for the insight. I will look at that link. I'm also playing with Bread::Board, but that's probably too heavyweight a solution for deferred or service oriented configuration.
Posted by: jjnapiorkowski.vox.com | 12/14/2009 at 07:57 AM
Just to say that the onion image can be found at https://pylons-webframework.readthedocs.org/en/latest/_images/pylons_as_onion.png
Posted by: Julián Esteves | 05/16/2013 at 11:55 AM