After I started playing with Plack, I tried to evaluate whether to continue using it for our mission-critical production stuff or give up, going back to the same techniques we already use (successfully).
I think it's time to develop and deploy a Plack based application. In my grand plan, :-), I'd like to deploy nginx with PSGI support, or even more ambitiously, nginx or apache with Starman as "backend" http server. We'll see…
In the meantime, I'd like to write here a couple of niceties about Plack and Starman, showing some real code I wrote when I started.
A real world PSGI application
Here's a sample PSGI application currently under development:
#!/usr/bin/env perl
#
# Sample PSGI application
#
use strict;
#se warnings;
use constant ENVIRONMENT => 'development';
use constant APACHE_DEPLOYMENT => (ENVIRONMENT eq 'production');
use constant ENABLE_ACCESS_LOG => (ENVIRONMENT eq 'development');
use constant ENABLE_DEBUG_PANELS => (ENVIRONMENT eq 'development');
use Plack::Builder;
use AuthOpera;
use AuthOpera::Account;
my $app = AuthOpera::Account->new();
builder {
enable_if { not APACHE_DEPLOYMENT }
'Plack::Middleware::Static',
path => qr{^/(bitmaps/|images/|js/|css/|downtime/|favicon.ico$|ping.html$)},
root => '..',
;
mount "/account" => builder {
enable_if { ENABLE_DEBUG_PANELS } 'StackTrace';
enable_if { ENABLE_DEBUG_PANELS } 'Debug'; # panels => [ qw(DBITrace Memory) ];
enable_if { ENABLE_DEBUG_PANELS } 'Lint';
enable_if { ENABLE_DEBUG_PANELS } 'Runtime';
enable_if { ENABLE_ACCESS_LOG } 'AccessLog';
$app;
}
}
Of course, the main application code is not here, but in the AuthOpera::Account
class. That's not really relevant to what we're discussing here. Let's just say that any class, to be a valid and complete PSGI application, has to:
- subclass from Plack::Component
- have a
call()
method
- the
call()
method must return a valid PSGI response. Example:
package MyPSGIApp;
use strict;
use Data:: Dumper ();
use parent 'Plack::Component';
sub call {
# $env is the full PSGI environment
my ($self, $env) = @_;
return [
# HTTP Status code
200,
# HTTP headers as arrayref
[ 'Content-type' => 'text/html' ],
# Response body as array ref
[ '<!DOCTYPE html>',
'<body><h1>Hello world</h1><pre>',
Data:: Dumper:: Dumper($env),
'</pre></body></html>',
],
];
}
1;
That's it, this is a full PSGI application that does dump all its PSGI environment.
Of course in a real example, you probably want a template engine to return the page content, etc… That's what we are building for our applications. Actually just assembling the components we already have developed during these years, so we have template classes, config classes, localization, database access, etc…
So we're basically just gluing these ready made components inside the PSGI application, and then using them. I don't think this is particularly original, but it allows us to quickly "port" our code to PSGI and thus run anywhere we want to.
app.psgi
in detail
Now, let's see the PSGI app in more detail.
use constant ENVIRONMENT => 'development';
use constant APACHE_DEPLOYMENT => (ENVIRONMENT eq 'production');
use constant ENABLE_ACCESS_LOG => (ENVIRONMENT eq 'development');
use constant ENABLE_DEBUG_PANELS => (ENVIRONMENT eq 'development');
These constants are used to turn on and off certain features mentioned later in the builder {}
block. I just found out the other day that these constants are near to useless. That is because plackup
and starman
already provide a -E environment
switch. If you start your application with:
starman -E development myapp.psgi # same with plackup, the default server
then Plack will by default enable the debugging panels and the Apache-style access log. I found out about this after having written that file. This means that the following enable_if
s are unnecessary:
mount "/myroot" => builder {
enable_if { ENABLE_DEBUG_PANELS } 'StackTrace';
enable_if { ENABLE_DEBUG_PANELS } 'Debug'; # panels => [ qw(DBITrace Memory) ];
enable_if { ENABLE_DEBUG_PANELS } 'Lint';
enable_if { ENABLE_DEBUG_PANELS } 'Runtime';
enable_if { ENABLE_ACCESS_LOG } 'AccessLog';
$app;
}
I think Plack enables by default at least StackTrace
, Debug
, and AccessLog
. In my case, however, I'm also enabling RunTime
and Lint
. But more importantly, I need to differentiate between Apache deployment and Starman deployment. That affects the way static files are served.
When deploying under Apache, I don't need the following:
enable_if { not APACHE_DEPLOYMENT }
'Plack::Middleware::Static',
path => qr{^/(bitmaps/|images/|js/|css/|downtime/|favicon.ico$|ping.html$)},
root => '..';
because my PSGI application is enabled in an Apache <Location> block, as in:
<Location /myroot/>
SetHandler perl-script
PerlResponseHandler Plack::Handler::Apache2
PerlSetVar psgi_app /my/path/to/app.psgi
</Location>
So Apache already takes care of serving the static files for me. However, when running completely under Starman, I need to tell it which folders or paths need to be served as static files, and where they are located. This is the purpose of the Static middleware:
enable_if { not APACHE_DEPLOYMENT } 'Plack::Middleware::Static',
path => qr{^/(images/|js/|css/|favicon.ico$)},
root => '/var/www/something';
If you're always deploying through plackup or starman, then, again, you don't need any enable_if
, just enable
. Maybe it's also a good idea to put everything under /static
. For me that wasn't possible, since I already had existing content:
enable 'Plack::Middleware::Static',
path => qr{^/static/},
root => '/var/www/something';
Plack::Builder
About the Plack::Builder
bit, and the related builder
function. That is a function that helps you specify what you want Plack to run and how. Example:
builder {
enable 'StackTrace';
enable 'Debug';
enable 'AccessLog';
$app
}
where StackTrace, Debug, and AccessLog are all middleware classes, so causes Plack to wrap your final $app
application first with the AccessLog middleware, then Debug and then StackTrace. I didn't check the code, but I believe this creates 3 different PSGI applications that are meant to fiddle with the response that your own application generates.
PSGI makes this possible, and it's just great. More middleware means easier and faster development. And ultimately, very good middleware makes for great reuse too.
The mount
wrapper
I used mount
in my example very basicly, but you can use mount
to assemble compounds of applications in a very simple way. The same thing you do, for example, with Django and urls.py
, except that, if you have seen a non-trivial urls.py, it looks like spaghetti after a while. Compare with this:
my $app1 = MyApp->new();
my $app2 = MyApp2->new();
#...
builder {
enable 'Plack::Middleware::Static',
path => qr{^/static/},
root => '/var/www/something';
mount "/path1" => builder {
enable 'StackTrace';
$app;
}
mount "/path2" => $app2;
mount "/path3" => builder {
enable 'SomeMiddleware';
$app3;
}
}
Of course, then you have to add some dispatcher logic to your applications, but in the Plack world, we don't lack good dispatchers.
Plack rocks.