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]