stas        2004/02/26 17:23:32

  Modified:    t/filter in_bbs_inject_header.t
               t/filter/TestFilter in_bbs_inject_header.pm
  Log:
  support keepalive connections. I needed to add an output connection filter
  which flags the input filter when the response is over, so that the input
  filter will know to reset its state and be ready to parse headers of the
  new request coming over the same connection.
  
  Revision  Changes    Path
  1.3       +49 -3     modperl-2.0/t/filter/in_bbs_inject_header.t
  
  Index: in_bbs_inject_header.t
  ===================================================================
  RCS file: /home/cvs/modperl-2.0/t/filter/in_bbs_inject_header.t,v
  retrieving revision 1.2
  retrieving revision 1.3
  diff -u -u -r1.2 -r1.3
  --- in_bbs_inject_header.t    7 Oct 2003 01:04:13 -0000       1.2
  +++ in_bbs_inject_header.t    27 Feb 2004 01:23:32 -0000      1.3
  @@ -1,7 +1,7 @@
   use strict;
   use warnings FATAL => 'all';
   
  -use Apache::Test ();
  +use Apache::Test;
   use Apache::TestUtil;
   
   use Apache::TestRequest;
  @@ -14,7 +14,53 @@
   
   my $config = Apache::Test::config();
   my $hostport = Apache::TestRequest::hostport($config);
  -my $content = "This body shouldn't be seen by the filter";
   t_debug("connecting to $hostport");
   
  -print POST_BODY_ASSERT $location, content => $content;
  +my $content = "This body shouldn't be seen by the filter";
  +
  +my $header1_key = 'X-My-Protocol';
  +my $header1_val = 'POST-IT';
  +
  +my %headers = (
  +    'X-Extra-Header2' => 'Value 2',
  +    'X-Extra-Header3' => 'Value 3',
  +);
  +
  +my $keep_alive_times     = 4;
  +my $non_keep_alive_times = 4;
  +my $tests = 2 + keys %headers;
  +my $times = $non_keep_alive_times + $keep_alive_times + 1;
  +
  +plan tests => $tests * $times;
  +
  +# try non-keepalive conn
  +validate(POST($location, content => $content)) for 1..$non_keep_alive_times;
  +
  +# try keepalive conns
  +Apache::TestRequest::user_agent(reset => 1, keep_alive => 1);
  +validate(POST($location, content => $content)) for 1..$keep_alive_times;
  +
  +# try non-keepalive conn
  +Apache::TestRequest::user_agent(reset => 1, keep_alive => 0);
  +validate(POST($location, content => $content));
  +
  +# 4 sub-tests
  +sub validate {
  +    my $res = shift;
  +
  +    die join "\n",
  +        "request has failed (the response code was: " . $res->code . ")",
  +        "see t/logs/error_log for more details\n" unless $res->is_success;
  +
  +    ok t_cmp($content, $res->content, "body");
  +
  +    ok t_cmp($header1_val,
  +             $res->header($header1_key),
  +             "injected header $header1_key");
  +
  +    for my $key (sort keys %headers) {
  +        ok t_cmp($headers{$key},
  +                 $res->header($key),
  +                 "injected header $key");
  +    }
  +}
  
  
  
  1.4       +79 -25    modperl-2.0/t/filter/TestFilter/in_bbs_inject_header.pm
  
  Index: in_bbs_inject_header.pm
  ===================================================================
  RCS file: /home/cvs/modperl-2.0/t/filter/TestFilter/in_bbs_inject_header.pm,v
  retrieving revision 1.3
  retrieving revision 1.4
  diff -u -u -r1.3 -r1.4
  --- in_bbs_inject_header.pm   9 Feb 2004 18:18:16 -0000       1.3
  +++ in_bbs_inject_header.pm   27 Feb 2004 01:23:32 -0000      1.4
  @@ -4,11 +4,16 @@
   # 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 first task is simple for non-keepalive connections -- 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.
   #
  +# it becomes much trickier with keepalive connection, since Apache
  +# provides no API to tell you whether a new request is coming in. The
  +# only way to work around that is to install a connection output
  +# filter and make it snoop on EOS bucket (when the response is
  +# completed an EOS bucket is sent, regardless of the connection type).
   #
   # 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
  @@ -25,14 +30,14 @@
   
   use Apache::RequestRec ();
   use Apache::RequestIO ();
  +use Apache::Connection ();
   use APR::Brigade ();
   use APR::Bucket ();
  +use APR::Table ();
   
  -use Apache::Test;
  -use Apache::TestUtil;
   use Apache::TestTrace;
   
  -use Apache::Const -compile => qw(OK DECLINED);
  +use Apache::Const -compile => qw(OK DECLINED CONN_KEEPALIVE);
   use APR::Const    -compile => ':common';
   
   my $header1_key = 'X-My-Protocol';
  @@ -74,25 +79,80 @@
       return 1;
   }
   
  -sub handler : FilterConnectionHandler {
  +sub flag_request_reset : FilterConnectionHandler {
  +    my($filter, $bb) = @_;
  +
  +    # we need this filter only when the connection is keepalive
  +    unless ($filter->c->keepalive == Apache::CONN_KEEPALIVE) {
  +        $filter->remove;
  +        return Apache::DECLINED;
  +    }
  +
  +    debug join '', "-" x 20 , " output filter called ", "-" x 20;
  +
  +    #ModPerl::TestFilterDebug::bb_dump("connection", "output", $bb);
  +
  +    for (my $b = $bb->first; $b; $b = $bb->next($b)) {
  +        next unless $b->is_eos;
  +        # end of request, the input filter may get a new request now,
  +        # so it should be ready to parse headers again
  +        debug "flagging the end of response";
  +        $filter->c->notes->set(reset_request => 1);
  +    }
  +
  +    return $filter->next->pass_brigade($bb);
  +}
  +
  +# instead of using FilterInitHandler, you could register the output
  +# filter explicitly in the configuration file. that will be more
  +# efficient for a production site, as one will need to do it only if
  +# they support KeepAlive configurations
  +sub push_output_filter : FilterInitHandler {
  +    my $filter = shift;
  +
  +    # at this point we don't know whether the connection is going to
  +    # be keepalive or not, since the relevant input headers weren't
  +    # parsed yet. we know only after ap_http_header_filter was called.
  +    # therefore we have no choice but to always add the flagging
  +    # output filter
  +    $filter->c->add_output_filter(\&flag_request_reset);
  +
  +    return Apache::OK;
  +}
  +
  +sub handler : FilterConnectionHandler
  +              FilterHasInitHandler(\&push_output_filter) {
       my($filter, $bb, $mode, $block, $readbytes) = @_;
   
  -    debug join '', "-" x 20 , " filter called ", "-" x 20;
  +    debug join '', "-" x 20 , " input filter called -", "-" x 20;
   
  -    my $ctx;
  -    unless ($ctx = $filter->ctx) {
  +    my $c = $filter->c;
  +
  +    my $ctx = $filter->ctx;
  +    unless ($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);
       }
   
  +    # reset the filter state, we start a new request
  +    if ($c->keepalive == Apache::CONN_KEEPALIVE &&
  +        $ctx->{done_with_headers} && $c->notes->get('reset_request')) {
  +        debug "a new request resetting the input filter state";
  +        $c->notes->set('reset_request' => 0);
  +        $ctx->{buckets} = [];
  +        $ctx->{seen_body_separator} = 0;
  +        $ctx->{done_with_headers} = 0;
  +    }
  +
       # handling the HTTP request body
       if ($ctx->{done_with_headers}) {
           # XXX: when the bug in httpd filter will be fixed all the
  @@ -110,7 +170,6 @@
       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;
  @@ -185,22 +244,18 @@
   sub response {
       my $r = shift;
   
  -    my $data = ModPerl::Test::read_post($r);
  -
  -    plan $r, tests => 2 + keys %headers;
  -
  -    ok t_cmp($request_body, $data);
  -
  -    ok t_cmp($header1_val,
  -             $r->headers_in->get($header1_key),
  -             "injected header $header1_key");
  +    # propogate the input headers and the input back to the client
  +    # as we need to do the validations on the client side
  +    $r->headers_out->set($header1_key => 
  +                         $r->headers_in->get($header1_key)||'');
   
       for my $key (sort keys %headers) {
  -        ok t_cmp($headers{$key},
  -                 $r->headers_in->get($key),
  -                 "injected header $key");
  +        $r->headers_out->set($key => $r->headers_in->get($key)||'');
       }
   
  +    my $data = ModPerl::Test::read_post($r);
  +    $r->print($data);
  +
       Apache::OK;
   }
   
  @@ -214,6 +269,5 @@
        SetHandler modperl
        PerlResponseHandler TestFilter::in_bbs_inject_header::response
     </Location>
  -
   </VirtualHost>
   </NoAutoConfig>
  
  
  

Reply via email to