Archive for the ‘mod_perl’ tag
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.
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.
Configuración dinámica en Apache
Una de las cosas más bonitas que puedes hacer gracias a mod_perl, son las PerlSections.
Básicamente con éstas, lo que puedes hacer es definir dinámicamente directivas de configuración de Apache y hacer cosas bien interesantes. Por ejemplo, recientemente, para Planeta Linux quería tener una sola configuración de Apache para los virtual hosts que son repetitivos dada cada instancia (mx.planetalinux.org, ve.planetalinux.org, gt.planetalinux.org, etc). Precisamente lo que quiero evitar es tener que poner una interminable lista de VirtualHost's, así que lo puedo hacer con secciones en Perl (que son denotadas en cualquier archivo de configuración en Apache con <Perl> y </Perl>):
<Perl>
my $names = { cl => 'PlanetaLinuxChile', co => 'PlanetaLinuxColombia', cr => 'PlanetaLinuxCostaRica', ec => 'PlanetaLinuxEcuador', sv => 'PlanetaLinuxElSalvador', es => 'PlanetaLinuxEspana', gt => 'PlanetaLinuxGuatemala', mx => 'PlanetaLinuxMexico', ni => 'PlanetaLinuxNicaragua', pa => 'PlanetaLinuxPanama', pe => 'PlanetaLinuxPeru', ve => 'PlanetaLinuxVenezuela', debian => 'PlanetaDebian', };
Defino una referencia a hash en $names que lista contra los "TLDs" de cada instancia y con su nombre largo.
my $instancias = [keys %{$names}];
Para facilitar trabajar con las llaves, creo una referencia a un arreglo que contiene exclusivamente las llaves del hash que previamente tenía definido.
$VirtualHost{"*:80"} = [];
Como voy a definir todos mis VirtualHost's apuntando hacia "*:80", defino el valor de la llave "*:80" del hash pre-definido $VirtualHost (pre-definido por mod_perl), hacia una referencia de arreglo vacío (por el momento).
for my $pais(@{$instancias}) {
Empiezo a interar cada uno de los elementos de $instancias y asigno cada valor en la interación a $pais.
my $vhost = $pais.".planetalinux.org"; if ($pais eq 'debian') { $vhost = 'planeta.debian.net'; }
El nombre del servidor será $pais.".planetalinux.org", a menos que sea "debian", que no lleva el dominio de Planeta Linux.
my $virtualh = { SuexecUserGroup => ["planetalinux", "planetalinux"], ServerAdmin => 'planetalinux@googlegroups.com', ServerName => $vhost, DocumentRoot => "/var/www/planetalinux/".$vhost, ErrorLog => "/var/log/apache2/planetalinux_".$pais."_error", CustomLog => ["/var/log/apache2/planetalinux_".$pais."_access", "combined"], LogLevel => "info", Alias => ["/images/", "/home/planetalinux/current/www/instancias/".$pais."/images/"], Redirect => ["/rss20.xml", "http://feedproxy.google.com/".$names->{$pais}], };
Aquí defino la referencia al hash principal del virtual host. Los valores son definidos con los nombres de las directivas que utiliza Apache:
SuexecUserGroup, pues utilizamos suexec en el servidor se asigna a una referencia de arreglo anónima con dos valores, que son los dos valores que se le pasan a Apache en sus directivas regulares.ServerAdmin, es el mismo para todas las instancias, nuestra lista de correos.ServerName, es el nombre que definí previamente como$vhost.DocumentRoot, depende también de$vhostpues así lo tenemos definido en nuestro sistema de archivos.ErrorLog, caso similar aDocumentRoot.CustomLog, caso similar a ErrorLog, pero defino, igual que enSuexecUserGroup, un arreglo anónimo con los dos valores que toma este parámetro.LogLevel, igual que en Apache.Alias. Defino un alias simple.Redirect, puedo utilizar cualquier tipo de directivas,Alias,Redirect, lo que sea. En este caso, realizo la redirección de los viejos feeds"/rss20.xml"hacia la nueva URL de FeedBurner, que es ahora como manejamos los feeds en Planeta Linux. El valor que se concatena al final es el valor del hash%{$names}dada la llave$pais.
push @{$VirtualHost{"*:80"}}, $virtualh;
Agrego cada $virtualh al arreglo de VirtualHosts en "*:80". Si tuviera una IP por cada uno de los VirtualHosts, no es necesario hacer push a un arreglo, simplemente tendría que declarar las variables $VirtualHost con la IP como llave. Sin embargo, si declaro muchas veces la misma variable, como en este caso, $VirtualHost{"*:80"}, en cada interación el valor se reescribirá, es por eso que mod_perl nos ofrece definir esa variable como referecia a arreglo en donde podemos meter cuantos virtual hosts como queramos.
}
Termino el ciclo for.
</Perl>
Las posibilidades son muchas para la configuración dinámica de Apache y en secciones Perl puedes utilizar cualquier cosa, abrir tus archivos para ver otros parámetros de configuración, conectarte a bases de datos para sacar información, lo que sea.
Si te interesa ver todo el archivo de configuración, está acá, en el GitHub público de Planeta Linux.



