- Detalles
- Por Juan Padial
- En Web y Servidores
- Visto: 45711
Una de las herramientas más poderosas de la que podemos hacer uso en nuestro archivo .htaccess es la posibilidad de reescribir las urls. Esto nos permite hacer manipulaciones profundas en nuestros links; por ejemplo, nos será muy útil para transformar url muy largas en url más cortas y amigables (tanto para motores de búsqueda como los usuarios), transformar url dinámicas en url de apariencia estática (?pagina=dinamica&metro=valor en algo como /noticias/articulo1.html), redirigir páginas no encontradas y mucho mucho más.
Usar mod_rewrite no es sencillo, es complejo y yo no soy un experto. En este artículo propondré algunas nociones que he aprendido del uso de mod_rewrite y RewriteRue con el objetivo de mostrar algunos ejemplos sencillos, fáciles de implementar y de los que creo pueden ser de más utilidad.
Antes de seguir puede que tengas que leer el post sobre expresiones regulares pues muchas de las construcciones que se usarán en la reescritura de las url utilizan la misma forma que una expresión regular y antes de seguir leyendo asegúrate de que tienes instalado mod_rewrite en tu servidor Apache.
Comenzando a reescribir urls
Como mencione unas líneas más arriba, tienes que tener instalado mod_rewrite. Puedes tener un archivo .htaccess en cada directorio de tu sitio. En cada uno de esos archivos es necesario poner estas líneas una sola vez:
Options +FollowSymlinks RewriteEngine on
La opción +FollowSymlinks es completamente necesaria para que funcione cualquier regla que pongamos a través RewriteRule, es un requerimiento de seguridad del motor de reescritura. "RewriteEngine on" simplemente activa el motor de reescritura. Si tienes ambas opciones puestas en el archivo .htaccess de tu directorio raíz puedes omitirlas en los archivos .htaccess que pongas en subdirectorios. No obstante, si las pones en todos no pasa nada.
Algunos proveedores de hosting tienen activado +FollowSymlinks a nivel raíz y no permiten su inclusión en archivos .htaccess individuales. Si experimentas un error 500 prueba no poniendo "Options +FollowSymlinks" y contacta con tu proveedor de hosting para preguntar si la tienen activada y si permiten su uso en tus archivos .htaccess.
Reescritura simple
Apache escanea todas las solicitudes entrantes, comprueba las coincidencias en nuestro archivo .htaccess y reescribe estas url en la forma en la que lo hayamos especificado. Por ejemplo:
RewriteRule ^(.*)\.htm$ $1.php [NC]
En la expresión anterior, si no usas archivos htm, se podría usar .html, .htm(.*), .htm?, etc. Esto sería útil si has pasado tu sitio de html estático a páginas dinámicas en php. Todas las solicitudes a las páginas estáticas (html) antiguas serán enviadas su equivalente en php en el lado del servidor sin que nadie note nada, ni el usuario ni los motores de búsqueda, se podrá acceder a través de las dos urls. El comando [NC] al final de la línea de RewriteRule significa "No Case", o lo que es lo mismo: insensible a mayúsculas/minúsculas. Con la regla anterior, siempre se enviará la solicitud a loquesea.php aunque en el barra del navegador se introduzca (y se vea) en él loquesea.htm....incluso si loquesea.htm no existe.
Vayamos con un paso más. Con la anterior regla de reescritura, tanto los usuarios, pero sobre todo los motores de búsqueda, seguirán intentando indexar las url en formato htm. Pero como dije, el contenido será accesible con las dos versiones de la URL. Esto puede ser visto como "servir el mismo contenido a través de urls diferentes" (contenido duplicado). Pero hay solución, hacer una redirección física real del navegador a la nueva url (o también implementar el link canonical con otro método):
RewriteRule ^(.+)\.htm$ http://dominio.com/$1.php [R,NC]
Esta vez le damos instrucciones a mod_rewrite para hacer una redirección real. Ahora no solo se realiza la reescritura "al vuelo" en el backend, en el lado del servidor, sino que el usuario (o el motor de búsqueda) es redirigido a la nueva url. Los motores de búsqueda actualizarán de forma automática sus links y el navegador del usuario mostrará la nueva url en su barra de direcciones. Esta redirección es dada por la instrucción [R].
Al respecto de la instrucción [R] decir que al usarla sola envía una cabecera HTTP "movido temporalmente" (código de redirección 302), pero se pueden enviar otro códigos. Es decir la siguiente instrucción RewriteRule sería exactamente igual que la anterior:
RewriteRule ^(.+)\.htm$ http://dominio.com/$1.php [R=302,NC]
Se puede enviar cualquier código de redirección, el más común es el 301 ("Movido permanentemente").
A tener en cuenta: si se añade "L" a las instrucciones, por ejemplo [R=301,NC,L], se indica "Last Rule", es decir, última regla. Esta instrucción indica que es la última regla de reescritura y Apache terminará aquí la reescritura para la presente solicitud.
Construyendo reglas de reescritura más sofisticadas
Como anoté al comienzo del artículo y como habrás podido notar en los ejemplos iniciales, mod_rewrite usa expresiones regulares para construir patrones que quedan capturados de modo que se puede hacer referencia a ellos después. Por ejemplo, el patrón (.+) usado en el ejemplo anterior queda capturado en la variable $1, es decir, (.+) = $1. Se pueden tener múltiples (.+) u otros patrones que quedarán capturados en $1, $2, $3, etc, según el orden en el que aparezcan (para más info vea el artículo sobre expresiones regulares). La posibilidad de usar expresiones regulares y la captura de patrones da unas amplias posibilidades en la reescritura de las url, una de las más importantes es la creación de url amigables, o url planas a partir url dinámicas con múltiples variables. Por ejemplo:
RewriteRule ^noticias/([^/]+)/([^/]+)\.html /noticias.php?seccion=$1noticia=$2 [NC]
Esta regla te permitiría utilizar el link http://www.misitio.com/noticias/economia/articulo1.html, que será interpretado en el lado del servidor como http://www.misitio.com/noticias.php?seccion=economia¬icia=articulo1. Con esto puedes presentar url amigables, conocidas como SEF (search engine friendly), sin perder el poder de tener una url dinámica que pueda ser procesada por cualquier script de tu sistema de gestión web.
Para mi no es tan importante crear SEF urls para los motores de búsqueda, pues hoy día los principales motores de búsqueda, y por supuesto Google, puede seguir e indexar url dinámicas sin problemas; poder usar SEF urls es, desde mi punto de vista, es mucho más importante para los usuarios de tu web ya que, por seguro, podrán seguir, recordar, copiar, escribir, etc, una url plana que una dinámica. Otro ejemplo:
RewriteRule ^noticias/arvhivo/([0-9]+)-([a-z]+) /archivo.php?year=$1&month=$2 [NC]
Esta última regla nos serviría para poder usar la url del archivo de noticias en la forma http://www.misitio.com/noticias/archivo/2010-enero, que en el servidor sería interpretada como http://www.misitio.com/archivo.php?year=2010&month=enero.
Como ves, con el manejo de expresiones regulares se pueden hacer manipulaciones de las url que muestren url realmente amigables tanto para motores de búsqueda como para los humanos. Los aspectos más básicos de las expresiones regulares para mod_rewrite son:
Carácter de escape: \ Con el carácter \ escapamos el carácter (o patrón de caracteres) que le sigue y así no será interpretado como carácter de expresión regular. Por ejemplo, el punto (.) indica "cualquier carácter" por lo que si lo usamos en nuestra regla de escritura hay que escaparlo tal y como se muestra en los ejemplos hasta ahora expuestos: (expresion\.html -> se escapa el punto para que se interprete como "punto" y no como "cualquier carácter"). Se debe escapar cualquier carácter especial que NO queremos sea interpretado como carácter especial: [].()\ etc.
. (punto): indica cualquier carácter.
[caracteres]: define una clase de caracteres.
[^caracteres]: define una clase de caracteres de exclusión: ^ indica "ninguno de los caracteres que le siguen".
| (rama alternativa): indica "o". Es el operador conocido como "rama alternativa". Por ejemplo: texto1|texto2 indica que debe aparecer el texto1 o el texto2, validará tanto si aparece texto1 o texto2.
Ejemplo:
[^/]: coincide con cualquier carácter menos /
(foo|bar)\.html coincide con foo.html y con bar.html.
Cuantificadores:
? -> 0 o 1 en relación al texto precedente
* -> 0 o más
+ -> 1 o más
Agrupación (patrones): un patrón o un grupo de caracteres se especifica introduciendo la expresión entre dos paréntesis. Los paréntesis también se deben usar para poner dentro las dos opciones del operador de rama alternativa |.
Anclaje: El carácter $ indica final y el carácter ^ indica principio. (nota que ^ en el interior de una clase de caracteres no indica principio sino que es carácter de exclusión).
Ejemplo:
^foo(.*) coincide con foo y foobar pero no eggfoo (foo ha de estar al principio)
(.*)l$ coincide con fool y cool, pero no foo (l ha de estar al final)
(.+)\.html? coincide con foo.htm y foo.html
Acortar URLs
Un uso común de mod_rewrite es usar sus fantásticas opciones y flexibilidad para crear urls más cortas y fáciles de recordar a partir de urls dinámicas. Pongamos un ejemplo:
RewriteRule ^bolsa /directorio-noticias/economia/bolsa/index.php
Con esta regla podríamos usar la siguiente url pública:
http://misitio.com/bolsa?=noticia3
Que sería interpretada en el servidor como:
http://misitio.com/directorio-noticias/economia/bolsa/index.php?=noticia3
Usando este tipo de reglas puedes incluso crear links planos realmente cortos por muy profundo que sea el link real en tu sitio:
RewriteRule ^noticia45(.*)$ /noticias?tipo=actualidad&loc=europa&time=12032011&id=45
Con esto tendríamos la url http://misitio.com/noticia45 que sería interpretada en el servidor como http://misitio.com/noticias?tipo=actualidad&loc=europa&time=12032011&id=45.
Capturar variables de una url dinámica para su uso en mod_rewrite
Añadiendo (.*) al final de la parte que indica la solicitud en RewriteRule funcionará para capturar la variable cuando solo se usa una variable $_GET simple. Pero puede que esta opción se quede corta si tus propósitos son otros como puede ser capturar solo algunas variables en particular. Usando la flexibilidad de mod_rewrite y los patrones de expresiones regulares podemos hacer practicamente lo que queramos: capturar una variable en particular, convertir las variables capturadas en otras variables en la url de destino, y un largo etcétera.
Al proponerse capturar variables hay que conocer algunas cosas, lo primero es la marca [QSA]. Al poner esta bandera en RewriteRule (como poníamos antes [R], [NC], etc) se pasarán todas las variables originales a la url de destino. Algo más complejo que nos dará un alto control sobre la url de destino es el uso de %{QUERY_STRING} y el uso de RewriteCond.
RewriteCond es similar a los bucles condicionales if de cualquier lenguaje de programación: si se cumple la condición especificada en RewriteCond, entonces se ejecutará la siguiente instrucción RewriteRule.
Veamos un ejemplo: con el siguiente código se comprueba si existe la variable "option" en la url solicitada y se captura su valor si está presente. En caso de que esté presente se realiza la instrucción RewriteRule siguiente en la que se envía la solicitud en el servidor a otra url y se pasa el valor de la variable "option" capturado a la variable "view" de la url de destino. En otras palabras, solo si existe una solicitud para la url noticias?option=algo, se ejecutará la reescritura a /view.php?view=algo. Veamos el código:
RewriteCond %{QUERY_STRING} option=(.*)
RewriteRule ^noticias(.*) /view.php?view=%1
Nota que en el código anterior, si hay más variables en la url de solicitud, se enviará igualmente a /view.php?view=aglo, no teniendo en cuenta el resto de variables. Con el ejemplo anterior tendríamos la url http://misitio.com/noticias?option=algo que sería interpretada en el servidor como http://misitio.com/view.php?view=aglo. (nota que como no hemos puesto [R], en el navegador aparecería /noticias?option=aglo aunque al servidor se pasaría la solicitud /view.php?view=algo).
A tener en cuenta: Las referencias a variables capturadas en RewriteCond se realizan con el carácter %. Su uso es exactamente igual que el carácter $ para referencias a patrones capturados en RewriteRule.
Adicionalmente, tras realizar la conversión de una variable en otra con un valor capturado previamente, aún podemos usar [QSA].
Ejemplo:
RewriteCond %{QUERY_STRING} option=(.+)
RewriteRule ^noticias/(.*) /%1/index.php?view=$1 [QSA]
En este ejemplo el valor de la variable "option" pasa a ser el nombre de un directorio y todas la variables de la url de solicitud son pasadas a la url de destino. Así podríamos usar la url:
http://misitio.com/noticias/economia?seccion=bolsa&option=portada
que sería leída en el servidor como
http://misitio.com/portada/index.php?view=economia&seccion=bolsa&option=portada
Con el siguiente código se podrían combinar links estáticos y captura de variables:
RewriteCond %{QUERY_STRING} version=(.+)
RewriteRule ^archivos/([^/]+)/(.*) /%1/index.php?seccion=$1&archivo=$2 [QSA]
El siguiente ejemplo muestra como eliminar cualquier variable de la url solicitada (justo lo contrario a lo que hemos visto); es decir, cualquier solicitud en forma archivo.php?var=aglo... sería siempre procesada en el servidor como archivo.php:
RewriteCond %{QUERY_STRING} .
RewriteRule ^archivo.php(.*)$ /archivo.php? [L]
Denegar acceso a determinados archivos
Haciendo uso del archivo .htaccess y RewriteRule podemos enviar a un usuario a otra localización si trata de acceder un tipo de arvhivos al que no queramos que tenga acceso. Esto se realiza chequeando el nombre del archivo solicitado con RewriteCond y aplicando un RewriteRule.
Ejemplo:
RewriteCond %{REQUEST_FILENAME} !^(.+)\.css$
RewriteCond %{REQUEST_FILENAME} !^(.+)\.js$
RewriteCond %{REQUEST_FILENAME} !view.php$
RewriteRule ^(.+)$ / [NC]
Con la anterior instrucción solo se permite el acceso a los archivos css (hojas de estilo), js (javascript) y al archivo view.php. Se pueden poner todos los que necesites en la misma línea usando | ("o").
Podemos incluso denegar el acceso desde una determinada IP:
RewriteCond %{REMOTE_ADDR} ^145\.164\.1\.8
RewriteRule ^(.*)$ / [R,NC,L]
Otro ejemplo interesante es permitir el acceso a los archivos de un directorio pero no que se liste el contenido del mismo. Si se accede a un directorio sin un index.html, index.php o sin el archivo de carga predeterminado configurado en Apache, se lista el contenido del directorio en el navegador del usuario, esto se puede prevenir del siguiente modo: en el siguiente ejemplo se permite el acceso a los archivos del directorio css pero no se permite el acceso al listado del mismo:
RewriteCond %{REQUEST_URI} !css/(.*)\.
RewriteRule ^(.*)$ / [R,NC,L]
Banear spiders
Hay muchos spiders y robots que puedas querer banear, por ejemplo consumen recursos que prefieres tener libres para los usuarios u otros motores como puede ser googlebot. Sea cual sea la razón que tengas, un determinado user-agent puede ser bloqueado mediante RewriteCond y RewriteRule.
Ejemplo: (fíjate en el uso de [OR])
RewriteCond %{HTTP_USER_AGENT} ^BlackWidow [NC,OR]
RewriteCond %{HTTP_USER_AGENT} ^Net\ Vampire [NC,OR]
#.....etc
RewriteRule . abuso.txt [L]
Prevenir Hot-Linking
El hot-linking es el uso de recursos de tu servidor desde otra web. Por ejemplo, un caso muy común es usar imágenes alojadas en tu servidor puestas en otras web. Si quieres prevenir esto y evitar el consumo de bandwidth desde otros sitios distintos al tuyo puedes realizar algo similar al siguiente ejemplo en el que se bloquea el acceso a todas las imágenes si el REFERRER no es tu sitio.
Ejemplo:
RewriteCond %{HTTP_REFERER} !^$
RewriteCond %{HTTP_REFERER} !^http://(www\.)?dominio\.com/ [NC]
RewriteCond %{REQUEST_URI} !hotlink\.(gif|png) [NC]
RewriteRule .*\.(gif|jpg|png)$ http://www.dominio.com/image/hotlink.png [NC]
En el anterior ejemplo el primer RewriteCond permite la solicitud directa pero no desde otras páginas (referrer vacío). La siguiente línea indica que si el navegador ha enviado una cabecera Referrer y esta no continene la palabra "dominio.com" se ejecutará el RewriteRule. La ultima instrucción RewriteCond indica que si en la url solicitada se encuentra el nombre de la imagen "hotlink" no se realizará el RewrtieRule; esto se pone porque la imagen hotlink.png va a ser la que vamos a usar en RewriteRule y si no ponemos este RewriteCond también sería redirigida la solicitud a esta imagen. La última instrucción del ejemplo es el RewriteRule que indica que cualquier solicitud a una imagen desde otro referrer será reescrita en el servidor hacia la imagen hotlink.png y esta será la imagen que se vea en la web que te esté intentando robar la imagen.
Redirigir entre dominions con "www" o sin "www"
Este tema lo trate en otro artículo que puedes ver en Redirigir entre dominios con "www" y sin "www" con .htaccess.
Algunas consideraciones finales sobre .htaccess y mod_rewrite
http.conf: Todas las reglas se pueden especificar en el archivo de configuración del servidor httpd.conf en lugar de usar un archivo .htaccess. En este caso, en el archivo httpd.conf hay que usar ^/.... en lugar de ^.... al comienzo de cada línea de instrucción RewriteRule.
Herencia: si creas reglas de reescritura y pones el archivo .htaccess en un determinado directorio, todas las reglas creadas son aplicadas (heredadas) en los subdirectorios del directorio en cuestión. Pero si pones un archivo .htaccess en un subdirectorio, se sobreescriben las instrucciones dadas en el archivo .htaccess que pudiera haber en directorios superiores. Es decir, puedes poner un .htaccess en un directorio superior y será aplicado a todos los subdirectorios, y puedes poner un .htaccess en los subdirectorios en los que necesites reglas específicas para ese subdirectorio. Hay que mencionar que si se encuentra un .htaccess en un subdirectorio y quieres que se apliquen las reglas de un .htaccess de un directorio superior tendrás que volver a incluirlas en el .htaccess del subdirectorio. Por supuesto, es posible incluir todas las reglas para tu sitio y de forma específica la reglas necesarias para todos los subdirectorios en un único .htaccess puesto en el directorio raíz de tu sitio aplicando la sintaxis correcta (inluyendo el path del subdirectorio en la regla de RewriteRule del .htaccess del directorio raíz).
Cookies: Es facil poner cookies con .htaccess sin usar mod_rewrite, el uso de mod_rewrite será para leer la cookie, y si existe con un determinado valor, ejecutar un RewriteRule:
Ejemplo:
#esto enviaría una cookie de nombre ejemplo-cookie y pondría su valor en true Header set Set-Cookie "ejemplo-cookie=true"
En el siguiente ejemplo buscamos la cookie ejemplo-cookie y si no existe o su valor no es "true", realizamos un RewriteRule en la que mostramos una página de error:
RewriteCond %{HTTP_COOKIE} !ejemplo-cookie=true
RewriteRule .* /error/401.php
Usa [R] en lugar de [R=301] mientras haces pruebas: Si utilizas [R=301] y estás realizando pruebas puede que no veas los efectos realizados en el archivo .htaccess hasta pasado un tiempo. Como solución tendrías que limpiar la caché del navegador, aunque lo mejor es que uses [R] y no especificar una redirección permanente (301) mientras realices pruebas
Creado el 26 05 2011 Actualizado el 03 12 2012
