Symfony 2.0, el libro oficial

14.5. Utilizando ESI

Las gateway caches son una excelente forma de mejorar el rendimiento de tu sitio web. Pero tienen una limitación: sólo pueden almacenar páginas enteras. Si no puedes guardar toda la página o si algunas partes de la página son muy dinámicas, estas cachés no te sirven. Afortunadamente, Symfony2 ofrece una solución para estos casos, basada ​​en una tecnología llamada ESI (del inglés, Edge Side Includes). La empresa Akamaï escribió esta especificación hace casi 10 años, y permite que algunas partes específicas de la página tengan una estrategia de caché diferente al del resto de la página.

La especificación ESI describe las etiquetas que puedes utilizar en tus páginas para comunicarte con el sistema de caché. Symfony2 sólo implementa una etiqueta, include, ya que es la única útil fuera del contexto de la empresa Akamaï:

<!DOCTYPE html>
    <html>
        <body>
        <!-- ... contenido normal ... -->

        <!-- incluir el contenido de otra página -->
        <esi:include src="http://..." />

        <!-- ... más contenido normal ... -->
    </body>
</html>

Nota Observa que en el ejemplo la etiqueta ESI utiliza una URL absoluta. En realidad, las etiquetas ESI representan fragmentos de página que se pueden obtener a través de una URL.

Cuando se procesa una petición, el sistema de caché obtiene la página almacenada en la caché o realiza una petición a la aplicación. Si la respuesta contiene una o más etiquetas ESI, se procesan de la misma manera. En otras palabras, o el sistema de caché obtiene los fragmentos almacenados en la caché o realiza una petición a la aplicación para obtenerlos. Cuando se obtienen todas las etiquetas ESI, el sistema de caché los combina con la página y el resultado se devuelve al cliente.

Todo lo anterior sucede de forma transparente para tu aplicación. Así que si decides utilizar ESI, Symfony2 se encarga de casi todo el trabajo.

14.5.1. Usando ESI en Symfony2

Para utilizar ESI, asegúrate primero de activarlo en la configuración de tu aplicación:

# app/config/config.yml
framework:
    # ...
    esi: { enabled: true }
<!-- app/config/config.xml -->
<framework:config ...>
    <!-- ... -->
    <framework:esi enabled="true" />
</framework:config>
// app/config/config.php
$container->loadFromExtension('framework', array(
    // ...
    'esi'    => array('enabled' => true),
));

Ahora, supongamos que tenemos una página que es relativamente estática, salvo por una pequeña zona de noticias recientes. Con ESI, puedes tratar a la zona de noticias de forma diferente al resto de la página.

public function indexAction()
{
    $response = $this->render('MyBundle:MyController:index.html.twig');
    // marca la página como pública y establece el tiempo de vida máximo
    $response->setSharedMaxAge(600);

    return $response;
}

En este ejemplo, la caché de la página completa tiene un tiempo de vida de diez minutos (600 segundos). A continuación, añade la sección de noticias embebiendo una acción en la plantilla mediante el helper render.

Como el contenido embebido viene de otra página (o controlador en este caso), Symfony2 configura las etiquetas ESI mediante el propio helper render:

{% render url('latest_news', { 'max': 5 }) with {}, {'standalone': true} %}
<?php echo $view['actions']->render(
    $view['router']->generate('latest_news', array('max' => 5), true),
    array(),
    array('standalone' => true)
); ?>

El primer parámetro de la etiqueta render es la URL absoluta de la acción es que se está embebiendo. Por tanto, debes definir una nueva ruta para el controlador que quieres embeber:

# app/config/routing.yml
latest_news:
    pattern:      /esi/latest-news/{max}
    defaults:     { _controller: AcmeNewsBundle:News:news }
    requirements: { max: \d+ }

Advertencia A menos que quieras que esta URL sea accesible a cualquiera, utiliza el firewall de Symfony para restringir su acceso (permite por ejemplo acceder a el solamente desde el rango de direcciones IP de tu proxy inverso).

Truco Una buena práctica consiste en montar todas las URL relacionadas con ESI bajo un mismo prefijo (por ejemplo, /esi). La primera ventaja de hacer esto es que facilita la gestión de las URL que utilizan ESI porque es muy fácil identificar las rutas relacionadas con ESI. En seguno lugar, facilita la gestión de la seguridad porque restringir el acceso a un grupo de URL que empiezan con el mismo prefijo siempre es más fácil que proteger las URL una a una.

El valor true de la opción standalone en la etiqueta render de Twig indica a Symfony que esta acción debe renderizarse mediante una etiqueta ESI. ¿Por qué utilizar un helper en vez de escribir directamente la etiqueta ESI? Porque de esta manera la aplicación funciona incluso cuando no hay disponible ningún sistema de caché.

Cuando el valor de la opción standalone is false (su valor por defecto), Symfony2 incluye el contenido embebido dentro de la propia página y envía el resultado al cliente. Pero si la opción vale true y Symfony2 detecta que existe un sistema de caché que soporta ESI, genera una etiqueta include de ESI. Si la caché no está disponible o no soporta ESI, Symfony2 simplemente incluye el contenido dentro de la propia página como si la opción standalone fuera false.

Nota Symfony2 detecta si el sistema de caché soporta ESI mediante otra especificación definida por la empresa Akamaï y que Symfony2 soporta de serie.

La acción embebida puede definir sus propias reglas de caché, que son completamente independientes de las de la página principal:

public function newsAction($max)
{
    // ...

    $response->setSharedMaxAge(60);
}

Gracias al uso de ESI, la página completa se guardará en la caché durante 600 segundos y la zona de las noticias solamente durante 60 segundos.

La principal ventaja de esta estrategia de caché es que puedes hacer tu aplicación tan dinámica como necesites y al mismo tiempo, ejecutar la aplicación lo menos posible para responder a las peticiones de los usuarios.

Nota Cuando utilices ESI, recuerda hacer uso de la directiva s-maxage en vez de max-age. Como el navegador sólo recibe la página completa (incluyendo las partes renderizadas mediante ESI), no conoce las diferentes partes de la página y por tanto, la directiva max-age hará que guarde la página completa en la caché.

El helper render soporta otras dos opciones de configuración:

  • alt: corresponde al atributo alt de la etiqueta ESI y permite indicar una URL alternativa que se utilizará cuando la ruta indicada en el atributo src no esté disponible o no se encuentre.
  • ignore_errors: si vale true, se añade el atributo onerror a la etiqueta ESI con un valor igual a continue, que indica que si se produce algún error, el sistema de caché simplemente elimina la etiqueta ESI pero no muestra ningún mensaje de error.