Mar 302014
 
Artículo PHP

Muchos sitios web están implementados en PHP. Cada vez que el servidor recibe una solicitud, ejecuta un script que genera dinámicamente el contenido de la página, a menudo realizando varias consultas a una base de datos, o realizando otro tipo de proceso que puede ser costoso en tiempo de proceso y uso de recursos, haciendo que la navegación por el sitio web resulte lenta.

En este artículo se explica un mecanismo para evitar este problema, guardando en disco el código HTML resultante de la ejecución del script, de manera que las siguientes veces que una misma página es solicitada por un navegador, el servidor la entrega leyendo el contenido preprocesado directamente del disco, sin necesidad de volver a ejecutar el script.

Introducción

Cuando un cliente realiza una petición a un servidor web de un sitio dinámico, éste lanza la ejecución de un script CGI (normalmente escrito en PHP) que genera el código HTML de la página. El servidor web recoge la salida del script CGI y se la entrega al cliente.

Para implementar una cache, la idea básica es redirigir a una variable la salida del script, que normalmente es enviada a standard output. Después, se envía al cliente el contenido de la variable  (el código HTML de la página), y en paralelo se guarda en un fichero cache.

En las sucesivas solicitudes que sean recibidas, el servidor web comprueba si la página existe en cache, y si es así la entrega al cliente leyéndola del disco. En caso contrario, el servidor web lanza la ejecución del script, que genera la salida, la entrega al cliente y la guarda en cache.

Los ejemplos de código que se presentan en este artículo corresponden a un sitio web dinámico implementado mediante scripts CGI escritos en PHP, sobre un servidor web apache que se ejecuta en una plataforma Linux Debian. Con ligeras modificaciones, pueden ser aplicables a otro tipo de servidor.

Redirigir a una variable la salida generada por el script

PHP incorpora la funcionalidad que permite realizar esta redirección, mediante una llamada a la función “ob_start()” (en donde ob son las iniciales de “output buffering”: almacenamiento temporal de la salida).

La llamada a ob_start() se debe incluir al comienzo de la ejecución del script, antes de que éste comience a generar la salida.

A continuación, después de las sentencias que generan el código HTML de la página, se realiza una llama a ob_get_contents() para guardar en una variable el código HTML generado.

Por último, se realiza una llamada a ob_end_flush(), para enviar a standard output el contenido del buffer:

Nota: Alternativamente, si queremos descartar el código HTML generado en vez de enviárselo al cliente, se puede sustituir la llamada a ob_end_flush() por una llamada a ob_end_clean().

Guardar en cache la página preprocesada

La variable “$html” contiene ahora el código HTML de la página generada y enviada al cliente. Ahora vamos a guardar dicho código en un fichero bajo un subdirectorio “cache” del directorio raíz (especificado en el parámetro de configuración DocumentRoot) de nuestro servidor web. Como nombre de fichero utilizaremos el URI con el que se ha accedido a la página, con extensión “.html”.

En el ejemplo, suponemos que el DocumentRoot es el directorio “/web”, y que hemos creado bajo el mismo un directorio “cache” para contener las páginas:

En el código del ejemplo, utilizamos una función “createPath” para crear recursivamente el directorio de destino. Esta función no existe en la distribución base de PHP, pero podemos definirla de la siguiente forma:

También guardaremos una versión comprimida del fichero, que será entregada si el cliente que solicita la página admite recibir datos comprimidos. El nombre del fichero comprimido es igual al nombre del fichero sin comprimir, con extensión “.html_gzip”:

Configurar el servidor web para utilizar las páginas en cache

Ahora, debemos configurar el servidor web para hacer uso de la cache.

En el caso de un servidor apache, añadimos las siguientes sentencias al fichero de configuración (o bien, pueden ser incluidas en un fichero .htaccess, si utilizamos este tipo de fichero):

Como vemos:

  • en la líneas 3 estamos indicandole al servidor web que los ficheros con extensión “.html_gzip” son ficheros comprimidos en formato gzip
  • en las líneas 4-6 indicamos que estos ficheros contienen código HTML codificado en UTF-8, para que el servidor web incluya esta información en el encabezado HTTP que envía al cliente
  • en las líneas 9 y 10, definimos una variable de entorno “PG_ENC” con valor “_gzip”, si el cliente admite recibir datos comprimidos en gzip
  • por último, en las líneas 11-14 entregamos al cliente el fichero en cache, si se cumplen tres condiciones
    • la solicitud recibida del cliente no es de tipo POST
    • la url no incluye argumentos (es decir no es de la forma “http://www.ejemplo.com/pagina?argumento=valor”)
    • el fichero existe en cache en su versión comprimida o no comprimida, según corresponda

Y con esto ya está implementada nuestra caché de páginas.

Consideraciones adicionales

Aunque la implementación descrita hasta ahora puede ser suficiente para un gran número de sitios web, en algunos casos puede ser necesario tener en cuenta algunas consideraciones adicionales:

Usuarios registrados

Algunos sitios web ofrecen servicios adicionales a los usuarios registrados. Normalmente, una vez que un usuario se ha validado introduciendo su nombre de usuario y contraseña, el sitio web le ofrece unos contenidos distintos y le da acceso a páginas adicionales.

Normalmente, el sitio web entrega al cliente un cookie en el momento en el que el usuario se valida, y utiliza el cookie recibido en los siguientes accesos para indentificar que proceden de un usuario registrado.

La manera más sencilla de tener esto en cuenta en nuestra implementación es impedir que a los accesos que realizan usuarios en sesión se les entreguen páginas de la cache. Para ello, incluimos una condición adicional en el fichero de configuración del servidor web:

En el ejemplo, se supone que el nombre del cookie que el sitio web entrega al cliente cuando el usuario se valida es “usuario_registrado”.

De la misma forma, si el CGI se ejecuta en respuesta a un acceso de un usuario en sesión, no debe generar nuevos ficheros cache.

En PHP, los cookies recibidos son accesible mediante la variable $_SERVER[‘HTTP_COOKIE’]. Una manera elemental de detectar la presencia del cookie  de usuario registrado y evitar generar el fichero cache sería:

Por otra parte, si el sitio web está desarrollado sobre una plataforma de gestion de contenidos standard, normalmente la librería asociada a la misma incluirá alguna función para comprobar si hay un usuario en sesión. Por ejemplo, en el caso de WordPress, existe la función is_user_logged_in() para este cometido, de modo que el código del ejemplo se reescribiría como:

Caducidad de la cache

En la implementación que hemos descrito, una vez que se ha generado un fichero en la cache, el servidor web continuará entregándoselo a los clientes para cualquier nueva petición de la misma página.

Pero puede ocurrir que el contenido de la página deba ser actualizado como consecuencia de cambios en la base de datos, o por cualquier otra causa. El desarrollador del sitio debera implementar un mecanismo que detecte las páginas a las que  afecta un cambio, y elimine los ficheros cache correspondientes, de manera que vuelvan a ser generados en el próximo acceso.

La manera más elemental de implementar el refresco de la cache es mediante un proceso que se ejecute periódicamente (por ejemplo, añadiendo una entrada en el crontab en un sistema Linux) y elimine todos los ficheros de la cache.

Indice de artículos sobre programación en lenguaje PHP

 Publicado por en 3:48 pm

 Deja un comentario

(requerido)

(requerido)