davido has asked for the wisdom of the Perl Monks concerning the following question:

I wanted to get a little clarification on using CGI::Carp along with its 'fatalsToBrowser' and 'set_message()' options. Specifically, using CGI::Carp in a way that interacts well with the CGI.pm object.

The perlman:CGI::Carp POD's state that you may pass set_message() a coderef, and that in order to properly intercept compile-time errors it is necessary to place the error handler in a BEGIN block.

The following snippet is provided in the perlman:CGI::Carp POD for CGI::Carp:

use CGI::Carp qw(fatalsToBrowser set_message); BEGIN { sub handle_errors { my $msg = shift; print "<h1>Oh gosh</h1>"; print "<p>Got an error: $msg</p>"; } set_message(\&handle_errors); }

So far so good, except that this approach hopes that the HTTP header has already been created, and it bypasses the object methods supplied by CGI.pm. The O'Reilly book, "CGI Programming with Perl, 2nd ed." has a slightly different version of the above code snippet. This one is made to interact with the CGI object from CGI.pm:

use CGI; use CGI::Carp qw(fatalsToBrowser); BEGIN { sub carp_error { my $error_message = shift; my $q = new CGI; print $q->start_html( "Error" ), $q->h1( "Error" ), $q->p( "Sorry, the following error occurred: " ), $q->p( $q->i( $error_message ) ), $q->end_html; } CGI::Carp::set_message( \&carp_error ); }

But this example still only hopes that the HTTP header was created, and itself creates a couple of problems. One is probably just a style issue, and the other a header issue. First the style issue. Does it really make sense in this context to create a new CGI object (possibly in addition to the object already being used within the main portion of the CGI script)? Clearly the BEGIN block runs before the main script's CGI object is created, but the "carp_error" routine is going to create a new CGI object every time it tries to carp an error to the browser. What issues could arise out of the creation of multiple CGI objects within the same script?

CGI.pm's POD (perlman:lib:CGI) doesn't warn against using multiple objects. But I suspect that both the carp_error CGI object and the main script's CGI object will hold the same parameter list since neither call to 'new CGI' explicitly passes any other parameter list. That right there could be an issue, since both would be capable of tinkering with the parameters that came in upon invocation of the script. Do modifications to one CGI object's parameters affect those of another CGI object, if they both are the result of the same GET/POST action?

The next issue was that of multiple HTTP headers possibly being generated. The 'textbook' solution was to not print a CGI header. But that's a less than ideal solution. CGI.pm has a safeguard against multiple headers being created by turneing on the use CGI qw( -unique_headers ); option. However my experience is that the "-unique_headers" option only prevents against multiple headers being generated for the same object. As already mentioned, the CGI book's example might have multiple objects created.

In thinking about it I came up with this workaround; a hybred of the various examples from books, CGI::Carp POD, and CGI.pm POD.

#!/usr/bin/perl -T use strict; use warnings; use CGI qw( -headers_once ); use CGI::Carp qw( fatalsToBrowser ); my $q; # The CGI object will get file scope. BEGIN { $q = new CGI; # Construct the CGI object # within the BEGIN block. sub carp_error { my $error_message = shift; print $q->header( "text/html" ), $q->start_html( "Error" ), $q->h1( "Error" ), $q->p( "Sorry, the following error occurred: " ), $q->p( $q->i( $error_message ) ), $q->end_html; } CGI::Carp::set_message( \&carp_error ); }

However, this solution has another problem. The CGI object is passed into 'carp_error' as a global variable rather than as an object reference. That means that the object always has to be called $q, or the 'carp_error' routine will be trying to use a non-existant CGI object. And besides, it just seems like a kludgy solution.

Ideally 'carp_error' or 'handle_errors' (whatever you choose to call your Carp error handler) would have the ability to take a parameter in addition to the one that CGI::Carp passes to it; a parameter holding a reference to the main CGI object.

I'd like to hear what others are using who have spent a lot more time down the CGI development road than I.

Thanks for any input!


Dave


"If I had my life to do over again, I'd be a plumber." -- Albert Einstein

Replies are listed 'Best First'.
Re: Proper use of CGI::Carp together with CGI.pm
by diotalevi (Canon) on Oct 14, 2003 at 02:15 UTC

    Your original code was fine. The CGI object is only being created when there is an actual error and carp_error() is called. This isn't happening on every page load (or it isn't *supposed* to anyway). I think you're in more trouble by assigning to the global $q instead of the function lexical $q since *then* you have to think about whether it is staying around or not.

    All in all - you're overworking yourself here. Its ok, really. Now if only I could find my lost dog I'd be happy.

Re: Proper use of CGI::Carp together with CGI.pm
by iltzu (Initiate) on Oct 14, 2003 at 14:43 UTC

    Your last code example is fine. It doesn't use global variables, since $q is declared as lexical by my. In effect, your carp_error subroutine is acting as a closure. In fact, you could go all the way and create your error-handling routine as an anonymous subroutine. That way it won't stay around cluttering your symbol tables.

    You also don't need to create your CGI object in a BEGIN block, as long as you make sure your error handler has some CGI object available. In my code example below, the line marked with "!!!" does that. Note the brackets ({}) on that line -- they make the "backup" CGI object completely independent from the actual HTTP request. That way, if your script dies because of a malformed request, it hopefully will still be able to generate its error message.

    use CGI::Carp qw( fatalsToBrowser ); use CGI qw( -headers_once ); my $q = new CGI; BEGIN { CGI::Carp::set_message sub { my $error = shift; $q ||= new CGI {}; # !!! print $q->header(), $q->start_html( "Error" ), $q->h1( "Error" ), $q->p( "Sorry, the following error occurred:" ), $q->pre( $q->i( $error ) ), $q->end_html(); }; }

    Also worth noting in the use of $q->pre() for the actual error. Perl error messages are preformatted with indents and line breaks, and look rather messy if smashed up in a single HTML paragraph.

    Now, once you've gone to all that trouble and finally test your code, you will notice that you're still sending double HTTP headers. Why? Well, there's this little detail in the CGI::Carp documentation:

    CGI::Carp arranges to send a minimal HTTP header to the browser so that even errors that occur in the early compile phase will be seen.

    Damn. Time to submit a patch, I guess. Meanwhile, I suppose you can work around the issue by inserting the following lines at the end of the BEGIN block:

    ## REALLY UGLY KLUGE FOLLOWS, USE AT YOUR OWN RISK: my $fatalsToBrowser = \&CGI::Carp::fatalsToBrowser; no warnings 'redefine'; *CGI::Carp::fatalsToBrowser = sub { local $ENV{GATEWAY_INTERFACE} = "CGI-PerlEx-FAKE"; &$fatalsToBrowser; }
      Addendum: I've just sent a patch to fix this to Lincoln Stein. Wait and see.