Cómo reescribir correctamente una URL desde el servidor (II)

Anteriormente habíamos visto cuándo y por qué debemos redirigir nuestras páginas web, así como todas las aproximaciones menos correctas para hacerlo. Así que ha llegado el momento de completar la respuesta que habíamos dejado abierta, y contar cómo se debe realizar la reescritura de una URL desde nuestro servidor en lugar de en el propio documento HTML.

Los códigos de redirección 3XX

Como ya habíamos adelantado, el modo correcto de realizar una redirección implica informar al agente (un navegador o una araña de un buscador) de que la página ya no se encuentra en su antigua ubicación, antes de devolverle ningún documento HTML, y que sea el agente el que, basándose en esa respuesta, realice la nueva petición y actualice de modo interno los datos que le puedan interesar, y que dependerán de la naturaleza de esa redirección.

Así pues, el servidor debe contestar con un código de estado de la familia de los 300. Veamos sus distintos significados:


300, múltiples opciones

El servidor devolverá un estado 300 cuando ante una petición del cliente tenga distintas páginas de destino o representaciones. Aunque el servidor puede establecer una de las opciones como preferida, será el cliente el que decida qué versión solicitar, dependiendo del content-type asociado, por ejemplo.

301, trasladado de forma permanente

Ésta es la opción más habitual en el caso de migraciones, ya que no sólo dice al cliente que solicite la nueva URL, sino que además le insta a modificar cualquier enlace que conserve almacenado o información asociada a dicha URL (como por ejemplo, el pagerank de Google). Realizar una redirección 301 implica que todos los backlinks y pagerank asociados a la página original se transmitan a la de destino, por lo que es el más aconsejable para trasladar contenido.

302, encontrado

Aunque la mayoría identifican al estado 302 como “trasladado de forma temporal”, su verdadero significado es simplemente “encontrado”, aunque indicando que temporalmente se encuentra en otra URI. Aquí el estándar fue mal utilizado por la industria, que lo implementó como el 303, así que para desambiguar se añadió un nuevo estado, el 307. Conclusión: finalmente es el 302 el que se suele usar para todos los traslados temporales, e incluso mucha gente usa el 301 cuando debería usar uno de éstos.

303, ver otro

En este caso, lo que se le indica al cliente es que la petición que ha realizado mediante POST debe ser satisfecha por GET, y se le otorga una URL distinta a la que hacerla.

304, no modificado

Básicamente indica que el contenido no se ha modificado desde la última vez que se solicitó. Podríamos entrar en detalles de cachés, fechas y demás, pero lo lógico es que nunca se provoque este estado a propósito, sino que el propio servidor decida cuando usarlo, así que no nos interesa para nuestra finalidad.

305, use un proxy

Originalmente, este código indicaba la URL del proxy que debía utilizarse, pero sin especificar si debía ser sólo para la petición actual o para todas las siguientes. Como consecuencia, podía provocar ciertas brechas de seguridad, por lo que diversos clientes (como Mozilla o Internet Explorer) no lo cumplen a rajatabla.

306, cambie de proxy

Y si el anterior código ya suscitaba quejas, éste todavía más, por lo que directamente ha caído en desuso.

307, redirección temporal

Como habíamos comentado antes, se añadió el 307 para diferenciarse del 303, en el que se obligaba a hacer la petición a la nueva URI por GET. En este caso, la nueva petición debe hacerse con el mismo método con el que se hizo la primera.

Redirigir desde el servidor

Una vez que tenemos claro el código que queremos usar (lo lógico es que sea el 301), la forma más rápida de realizar la redirección, asegurándonos de que afecte a todos los documentos (independientemente de si son HTML o de algún lenguaje de servidor) y que además se va a producir la respuesta en el periodo más corto de tiempo, es configurar la redirección en el propio servidor.

Esta operación, conocida con el nombre de reescritura de URLs, es realizada por el motor de reescritura. En el caso de servidores Apache, es el módulo mod_rewrite el responsable, mientras que en IIS lo encontraremos como una extensión ISAPI.

