Redirecciones de url en script perl

Es muy común que en lugar de devolver una página web, un script tenga que redirigir hacia otro lugar. Veamos cómo hacer una redirección de url (url redirection) en un script Perl

Imprimir directamente la cabecera Location

Para ciertas cosas Perl resulta mágico: sólo con una línea de código puede resolver el problema

#!/usr/bin/perl
print "Location: http://example.com/\n\n"; #Status code 302

Importante: la redirección tiene que ser lo primero que se envíe, dado que corresponde a las cabeceras HTTP de la respuesta. En el momento en que se imprimen los 2 saltos de línea (\n\n) explícitamente se indica la terminación de esas cabeceras. Todo lo que venga después será tratado como contenido y presentado por el navegador.

 

Usar el módulo CGI

Mediante la función redirect del módulo CGI se puede hacer la redirección también de una manera muy sencilla:

#!/usr/bin/perl
use CGI qw(:standard);
print redirect('http://example.com'); #Status code 302

De manera análoga, se puede crear una instancia de CGI y sobre la misma invocar el método redirect.

#!/usr/bin/perl
use CGI qw(:standard);
my $q = new CGI;
print $q->redirect('http://example.com'); #Status code 302

En ninguno de los casos, tal como en el ejemplo anterior, se debe imprimir otras cabeceras previamente.

Adicionamente, se soportan parámetros nombrados en la invocación:

print $q->redirect(
 -url => 'http://example.com',
 -status => '301 Moved Permanently'
);

Es a través del parámetro -status que se puede establecer el estado de la redirección. El estado por defecto en perl, si no es especificado, es 302.

 

Estados posibles para la redirección

HTTP define los siguientes códigos de estado para la redirección

  • 301 Moved Permanently
    • Esta petición y todas las siguientes se deben repetir con la url indicada
  • 302 Found
    • La petición debe repetirse con otra url. Futuras peticiones deben seguir utilizando la url original.
    • Este estado ha sido reemplazado por 303 y 307:
      • HTTP/1.0 requería que el cliente realizara una redirección temporal (la frase original de la descripción era “Moved Temporarily”) sin cambiar el método utilizado en la petición original. Pero muchos navegadores implementaron este código forzando el método de la nueva petición a GET, sin importar el utilizado en la petición original. Es un ejemplo de cuando la práctica de la industria termina contradiciendo el estándar.
      • HTTP/1.1 agregó nuevos códigos de estado a fin de distinguir ambos comportamientos: 303 para cambiar el método a GET y 307 para preservar el método originalmente usado.
      • Muchos frameworks y aplicaciones web lo siguen soportando para mantener la compatibilidad hacia atrás.
  • 303 See Other (desde HTTP/1.1)
    • La petición debe repetirse con otra url usando el método GET.
    • Cuando se recibe en respuesta a POST (o PUT/DELETE), el cliente debe asumir que el servidor ha recibido los datos y debe solicitar una petición GET a la url indicada
  • 307 Temporary Redirect (desde HTTP/1.1)
    • Esta petición debe repetirse con otra url. Futuras peticiones deben seguir utilizando la url original.
    • A diferencia de 302, no permite que cambie el método cuando se vuelve a realizar la petición.
  • 308 Permanent Redirect
    • Esta petición y todas las siguientes se deben repetir con la url indicada
    • A diferencia de 301, no permite que cambie el método cuando se vuelve a realizar la petición.

Si bien es posible utilizar cualquier código de estado diferente a los especificados, probablemente se rompa la redirección. Notar también que la frase a continuación del código se espera también como parte del mismo.

OBS: los códigos 304, 305 y 306 existen pero no se utilizan para la redirección.

Anuncios

Acceder a página protegida con htaccess mediante Perl (LWP)

Ya vimos como acceder a una página que se encuentra protegida por htaccess desde un script PHP (con curl y con file_get_contents). Ahora vamos a hacer lo mismo con LWP en Perl. Las principales diferencias con los métodos vistos para PHP son que en este caso se debe indicar junto a las credenciales:

  • el dominio con el puerto
  • el “realm” (ámbito), que representa el valor del AuthName definido en el archivo htaccess.
    • Importante: Deben coincidir perfectamente

Entonces, el bloque de código modelo para implementar LWP es el siguiente:

 #!/usr/bin/perl
 use strict;
 use LWP;
 
 my $url = 'http://www.destino.com/secure/index.cgi';
 
 my $domain = 'www.destino.com';
 my $port = 80;
 my $username = 'ht_user';
 my $password = 'ht_pass';
 my $realm = 'Secured directory';
 
 my $ua = LWP::UserAgent->new();
 
 //Credenciales htaccess 
 $ua->credentials( "$domain:$port", $realm ,$username => $password);

 my $response = $ua->get($url);

 print "Content-type:text/html\n\n";
 my $data = $response->content;

En este ejemplo, obtendremos dentro la variable $data el contenido de la página index.cgi, para poder utilizarlo en el resto del script.

