Show Contents Previous Page Next Page Chapter 4 - Content Handlers / Content Handlers as File Processors
The obvious limitation of the We can provide more power than Apache::Sandwich by using server-side includes. Server-side includes are small snippets of code embedded within HTML comments. For example, in the standard server-side includes that are implemented in Apache, you can insert the current time and date into the page with a comment that looks like this: Today is <!--#echo var="DATE_LOCAL"-->.
In this section, we use <!----> A directive name consists of any sequence of alphanumeric characters or underscores. This is followed by a series of optional parameters, separated by spaces or commas. Parameters that contain whitespace must be enclosed in single or double quotes in shell command style. Backslash escapes also work in the expected manner. The directives themselves are not hardcoded into the module but are instead dynamically loaded from one or more configuration files created by the site administrator. This allows the administrator to create a standard menu of includes that are available to the site's HTML authors. Each directive is a short Perl subroutine. A simple directive looks like this one: sub HELLO { "Hello World!"; } This defines a subroutine named HELLO() that returns the string "Hello World!" A document can now include the string in its text with a comment formatted like this one: I said <!--#HELLO--> A more complex subroutine will need access to the Apache object and the server-side include parameters. To accommodate this, the Apache object is passed as the first function argument, and the server-side include parameters, if any, follow. Here's a function definition that returns any field from the incoming request's HTTP header, using the Apache object's header_in() method: sub HTTP_HEADER { my ($r,$field) = @_; $r->header_in($field); }
With this subroutine definition in place, HTML authors can insert the You are using the browser <!-- #HTTP_HEADER User-Agent -->. Example 4-2 shows an HTML file that uses a few of these includes, and Figure 4-2 shows what the page looks like after processing. Figure 4-2. A page generated by Apache::ESSI Example 4-2. An HTML File That Uses Extended Server-Side Includes <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> The file size is <strong><!-- #FSIZE --></strong>, and it was <!--#FOOTER--> </body> </html> Implementing this type of server-side include system might seem to be something
of a challenge, but in fact the code is surprisingly compact (Example 4-3).
This module is named Again, we'll step through the code one section at a time. package Apache::ESSI; use strict; use Apache::Constants qw(:common); use Apache::File (); use Text::ParseWords qw(quotewords);
We start as before by declaring the package name and loading various Perl library modules. In addition to the modules that we loaded in the sub handler { my $r = shift; $r->content_type() eq 'text/html' || return DECLINED; my $fh = Apache::File->new($r->filename) || return DECLINED; my $sub = read_definitions($r) || return SERVER_ERROR; $r->send_http_header; $r->print($sub->($r, $fh)); return OK; }
The handler() subroutine is quite short. As in the
Once the file is opened, we call an internal function named read_definitions(). This function reads the server-side includes configuration file and generates an anonymous subroutine to do the actual processing of the document. If an error occurs while processing the configuration file, read_definitions() returns undef and we return sub read_definitions { my $r = shift; my $def = $r->dir_config('ESSIDefs'); return unless $def; return unless -e ($def = $r->server_root_relative($def)); Most of the interesting work occurs in read_definitions(). The idea
here is to read the server-side include definitions, compile them, and then
use them to generate an anonymous subroutine that does the actual substitutions.
In order to avoid recompling this subroutine unnecessarily, we cache its code
reference in the package variable
The read_definitions() subroutine begins by retrieving the path to the file that contains the server-side include definitions. This information is contained in a per-directory configuration variable named return $SUBSTITUTION{$def} if $MODIFIED{$def} && $MODIFIED{$def} <= -M _;
Having recovered the name of the definitions file, we next check the cache to see whether the subroutine definitions are already cached and, if so, whether the file hasn't changed since the code was compiled and cached. We use two hashes for this purpose. The my $package = join "::", __PACKAGE__, $def; $package =~ tr/a-zA-Z0-9_/_/c;
The next two lines are concerned with finding a unique namespace in which to compile the server-side include functions. Putting the functions in their own namespace decreases the chance that function side effects will have unwanted effects elsewhere in the module. We take the easy way out here by using the path to the definition file to synthesize a package name, which we store in a variable named eval "package $package; do '$def'"; if($@) { $r->log_error("Eval of $def did not return true: $@"); return; } We then invoke eval() to compile the subroutine definitions into the newly chosen namespace. We use the package declaration to set the namespace and do to load and run the definitions file. We use do here rather than the more common require because do unconditionally recompiles code files even if they have been loaded previously. If the eval was unsuccessful, we log an error and return undef. $SUBSTITUTION{$def} = sub { do_substitutions($package, @_);
Before we exit read_definitions(), we create a new anonymous subroutine that invokes the do_substitutions() function, store this subroutine in sub do_substitutions { my $package = shift; my($r, $fh) = @_;
When handler() invokes the anonymous subroutine, it calls do_substitutions() to do the replacement of the server-side include directives with the output of their corresponding routines. We start off by localizing the We then invoke a string substitution function to replace properly formatted comment strings with the results of invoking the corresponding server-side include function. The substitution uses the e flag to treat the replacement part as a Perl expression to be evaluated and the g flag to perform the search and replace globally. The search half of the function looks like this: /<!--\s*\#(\w+)\s*(.*?)\s*-->/
This detects the server-side include comments while capturing the directive name in The replacement of the function looks like this: /call_sub($package, $1, $r, $2)/ This just invokes another utility function, call_sub(), passing it the package name, the directive name, the request object, and the list of parameters. sub call_sub { my($package, $name, $r, $args) = @_; my $sub = \&{join '::', $package, $name}; $r->chdir_file; my $res = eval { $sub->($r, quotewords('[ ,]',0,$args)) }; return "<em>[$@]</em>" if $@; return $res; }
The call_sub() routine starts off by obtaining a reference to the subroutine using its fully qualified name. It does this by joining the package name to the subroutine name and then using the funky Perl
The server-side include function is now invoked, passing it the request object and the optional arguments. We call quotewords() to split up the arguments on commas or whitespace. In order to trap fatal runtime errors that might occur during the function's execution, the call is done inside an eval{} block. If the call function fails, we return the error message it died with captured within At the bottom of Example 4-3 is an example
entry for perl.conf (or httpd.conf if you prefer). The idea
here is to make
In addition to the <Files> section, we need to ensure that Apache knows that
You could also use <Location> or <Directory> to assign the Example 4-3. An Extensible Server-Side Include System package Apache::ESSI; # file: Apache/ESSI.pm use strict; use Apache::Constants qw(:common); use Apache::File (); use Text::ParseWords qw(quotewords); my (%MODIFIED, %SUBSTITUTION); sub handler { my $r = shift; $r->content_type() eq 'text/html' || return DECLINED; my $fh = Apache::File->new($r->filename) || return DECLINED; my $sub = read_definitions($r) || return SERVER_ERROR; $r->send_http_header; $r->print($sub->($r, $fh)); return OK; } sub read_definitions { my $r = shift; my $def = $r->dir_config('ESSIDefs'); return unless $def; return unless -e ($def = $r->server_root_relative($def)); return $SUBSTITUTION{$def} if $MODIFIED{$def} && $MODIFIED{$def} <= -M _; my $package = join "::", __PACKAGE__, $def; $package =~ tr/a-zA-Z0-9_/_/c; eval "package $package; do '$def'"; if($@) { $r->log_error("Eval of $def did not return true: $@"); return; } $SUBSTITUTION{$def} = sub { do_substitutions($package, @_); }; $MODIFIED{$def} = -M $def; # store modification date return $SUBSTITUTION{$def}; } sub do_substitutions { my $package = shift; my($r, $fh) = @_; # Make sure that eval() errors aren't trapped. local $SIG{__WARN__}; local $SIG{__DIE__}; local $/; #slurp $fh my $data = <$fh>; $data =~ s/<!--\s*\#(\w+) # start of a function name \s*(.*?) # optional parameters \s*--> # end of comment /call_sub($package, $1, $r, $2)/xseg; $data; } sub call_sub { my($package, $name, $r, $args) = @_; my $sub = \&{join '::', $package, $name}; $r->chdir_file; my $res = eval { $sub->($r, quotewords('[ ,]',0,$args)) }; return "<em>[$@]</em>" if $@; return $res; } 1; __END__
Here are some <Files ~ "\.ehtml$"> SetHandler perl-script PerlHandler Apache::ESSI PerlSetVar ESSIDefs conf/essi.defs At this point you'd probably like a complete server-side include definitions
file to go with the module. Example 4-4 gives
a short file that defines a core set of functions that you can build on top
of. Among the functions defined here are ones for inserting the size and modification
date of the current file, the date, fields from the browser's HTTP request header,
and a function that acts like the C preprocessor #include macro to
insert the contents of a file into the current document. There's also an include
called The INCLUDE() function inserts whole files into the current document. It accepts either a physical pathname or a "virtual" path in URI space. A physical path is only allowed if it lives in or below the current directory. This is to avoid exposing sensitive files such as /etc/passwd.
If the $file = $r->lookup_uri($path)->filename; The request object's lookup_uri() method creates an Apache subrequest for the specified URI. During the subrequest, Apache does all the processing that it ordinarily would on a real incoming request up to, but not including, activating the content handler. lookup_uri() returns an Apache::SubRequest object, which inherits all its behavior from the Apache request class. We then call this object's filename() method in order to retrieve its translated physical file name. Example 4-4. Server-Side Include Function Definitions # Definitions for server-side includes. # This file is require'd, and therefore must end with # a true value. use Apache::File (); use Apache::Util qw(ht_time size_string); # 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 ht_time(time, $format, 0); } # insert the modification time of the document, possibly modified # by a strftime() format string. sub MODTIME { my ($r,$format) = @_; my $mtime = (stat $r->finfo)[9]; return localtime($mtime) unless $format; return ht_time($mtime, $format, 0); } # insert the size of the current document sub FSIZE { my $r = shift; return size_string -s $r->finfo; } # divide 10 by the argument (used to test runtime error trapping) sub OOPS { 10/$_[1]; } # 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> # insert the named field from the incoming request #ensure that path is relative, and does not contain ".." sub is_below_only { $_[0] !~ m:(^/|(^|/)\.\.(/|$)): } # Insert the contents of a file. If the $virtual flag is set # does a document-root lookup, otherwise treats filename as a # physical path. 1;
If you're a fan of server-side includes, you should also check out the Apache Embperl and ePerl packages. Both packages, along with several others available from the CPAN, build on Copyright © 1999 by O'Reilly & Associates, Inc. |
HIVE: All information for read only. Please respect copyright! |