stas 2002/08/13 07:26:19 Modified: src/docs/2.0/user/handlers handlers.pod Log: present and document MyApache::FilterSnoop Revision Changes Path 1.8 +339 -9 modperl-docs/src/docs/2.0/user/handlers/handlers.pod Index: handlers.pod =================================================================== RCS file: /home/cvs/modperl-docs/src/docs/2.0/user/handlers/handlers.pod,v retrieving revision 1.7 retrieving revision 1.8 diff -u -r1.7 -r1.8 --- handlers.pod 13 Aug 2002 11:39:02 -0000 1.7 +++ handlers.pod 13 Aug 2002 14:26:19 -0000 1.8 @@ -721,14 +721,6 @@ I<bucket brigades>. Both input and output filters work on these bucket brigades and modify them if necessary. -META: move the next para down? - -mod_perl provides two interfaces to filtering: a direct bucket -brigades manipulation interface and a simpler, stream-oriented -interface (XXX: as of this writing the latter is available only for -the output filtering). The examples in the following sections will -help you to understand the difference between the two interfaces. - Currently the mod_perl filters allow connection and request level filtering. Apache supports several other types, which mod_perl 2.0 will probably support in the future. mod_perl filter handlers specify @@ -828,11 +820,21 @@ ] -Now let's look at the input and output filters in details. +mod_perl provides two interfaces to filtering: a direct bucket +brigades manipulation interface and a simpler, stream-oriented +interface (XXX: as of this writing the latter is available only for +the output filtering). The examples in the following sections will +help you to understand the difference between the two interfaces. + =head2 PerlInputFilterHandler +The C<PerlInputFilterHandler> handler registers a filter for input +filtering. + +This handler is of type C<VOID>. +The handler's configuration scope is C<DIR>. =head2 PerlOutputFilterHandler @@ -842,6 +844,334 @@ This handler is of type C<VOID>. The handler's configuration scope is C<DIR>. + +=head2 Filters All-in-One + +Before we delve into the details of how to write filters that do +something, lets first write a simple filter that does nothing but +snooping on the data that goes through it. We are going to develop the +C<MyApache::FilterSnoop> handler which can snoop on request and +connection filters, in input and output modes. + +But first let's develop a simple response handler that simply dumps +the request's I<args> and I<content> strings: + + file:MyApache/Dump.pm + --------------------- + package MyApache::Dump; + + use strict; + use warnings; + + use Apache::RequestRec (); + use Apache::RequestIO (); + + use Apache::Const -compile => qw(OK M_POST); + + sub handler { + my $r = shift; + $r->content_type('text/plain'); + + $r->print("args:\n", $r->args, "\n"); + + if ($r->method_number == Apache::M_POST) { + my $data = content($r); + $r->print("content:\n$data\n"); + } + + return Apache::OK; + } + + sub content { + my $r = shift; + + $r->setup_client_block; + + return '' unless $r->should_client_block; + + my $len = $r->headers_in->get('content-length'); + my $buf; + $r->get_client_block($buf, $len); + + return $buf; + } + + 1; + +which is configured as: + + PerlModule MyApache::Dump + <Location /dump> + SetHandler modperl + PerlResponseHandler MyApache::Dump + </Location> + +If we issue the following request: + + % echo "mod_perl rules" | POST 'http://localhost:8002/dump?foo=1&bar=2' + +the response will be: + + args: + foo=1&bar=2 + content: + mod_perl rules + +As you can see it has simply dumped the query string and the posted +data. + +Now let's write the snooping filter: + + file:MyApache/FilterSnoop.pm + ---------------------------- + package MyApache::FilterSnoop; + + use strict; + use warnings; + + use base qw(Apache::Filter); + use Apache::FilterRec (); + use APR::Brigade (); + + use Apache::Const -compile => qw(OK DECLINED); + use APR::Const -compile => ':common'; + + sub connection : FilterConnectionHandler { snoop(@_) } + sub request : FilterRequestHandler { snoop(@_) } + + sub snoop { + my($filter, $bb, $mode, $block, $readbytes) = @_; + + # $mode, $block, $readbytes are passed only for input filters + my $stream = defined $mode ? "input" : "output"; + my $phase = $filter->r ? "request" : "connection"; + + # read the data and pass-through the bucket brigades unchanged + my $ra_data = ''; + if (defined $mode) { + # input filter + my $rv = $filter->next->get_brigade($bb, $mode, $block, $readbytes); + return $rv unless $rv == APR::SUCCESS; + $ra_data = bb_sniff($bb); + } + else { + # output filter + $ra_data = bb_sniff($bb); + my $rv = $filter->next->pass_brigade($bb); + return $rv unless $rv == APR::SUCCESS; + } + + # send the sniffed info to stderr so not to interfere with normal + # output + warn "[$phase ($stream)]\n"; + while (my($btype, $data) = splice @$ra_data, 0, 2) { + $data = join "", map " $_\n", split /\n/, $data; + warn " $btype:\n$data\n"; + } + + return Apache::OK; + } + + sub bb_sniff { + my $bb = shift; + my @data; + for (my $b = $bb->first; $b; $b = $bb->next($b)) { + $b->read(my $bdata); + $bdata = '' unless defined $bdata; + push @data, $b->type->name, $bdata; + } + return [EMAIL PROTECTED]; + } + + 1; + +This package provides two filter handlers, one for connection and +another for request filtering: + + sub connection : FilterConnectionHandler { snoop(@_) } + sub request : FilterRequestHandler { snoop(@_) } + +Both handlers forward their arguments to the C<snoop()> function that +does the real job. We needed to add these two subroutines in order to +assign the two different attributes. + +It's easy to know whether a filter handler is running in the input or +the output mode. The arguments C<$filter> and C<$bb> are always +passed, whereas the arguments C<$mode>, C<$block>, and C<$readbytes> +are passed only to input filter handlers. + +If we are in the input mode, we retrieve the bucket brigade and +immediately link it to C<$bb> which makes the brigade available to the +next filter. When this filter handler returns, the next filter on the +stack will get the brigade. If we forget to perform this linking our +filter will become a black hole in which data simply disappears. Next +we call C<bb_sniff()> which returns the type and the content of the +buckets in the brigade. + +If we are in the output mode, C<$bb> already points to the current +bucket brigade. Therefore we can read the contents of the brigade +right away. After that we pass the brigade to the next filter. + +Finally we dump to STDERR the information about the type of the +current mode, and the content of the bucket bridage. + +Let's snoop on connection and request levels in both directions, by +applying the following configuration: + + Listen 8008 + <VirtualHost _default_:8008> + PerlModule MyApache::FilterSnoop + PerlModule MyApache::Dump + + # Connection filters + PerlInputFilterHandler MyApache::FilterSnoop::connection + PerlOutputFilterHandler MyApache::FilterSnoop::connection + + <Location /dump> + SetHandler modperl + PerlResponseHandler MyApache::Dump + PerlInputFilterHandler MyApache::FilterSnoop::request + PerlOutputFilterHandler MyApache::FilterSnoop::request + </Location> + + </VirtualHost> + +Notice that we use a virtual host because we want to install +connection filters. + +If we issue the following request: + + % echo "mod_perl rules" | POST 'http://localhost:8008/dump?foo=1&bar=2' + +We get the same response, because our snooping filter didn't change +anything. Though there was a lot of output printed to I<error_log>. We +present it all here, since it helps a lot to understand how filters +work. + +First we can see the connection input filter at work, as it processes +the HTTP headers. We can see that each header is put into a separate +brigade with a single bucket: + + [connection (input)] + HEAP: + POST /dump?foo=1&bar=2 HTTP/1.1 + + [connection (input)] + HEAP: + TE: deflate,gzip;q=0.3 + + [connection (input)] + HEAP: + Connection: TE, close + + [connection (input)] + HEAP: + Host: localhost:8008 + + [connection (input)] + HEAP: + User-Agent: lwp-request/2.01 + + [connection (input)] + HEAP: + Content-Length: 15 + + [connection (input)] + HEAP: + Content-Type: application/x-www-form-urlencoded + + [connection (input)] + HEAP: + + +Here the HTTP header has been terminated by a double new line. So far +all the buckets were of the I<HEAP> type, meaning that they were +allocated from the heap memory. Notice that the request input filters +will never see the bucket brigade with HTTP header, it has been +consumed by the last connection Apache core handler. + +The following two entries are generated when +C<MyApache::Dump::handler> reads the POSTed content: + + [connection (input)] + HEAP: + mod_perl rules + + [request (input)] + HEAP: + mod_perl rules + + EOS: + +as we saw earlier on the diagram, the connection input filter is run +before the request input filter. Since our connection input filter was +passing the data through unmodified and no other connection input +filter was configured, the request input filter sees the same +data. The last bucket in the brigade received by the request input +filter is of type I<EOS>, meaning that all the input data from the +current request has been received. + +Next we can see that C<MyApache::Dump::handler> has generated its +response. However only the request output filter is filtering it at +this point: + + [request (output)] + TRANSIENT: + args: + foo=1&bar=2 + content: + mod_perl rules + +This happens because Apache hasn't sent yet the response HTTP headers +to the client. Apache postpones the header sending so it can calculate +and set the C<Content-Length> header. This time the brigade consists +of a single bucket of type I<TRANSIENT> which is allocated from the +stack memory, which will eventually be converted to the I<HEAP> type, +before the body of the response is sent to the client. + +When the content handler returns Apache sends the HTTP headers through +connection output filters (notice that the request output filters +don't see it): + + [connection (output)] + HEAP: + HTTP/1.1 200 OK + Date: Tue, 13 Aug 2002 12:36:52 GMT + Server: Apache/2.0.40-dev (Unix) mod_perl/1.99_05-dev Perl/v5.8.0 mod_ssl/2.0.40-dev OpenSSL/0.9.6d DAV/2 + Content-Length: 43 + Connection: close + Content-Type: text/plain; charset=ISO-8859-1 + + +Now the response body in the bucket of type I<HEAP> is passed through +the connection output filter, followed by the I<EOS> bucket to mark +the end of the request: + + [connection (output)] + HEAP: + args: + foo=1&bar=2 + content: + mod_perl rules + + EOS: + +Finally the output is flushed, to make sure that any buffered output +is sent to the client: + + [connection (output)] + FLUSH: + +This module helps to understand that each filter handler can be called +many time during each request and connection. It's called for each +bucket brigade. + +Also it's important to notice that the request input filter is called +only if there is some POSTed data to read, if you run the same request +without POSTing any data or simply running a GET request, the request +input filter won't be called. + +=head2 XXX The stream-orientered output filter in the following example reverses every line of the response, preserving the new line characters in
--------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]