Show Contents Previous Page Next Page Chapter 4 - Content Handlers In this section...
Instead of synthesizing a document, a content handler has the option of redirecting the browser to fetch a different URI using the HTTP redirect mechanism. You can use this facility to randomly select a page or picture to display in response to a URI request (many banner ad generators work this way) or to implement a custom navigation system. Redirection is extremely simple with the Apache API. You need only add a Location
field to the HTTP header containing the full or partial URI of the desired destination,
and return a
The module begins by importing the
The final step is to return the % telnet localhost 80 Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. GET /gohome HTTP/1.0 HTTP/1.1 302 Moved Temporarily Date: Mon, 05 Oct 1998 22:15:17 GMT Server: Apache/1.3.3-dev (Unix) mod_perl/1.16 Location: http://www.ora.com/ Connection: close Content-Type: text/html <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"> <HTML><HEAD> <TITLE>302 Moved Temporarily</TITLE> </HEAD><BODY> <H1>Moved Temporarily</H1> The document has moved <A HREF="http://www.ora.com/">here</A>.<P> </BODY></HTML> Connection closed by foreign host.
You'll notice from this example that the Example 4-8. Generating a Redirect from a Content Handler package Apache::GoHome; # file: Apache/GoHome.pm use strict; use Apache::Constants qw(REDIRECT); sub handler { my $r = shift; $r->content_type('text/html'); $r->header_out(Location => 'http://www.ora.com/'); return REDIRECT; } 1; __END__ As a more substantial example of redirection in action, consider Apache::RandPicture (Example 4-9) which randomly selects a different image file to display each time it's called. It works by selecting an image file from among the contents of a designated directory, then redirecting the browser to that file's URI. In addition to demonstrating a useful application of redirection, it again shows off the idiom for interconverting physical file names and URIs. The handler begins by fetching the name of a directory to fetch the images from, which is specified in the server configuration file by the Perl variable PictureDir. Because the selected image has to be directly fetchable by the browser, the image directory must be given as a URI rather than as a physical path.
The next task is to convert the directory URI into a physical directory path. The subroutine adds a my $subr = $r->lookup_uri($dir_uri); my $dir = $subr->filename; Now we need to obtain a listing of image files in the directory. The simple way to do this would be to use the Perl glob operator, for instance: chdir $dir; @files = <*.{jpg,gif}>; However, this technique is flawed. First off, on many systems the glob operation launches a C subshell, which sends performance plummeting and won't even work on systems without the C shell (like Win32 platforms). Second, it makes assumptions about the extension types of image files. Your site may have defined an alternate extension for image files (or may be using a completely different system for keeping track of image types, such as the Apache MIME magic module), in which case this operation will miss some images. Instead, we create a DirHandle object using Perl's directory handle object wrapper. We call the directory handle's read() method repeatedly to iterate through the contents of the directory. For each item we ask Apache what it thinks the file's MIME type should be, by calling the lookup_uri() method to turn the filename into a subrequest and content_type() to fetch the MIME type information from the subrequest. We perform a pattern match on the returned type and, if the file is one of the MIME image types, add it to a growing list of image URIs. The subrequest object's uri() method is called to return the absolute URI for the image. The whole process looks like this: my @files; for my $entry ($dh->read) { # get the file's MIME type my $rr = $subr->lookup_uri($entry); my $type = $rr->content_type; next unless $type =~ m:^image/:; push @files, $rr->uri; }
Note that we look up the directory entry's filename by calling the subrequest object's lookup_uri() method rather than using the main request object stored in The next step is to select a member of this list randomly, which we do using this time-tested Perl idiom: my $lucky_one = $files[rand @files];
The last step is to set the Location header to point at this file (being sure to express the location as a URI) and to return a Example 4-9. Redirecting the Browser to a Randomly Chosen Picture package Apache::RandPicture; # file: Apache/RandPicture.pm use strict; use Apache::Constants qw(:common REDIRECT); use DirHandle (); sub handler { my $r = shift; my $dir_uri = $r->dir_config('PictureDir'); unless ($dir_uri) { $r->log_reason("No PictureDir configured"); return SERVER_ERROR; } $dir_uri .= "/" unless $dir_uri =~ m:/$:; my $subr = $r->lookup_uri($dir_uri); my $dir = $subr->filename; # Get list of images in the directory. my $dh = DirHandle->new($dir); unless ($dh) { $r->log_error("Can't read directory $dir: $!"); my @files; for my $entry ($dh->read) { # get the file's MIME type my $rr = $subr->lookup_uri($entry); my $type = $rr->content_type; next unless $type =~ m:^image/:; push @files, $rr->uri; } $dh->close; unless (@files) { $r->log_error("No image files in directory"); return SERVER_ERROR; } my $lucky_one = $files[rand @files]; $r->header_out(Location => $lucky_one); return REDIRECT; } 1; __END__
A configuration section to go with <Location /random/picture> SetHandler perl-script PerlHandler Apache::RandPicture PerlSetVar PictureDir /banners </Location> And you'd use it in an HTML document like this: <image src="/random/picture" alt="[Our Sponsor]"> Although elegant, this technique for selecting a random image file suffers from a bad performance bottleneck. Instead of requiring only a single network operation to get the picture from the server to the browser, it needs two round-trips across the network: one for the browser's initial request and redirect and one to fetch the image itself.
You can eliminate this overhead in several different ways. The more obvious technique is to get rid of the redirection entirely and simply send the image file directly. After selecting the random image and placing it in the variable $subr = $r->lookup_uri($lucky_one); $r->content_type($subr->content_type); $r->send_http_header; return OK unless $r->header_only; my $fh = Apache::File->new($subr->filename) || return FORBIDDEN; $r->send_fd($fh); We create yet another subrequest, this one for the selected image file, then use information from the subrequest to set the outgoing content type. We then open up the file and send it with the send_fd() method. However, this is still a little wasteful because it requires you to open up the file yourself. A more subtle solution would be to let Apache do the work of sending the file by invoking the subrequest's run() method. run() invokes the subrequest's content handler to send the body of the document, just as if the browser had made the request itself. The code now looks like this: my $subr = $r->lookup_uri($lucky_one); unless ($subr->status == DOCUMENT_FOLLOWS) { $r->log_error("Can't lookup file $lucky_one}: $!"); return SERVER_ERROR; } $r->content_type($subr->content_type); $r->send_http_header; return OK if $r->header_only; $subr->run; return OK;
We call lookup_uri() and check the value returned by its status() method in order to make sure that it is Copyright © 1999 by O'Reilly & Associates, Inc. |
HIVE: All information for read only. Please respect copyright! |