May 16

El motor de PHP es uno de los más rápidos generando páginas web aún así nunca será tan rápido como no tener que generar nada y que el servidor web devuelva páginas estáticas. A pesar de disponer de una aplicación web escrita en PHP podemos conseguir la misma velocidad que en una web estática cacheando las páginas generadas. Lo que se porpone a continuación es un sistema de cache donde las páginas sólo se generarán la primera vez que se acceda a ellas, en siguientes peticiones el servidor web devolverá las páginas estáticas generadas.

Existen varias propuestas de sistemas de cache donde siempre se ejecuta un script PHP trabajando con el output-buffer para guardar la respuesta. Estos sistemas de cache tiene el inconveniente de que a pesar de tener la página cacheada siempre estamos ejecutando un script PHP con el coste en rendimiento que ello conlleva. Como solución se propone trabajar con el output-buffer pero usar la directiva de configuración ErrorDocument del Apache para que si ya tenemos la página generada no se ejecute ni un script PHP.

La directiva ErrorDocument se usa para configurar lo que el servidor devolverá al cliente en caso de error. Pues bien, se trata de configurar el error que indica que no existe la página solicitada (error 404) para que apunte a un script PHP donde generaremos la página. En la siguiente petición a la misma página el Apache devolverá directamente el archivo HTML generado.

Algunas consideraciones previas:

  • Necesitas tener permisos de escritura en el directorio donde pretendas guardar las páginas. (esto lo puedes conseguir fijando los permisos a 777).
  • Necesitas poder configurar la directiva ErrorDocument de tu servidor Apache. (algunos hostings no lo permiten).

[1] Configurar el Apache

Debemos añadir lo siguiente en nuestro .htaccess o en la configuración del servidor Apache:

ErrorDocument 404 genera_pagina.php

[2] Añadir el código necesario

Es necesario añadir un bloque de código al principio y al fin de cada script. A continuación una implementación de ejemplo:

[php] //obtenemos el nombre de la página solicitada
$pagina = array_pop(explode(‘/’,$_SERVER[‘REQUEST_URI’]));
//iniciamos el output buffer
ob_start();
//………………………..
//código que genera la página
echo ‘hola’;
//………………………..
//obtenemos el output bufer
$contenido = ob_get_contents();
//guardamos la pagina estática
file_put_contents($pagina,$contenido);
//volcamos por pantalla y cerramos el output buffer
ob_end_flush(); [/php]

Se puede guardar el anterior código en un script llamado “genera_pagina.php” sustituyendo la linea echo “hola” por el código de nuestra aplicación (p.e. con un include()) o podemos dividir el código en dos scripts y usar las directivas auto-append-file y auto-prepend-file para no tener que modificar nuestra aplicación (cortar por las lineas de puntos).

En el ejemplo se obtiene sólo el nombre de la página solicitada y se crea una archivo con el mismo nombre con el contenido de la respuesta. Pero no soporta directorios, está pensado para trabajar sobre un directorio en concreto o en una web sin directorios en las URL. Cambiando la primera linea del ejemplo podéis hacer que se comporte de otra forma, el ejemplo simplemente obtiene el último trozo de la URL usando como delimitador “/”.

A diferencia de otras propuestas de cache aquí estamos modificando el Apache para que ejecute un script en caso de no encontrar una página. Es por esto que en vuestra aplicación debéis generar una página de error en el caso de que la página solicitada no exista. Lo correcto es que en esta página de error se envíe la header correspondiente tal y como lo haría el Apache por defecto:

[php] header(“HTTP/1.0 404 Not Found”); [/php]

[3] Mantenimiento de la cache

Por ultimo nos falta programar un proceso en el cron para que elimine los archivos de la cache que han caducado. Por ejemplo para mantener una cache de una hora:

0 * * * * find /web -mtime +1h -print0 | xargs -0 rm -f

Se ha de sustituir “/web” por la ruta física donde tenemos nuestra aplicación web. Si no queremos programar un cron siempre podemos hacer lo anterior desde algún script PHP que se ejecute constantemente en nuestra web.

Lo que comento en este post no es nada nuevo, ya fue propuesto por Rasmus Lerdorf en el PHPCon del 2002 (aunque un poco caducada la presentación que enlazo merece la pena).

Tagged with:
Jan 14

Tal como comentaba en el post anterior voy a explicar como capturar los fatal errors del PHP para poder tratarlos en la medida de lo posible. Según este mensaje de la lista de desarrollo del PHP (fijaros en los autores) los fatal errors nunca podrán ser capturados en el userland, por ejemplo con un handler de errores personalizado, ya que en estos casos el intérprete es probable que quede en un estado inestable y es necesario de una terminación rápida y limpia.

Podemos conseguir un buen comportamiento ante los fatal error configurando adecuadamente las directivas implicadas. Sólo editando el php.ini podemos conseguir que se muestre un mensaje personalizado por pantalla, que se guarde en archivo o no, etc. Para más detalles podéis consultar el capítulo sobre el tema en el manual del PHP. Lo que aquí se describe es una forma alternativa que nos permite tenerlo controlado desde nuestro código PHP. Como requisitos hemos de tener configurado el PHP para que nos muestre errores por pantalla mediante la directiva de configuración display_errors.

La idea es sencilla, se trata de capturar todo el output buffer de nuestra aplicación y antes de enviarlo al usuario parsearlo en busca de la palabra “error” en negrita seguida de dos puntos con un mensaje y un salto de línea. El fallo es que, lógicamente, si nosotros sacamos por pantalla un string que cumpla lo anterior lo detectaremos como un fatal error del intérprete… aunque es muy difícil que tengamos una cadena de texto que cumpla estas condiciones a no ser que sean errores de nuestra aplicación, en ese caso siempre les podemos cambiar el texto o también podemos configurar la directiva error_prepend_string con una key identificable y modificar el código aquí incluido.

El código es el siguiente:

[php] function fatal_error_handler($buffer) {
if (ereg(“(error:)(.+)(/”,””,$regs[2]);
error_log($err,1,’tu.email@de.alertas.com’);
return ‘Mensaje de error personalizado para el usuario’;
}
return $buffer;
}
ob_start(“fatal_error_handler”);
funcion_que_no_existe();
ob_end_flush(); [/php]

Entre lo aquí descrito y el handler de errores detallado en el post anterior deberíamos tener cualquier error del PHP bajo control. Existen otros errores que no vamos a poder controlar nunca como son todos los errores de sintaxis que provocan parse errors, por ejemplo que falte un “;”, estos normalmente se detectan en la etapa de desarrollo y no se espera que sucedan en producción… y si suceden sólo nos queda tener correctamente configuradas las directivas pertinentes del PHP.

Tagged with:
preload preload preload