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 4 - Content Handlers
Handling Errors

In this section...

Introduction
Error Logging
The ErrorDocument System
HTTP Headers and Error Handling

Introduction

   Show Contents   Go to Top   Previous Page   Next Page

Errors in Apache modules do occur, and tracking them down is significantly trickier than in standalone Perl or C programs. Some errors are due to bugs in your code, while others are due to the unavoidable hazards of running in a networked environment. The remote user might cancel a form submission before it is entirely done, the connection might drop while you're updating a database, or a file that you're trying to access might not exist.

A virtuous Apache module must let at least two people know when a problem has occurred: you, the module's author, and the remote user. You can communicate errors and other exception conditions to yourself by writing out entries to the server log. For alerting the user when a problem has occurred, you can take advantage of the simple but flexible Apache ErrorDocument system, use CGI::Carp, or roll your own error handler.

Error Logging

   Show Contents   Go to Top   Previous Page   Next Page

We talked about tracking down code bugs in Chapter 2 and will talk more about C-language specific debugging in Chapter 10. This section focuses on defensive coding techniques for intercepting and handling other types of runtime errors.

The most important rule is to log everything. Log anything unexpected, whether it is a fatal error or a condition that you can work around. Log expected but unusual conditions too, and generate routine logging messages that can help you trace the execution of your module under normal conditions.

Apache versions 1.3 and higher offer syslog-like log levels ranging in severity from debug, for low-priority messages, through warn, for noncritical errors, to emerg, for fatal errors that make the module unusable. By setting the LogLevel directive in the server configuration file, you can adjust the level of messages that are written to the server error log. For example, by setting LogLevel to warn, messages with a priority level of warn and higher are displayed in the log; lower-priority messages are ignored.

To use this adjustable logging API, you must load the standard Apache::Log module. This adds a log() method to the Apache request object, which will return an Apache::Log object. You can then invoke this object's methods in order to write nicely formatted log entries to the server's error log at the priority level you desire. Here's a short example:

use Apache::Log ();
my $log = $r->log;
$log->debug("Trying to lock guestbook file now");
unless (lock($GUESTBOOKFILE,1)) {
  $log->emerg("Can't get lock!");
  return SERVER_ERROR;
}
$log->debug("Got lock");

In this example, we first obtain a log object by calling the request object's log() method. We call the log object's debug() method to send a debug message to the error log and then try to perform a locking operation. If the operation fails, we log an error message at the emerg priority level using the log object's emerg() method and exit. Otherwise, we log another debugging message.

You'll find the full list of method calls made available by Apache::Log in Chapter 9, in the subsection "Logging Methods" under "The Apache Request Object." In addition, the Apache Perl API offers three simpler methods for entering messages into the log file. You don't have to import the Apache::Log module to use these methods, and they're appropriate for smaller projects (such as most of the examples in this book).

$r->log_error($message)
log_error() writes out a time-stamped message into the server error log using a facility of error. Use it for critical errors that make further normal execution of the module impossible. This method predates the 1.3 LogLevel API but still exists for backward compatibility and as a shortcut to $r->log->error.
$r->warn($message)
warn() will log an error message with a severity level of warn. You can use this for noncritical errors or unexpected conditions that you can work around. This method predates the 1.3 LogLevel API but still exists for backward compatibility and as a shortcut to $r->log->warn.
$r->log_reason($message,$file)
This is a special-purpose log message used for errors that occur when a content handler tries to process a file. It results in a message that looks something like this:
access to /usr/local/apache/htdocs/index.html failed for ppp12.yahoo.com,
   reason: user phyllis not authorized

You might also choose to include a $DEBUG global in your modules, either hard-coding it directly into the source, or by pulling its value out of the configuration file with Apache::dir_config(). Your module can then check this global every time it does something significant. If set to a true value, your script should send verbose informational messages to the Apache error log (or to an alternative log file of your choice).

The ErrorDocument System

   Show Contents   Go to Top   Previous Page   Next Page

Apache provides a handy ErrorDocument directive that can be used to display a custom page when a handler returns a non-OK status code. The custom page can be any URI, including a remote web page, a local static page, a local server-side include document, or a CGI script or module. In the last three cases, the server generates an internal redirect, making the redirection very efficient.

For example, the configuration file for Lincoln's laboratory site contains this directive:

ErrorDocument 404 /perl/missing.cgi

