stas 2003/10/03 15:41:39
Added: t/filter in_bbs_inject_header.t
t/filter/TestFilter in_bbs_inject_header.pm
Log:
this filter demonstrates two things:
1. how to write a filter that will work only on HTTP headers
2. how to inject extra HTTP headers
- it still needs a bit of work
Revision Changes Path
1.1 modperl-2.0/t/filter/in_bbs_inject_header.t
Index: in_bbs_inject_header.t
===================================================================
use strict;
use warnings FATAL => 'all';
use Apache::Test ();
use Apache::TestUtil;
use Apache::TestRequest;
my $module = 'TestFilter::in_bbs_inject_header';
my $location = "/" . Apache::TestRequest::module2path($module);
Apache::TestRequest::scheme('http'); #force http for t/TEST -ssl
Apache::TestRequest::module($module);
my $config = Apache::Test::config();
my $hostport = Apache::TestRequest::hostport($config);
t_debug("connecting to $hostport");
print POST_BODY_ASSERT $location, content => "whatever";
1.1 modperl-2.0/t/filter/TestFilter/in_bbs_inject_header.pm
Index: in_bbs_inject_header.pm
===================================================================
package TestFilter::in_bbs_inject_header;
# this filter demonstrates two things:
# 1. how to write a filter that will work only on HTTP headers
# 2. how to inject extra HTTP headers
#
# the first task is simple -- as soon as a bucket which matches
# /^[\r\n]+$/ is read we can store that event in the filter context and
# simply 'return Apache::DECLINED on the future invocation, so not to
# slow things.
#
#
# the second task is a bit trickier, as the headers_in core httpd
# filter is picky and it wants each header to arrive in a separate
# bucket, and moreover this bucket needs to be in its own brigade.
# so this test arranges for this to happen.
#
# the test shows how to push headers at the end of all headers
# and in the middle, whichever way you prefer.
use strict;
use warnings;# FATAL => 'all';
use base qw(Apache::Filter);
use Apache::RequestRec ();
use Apache::RequestIO ();
use APR::Brigade ();
use APR::Bucket ();
use Apache::Test;
use Apache::TestUtil;
use Apache::TestTrace;
use Apache::Const -compile => qw(OK DECLINED);
use APR::Const -compile => ':common';
my $header1_key = 'X-My-Protocol';
my $header1_val = 'POST-IT';
my %headers = (
'X-Extra-Header2' => 'Value 2',
'X-Extra-Header3' => 'Value 3',
);
# returns 1 if a bucket with a header was inserted to the $bb's tail,
# otherwise returns 0 (i.e. if there are no buckets to insert)
sub inject_header_bucket {
my ($bb, $ctx) = @_;
return 0 unless @{ $ctx->{buckets} };
my $bucket = shift @{ $ctx->{buckets} };
$bb->insert_tail($bucket);
if (1) {
# extra debug, wasting cycles
my $data;
$bucket->read($data);
debug "injected header: [$data]";
}
else {
debug "injected header";
}
# next filter invocations will bring the request body if any
if ($ctx->{seen_body_separator} && [EMAIL PROTECTED] $ctx->{buckets} }) {
$ctx->{done_with_headers} = 1;
$ctx->{seen_body_separator} = 0;
}
return 1;
}
sub handler : FilterConnectionHandler {
my($filter, $bb, $mode, $block, $readbytes) = @_;
debug join '', "-" x 20 , " filter called ", "-" x 20;
use Data::Dumper;
warn Dumper $filter->ctx;
my $ctx;
unless ($ctx = $filter->ctx) {
debug "filter context init";
$ctx = {
buckets => [],
done_with_headers => 0,
seen_body_separator => 0,
};
# since we are going to manipulate the reference stored in
# ctx, it's enough to store it only once, we will get the same
# reference in the following invocations of that filter
$filter->ctx($ctx);
}
# handling the HTTP request body
if ($ctx->{done_with_headers}) {
# XXX: when the bug in httpd filter will be fixed all the
# code in this branch will be replaced with $filter->remove;
# at the moment (2.0.48) it doesn't work
# so meanwhile tell the mod_perl filter core to pass-through
# the brigade unmodified
debug "passing the body through unmodified";
return Apache::DECLINED;
#my $rv = $filter->next->get_brigade($bb, $mode, $block, $readbytes);
#return $rv unless $rv == APR::SUCCESS;
#return Apache::OK;
}
# any custom HTTP header buckets to inject?
return Apache::OK if inject_header_bucket($bb, $ctx);
# normal HTTP headers processing
my $c = $filter->c;
my $ctx_bb = APR::Brigade->new($c->pool, $c->bucket_alloc);
my $rv = $filter->next->get_brigade($ctx_bb, $mode, $block, $readbytes);
return $rv unless $rv == APR::SUCCESS;
while (!$ctx_bb->empty) {
my $data;
my $bucket = $ctx_bb->first;
$bucket->remove;
if ($bucket->is_eos) {
debug "EOS!!!";
$bb->insert_tail($bucket);
last;
}
my $status = $bucket->read($data);
debug "filter read:\n[$data]";
if ($status != APR::SUCCESS) {
return $status;
}
if ($data and $data =~ /^POST/) {
# demonstrate how to add a header while processing other headers
my $header = "$header1_key: $header1_val\n";
push @{ $ctx->{buckets} }, APR::Bucket->new($header);
debug "queued header [$header]";
}
elsif ($data =~ /^[\r\n]+$/) {
# normally the body will start coming in the next call to
# get_brigade, so if your filter only wants to work with
# the headers, it can decline all other invocations if that
# flag is set. However since in this test we need to send
# a few extra bucket brigades, we will turn another flag
# 'done_with_headers' when 'seen_body_separator' is on and
# all headers were sent out
debug "END of original HTTP Headers";
$ctx->{seen_body_separator}++;
# we hit the headers and body separator, which is a good
# time to add extra headers:
for my $key (keys %headers) {
my $header = "$key: $headers{$key}\n";
push @{ $ctx->{buckets} }, APR::Bucket->new($header);
debug "queued header [$header]";
}
# but at the same time we must ensure that the
# the separator header will be sent as a last header
# so we send one newly added header and push the separator
# to the end of the queue
push @{ $ctx->{buckets} }, $bucket;
debug "queued header [$data]";
inject_header_bucket($bb, $ctx);
next; # inject_header_bucket already called insert_tail
# notice that if we didn't inject any headers, this will
# still work ok, as inject_header_bucket will send the
# separator header which we just pushed to its queue
}
else {
# fall through
}
$bb->insert_tail($bucket);
}
return Apache::OK;
}
sub response {
my $r = shift;
plan $r, tests => 2 + keys %headers;
my $data = ModPerl::Test::read_post($r);
ok t_cmp(8, length($data), "whatever");
ok t_cmp($header1_val,
$r->headers_in->get($header1_key),
"injected header $header1_key");
for my $key (sort keys %headers) {
ok t_cmp($headers{$key},
$r->headers_in->get($key),
"injected header $key");
}
Apache::OK;
}
1;
__END__
<NoAutoConfig>
<VirtualHost TestFilter::in_bbs_inject_header>
# must be preloaded so the FilterConnectionHandler attributes will
# be set by the time the filter is inserted into the filter chain
PerlModule TestFilter::in_bbs_inject_header
PerlInputFilterHandler TestFilter::in_bbs_inject_header
<Location /TestFilter__in_bbs_inject_header>
SetHandler modperl
PerlResponseHandler TestFilter::in_bbs_inject_header::response
</Location>
</VirtualHost>
</NoAutoConfig>