Se puede utilizar este medio para invocar otros scripts que devuelvan urls, json, etc. como si fuera un API a la aplicación.

 

Si desconocemos el “realm” podemos obtenerlo haciendo un get inicial de la url sin credenciales y leyendo el header WWW-Authenticate de la respuesta. El resultado para el ejemplo presentado devolvería:

Basic realm="Secured directory"

Tratamiento de los errores del módulo DBI de Perl

Cuando utilizamos el módulo DBI para conectarnos a una base de datos en Perl, existen algunos atributos que definen cómo tratar las alertas y errores que sucedan:

PrintWarn

Valor por defecto: Si los warnings de Perl están activados, es 1. De lo contrario, 0.

Controla la impresión de los warnings que determina el driver. Utiliza la función warn.

Como los drivers lo utilizan poco, considero que se puede dejar en el valor por defecto (sea cual fuere)

PrintError

Valor por defecto: 1

Además de devolver los errores de la forma normal, fuerza warnings. Utiliza la función warn.

Considero que es correcto generar los warnings adicionales, manteniendo el valor por defecto, para que se alerten todos los errores.

RaiseError

Valor por defecto: 0

En lugar de devolver los errores de la forma normal, fuerza una excepción. Utiliza la función die. Si está activado, por lo general se desactiva PrintError. Pero si ambos estuvieran activados, se procesa primero PrintError (warning) y luego RaiseError (excepción). En el momento de la conexión, curiosamente, se procesan al revés!

Considero que debe activarse cuando el uso de la base de datos sea esencial para la aplicación (lo más común), de lo contrario la ejecución continúa aún cuando el módulo DBI detecte un error.

 

Los valores por defecto de estos atributos se establecen cuando se realiza la conexión:

my $dbh = DBI->connect("<DBI:CONNECTION>", "<DB_USER>", "<DB_PASS>");

En este caso: PrintWarn depende de si los warnings están activados, PrintError será 1 y RaiseError tendrá el valor 0.

 

Los atributos se pueden redefinir en el momento de la conexión:

my $dbh = DBI->connect("<DBI:CONNECTION>", "<DB_USER>", "<DB_PASS>",{RaiseError => <BOOL>, PrintError => <BOOL>, PrintWarn => <BOOL>});

Ejemplo:

my $dbh = DBI->connect("dbi:mysql:test:localhost:3306", "test", "secure",{RaiseError => 1, PrintError => 0});

En este caso determinamos el forzar una excepción ante un error, desactivando el warning que su hubiera generado en su lugar.

 

También se pueden modificar en cualquier momento posterior para realizar alguna acción en particular:

Ejemplo:

$dbh->{RaiseError} = 0;

 

Se puede agregar código Perl para manejar los warnings y las excepciones generadas en cada caso.

 

Basado en:

http://search.cpan.org/~timb/DBI-1.631/DBI.pm

http://oreilly.com/catalog/perldbi/chapter/ch04.html

Manejar warnings y excepciones en Perl

Cuando el código de nuestro script Perl genera warnings o excepciones ambos se envían al STDERR (una de los flujos estándares de datos). Adicionalmente podemos “atraparlos” (catch) y “manejarlos” (handle) con ciertos bloques de código.

Los warnings se pueden atrapar de la siguiente manera:

local $SIG{__WARN__} = sub {
  my $message = shift;
  #CODIGO DE TRATAMIENTO DE WARNING
};

Ejemplo:

