Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl Monk, Perl Meditation
 
PerlMonks  

Serving Dynamic Content with Plack

by Logicus
on Oct 24, 2011 at 04:51 UTC ( #933264=perlmeditation: print w/replies, xml ) Need Help??

Preamble

In the first two parts of this serise of articles ("meet joe plack" and "serving static content with plack") on PSGI/Plack I introduced the concept of PSGI and provided some simple patterns to wet your appetite for it. Now in this 3rd part, I am going to introduce some more concepts and show how to begin using Plack to serve dynamic content.

Plack Builder

In part two, I used the module Plack::Builder to 'mount', various paths and bind them to apps based on Plack::App::File in order to serve up static content.

fragment of action.psgi ----------------------- my $app = builder { mount "/images" => $images; mount "/css" => $css; mount "/js" => $js; mount "/favicon.ico" => $ico; mount "/" => $htdocs; };

This is all well and good, and nicely implements a powerful static content server in PSGI.

Plack::Middleware

In order to make this more useful and to generate dynamic content, you will need to become familiar with the concept of Plack Middleware. In a nutshell, Plack Middleware components take a standard PSGI input, do some processing and then give a standard PSGI output, and thus Middleware components can be layered or stacked in a manner emulating Ruby's Rack system.

use Plack::Builder; use Plack::App::File; my $css = Plack::App::File->new(root => "/var/www/css"); my $js = Plack::App::File->new(root => "/var/www/js"); my $images = Plack::App::File->new(root => "/var/www/images"); my $ico = Plack::App::File->new(root => "/var/www"); my $action = sub { ($sec,$min,$hour,$mday,$mon, $year,$wday,$yday,$isdst) = localtime(time); return [200, [ 'Content-Type' => 'text/html' ], [ "<html> <head> <title>Plack Clock</title> </head> <body> <p>the time is $hour:$min:$sec</p> </body> </html>" ] ]; }; my $app = builder { mount "/images" => $images; mount "/css" => $css; mount "/js" => $js; mount "/favicon.ico" => $ico; mount "/" => builder { enable 'ContentLength'; $action; } };

So what's going on here then? Well first of all, we have now added a new sub called "action", which returns a basic html skeleton displaying the current time. Also, we have now mounted "/" to another instance of builder, which enables the Plack Middleware component called ContentLength (Plack::Middleware::ContentLength). This component takes the PSGI output of $action, calculates it's length and sets the appropriate response header.

Setup like this, all requests which don't start with "/images", "/css", "/js", of "/favicon.ico", will be routed to the $action sub, and will result in the browser showing the same page with the current time on it.

More Middleware

There are many middleware components available, and it is beyond the scope of this tut to go into them and describe them all so I am just going to show you a couple more and make some notes about layering them correctly before I sign off.

mount "/" => builder { enable 'ContentLength'; enable 'Debug', panels => [ qw(Memory Timer) ]; enable 'Session' store => 'File'; $action; }

We have now added 2 more Middleware components to the stack; Debug which adds a nifty collapsable panel to the right hand side of the browser display, and Session which of course gives access to a neat and secure session management system. (More about that later)

You may notice that the two new components have extra arguments compared to the ContentLength component. These extra arguments are passed to the Middleware component and modify it's behaviour. In this case we are telling Session to use the non-volatile file storage method of holding it's data, and we are telling the Debug component that we want to see two panels in it's display; Memory usage and a performance Timer

Stacking order

It's important to think about the logical order of the stack as you are building it. For instance, we put ContentLength at the top because we want the ContentLength component to be the LAST component to be run prior to output. This is so that the Content-Length header is correct. If we were to enable it in the wrong position, and a later component was to modify the length of the document, the header would be incorrect and may cause rendering errors on some browsers.

don't do this; wrong -------------------- mount "/" => builder { enable 'Debug', panels => [ qw(Memory Timer) ]; enable 'ContentLength'; enable 'Session' store => 'File'; $action; }

Now because we changed the order, the ContentLength is going to be calculated before the Debug Panel is added to the output, which may result in the debug panel not being visible at all.

Ok that's all for now folks, In the next article I am going to start looking in more depth at the actual content generation phase and in using any values generated by the Middleware in your app.

Have a good one!

Replies are listed 'Best First'.
Re: Serving Dynamic Content with Plack
by Haarg (Curate) on Oct 25, 2011 at 08:07 UTC
    Your last point isn't entirely correct. The Debug middleware will remove the Content-Length header if it exists, preventing any issues. Most middleware components that modify the body will use Plack::Util::response_cb to do so, and it takes care of removing the header. Having them out of order like that would still be bad though, as you would be calculating the length then immediately throwing it away.
Re: Serving Dynamic Content with Plack
by sundialsvc4 (Abbot) on Oct 26, 2011 at 02:20 UTC

    Good article.   It is also worth mentioning, if only for the record, that “Plack is not, strictly speaking, ‘a tool that (say...) serves static content,’ ” although it does very-conveniently provide one should you elect to use it.   All in all, Plack is a very nice and well thought out combination of both the lowest-level “plumbing” and a sensible set of higher-level tools (such as these) which you are sure to find very useful when actually deploying solutions in production settings.

    I guess it is partly because Plack isn’t specific to any one web-server, that it quite naturally provides a set of tools and plugins that do (in a very low-overhead fashion) the usual web-server chores ... “use them or not, as you prefer.”

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlmeditation [id://933264]
Approved by Limbic~Region
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others taking refuge in the Monastery: (7)
As of 2020-11-25 23:25 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found

    Notices?