Symfony 2.3, 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:

{# puedes hacer referencia a un controlador #}
{{ render_esi(controller('...:news', { 'max': 5 })) }}

{# y también puedes usar como referencia una URL #}
{{ render_esi(url('latest_news', { 'max': 5 })) }}
<?php echo $view['actions']->render(
    new ControllerReference('...:news', array('max' => 5)),
    array('strategy' => 'esi'))
?>

<?php echo $view['actions']->render(
    $view['router']->generate('latest_news', array('max' => 5), true),
    array('strategy' => 'esi'),
) ?>

Al utilizar el renderizador esi (mediante la función render_esi de Twig), le estás diciendo a Symfony2 que la acción se debe renderizar a través de una etiqueta ESI. Quizás te preguntes por qué tienes que utilizar un helper en vez de escribir directamente la etiqueta ESI tú mismo. El motivo es que el helper hace que la aplicación funcione incluso cuando no está disponible una caché de tipo gateway.

Cuando se utiliza la función render (o cuando se utiliza la función inline en el renderizador), Symfony2 fusiona el contenido incluido con el contenido principal de la página antes de enviar la respuesta al usuario. Pero si utilizas el renderizador esi (es decir, si empleas la función render_esi) y si Symfony2 detecta que está disponible una caché de tipo gateway que soporte ESI, entonces sí que se genera una etiqueta ESI. Si no está disponible la caché o si no soporta ESI, Symfony2 simplemente fusiona el contenido incluido de la misma manera que se hubiera hecho con la función render tradicional.

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.

Si utilizas como referencia un controlador, la etiqueta ESI debería apuntar a una acción accesible mediante una URL propia, de manera que la caché pueda acceder a ella de forma independiente al resto de la página. Symfony2 se encarga de generar una URL única para cada controlador al que hagas referencia y también es capaz de generar las rutas mediante un listener que debes activar en tu configuración:

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

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.

Truco El listener solamente responde a direcciones IP locales o a los proxies que hayas configurado como confiables.

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_esi 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.