stas 2002/08/28 05:30:24 Modified: src/docs/2.0/user/handlers handlers.pod Log: handlers to go: PerlInitHandler, PerlHeaderParserHandler, PerlTransHandler Revision Changes Path 1.14 +309 -36 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.13 retrieving revision 1.14 diff -u -r1.13 -r1.14 --- handlers.pod 27 Aug 2002 09:48:59 -0000 1.13 +++ handlers.pod 28 Aug 2002 12:30:24 -0000 1.14 @@ -838,7 +838,8 @@ parsed. This phase is usually used to do processings that must happen once per -request. +request. For example C<Apache::Reload> is usually invoked at this +phase to reload modified Perl modules. This phase is of type C<L<RUN_ALL|/item_RUN_ALL>>. @@ -847,7 +848,7 @@ phase the request has not yet been associated with a particular filename or directory. -Consider the following registry script: +Now, let's look at an example. Consider the following registry script: touch.pl -------- @@ -866,26 +867,31 @@ printf "$conf_file is %0.2f minutes old", 60*24*(-M $conf_file); This registry script is supposed to print when the last time -I<httpd.conf> has been modified. If you run this script several times -you might be surprised that it reports the same thing all the -time. Unless you happen to hit a recently started child process which -will then report a different value. +I<httpd.conf> has been modified, compared to the start of the request +process time. If you run this script several times you might be +surprised that it reports the same value all the time. Unless the +request happens to be served by a recently started child process which +will then report a different value. But most of the time the value +won't be reported correctly. This happens because the C<-M> operator reports the difference between -file's modification time and the value of a special perl variable +file's modification time and the value of a special Perl variable C<$^T>. When we run scripts from the command line, this variable is always set to the time when the script gets invoked. Under mod_perl this variable is getting preset once when the child process starts and -doesn't change since then, so all requests see the same time +doesn't change since then, so all requests see the same time, when +operators like C<-M>, C<-C> and C<-A> are used. -Armed with this knowledge, in order to make our code behave like the -command line programs we need to reset C<$^T> to the request's start -time, before C<-M> is used. We can change the script itself, but what -if we need to do the same change for several other scripts and +Armed with this knowledge, in order to make our code behave similarly +to the command line programs we need to reset C<$^T> to the request's +start time, before C<-M> is used. We can change the script itself, but +what if we need to do the same change for several other scripts and handlers? A simple C<PerlPostReadRequestHandler> handler, which will be executed as the very first thing of each requests, comes handy here: + file:MyApache/TimeReset.pm + -------------------------- package MyApache::TimeReset; use Apache::RequestRec (); @@ -903,27 +909,36 @@ $^T = time(); -But to make things more efficient we use C<$r-E<gt>request_time> which -already stores the request's start time, and returns it without doing -an additional system call. +But to make things more efficient we use C<$r-E<gt>request_time> since +the request object C<$r> already stores the request's start time, so +we get it without performing an additional system call. To enable it just add to I<httpd.conf>: PerlPostReadRequestHandler MyApache::TimeReset +either to the global section, or to the C<E<lt>VirtualHostE<gt>> +section if you want this handler to be run only for a specific virtual +host. + + + + + =head2 PerlTransHandler -The I<translate> phase provides an opportunity to translate the -request's URI into an corresponding filename. +The I<translate> phase is used to perform the translation of a +request's URI into an corresponding filename. If no custom handler is +provided, the server's standard translation rules (e.g., C<Alias> +directives, mod_rewrite, etc.) will continue to be used. A +C<PerlTransHandler> handler can alter the default translation +mechanism or completely override it. In addition to doing the translation, this stage can be used to modify the URI itself and the request method. This is also a good place to register new handlers for the following phases based on the URI. -If no custom handlers is provided, the server's default rules -(C<Alias> directives and the like) will continue to be followed. - This phase is of type C<L<RUN_FIRST|/item_RUN_FIRST>>. The handler's configuration scope is @@ -931,21 +946,58 @@ phase the request has not yet been associated with a particular filename or directory. -Example: +There are many useful things that can be performed at this +stage. Let's look at the example handler that rewrites request URIs, +similar to what mod_rewrite does. For example, if your website was +originally made of static pages, and now you have moved to a dynamic +page generation chances are that you don't want to change the old +URIs, because you don't want to break links for those who link to your +site. If the URI: + http://example.com/news/20021031/09/index.html -=head2 PerlInitHandler +is now handled by: -When configured inside any section, but C<E<lt>VirtualHostE<gt>> this -handler is an alias for C<L<PerlHeaderParserHandler>> described later. -Otherwise it acts as an alias for C<L<PerlPostReadRequestHandler>> -descibed earlier. + http://example.com/perl/news.pl?date=20021031&id=09&page=index.html -It is the first handler to be invoked when serving a request. +the following handler can do the rewriting work transparent to +I<news.pl>, so you can still use the former URI mapping: + + file:MyApache/RewriteURI.pm + --------------------------- + package MyApache::RewriteURI; + + use strict; + use warnings; + + use Apache::RequestRec (); + + use Apache::Const -compile => qw(DECLINED); + + sub handler { + my $r = shift; + + my ($date, $id, $page) = $r->uri =~ m|^/news/(\d+)/(\d+)/(.*)|; + $r->uri("/perl/news.pl"); + $r->args("date=$date&id=$id&page=$page"); + + return Apache::DECLINED; + } + 1; + +The handler matches the URI and assigns a new URI via C<$r-E<gt>uri()> +and the query string via C<$r-E<gt>args()>. It then returns +C<Apache::DECLINED>, so the next translation handler will get invoked, +if more rewrites and translations are needed. + +Of course if you need to do a more complicated rewriting, this handler +can be easily adjusted to do so. + +To configure this module simply add to I<httpd.conf>: + + PerlTransHandler +MyApache::RewriteURI -This phase is of type C<L<RUN_ALL|/item_RUN_ALL>>. -Example: @@ -953,19 +1005,240 @@ =head2 PerlHeaderParserHandler The I<header_parser> phase is the first phase to happen after the -request has been mapped to its C<E<lt>LocationE<gt>> (or -equivalent). At this phase the handler can examine the request headers +request has been mapped to its C<E<lt>LocationE<gt>> (or an equivalent +container). At this phase the handler can examine the request headers and to take a special action based on these. For example this phase -can be used to block evil clients, while little resources were wasted -on these. +can be used to block evil clients targetting certain resources, while +little resources were wasted so far. This phase is of type C<L<RUN_ALL|/item_RUN_ALL>>. The handler's configuration scope is C<L<DIR|docs::2.0::user::config::config/item_DIR>>. -Example: +This phase is very similar to +C<L<PerlPostReadRequestHandler|/PerlPostReadRequestHandler>>, with the +only difference that it's run after the request has been mapped to the +resource. Both phases are useful for doing something once per request, +as early as possible. And usually you can take any +C<L<PerlPostReadRequestHandler|/PerlPostReadRequestHandler>> and turn +it into C<L<PerlHeaderParserHandler|/PerlHeaderParserHandler>> by +simply changing the directive name in I<httpd.conf> and moving it +inside the contrainer where it should be executed. Moreover, because +of this similarity mod_perl provides a special directive +C<L<PerlInitHandler|/PerlInitHandler>> which if found outside resource +countainers behaves as +C<L<PerlPostReadRequestHandler|/PerlPostReadRequestHandler>>, +otherwise as C<L<PerlHeaderParserHandler|/PerlHeaderParserHandler>>. + +You already know that Apache handles the C<HEAD>, C<GET>, C<POST> and +several other HTTP methods. But did you know that you can invent your +own HTTP method as long as there is a client that supports it. If you +think of emails, they are very similar to HTTP messages: they have a +set of headers and a body, sometimes a multipart body. Therefore we +can develop a handler that extends HTTP by adding a support for the +C<EMAIL> method. We can enable this protocol extension during and +push the real content handler during the +C<L<PerlHeaderParserHandler|/PerlHeaderParserHandler>> phase: + + <Location /email> + PerlHeaderParserHandler MyApache::SendEmail + </Location> + +and here is the C<MyApache::SendEmail> handler: + + file:MyApache/SendEmail.pm + -------------------------- + package MyApache::SendEmail; + + use Apache::RequestRec (); + use Apache::RequestIO (); + use Apache::RequestUtil (); + + use Apache::Const -compile => qw(DECLINED OK); + + use constant METHOD => 'EMAIL'; + use constant SMTP_HOSTNAME => "localhost"; + + sub handler { + my $r = shift; + + return Apache::DECLINED unless $r->method eq METHOD; + + Apache::method_register($r->pool, METHOD); + $r->handler("perl-script"); + $r->push_handlers(PerlHandler => \&send_email_handler); + + return Apache::OK; + } + + sub send_email_handler { + my $r = shift; + + my %headers = map {$_ => $r->headers_in->get($_)} qw(To From Subject); + my $content = $r->content; + + my $status = send_email(\%headers, \$content); + + $r->content_type('text/plain'); + $r->print($status ? "ACK" : "NACK"); + 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; + } + + sub send_email { + my($rh_headers, $r_body) = @_; + + require MIME::Lite; + MIME::Lite->send("smtp", SMTP_HOSTNAME, Timeout => 60); + + my $msg = MIME::Lite->new(%$rh_headers, Data => $$r_body); + #warn $msg->as_string; + $msg->send; + } + + 1; + +Let's get the less interesting code out of the way. The function +content() grabs the request body. The function send_email() sends the +email over SMTP. You should adjust the constant C<SMTP_HOSTNAME> to +point to your outgoing SMTP server. You can replace this function with +your own if you prefer to use a different method to send email. + +Now to the more interesting functions. The function C<handler()> +returns immediately and passes the control to the next handler if the +request method is not equal to C<EMAIL> (set in the C<METHOD> +constant): + + return Apache::DECLINED unless $r->method eq METHOD; + +Next it tells Apache that this new method is a valid one and that the +C<perl-script" handler will do the processing. Finally it pushes the +function C<send_email_handler()> to the C<PerlResponseHandler> list of +handlers: + + Apache::method_register($r->pool, METHOD); + $r->handler("perl-script"); + $r->push_handlers(PerlResponseHandler => \&send_email_handler); + +The function terminates the header_parser phase by: + + return Apache::OK; + +All other phases run as usual, so you can reuse any HTTP protocol +hooks, such as authentication and fixup phases. + +When the response phase starts C<send_email_handler()> is invoked, +assuming that no other response handlers were inserted before it. The +response handler consists of three parts. Retrieve the email headers +C<To>, C<From> and C<Subject>, and the body of the message: + + my %headers = map {$_ => $r->headers_in->get($_)} qw(To From Subject); + my $content = $r->content; + +Then send the email: + + my $status = send_email(\%headers, \$content); + +Finally return to the client a simple response acknowledging that +email has been sent and finish the response phase by returning +C<Apache::OK>: + + $r->content_type('text/plain'); + $r->print($status ? "ACK" : "NACK"); + return Apache::OK; + +Of course you will want to add extra validations if you want to use +this code in production. This is just a proof of concept +implementation. + +As already mentioned when you extend an HTTP protocol you need to have +a client that knows how to use the extension. So here is a simple +client that uses C<LWP::UserAgent> to issue an C<EMAIL> method request +over HTTP protocol: + + use strict; + use warnings; + + require LWP::UserAgent; + + my $url = "http://localhost:8002/email/"; + + my %headers = ( + From => '[EMAIL PROTECTED]', + To => '[EMAIL PROTECTED]', + Subject => '3 weeks in Tibet', + ); + + my $content = <<EOI; + I didn't have an email software, + but could use HTTP so I'm sending it over HTTP + EOI + + my $headers = HTTP::Headers->new(%headers); + my $req = HTTP::Request->new("EMAIL", $url, $headers, $content); + my $res = LWP::UserAgent->new->request($req); + print $res->is_success ? $res->content : "failed"; + +most of the code is just a custom data. The actual code is made of +four lines. Create C<HTTP::Headers> and C<HTTP::Request> object. +Issue a request. At the end we print the response's content if it was +successful or I<failed> if not. + + + +=head2 PerlInitHandler + +When configured inside any container directive, except +C<E<lt>VirtualHostE<gt>>, this handler is an alias for +C<L<PerlHeaderParserHandler|/PerlHeaderParserHandler>> described +later. Otherwise it acts as an alias for +C<L<PerlPostReadRequestHandler|/PerlPostReadRequestHandler>> described +earlier. + +It is the first handler to be invoked when serving a request. + +This phase is of type C<L<RUN_ALL|/item_RUN_ALL>>. + +The best example here would be to use +C<L<Apache::Reload|docs::2.0::api::mod_perl-2.0::Apache::Reload>> +which takes the benefit of this directive. Usually +C<L<Apache::Reload|docs::2.0::api::mod_perl-2.0::Apache::Reload>> is +configured as: + + PerlInitHandler Apache::Reload + PerlSetVar ReloadAll Off + PerlSetVar ReloadModules "MyApache::*" + +which will monitor and reload all C<MyApache::*> modules that have +been modified since the last request. However if we move the global +configuration into a C<E<lt>LocationE<gt>> container: + + <Location /devel> + PerlInitHandler Apache::Reload + PerlSetVar ReloadAll Off + PerlSetVar ReloadModules "MyApache::*" + SetHandler perl-script + PerlHandler ModPerl::Registry + Options +ExecCGI + </Location> +C<L<Apache::Reload|docs::2.0::api::mod_perl-2.0::Apache::Reload>> will +reload the modified modules, only when a request to the I</devel> +namespace is issued, because C<L<PerlInitHandler|/PerlInitHandler>> +plays the role of +C<L<PerlHeaderParserHandler|/PerlHeaderParserHandler>> here. @@ -2110,7 +2383,7 @@ -=head1 Handler (Hook) Types +=head1 Perl*Handler Types For each phase there can be more than one handler assigned (also known as I<hooks>, because the C functions are called @@ -2173,7 +2446,7 @@ =back Also see L<mod_perl Directives Argument Types and Allowed -Location|user::config::config/mod_perl_Directives_Argument_Types_and_Allowed_Location> +Location|docs::2.0::user::config::config/mod_perl_Directives_Argument_Types_and_Allowed_Location>
--------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]