En Apache, las directivas de reescritura pueden configurarse tanto a nivel de todo el servidor, en el httpd.conf, como a nivel de cada directorio, en su fichero .htaccess correspondiente. Como el conjunto de directivas que se pueden usar es bastante amplio y complejo, vamos a ver algunos ejemplos prácticos de situaciones habituales.

Reescribir una única página

El caso más simple que se nos puede presentar es el de querer reescribir una única página. Supongamos que tienes una página llamada asdkofjhSFD9Hosdfhqwo.htm y que por su alta cantidad de visitas quieres que sea accesible desde una URL más amigable, como por ejemplo vendomotos.htm.

RewriteEngine On
RewriteRule   ^vendomotos.htm$   asdkofjhSFD9Hosdfhqwo.htm

Los caracteres ^ y $ indican el inicio y final del path, por lo que si no los incluyésemos también se cumpliría la redirección cuando alguien intentase acceder a yotevendomotos.htm o vendomotos.html.

Uso de parámetros y expresiones regulares

Pero el verdadero potencial de la reescritura está en el uso de expresiones regulares para reconocer parámetros y ofrecer URLs externas con una apariencia limpia de directorios que en realidad se correspondan a direcciones internas con múltiples parámetros incluidos en la cadena. Tomemos un ejemplo bastante conocido: al acceder a la página http://es.wikipedia.org/wiki/Motocicleta, la dirección que internamente usa la Wikipedia para contestarnos es http://es.wikipedia.org/w/index.php?title=Motocicleta.

RewriteEngine On
RewriteRule   ^wiki/(.+)$   w/index.php?title=$1   [L]

La regla utilizada para esa reescritura es la anterior. Analicémosla:

  • ^wiki/ nos indica que el path debe comenzar por wiki/ tras el nombre del dominio.

  • (.+) es una expresión regular que nos indica que un carácter cualquiera (representado por el punto) puede repetirse una o más veces (esto es lo que significa el más) y que debe recordarse el valor de esta cadena para ser utilizado posteriormente (éste es el significado de los paréntesis).

  • w/index.php?title=$1 es la dirección de una página PHP que recibe como parámetro el título correspondiente de sustituir $1 por el primer valor recordado por Apache en la parte izquierda de la sustitución.

  • [L] es un flag que indica a Apache que tras esta reescritura no debe aplicar más reglas a la URL actual.

Redirección del dominio

Como veníamos comentando, el caso donde más necesaria se hace la reescritura de url es cuando migramos todo un dominio. Imaginemos que el día de mañana queremos dejar de usar nuestro dominio actual para pasar a www.comomolagenbetadev.com, pero que obviamente no queremos perder nada de lo que tenemos actualmente: la estructura de páginas, los enlaces de los buscadores con su correspondiente pagerank, etc. El modo de proceder sería el siguiente:

RewriteEngine On
RewriteCond   %{HTTP_HOST}   ^www.genbetadev.com$         [NC]
RewriteRule   (.*)           http://www.comomolagenbetadev.com/$1  [L,R=301]

Por esta vez vamos a analizarlo de abajo a arriba. La regla inferior es muy sencilla, toda petición que coincida con el patrón (.*), o lo que es lo mismo, cero o más caracteres, será redirigido al nuevo dominio, sustituyendo el path por el original. Es decir, de todas las peticiones se cambiará el dominio, indicando además con el flag [R=301] que la redirección es permanente.

El único fallo de esta regla es que redirige absolutamente todo el tráfico que le llegue, aunque la petición original ya se realizase al dominio definitivo, o si fuese otro dominio dentro del mismo servidor. Es por eso que antes de la regla hemos añadido una condición de reescritura, de tal modo que sólo se aplique la regla cuando la condición sea verdadera.

Así pues, con esa regla comparamos el host HTTP, usando el flag [NC] para ignorar las mayúsculas o minúsculas, y sólo cuando sea www.genbetadev.com lo redirigimos al nuevo. Así, las peticiones que lleguen a www.comomolagenbetadev.com o a www.genbeta.com, ignorarán esa regla y se procesarán de modo normal, sin reescritura alguna.

Redirigir de modo programático

