modperl2 - How to send HTTP error messages from a request output filter
When developing a WEB appication with modperl2 one has to decide whether to write a PerlResponseHandler or a PerlOutputFilterHandler
or if it may be better perhaps to process the request in a phase prior to the response generation, write a temporary file, have it shipped by the default handler and delete it
afterwards. All these methods have their pros and cons. Until yesterday I'd have listed among the cons of the output filter method that it is not possible to generate
proper ErrorDocuments.
Not true!
The central Apache API function to generate ErrorDocuments is ap_die() and hidden in Apache2::HookRun lays the
Perl interface. So, the key point of entering standard error processing would look like:
use Apache2::HookRun ();
$r->die($status); # where $status is a valid HTTP code
However, one thing has to be looked out for. ap_die() is mainly made to report errors that occur in processing phases prior to the content generation.
If any content has already been sent any error processing has to be avoided. Otherwise the complete HTTP message would be mixed up with the other content.
How to determine if output has already been sent?
During normal request processing HTTP headers are gathered in $r->headers_out. Just before the first chunk of data is really sent to the client
a special output filter, the http_header filter, kicks in and sends the header fields. The last thing this filter does is removing itself from the filter chain.
So, a way to check if output has been sent is to check if this filter is still present.
To sum it up, a filter_die function might look like:
sub filter_die {
my ($f, $status)=@_;
# avoid further invocation
$f->remove;
# Check if we still can send an error message or better check if
# any output has already been sent. If so the HTTP_HEADER filter is missing
# in the output chain. If it is still present we can send a normal
# error message, see ap_die() in httpd-2.2.x/modules/http/http_request.c.
for( my $n=$f->next; $n; $n=$n->next ) {
if( $n->frec->name eq HTTP_HEADER_FILTER_NAME ) {
$f->r->die($status);
last;
}
}
return Apache2::Const::OK;
}
Now a filter code can report errors in the standard way taking into account the adminstrator's ErrorDocument settings:
{ # a simple exception object class
package My::Exception;
sub new {
shift;
bless {@_}, __PACKAGE__;
}
}
sub Filter : FilterRequestHandler {
my ($f, $bb)=@_;
eval {
...
};
if( $@ ) {
return filter_die($f, $@->{code}) if( ref $@ eq 'My::Exception' );
chomp $@;
$f->r->log_reason($@);
return filter_die($f, Apache2::Const::SERVER_ERROR);
}
return Apache2::Const::OK;
}
This approach is now available as CPAN module ModPerl2::Tools.
Letzte Aktualisierung: 14.03.2010