When the server encounters a 404 "Not Found" status code, whether generated by a custom module or by the default content handler, it will generate an internal redirect to a mod_perl script named missing.cgi. Before calling the script, Apache sets some useful environment variables including the following:

REDIRECT_URL
The URL of the document that the user was originally trying to fetch.
REDIRECT_STATUS
The status code that caused the redirection to occur.
REDIRECT_REQUEST_METHOD
The method (GET or POST) that caused the redirection.
REDIRECT_QUERY_STRING
The original query string, if any.
REDIRECT_ERROR_NOTES
The logged error message, if any.

A slightly simplified version of missing.cgi that works with Apache::Registry (as well as a standalone CGI script) is shown in Example 4-16. For a screenshot of what the user gets when requesting a nonexistent URI, see Figure 4-9.

Figure 4-9. The missing.cgi script generates a custom page to display when a URI is not found.

Example 4-16. A Simple Apache::Registry ErrorDocument Handler

#!/usr/local/bin/perl
# file: missing.cgi
use CGI qw(:standard);
use strict;
print header,
     start_html(-title => 'Missing Document', -bgcolor => 'white'),
     h1(img({-src => '/icons/unknown.gif'}),
     'Document Not Found'),
     p("I'm sorry, but the document you requested,",
       strong($ENV{REDIRECT_URL}),
       "is not available.  Please try the",
       a({-href => "/search.html"}, "search page"),
       "for help locating the document."),
     hr,
     address(a({-href => "mailto:$ENV{SERVER_ADMIN}"}, 'webmaster')),
     end_html;

If you want to implement the ErrorDocument handler as a vanilla Apache Perl API script, the various REDIRECT_ environment variables will not be available to you. However, you can get the same information by calling the request object's prev() method. This returns the request object from the original request. You can then query this object to recover the requested URI, the request method, and so forth.

Example 4-17 shows a rewritten version of missing.cgi that uses prev() to recover the URI of the missing document. The feature to note in this code is the call to $r->prev on the fifth line of the handler() subroutine. If the handler was invoked as the result of an internal redirection, this call will return the original request object, which we then query for the requested document by calling its uri() method. If the handler was invoked directly (perhaps by the user requesting its URI), the original request will be undefined and we use an empty string for the document URI.

Example 4-17. An ErrorDocument Handler Using the Vanilla Apache API

package Apache::Missing;
# File: Apache/Missing.pm
use strict;
use Apache::Constants qw(:common);
use CGI qw(:html);
sub handler {
   my $r = shift;
   $r->content_type('text/html');
   $r->send_http_header;
   return OK if $r->header_only;
    my $original_request = $r->prev;
   my $original_uri = $original_request ? $original_request->uri : '';
   my $admin = $r->server->server_admin;
    $r->print(
            start_html(-title => 'Missing Document',
                       -bgcolor => 'white'),
            h1(img({-src => '/icons/unknown.gif'}),
               'Document Not Found'),
            p(
              "I'm sorry, but the document you requested,",
              strong($original_uri),
              ", is not available.  Please try the",
              a({-href => "/search.html"}, "search page"),
              "for help locating the document."
              ),
            hr,
            address(a({-href => "mailto:$admin"}, 'webmaster')),
            end_html
            );
    return OK;
}
1;
__END__

Here's an example using Apache::Missing in the configuration file:

<Location /Missing>
  SetHandler  perl-script
  PerlHandler Apache::Missing
</Location>

If the static nature of the Apache ErrorDocument directive is inadequate for your needs, you can set the error document dynamically from within a handler by calling the request object's custom_response() method. This method takes two arguments: the status code of the response you want to handle and the URI of the document or module that you want to pass control to. This error document setting will persist for the lifetime of the current request only. After the handler exits, the setting returns to its default.

For example, the following code snippet sets up a custom error handler for the SERVER_ERROR error code (a generic error that covers a variety of sins). If the things_are_ok() subroutine (not implemented here) returns a true value, we do our work and return an OK status. Otherwise, we set the error document to point to a URI named /Carp and return a SERVER_ERROR status.

package Apache::GoFish;
# file: Apache/GoFish.pm
use strict;
use Apache::Constants qw(:common);
sub handler {
  my $r = shift;
  if (things_are_ok($r)) {
    do_something();
    return OK;
  }
  $r->custom_response(SERVER_ERROR, "/Carp");
  return SERVER_ERROR;
}
1;
__END__
   Show Contents   Go to Top   Previous Page   Next Page
Copyright © 1999 by O'Reilly & Associates, Inc.