Running Mason under FastCGI on a Shared Host


As a Perl programmer, I am a fan of the Mason templating framework. Being a good engineer, I naturally want anything I develop to perform as well as possible and to be able to scale the system with minimal additional effort.

Through intelligent, modular design, we can achieve surprising levels of performance and scalability using extremely modest resouces, while providing a clear and simple migration route to handle growth.

Dreamhost is an excellent, low-cost shared hosting provider that I've been using since 2005. These instructions are mainly geared towards them, but should be applicable to any other shared hosting provider that supports Perl and FastCGI

Implementing a FastCGI handler for Mason

There are examples of how to run Mason on Dreamhost as a FastCGI on both the dreamhost wiki and at mason hq. These reference implementations provide a good starting point -- the first version of my script was put together using elements from these two sources. However, in the spirit of TMTOWTDI, I will present a more sophisticated and evolved solution.

For those who wish to cut to the chase, you may download the script now. The included POD documentation is reasonably comprehensive. I have made it as easy as I can for a novice to use, although a working knowledge of Perl, Mason, and Apache is assumed.

Limitations of Reference Implementations

The introductory solutions referenced earlier both fail to address a key issue: dhandlers. If you are not familiar with them, dhandlers are discussed at length in Chapter 3 of Embedding Perl in HTML with Mason. Since dhandlers are probably the single most important feature of mason for generating dynamic content, this is a significant oversight.

On a shared host, we have to do all of our configuration via .htaccess, which limits us to using the <files> and <filesmatch> directives, versus the more flexible location/locationmatch and diretory/directorymatch directives. Using filesmatch eliminates the possibility of a dhandler ever firing: if the requested file does not exist, Apache immediately issues a redirect to the site's 404 page, completely bypassing our handler script and Mason.

The solution I came up with to this problem was to use mod_rewrite to rewrite any request for a non-existant file or directory to a dummy page:

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /dummy.html [L]

With this, Apache will pass control to our Mason handler as long as dummy.html actually exists. However, doing so will change PATH_INFO to the rewritten value, which is not what we want. Therefore the handler script has to take the value of REQUEST_URI (which has the real uri the user requested) and stuff it back into PATH_INFO before calling Mason:

$mason->handle_request(); # HTML::Mason::CGIHandler object

The only additional logic the actual script includes is a check to prevent the dummy.html page from ever being displayed. If the requested component exists, PATH_INFO and REQUEST_URI are already the same, so there's really no reason to check, although it wouldn't hurt us much to add a conditional.

Unfortunately, this breaks DirectoryIndex handling, so we need to add root dhandler so we serve up the index.html page (if it exists) when we're given a bare directory request:

my $page = $r->path_info;
$page .= 'index.html' if $page =~ /\/$/;
if ( $m->comp_exists($page) ) { $m->redirect($page); }
else { $m->redirect('/error/404.html'); }

This can be overridden in any subdirectory by giving it a 'real' dhandler. You could use $m->comp instead of $m->redirect for index pages, but this breaks down if you have methods defined in your autohandler that you want to override in the target component. If you used $m->comp, the autohandler would inherit it's methods from the dhandler instead of the target component. The advantage of using $m->comp is that we avoid the performance hit of a redirect, and we have cleaner looking uri's. Since I use autohandler methods extensively, I have to accept this trade-off.

Having to rely on redirects is not the highest-performing solution, but given the constraints of the environment it is our only practical option. Fortunately this is all hidden from the Mason application. If growth necessitates that we move to a mod_perl environment on a private (virtual or dedicated) host or set of hosts, nothing needs to be changed at the application layer.

Benefits of FastCGI

FastCGI offers many of the same advantages as mod_perl, in a manner that's much more secure in a shared hosting environment. As configured on Dreamhost, a FastCGI request spawns a process (the handler) which runs under your uid; this process persists between hits. This allows you to avoid the start-up overhead normally associated with CGI. The performance benefit is impressive. This site enjoys an order-of-magnitude improvement in page generation times running under FastCGI versus CGI. Under CGI, average page generation time for this page is 77.3 ms. This drops to 4.8 ms under FastCGI (assuming the handler process exists).

The drawback of FastCGI is that many errors will leave the site in a non-responsive state for 60 seconds until the FastCGI process times out. Therefore, it is often useful to run the script under regular CGI while making changes and revert to FastCGI when your are confident everything is working. Obviously, doing development work on a live site is not a wise practice. Fortunately, Dreamhost allows you to host an unlimited number of sites, so it is trivial to set up a subdomain site for development and testing.

Migration Path and Scalability

Because FastCGI gives us many of the advantages we would normally need mod_perl for, we can support an appreciable level of traffic with our low-cost shared hosting provider. However, there will likely come a point where this is no longer adequate and we need to move to a dedicated host (either on virtual or physical hardware). Fortunately, this is completely painless. Once mod_perl is configured on the new server, migrating the application itself should be as simple as copying the docroot over to it's new home.

How we would scale beyond a single dedicated server depends greatly on the nature of the application. Any external resources the application depends on, such as a database, will also likely need to be scaled. Ideally, we want to architect the application with horizontal scalability in mind. While a full discussion of this is well beyond the scope of this article, I will simply note that Perl+Mason is an excellent choice for implementing systems using the Model-Controller-View design pattern.