Si nuestro servidor no nos da la capacidad de configurar las redirecciones, o no podemos modificar los ficheros, o simplemente queremos realizar ciertos procesamientos antes de decidir si redirigir o no una página, siempre nos queda la posibilidad de cambiar la cabecera de la respuesta dentro de nuestra aplicación, utilizando el lenguaje que queramos. La única pega es que esta opción no afectará al contenido estático, como los documentos HTML o los recursos (imágenes, css, etc.).

Es ligeramente más lento que la configuración en servidor, ya que en principio se tramita la petición original, y es cuando el intérprete la recibe cuando decide procesarla y contestar con un estado de redirección. También por eso es importante incluir el código responsable de la redirección empotrado en el inicio del documento, y no en el code-behind, para que el cliente no intente visualizar toda la página, sino que nada más encontrar la nueva cabecera pare la carga del documento y realice la nueva petición. Incluso en algún caso hay que solicitar explícitamente que se cierre la conexión.

Así se haría en algunos de los lenguajes más habituales:

En ASP.NET

<script runat="server">
private void Page_Load(object sender, System.EventArgs e) {
    Response.Status = "301 Moved Permanently";
    Response.AddHeader("Location","http://www.dominiodedestino.com/paginadedestino.aspx");
}
</script>

En JSP

<%
response.setStatus(301);
response.setHeader( "Location", "http://www.dominiodedestino.com/paginadedestino.jsp" );
response.setHeader( "Connection", "close" );
%>

En PHP

header( "HTTP/1.1 301 Moved Permanently" );
header( "Location: http://www.dominiodedestino.com/paginadedestino.php" );

En Ruby on Rails

def old_action
    headers["Status"] = "301 Moved Permanently"
    redirect_to "http://www.dominiodedestino.com/paginadedestino"
end

En Perl

#!/usr/bin/perl -w
use strict;
print "Status: 301 Moved Permanently\r\n", "Location: http://www.dominiodedestino.com/paginadedestino\r\n\r\n";
exit;

¡Cuidado con las redirecciones infinitas!

Programador muerto mientras se duchaba. En su mano se encuentra un bote de champú donde decía: “1.Lavar. 2.Aclarar. 3.Volver al paso 1.”

Este chiste tan malo es una buena muestra de las situaciones aparentemente absurdas que pueden acabar ocurriendo, con el consecuente bochorno para el artífice, malgasto de ancho de banda y cabreo para el usuario, que no acaba llegando a la página solicitada.

Cuando se desarrolló el protocolo, se fijó en 5 el máximo de redirecciones consecutivas que se podían realizar (A redirige a B, B redirige a C, y así hasta llegar a F), pero lo cierto es que cada navegador o araña suele usar su propio criterio. Además, lo habitual no es tener una cadena de redirecciones como las anteriores, sino que los problemas se suelen producir por redirecciones infinitas de uno de estos dos tipos:

  • A redirige a A

  • A redirige a B, B redirige a A

Ante estos casos, no existe ningún truco mágico. Las reglas a seguir, si acaso, serían dos. La primera, usar el sentido común: comprobar y requetecomprobar lo escrito, que siempre se redirija a una página final y no a otra redirección. Es mejor tener A => C y B => C que hacerlo transitivo con un A => B => C, por una cuestión de claridad, por evitar riesgos, y por corrección con el significado del código 301 (el contenido de A realmente está en C).

Y la segunda regla es probar primero a pequeña escala, en un sólo documento o directorio, y si vemos que todo va como debería, extrapolar ya a todo el dominio afectado.

En cualquier caso, y especialmente si tenemos reglas complejas, siempre puede quedar alguna redirección concreta mal formateada, por lo que conviene revisar los logs del servidor, por si se están produciendo errores a la hora de localizar archivos, o incluso los errores de rastreo de Google, que nos darán información completa de qué fallos ha detectado el robot rastreador.

Manual | Módulo Apache mod_rewrite
Más información | Códigos de estado HTTP
En Genbeta Dev | La reescritura de URL y sus ventajas a la hora de migrar webs (I)

Portada de Genbeta