sub db_connect {
  ...
  local $SIG{__WARN__} = sub {
    my $message = shift;
    return 'DB_CONN_ERROR';
  };
  ...
  my $dbh = DBI->connect("dbi:mysql:test:localhost:3306", "test", "secure",{PrintError => 1});

Así, cuando falle la conexión a la base de datos la función devolverá el código de error correspondiente para que la aplicación responda a esa situación.

Las excepciones se pueden atrapar de manera análoga a los warnings:

local $SIG{__DIE__} = sub {
  my $message = shift;
  #CODIGO DE TRATAMIENTO DE EXCEPCION
};

En este caso, también existe una forma alternativa:

eval {
  #CODIGO QUE PUEDE GENERAR EXCEPCION
};
if( $@ ){
  #CODIGO DE TRATAMIENTO DE EXCEPCION
}

Ejemplo:

eval {
  mkpath($ubicacion);
}
if( $@ ){
  $path = $ubicacion;
}

Así, cuando falle la la creación del path indicado en la variable $ubicacion porque el mismo ya existe, se asigna a la variable $path.

Activar warnings en Perl

Por defecto los warnings están desactivados en Perl. Si nuestro código no se comporta correctamente no veremos estas advertencias de manera temprana y podríamos encontrarnos con problemas graves a futuro.

Los warnings se pueden activar de varias formas, estas son algunas de ellas:

Con el pragma warnings en un bloque de código

El indicar use warnings; activa los warnings hasta el final del bloque o bien hasta que se indique no warnings;

No afecta a los módulos incluidos (con use, require o do)

Esta es la forma recomendada. Provee más control de los warnings porque se puede activar donde y cuando se desee.

Con el parámetro -w o -W en el shebang del script

En la primer línea del script (shebang) se agrega uno de los 2 parámetros:

#!/usr/bin/perl -w

Con -w se activan los warnings en todo el código del script y módulos incluidos, excepto donde se utilice el pragma warnings (que tiene precedencia)

Con -W se activan los warnings en todo el código del script y módulos incluidos sin excepción.

Con la variable $^W = 1

Con ambos parámetros -w y -W, la variable Perl $^W se establece en 1. De la misma manera, podemos hacer esto directamente en el código:

$^W = 1;

Activa los warnings hasta el final del script y los módulos que se incluyan (solo con require o do), excepto donde se utilice el pragma warnings (que tiene precedencia)

Límites de fecha en timelocal de Perl

Una forma de validar una fecha en Perl es mediante la función timelocal del módulo Time::Local. El problemas es que en versiones de Perl menores a 5.12, las fechas que soporta están limitadas. En la documentación se indica “from Dec 1901 to Jan 2038”. Quise probar realmente estos límites y ahora les muestro un ejemplo práctico en Perl 5.8

Creamos un script con el siguiente código y lo llamamos test_timelocal.pl

#!/usr/bin/perl

use strict;

use Time::Local;

my ($fecha) = @ARGV;
$fecha =~ /^(\d{4})-(\d{2})-(\d{2})$/;
my ( $a, $m, $d ) = ($1,$2,$3);

my $time = undef;

eval {
      $time = timelocal(0,0,0,$d,--$m,$a); #se indica la cantidad de meses desde Enero (0..11)
};
if ( $@ ) { print "Fecha no definida"; }
else      { print $time; }

El mismo acepta como único parámetro una fecha. Obtendremos como resultado la cantidad de segundos desde el epoch del sistema.

Procedemos entonces a ejecutar desde la línea de comandos el script, intentando encontrar las fechas límite:

perl test_timelocal.pl 1901-12-16
-> Fecha no definida

perl test_timelocal.pl 1901-12-17
-> -2023488000

perl test_timelocal.pl 2038-01-16
-> 2147223600

perl test_timelocal.pl 2038-01-17
-> Fecha no definida

Comprobado empíricamente, en este caso las fechas soportadas por timelocal son aquellas que se encuentren en el rango desde 17/12/1901 al 16/01/2038, para versiones de Perl menores a 5.12

 

Basado en:

http://perldoc.perl.org/Time/Local.html#Limits-of-time_t

Ejecutar scripts CGI con código Perl desde Apache en Windows

Los scripts CGI comienzan con una línea llamada “shebang” denominada así por los caracteres #!  (cuyo número mágico en ASCII es 23 21) los cuales indican que el archivo es un script ejecutable (por las funciones de tipo “exec”). En la misma se indica el intérprete que se utilizará para procesar el código. Por lo general, el intérprete ignora la línea debido a que comienza con el caracter #, utilizado para comentarios en muchos lenguajes.

Una vez que Apache esté configurado para utilizar scripts CGI, utilizará esta línea para poder procesar correctamente los CGIs cuando se los soliciten (la utiliza efectivamente para lanzar el intérprete asociado al script).

El formato de la línea shebang es:

#!/<PATH_ABSOLUTO>/<INTERPRETE>

donde indicamos el PATH_ABSOLUTO y el INTERPRETE que corresponda.

La línea del shebang típica de los scripts Perl en sistemas con Linux es:

#!/usr/bin/perl

Para mantenerla en sistemas Windows, lo que les recomiendo es:

  1. Determinar la unidad en que están los archivos del sitio asociado al VirtualHost
    • Si los archivos están en C:\www\site\, la unidad es C:
  2. Crear el directorio “usr” en esta unidad y dentro de éste, crear el directorio “bin”
  3. Buscar el el archivo “perl.exe” dentro directorio donde hayan instalado Perl
  4. Copiar “perl.exe” desde su ubicación original a la ruta recientemente creada

De esta manera logramos el objetivo deseado porque Windows entiende que:

  • /usr/bin/ es lo mismo que C:\usr\bin\ o D:\usr\bin\ dependiendo de la unidad
  • perl es lo mismo que perl.exe debido a que es el único ejecutable con ese nombre en ese directorio
Alternativamente, podríamos cambiar la línea para que apunte a la ubicación donde está instalado Perl en Windows. Esta opción tiene algunas contras en ciertas situaciones que obligarán a cambiarla con frecuencia:
  • Se pueden estar desarrollando los scripts del mismo sistema en computadoras con Windows y Linux
  • Perl puede estar instalado en distintas ubicaciones en cada computadora
  • Podemos tener varios checkouts del código en distintas unidades dentro de la misma computadora