Archive for the ‘apache’ Category
Run a PHP script from mod_perl so PerlCleanupHandler can be used
Situation
You need to run a time consuming task after a PHP script is run. The task will have to reuse the POST/GET data that is being sent to the service. The task would have to be done once the client has gone away.
Solution
Use mod_perl's PerlFixupHandler to take the POST data, set the handler to be run by mod_php (or whatever else you are running it). Finally, use PerlCleanupHandler to run that task since that's exactly what that phase is for, once the client went away.
Explanation
<Files ~ "myscript\.php$">
SetHandler modperl
PerlFixupHandler My::App::Fixup
</Files>
Now, we are just indicating that we want a Fixup handler which is going to be run by My::App::Fixup. The Fixup phase runs right before content generation and delivery starts, which is the perfect moment to pass the execution of the PHP script.
So our handler would look like this:
package My::App::Fixup;
use strict;
use warnings;
use Apache2::Const -compile => qw/:common/;
use Apache2::Request;
use Apache2::RequestIO ();
use Apache2::RequestRec ();
use Apache2::RequestUtil ();
use Apache2::ServerUtil ();
Up to this moment, usual regular module-loading.
sub handler {
my($r) = shift;
Now, we'll take the request object and assign it to $req.
my $req = Apache2::Request->new($r);
We now register a subroutine, cleanup, to be hooked with the Cleanup phase. PerlCleanupHandler is great, it's the very last phase of a mod_perl execution, it will basically run after the client that made the request has gone away, once the connection with it has been terminated with the server. It's because of that nature, that it makes it the best place to make any kind of time consuming task, since we wouldn't want the client to wait for termination of that job. This phase is also not implemented in Apache, this is mod_perl specific.
$r->push_handlers(PerlCleanupHandler => \&cleanup);
Now, we set the handler for the next Apache phase to be handled as PHP:
$r->handler("application/x-httpd-php");
In this point, I will have to start reading the POST information that was sent, since that will be gone when the Cleanup is reached:
my $body = $req->body;
my $st = {};
for my $key ( keys %$body ) {
$st->{$key} = $req->body($key);
}
All the key-value pairs of the POST data are on the $st hash reference and I record it now on a "pnote", so I can catch it later:
$r->pnotes("POST", $st);
return Apache2::Const::OK;
}
So, this is the cleanup subroutine I registered previously. I prefered to do it this way, I could also have set an specific handler for PerlCleanupHandler on the Apache configuration, but I just wanted to do it this way.
sub cleanup {
my($r) = shift;
I take now the request object again. The POST data is gone already, this is only for GET.
my $req = Apache2::Request->new($r);
my $table = $req->param;
I retrieve the information that I left before on the pnote.
my $st = $r->pnotes("POST");
for my $key ( keys %$table ) {
$st->{$key} = $req->param($key);
}
In this moment, $st has all POST and GET data on a hash reference. It's according to your application needs how to have proceeded with this.
# All your time-consuming work
# sleep 600;
# or whatever you want
# for the sake of this example, I'll just write the values to a file:
open my $fh, ">/tmp/myexample" or die $!;
while(my($k, $v) = each %$st) {
print $fh "$k ->$v", "\n";
}
return Apache2::Const::OK;
}
1;
The good thing about this is that, you can run arbitrary time-consuming code from a Cleanup phase, and that it doesn't matter if you have to set the handler for the response phase to something else, like PHP, mod_perl will be able to handle it.
Now, myscript.php would look like this:
<?php
print "<pre>";
print_r($_REQUEST);
print "</pre>";
?>
So, let's test it:
cerdo ~ $ curl -d 'name=david&skill=lousy' http://localhost:82/damog/php/myscript.php?arg1=1
<pre>Array
(
[arg1] => 1
[name] => david
[skill] => lousy
)
</pre>
cerdo ~ $ cat /tmp/myexample
arg1 -> 1
skill -> lousy
name -> david
cerdo ~ $
As you can see, I'm sending both POST and GET parameters with curl. PHP displays it just fine, but also my temporary test file.
How are you using PerlCleanupHandler?
And now Apache2::EmbedMP3 for your songs collections!
After I spent time working on Apache2::EmbedFLV, I thought it was a good idea to do the same for audio files, specifically MP3 files.
So now that you have your MP3 files on your web server and they are accessible to the world, you may want to show them with an embedded audio player. Well, Apache2::EmbedMP3 is exactly for you!
EmbedMP3 uses the fabulous WP Audio Player, a small, elegant, GPL Flash audio player, similarly to how Apache2::EmbedFLV uses Flowplayer. The interesting thing about EmbedMP3 is that you can display more interesting data on the template such as song name, artist, album, year and also lyrics, which was an interesting feature I added. In this fashion, it's very easy to just drop a whole bunch of files into a directory and all be served with the custom interface.
You have to instruct Apache2:
<Files ~ "\.mp3$">
SetHandler modperl
PerlSetVar wpaudioplayer /audio-player/player.swf
PerlSetVar wpaudioplayer_js /audio-player/wpaudioplayer.js
PerlResponseHandler Apache2::EmbedMP3
</Files>
And that's it. Take a look at the documentation to see how to point it to specific locations for WP Audio Player, template file, etc.
You can see the hack in action at http://dev.axiombox.com/~david/mp3. If it struggles a bit by buffering, it's because that's directly from my home Internet connection, so give it a small break
And, as I suggested the Radiohead video with EmbedFLV, I suggest now this Black Eyed Peas song, Like That, it's very catchy and I love it
Go get the code at the Git repo or at CPAN (once it's updated and published).
Introducing Apache2::EmbedFLV – Exposing FLVs with Flowplayer and a customized interface
Situation
You have a bunch of Flash videos, FLV files. Every once in a while you dump new files on your publicly accessible Apache directory. You would like to give your users the ability to play those FLVs within a webpage you provide, instead of just serving them the files for a direct download. Maybe you want this for all FLVs, not only an specific directory.
Solution
<Files ~ "\.flv$">
SetHandler modperl
PerlResponseHandler Apache2::EmbedFLV
</Files>
So now, all your FLV files will be served through Apache2::EmbedFLV on mod_perl with a neat interface you can define yourself, and using the very nice and GPL'ed Flowplayer.
Apache2::EmbedFLV uses a default template, but you can define your own with:
<Files ~ "\.flv$">
SetHandler modperl
PerlSetVar template /path/to/your/template.tt
PerlResponseHandler Apache2::EmbedFLV
</Files>
Also, Apache2::EmbedFLV expects to find Flowplayer (flowplayer.swf and flowplayer.controls.swf) on http://your.server.com/flowplayer.swf, the root of your server. So you can do something like this to make it work:
Alias /flowplayer.swf /home/web/flowplayer/flowplayer.swf
Alias /flowplayer.controls.swf /home/web/flowplayer/flowplayer.controls.swf
Or, you just define the flowplayer variable:
<Files ~ "\.flv$">
SetHandler modperl
# relative to the document root
PerlSetVar flowplayer /swf/flowplayer.swf
# or absolute:
PerlSetVar flowplayer http://my.other.server/was/hacked/flowplayer.swf
PerlResponseHandler Apache2::EmbedFLV
</Files>
Action!
You can see it in action at http://axiombox.com/apache2-embedflv/flv.
This Radiohead video is particularly cool
Getting it
- More information at its homepage, http://axiombox.com/apache2-embedflv
- CPAN module.
- Git repository, as usual, on GitHub.
UPDATE: Oops! Fixed link to Flowplayer's website! flowplayer.org. Thanks to those who noticed and let me knew.
FeatherCast: Apache Software Foundation /unofficial/ podcast
I recently stumbled upon FeatherCast, a nice little project made by Rich Bowen and David Reid, the former, a recognized documenter within the Apache community, author of a few books on the webserver, mainly the Apache Cookbook. FeatherCast is an unofficial podcast; its primary goal is supporting the Apache Software Foundation community. I've found it to be greatly interesting, modern and up to date; more recently, bringing lights to some of the content and people, for instance, featured on ApacheCon, which took place in New Orleans, LA, late last year.
Great effort, great initiative: Subscribe and support FeatherCast!
Returning an specific HTTP status/return code and content with mod_perl 2
Making a simple PerlResponseHandler that does something like this:
sub handler {
my($r) = shift;
$r->content_type("text/plain");
$r->print("You suck.\n");
return Apache2::Const::HTTP_BAD_REQUEST;
}
…doesn't make what you'd be expecting. This is because mod_perl assumes, since you are specifying content type and something to print, that this is a legit and valid (200-ish) request. This is what you get:
POST http://myserver/resource --> 200 OK
Connection: close
Date: Mon, 05 Jan 2009 23:02:57 GMT
Content-Type: text/plain; charset=UTF-8
Client-Date: Mon, 05 Jan 2009 23:03:00 GMT
Client-Response-Num: 1
Client-Transfer-Encoding: chunked
[ some headers snipped ]
You suck.
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>200 OK<</title>
</head><body>
<h1>OK</h1>
<p>Your browser sent a request that this server could not understand.<br />
</p>
</body></html>
So, yes, I was expecting to return a 400 Bad Request return with just "You suck" on the response body. mod_perl disagrees, which, kind of sucks if you are trying to make a RESTful service with mod_perl HTTP handlers and filters.
To avoid this behaviour, this is one way to go:
sub handler {
my($r) = shift;
$r->status(400); # or the one you are interested in
$r->content_type("text/plain");
$r->print("You suck.");
return Apache2::Const::OK;
}
You can also take a look at status_line that overrides status. You can also avoid the content_type method call if you are sloth enough. Now, this is what you get:
GET https://myserver/resource --> 400 Bad Request
Connection: close
Date: Mon, 05 Jan 2009 23:27:08 GMT
Content-Type: text/plain; charset=UTF-8
Client-Date: Mon, 05 Jan 2009 23:27:10 GMT
Client-Peer: 209.234.249.38:443
Client-Response-Num: 1
[ some headers snipped
]
You suck.
And so, beer awaits.
DirectoryIndex and the "perl-script" handler
Wouter Verhelst blogs about combining ScriptAlias (or a way to run CGIs) and DirectoryIndex in Apache.
Recently, developing a TinyURL clone I had a similar (not identical, though) issue.
Basically, I set SetHandler to perl-script on the "/" location, which basically means, everything accessed on a given virtual host, is affected. Because you are changing the handler for the entire location, DirectoryIndex will have no effect on it because mod_dir is the one dealing with the DirectoryIndex function, that is, using the DIR_MAGIC_TYPE handler.
To fix this you can use a mod_perl (2, I never really used 1) Fixup phase handler:
<VirtualHost *:80>
...
# some stuff
...
# PerlSections rule.
<Perl>
$Location{"/"} = {
SetHandler => 'perl-script',
# some stuff
PerlFixupHandler => 'Axiombox::Awbox::Fixup',
# some other stuff
DirectoryIndex => 'whatever.html',
};
</Perl>
</VirtualHost>
If it looks incomplete is because some other information, out of the scope of this sample, like DocumentRoot or other mod_perl directives, are hidden as the "other stuff". My Fixup.pm handler looks like this:
package Axiombox::Awbox::Fixup;
use strict;
use warnings FATAL => qw(all);
use Apache2::Const -compile => qw(DIR_MAGIC_TYPE OK DECLINED);
use Apache2::RequestRec;
sub handler {
my $r = shift;
if ($r->handler eq 'perl-script' &&
-d $r->filename &&
$r->is_initial_req)
{
$r->handler(Apache2::Const::DIR_MAGIC_TYPE);
return Apache2::Const::OK;
}
return Apache2::Const::DECLINED;
}
1;
Which is very straight-forward: If the request is set to perl-script, the requested file is a directory and if the current request is the main one, then change the handler and return to the normal flow of phases. Otherwise, decline the Fixup phase handler.



