home | O'Reilly's CD bookshelfs | FreeBSD | Linux | Cisco | Cisco Exam  


Writing Apache Modules with Perl and C
By:   Lincoln Stein and Doug MacEachern
Published:   O'Reilly & Associates, Inc.  - March 1999

Copyright © 1999 by O'Reilly & Associates, Inc.


 


   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.