Server-Side Includes with mod_perl

Create your own server-side include system in 50 lines of code. Compete with Cold Fusion and PHP!

Sample Template File

 <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
 <html> <head> <title>Server-Side Includes</title></head>
 <body bgcolor=white>
 <h1>Server-Side Includes</h1>
 This is some straight text.<p>

 This is a "<!-- #HELLO -->" include.<p>

 This file was last modified on <!-- #MODTIME %x --><p>

 Today is  <!-- #DATE "%A, in <em>anno domini</em> %Y"-->.<p>

 The user agent is <em><!--#HEADER_FIELD user-agent--></em>.<p>

 <!--#FOOTER-->
 </body> </html>

Sample Definition File

e.g. ~www/conf/essi.defs
 use POSIX 'strftime';
 # insert the string "Hello World!"
 sub HELLO {
     my $r = shift;
     "Hello World!";
 }
 # insert today's date possibly modified by a strftime() format
 # string
 sub DATE {
     my ($r,$format) = @_;
     return scalar(localtime) unless $format;
     return strftime($format,localtime);
 }
 # insert the modification time of the document, possibly modified
 # by a strftime() format string.
 sub MODTIME {
     my ($r,$format) = @_;
     my $file = $r->filename;
     return localtime((stat($file))[9]) unless $format;
     return strftime($format,localtime((stat($file))[9]));
 }
 # insert a canned footer
 sub FOOTER {
     my $r = shift;
     my $modtime = MODTIME($r);
     return <<END;
     <hr>
 © 1998 <a href="http://www.ora.com/">O\'Reilly & Associates</a><br>
 <em>Last Modified: $modtime</em>
 END
 ;
 }
 # insert the named field from the incoming HTTP request
 sub HEADER_FIELD {
     my ($r,$h) = @_;
     $r->header_in($h);
 }
 1;
To modify all files located in the footer/ subdirectory of document tree:
  <Location /footer>
    SetHandler perl-script
    PerlHandler Apache::Footer
  </Location>
To modify all files with extension .footer:
  AddType text/html .footer
  <Files ~ "\.footer$">
     SetHandler  perl-script
     PerlHandler Apache::Footer
  </Files>

srm.conf Entry

 <Files ~ "\.ehtml$">
   SetHandler  perl-script
   PerlHandler Apache::ESSI
   PerlSetVar  ESSIDefs conf/essi.defs
 </Files>
 AddType text/html .ehtml

Script III.2.2 Apache::ESSI

 package Apache::ESSI;

 use strict vars;
 use Apache::Constants ':common';
 use IO::File;
 my (%MODIFIED,%SUBSTITUTION);

 sub handler {
    my $r = shift;
    $r->content_type() eq 'text/html' || return DECLINED;
    my $fh=IO::File->new($r->filename)|| return DECLINED;
    my $sub = read_definitions($r)    || return SERVER_ERROR;
    $r->send_http_header;
    $r->print($sub->(<$fh>));
    return OK;
 }

 sub read_definitions {
    my $r = shift;
    return undef unless my $def = $r->dir_config('ESSIDefs');
    return undef unless -e ($def = $r->server_root_relative($def));
    return $SUBSTITUTION{$def} 
        if $MODIFIED{$def} && $MODIFIED{$def} <= -M _;

    my $package = "Apache::ESSI::$def";
    $package=~tr/a-zA-Z0-9_/_/c;
    $SUBSTITUTION{$def} = eval <<END;
 package $package;
 use Text::ParseWords 'quotewords';
 do '$def';
 sub {
    # Make sure that eval() errors aren't trapped.
    local \$SIG{__WARN__}= \\&CORE::warn; 
    local \$SIG{__DIE__} = \\&CORE::die;
    my \@lines = \@_;
    my \$data = join('',\@lines);
    \$data =~ s/<!--\\s*\\#(\\w+) # start of a function name
	        \\s*(.*?)         # optional parameters
 	        \\s*-->           # end of comment
	        /eval {&{\$1}(\$r,quotewords('[ ,]',0,\$2))} 
                     || "<em>[\$@]<\\/em>"/xseg;
    \$data;
 };
 END
    unless ($SUBSTITUTION{$def}) {
	$r->log_error("Eval of $def did not return true: $@");
	return undef;
    }
    $MODIFIED{$def} = -M $def;  # store modification date
    return $SUBSTITUTION{$def};
 }

 1;

What it Looks Like

The unfiltered document:
http://localhost/conference/ssi_test.html

Under the control of Apache::ESSI;
http://localhost/conference/ssi_test.ehtml

<< Previous
Contents >> Next >>

Lincoln D. Stein, lstein@cshl.org
Cold Spring Harbor Laboratory
Last modified: Mon Aug 17 10:49:05 EDT 1998