While we are on the subject of handling output, we should also look at handling errors. One of the things that distinguishes an experienced developer from a novice is adequate error handling. Novices expect things to always work as planned; experienced developers have learned otherwise.
The most common method that Perl developers use for handling errors is Perl's built-in die function. Here is an example:
open FILE, $filename or die "Cannot open $filename: $!";
If Perl is unable to open the file specified by $filename, die will print an error message to STDERR and terminate the script. The open function, like most Perl commands that interact with the system, sets $! to the reason for the error if it fails.
Unfortunately, die is not always the best solution for handling errors in your CGI scripts. As you will recall from Chapter 3, "The Common Gateway Interface", output to STDERR is typically sent to the web server's error log, triggering the web server to return a 500 Internal Server Error . This is certainly not a very user-friendly response.
You should determine a policy for handling errors on your site. You may decide that 500 Internal Server Error pages are acceptable for very uncommon system errors like the inability to read or write to files. However, you may decide that you wish to display a formatted HTML page instead with information for users such as alternative actions they can take or who to notify about the problem.
It is possible to trap die so that it does not generate a 500 Internal Server Error automatically. This is especially useful because many common third-party modules use die (and variants such as croak) as their manner for responding to errors. If you know that a particular subroutine may call die, you can catch this with an eval block in Perl:
eval { dangerous_routine( ); 1; } or do { error( $q, $@ || "Unknown error" ); };
If dangerous_routine does call die, then eval will catch it, set the special variable $@ to the value of the die message, pass control to the end of the block, and return undef. This allows us to call another subroutine to display our error more gracefully. Note that an eval block will not trap exit.
This works, but it certainly makes your code a lot more complex, and if your CGI script interacts with a lot of subroutines that might die, then you must either place your entire script within an eval block or include lots of these blocks throughout your script.
Fortunately, there is a better way. You may already know that it is possible to create a global signal handler to trap Perl's die and warn functions. This involves some rather advanced Perl; you can find specific information in Programming Perl. Fortunately, we don't have to worry about the specifics, because there is a module that not only does this, but is written specifically for CGI scripts: CGI::Carp.
CGI::Carp is not part of the CGI.pm module, but it is also by Lincoln Stein, and it is distributed with CGI.pm (and thus included with the most recent versions of Perl). It does two things: it creates more informative entries in your error log, and it allows you to create a custom error page for fatal calls like die. Simply by using the module, it adds a timestamp and the name of the running CGI script to errors written to the error log by die , warn, carp, croak, and confess. The last three functions are provided by the Carp module (included with Perl) and are often used by module authors.
This still does not stop your web server from displaying 500 Internal Server Error responses for these calls, however. CGI::Carp is most useful when you ask it to trap fatal calls. You can have it display fatal error messages in the browser instead. This is especially helpful during development and debugging. To do this, simply pass the fatalsToBrowser parameter to it when you use the module:
use CGI::Carp qw( fatalsToBrowser );
In a production environment, you may not want users to view your full error information if they encounter an error. Fortunately, you can have CGI::Carp trap errors and display your own custom error message. To do this, you pass CGI::Carp::set_message a reference to a subroutine that takes a single argument and displays the content of a response.
use CGI::Carp qw( fatalsToBrowser ); BEGIN { sub carp_error { my $error_message = shift; my $q = new CGI; $q->start_html( "Error" ), $q->h1( "Error" ), $q->p( "Sorry, the following error has occurred: " ); $q->p( $q->i( $error_message ) ), $q->end_html; } CGI::Carp::set_message( \&carp_error ); }
We will see how to incorporate this into a more general solution later in Example 5-3.
Most of our examples up to now and throughout the book include subroutines or blocks of code for displaying errors. Here is an example:
sub error { my( $q, $error_message ) = shift; print $q->header( "text/html" ), $q->start_html( "Error" ), $q->h1( "Error" ), $q->p( "Sorry, the following error has occurred: " ); $q->p( $q->i( $error_message ) ), $q->end_html; exit; }
You can call this with a CGI object and a reason for the error. It will output an error page and then exit in order to stop executing your script. Note that we print the HTTP header here. One of the biggest challenges in creating a general solution for catching errors is knowing whether or not to print an HTTP header: if one has already been printed and you print another, it will appear at the top of your error page; if one has not been printed and you do not print one as part of the error message, you will trigger a 500 Internal Server Error instead.
Fortunately, CGI.pm has a feature that will track whether a header has been printed for you already. If you enable this feature, it will only output an HTTP header once per CGI object. Any future calls to header will silently do nothing. You can enable this feature in one of three ways:
You can pass the -unique_headers flag when you load CGI.pm:
use CGI qw( -unique_headers );
You can set the $CGI::HEADERS_ONCE variable to a true value after you use CGI.pm, but before you create an object:
use CGI; $CGI::HEADERS_ONCE = 1; my $q = new CGI;
Finally, if you know that you always want this feature, you can enable it globally for all of your scripts by setting $HEADERS_ONCE to a true value within your copy of CGI.pm. You can do this just like $POST_MAX and $DISABLE_UPLOADS variables we discussed at the beginning of the chapter. You will find $HEADERS_ONCE is in the same configurable section of CGI.pm:
# Change this to 1 to suppress redundant HTTP headers $HEADERS_ONCE = 0;
Although adding subroutines to each of your CGI scripts is certainly an acceptable way to catch errors, it's still not a very general solution. You will probably want to create your own error pages that are customized for your site. Once you start including complex HTML in your subroutines, it will quickly become too difficult to maintain them. If you build error subroutines that output error pages according to your site's template, and then later someone decides they want to change the site's look, you must go back and update all of your subroutines. Clearly, a much better option is to create a general error handler that all of your CGI scripts can access.
It is a good idea to create your own Perl module that's specific to your site. If you host different sites, or have different applications within your site with different looks and feels, you may wish to create a module for each. Within this module, you can place subroutines that you find yourself using across many CGI scripts. These subroutines will vary depending on your site, but one should handle errors.
If you have not created your own Perl module before, don't worry, it's quite simple. Example 5-3 shows a very minimal module.
#!/usr/bin/perl -wT package CGIBook::Error; # Export the error subroutine use Exporter; @ISA = "Exporter"; @EXPORT = qw( error ); $VERSION = "0.01"; use strict; use CGI; use CGI::Carp qw( fatalsToBrowser ); BEGIN { sub carp_error { my $error_message = shift; my $q = new CGI; my $discard_this = $q->header( "text/html" ); error( $q, $error_message ); } CGI::Carp::set_message( \&carp_error ); } sub error { my( $q, $error_message ) = @_; print $q->header( "text/html" ), $q->start_html( "Error" ), $q->h1( "Error" ), $q->p( "Sorry, the following error has occurred: " ), $q->p( $q->i( $error_message ) ), $q->end_html; exit; } 1;
The only difference between a Perl module and a standard Perl script is that you should save your file with a .pm extension, declare the name of module's package with the package function (this should match the file's name except without the .pm extension and substituting :: for /),[7] and make sure that it returns a true value when evaluated (the reason for the 1; at the bottom).
[7]When determining the package name, the file's name should be relative to a library path in @INC. In our example, we store the file at /usr/lib/perl5/site_perl/5.005/CGIBook/Error.pm. /usr/lib/perl5/site_perl/5.005 is a library directory. Thus, the path to the module relative to the library directory is CGIBook/Error.pm so the package is CGIBook::Error.
It is standard practice to store the version of the module in $VERSION. For the sake of convenience, we also use the Exporter module to export the error subroutine. This allows us to refer to it in our scripts as error instead of CGIBook::Exporter::error. Refer to the Exporter manpage or a primary Perl text, such as Programming Perl, for details on using Exporter.
You have a couple options for saving this file. The simplest solution is to save it within the site_perl directory of your Perl libraries, such as /usr/lib/perl5/site_perl/5.005/CGIBook/Error.pm. The site_perl directory includes modules that are site-specific (i.e., not included in Perl's standard distribution). The paths of your Perl libraries may differ; you can locate them on your system with the following command:
$ perl -e 'print map "$_\n", @INC'
You probably want to create a subdirectory that is unique to your organization, as we did with CGIBook, to hold all the Perl modules you create.
You can use the module as follows:
#!/usr/bin/perl -wT use strict; use CGI; use CGIBook::Error; my $q = new CGI; unless ( check_something_important( ) ) { error( $q, "Something bad happened." ); }
If you do not have the permission to install the module in your Perl library directory, and if you cannot get your system administrator to do it, then you can place the module in another location, for example, /usr/local/apache/perl-lib/CGIBook/Error.pm. Then you must remember to include this directory in the list that Perl searches for modules. The simplest way to do this is with the lib pragma:
#!/usr/bin/perl -wT use strict; use lib "/usr/local/apache/perl-lib"; use CGI; use CGIBook::Error; . . .
Copyright © 2001 O'Reilly & Associates. All rights reserved.