Project

General

Profile

Using Mason with lighttpd (via FastCGI)

Mason can be used with Lighttpd and is (arguably :-) easier to setup than Apache + mod_perl.

Here's my current setup. Requirements:

  • Mason handles files ending with '/' or '.html' or '.css'.
  • Other files statically served by lighttpd
  • Load perl modules at fastcgi process start
  • Disallow access to .mhtml files

First we need a mason handler, that lighttpd will use via FastCGI. I've called it mason_lighttpd_handler.fcgi. It does not need to be in any particular location.


#!perl
#!/usr/bin/perl
use CGI::Fast;
use HTML::Mason::CGIHandler;
use URI;

{
    package HTML::Mason::Commands;

    ## anything you want available to components
    use Storable qw(freeze thaw);
    use HTTP::BrowserDetect;

    ## An example of how to keep a $dbh persistent
    ##our $dbh = DBI->connect_cached(
    ##    'dbi:Pg:dbname=dbname'
    ##    , 'username'
    ##    , 'password'
    ##    , {AutoCommit=>0, RaiseError=>1, PrintError=>1}
    ##) || die "Could not Connect to DB".$dbi::errstr ;

}

# lazily-instantiated variables
my $cgi;
my $h;

while ($cgi = new CGI::Fast())
{
        ## make sure it is alive! (if not it will reconnect)
        ## $HTML::Mason::Commands::dbh->ping; 

    my $uri = URI->new( $ENV{REQUEST_URI} );

        ## this is a hack, that emulates mod_perl behavior see notes at bottom
        ## You might not want this hack, and it might be worse than not having it
    $uri->path( $uri->path . 'index.html' )
        if $uri->path =~ /\/$/
    ;

    $ENV{PATH_INFO}    = $uri->path;
    $ENV{QUERY_STRING} = $uri->query;
    $ENV{REQUEST_URI}  = "$uri";

    # this is lazily instantiated because %ENV is not set at startup time
    if (! $h) {
        $h = HTML::Mason::CGIHandler->new(
            comp_root       => $ENV{MASON_COMP_ROOT}
            , data_dir      => $ENV{MASON_DATA_ROOT}
            , error_mode    => 'fatal'
            , error_format  => 'line'
            ## Three good globals dbh user and session
            , allow_globals => [qw/$dbh $U $S/]
        );
    }

    ## hand off to mason
    eval { $h->handle_cgi_object($cgi) };

        ## catch error
    if ( my $raw_error = $@ ) {
        $HTML::Mason::Commands::dbh->rollback; ## roll back
        warn $raw_error;
        # print out a pretty system error page and log $raw_error
    }

    ## things went well
    else {
        $HTML::Mason::Commands::dbh->commit;
    }

}

exit 0;

Next we need to tell Lighttpd to process requests for this site and process them via this script and FastCGI. Here is the relevant fragment. I'm using a regexp for the hostname expression
but you may need something less complicated - see the Lighttpd docs for more information:


#!python
$HTTP["host"] =~ "hostnameexpression" {
        server.document-root   = "/path/to/your/document/root" 
        ## map .css and '/' to .html so they will be handled by FastCGI
        fastcgi.map-extensions   = ( ".css" => ".html", "/" => ".html" )    
        index-file.names         = ( "index.html" )
        url.access-deny          = ( ".mhtml" ) ## add autohandler/dhandler if you'd like
        fastcgi.server           = ( ".html" =>
        (( 
             "socket"      => "/tmp/fastcgi.socket",
             "bin-path"    => "/path/to/the/mason_lighttpd_handler.fcgi",
             "check-local" => "disable" 
          ))
        )
        bin-environment          = (
          MASON_COMP_ROOT  => '/your/comp/root',
          MASON_DATA_ROOT  => '/your/data/root'
        )
}

That's it!

When you start lighttpd it will start several copies of your mason handler script, passing requests to them as appropriate.

One nice side-effect of this is that if you change a perl module (not a mason component) that would require you to restart/reload an apache mod_perl server, in this case you can just kill the perl fastcgi processes - lighttpd will notice and restart them, of course with your new module code in place.

Add debugging (via 'warn') to your handler script if you are getting unexpected results, you can see the output from it in the lighttpd eror log.

Known issues

$m->abort does not work correctly, see this page for details on why:

http://www.masonhq.com/docs/manual/1.28/CGIHandler.html#calling_abort___under_cgihandler

It looks like this is something that has to be fixed in Mason.

Migration from Apache/mod_* (mod_perl)

First a prequil, mod_* (lang) embeeds the .so for the language in Apache. In doing so it knows things that Fastcgi doesn't, and can do things Fastcgi can't. Some of this behaviors users might be utilizing without knowing, here is a prime example:

In Apache, (mod_* not fastcgi) a request to '/' would be processed by:

- first looking for the DirectoryIndex (apache would do this.)

- call the wrapper with the found DirectoryIndex; or, call the wrapper, (which would call a dhandler) on the unfound file.

In Lighttpd, or just cgi in general, the resolution to the index-file is missing, so the wrapper will either be called on the requested file or not. See the "check-local" directive in lighttpd. In cgi, the index-file (Apache's DirectoryIndex) has no influence. For comparison Lighttpd/fastcgi looks like this:

- call the wrapper with the found file; or, call the wrapper with the un-found file dependent on check-local being disabled.

One horrible solution to this is to redirect all directories to the directory's index.html. A less-horrible solution would be to write the translation process in the wrapper (this only works if check-local is disabled).

Another problem -- expanding on the previously mentioned problem is an address with no path, just a query string. ie:
http://foo.com/?test.html or /?test.html, or /bar/?test.html

In Apache, the resolution looks like this:

- first looking for the DirectoryIndex (Apache would do this.)

- calling the wrapper with the found DirectoryIndex; with the query.

In Lighttpd, (i.e. cgi) this isn't so. Bottom line: index-files don't matter if you're sending everything to the wrapper (not checking for files existence.) If you app makes use of Apache's resolution this can sting. If you don't send everything to the wrapper, you'll completely lose the dhandler functionality.

UTF-8 support

If your Mason pages are written with UTF-8 encoding then lighttpd will probably show them in the wrong encoding. To treat all pages as UTF8, change the CGIHandler call like this:

$h = HTML::Mason::CGIHandler->new(
            comp_root       => $ENV{MASON_COMP_ROOT}
            , data_dir      => $ENV{MASON_DATA_ROOT}
            , error_mode    => 'fatal'
            , error_format  => 'line'
            , preamble      => 'use utf8;'
            ## Three good globals dbh user and session
            , allow_globals => [qw/$dbh $U $S/]
        );

If you use MySQL it's probably also a good idea to pull the database records as UTF8 strings. The database bit should look something like this:

our $dbh = DBI->connect_cached($dsn, 'username', 'password',
                {mysql_enable_utf8 => 1, AutoCommit => 1, RaiseError => 1
                , PrintError => 1})
            || die "Could not Connect to DB".$dbi::errstr ;

Credits

Latest rewrite (Evan Carroll <>)
- added env variables for comp/data root
- added example of DBI persistence with fastcgi
- removed original query-parsing regex, used URI instead.
- added some globals to be shared

(Justin Hawkins <>) First Draft.

This is very much in the category of "seems to work" at this stage. (initial note still applies)