Show Contents Previous Page Next Page
Chapter 9 - Perl API Reference Guide / The Apache::File Class Using Apache::File to Send Static Files Apache's http_core module already has a default handler to send
files straight from disk to the client. Such files include static HTML, plain
text, compressed archives, and image files in a number of different formats.
A bare-bones handler in Perl only requires a few lines of code, as Example 9-1
shows. After the standard preamble, the handler() function attempts
to open $r->filename . If the file cannot be opened, the handler
simply assumes file permission problems and returns FORBIDDEN .
Otherwise, the entire contents of the file are passed down the HTTP stream
using the request object's send_fd() method. It then does a little
tidying up by calling close() on the filehandle and returns OK
so that Apache knows the response has been sent.
Example 9-1. A Simple but Flawed
Way to Send Static Files
package Apache::EchoFile;
use strict;
use Apache::Constants qw(:common);
use Apache::File ();
sub handler {
my $r = shift;
my $fh = Apache::File->new($r->filename) or return FORBIDDEN;
$r->send_fd($fh);
close $fh;
return OK;
}
1;
__END__
While this works well in most cases, there is more involved in sending a
file over HTTP than you might think. To fully support the HTTP/1.1 protocol,
one has to handle the PUT and OPTIONS methods, handle GET requests that contain
a request body, and provide support for If-modified-since requests.
Example 9-2 is the Apache::SendFile
module, a Perl version of the http_core module default handler. It
starts off as before by loading the Apache::Constants module. However,
it brings in more constants than usual. The :response group pulls
in the constants we normally see using the :common tag, plus a few
more including the NOT_IMPLEMENTED constant. The :methods
group brings in the method number constants including M_INVALID ,
M_OPTIONS , M_PUT , and M_GET . The :http
tag imports a few of the less commonly used status codes, including HTTP_METHOD_NOT_ALLOWED .
We next bring in the Apache::File module in order to open and read
the contents of the file to be sent and to load the HTTP/1.1-specific file-handling
methods. The first step we take upon entering the handler() function is
to call the discard_request_body() method. Unlike HTTP/1.0, where
only POST and PUT requests may contain a request body, in HTTP/1.1 any method
may include a body. We have no use for it, so we throw it away to avoid potential
problems. We now check the request method by calling the request object's method_number()
method. Like the http_core handler, we only handle GET requests (method
numbers M_GET ). For any other type of request we return an error,
but in each case the error is slightly different. For the method M_INVALID ,
which is set when the client specifies a request that Apache doesn't understand,
we return an error code of NOT_IMPLEMENTED . For M_OPTIONS ,
which is sent by an HTTP/1.1 client that is seeking information about the
capabilities of the server, we return DECLINED in order to allow
Apache's core to handle the request (it sends a list of allowed methods).
The PUT method is applicable even if the resource doesn't exist, but we
don't support it, so we return HTTP_METHOD_NOT_ALLOWED in this
case. At this point we test for existence of the requested file by applying
the -e file test to the cached stat() information returned
by the request object's finfo() method. If the file does not exist,
we log an error message and return NOT_FOUND . Finally, we specifically
check for a request method of M_GET and again return HTTP_METHOD_NOT_ALLOWED
if this is not the case. Provided the request has passed all these checks, we attempt to open the
requested file with Apache::File. If the file cannot be opened, the
handler logs an error message and returns FORBIDDEN . At this point, we know that the request method is valid and the file exists
and is accessible. But this doesn't mean we should actually send the file
because the client may have cached it previously and has asked us to transmit
it only if it has changed. The update_mtime(), set_last_modified(),
and set_etag() methods together set up the HTTP/1.1 headers that
indicate when the file was changed and assign it a unique entity tag that
changes when the file changes. We then call the meets_conditions() method to find out if the file
has already been cached by the client. If this is the case, or some other
condition set by the client fails, meets_conditions() returns a response
code other than OK , which we propagate back to Apache. Apache
then does whatever is appropriate. Otherwise we call the set_content_length() method to set the outgoing
Content-length header to the length of the file, then call send_http_header()
to send the client the full set of HTTP headers. The return value of header_only()
is tested to determine whether the client has requested the header only; if
the method returns false, then the client has requested the body of the file
as well as the headers, and we send the file contents using the send_fd()
method. Lastly, we tidy up by closing the filehandle and returning OK .
The real default handler found in http_core.c actually does a bit
more work than this. It includes logic for sending files from memory via mmap()
if USE_MMAP_FILES is defined, along with support for HTTP/1.1
byte ranges and Content-MD5. After reading through this you'll probably be completely happy to return
DECLINED when the appropriate action for your module is just
to return the unmodified contents of the requested file!
Example 9-2. A 100-Percent Pure
Perl Implementation of the Default http_core Content Handler
package Apache::SendFile;
use strict;
use Apache::Constants qw(:response :methods :http);
use Apache::File ();
use Apache::Log ();
sub handler {
my $r = shift;
if ((my $rc = $r->discard_request_body) != OK) {
return $rc;
}
if ($r->method_number == M_INVALID) {
$r->log->error("Invalid method in request ", $r->the_request);
return NOT_IMPLEMENTED;
}
if ($r->method_number == M_OPTIONS) {
return DECLINED; #http_core.c:default_handler() will pick this up
}
if ($r->method_number == M_PUT) {
return HTTP_METHOD_NOT_ALLOWED;
}
unless (-e $r->finfo) {
$r->log->error("File does not exist: ", $r->filename);
return NOT_FOUND;
}
if ($r->method_number != M_GET) {
return HTTP_METHOD_NOT_ALLOWED;
}
my $fh = Apache::File->new($r->filename);
unless ($fh) {
$r->log->error("file permissions deny server access: ",
$r->filename);
return FORBIDDEN;
}
$r->update_mtime(-s $r->finfo);
$r->set_last_modified;
$r->set_etag;
if((my $rc = $r->meets_conditions) != OK) {
return $rc;
}
$r->set_content_length;
$r->send_http_header;
unless ($r->header_only) {
$r->send_fd($fh);
}
close $fh;
return OK;
}
1;
__END__
Show Contents Previous Page Next Page Copyright © 1999 by O'Reilly & Associates, Inc. |