Gestion du cache HTTP avec Symfony2

7 gravatar Par Grégoire Marchal - 27/08/2012

Avant de vous lancer dans la lecture de cet article, je vous conseille de lire la documentation Symfony concernant le cache. Comme d'habitude c'est assez clair, et ça me permet de ne pas avoir à tout vous expliquer :).

Pour ce blog, la stratégie que j'ai adoptée est la suivante : lors de l'affichage d'un article, j'utilise la date de dernière modification de celui-ci pour définir l'en-tête HTTP "Last-Modified". Si dans la requête je vois que le client a déjà cette version en cache, je peux arrêter le traitement et retourner le code HTTP 304 "Not Modified". Dans le cas contraire, je continue le traitement pour servir la page au client, qui au passage la stockera en cache pour un éventuel futur appel.

Niveau code, voilà ce que ça donne dans mon contrôleur :

public function displayAction(Post $post)
{
    $response = new Response();
    $response->setLastModified($post->getModificationDate());
    $response->setPublic();

    if ($response->isNotModified($this->getRequest())) {
        return $response; // this will return the 304 if the cache is OK
    }

    // Do some stuff here...

    return $this->render('...:display.html.twig', array(
        'post' => $post,
        // ...
    ), $response);
}

Les 3 premières lignes permettent de définir la stratégie utilisée pour la gestion du cache. Notez l'appel à setPublic() pour définir que le cache est commun à tous les utilisateurs, et non pas propre à l'utilisateur courant. Cela permet aux caches partagés (c'est-à-dire les proxy caches et les gateway caches) de stocker le cache également. Ainsi, si un utilisateur A a déjà affiché un article, un utilisateur B pourra profiter du cache qui a été généré pour l'utilisateur A, sans re-générer toute la page. Dans mon cas, c'est Symfony2 qui joue le rôle de gateway cache.

Ensuite, j'appelle la méthode $response->isNotModified() en passant la requête en paramètre. C'est donc le framework qui va décider si on retourne un code 304 ou non, en comparant la date du header "Last-Modified" (que l'on vient de définir) à la date du header "If-Modified-Since" envoyé par le client (date de la version qu'il a dans son cache). Si l'article n'a pas été modifié depuis sa mise en cache, on n'a plus qu'à retourner l'objet réponse, qui représente alors le fameux code 304 "Not Modified".

Dans le cas contraire, on poursuit le traitement du contrôleur. Dans mon cas il s'agit principalement de préparer le formulaire pour l'ajout de commentaires... Enfin je traite mon template, en n'oubliant de passer mon objet $response en 3e paramètre de $this->render(), afin que le header "Last-Modified" soit bien ajouté à la réponse pour la mise en cache ; si vous oubliez ce dernier point, Symfony va créer un nouvel objet Response standard, sans header pour la gestion du cache, et donc nos clients ne stockeront jamais notre page en cache !

Prochainement, je vous parlerai des "edge side includes" pour gérer le cache de la colonne de gauche de mon blog, et aussi des event listeners pour gérer la date de modification des articles en prenant en compte les commentaires...

A bientôt !

Retour à l'accueil

Commentaires (7)

gravatar Par Christophe, le 28/08/2012 à 06:19
Merci pour ces premières précisions :)
J'ai remarqué hier qu'après avoir posté un commentaire sur un article, le compteur de commentaires de la page d'accueil du blog ne répercute pas de suite ce commentaire. Je suppose que sur cette page d'accueil du blog, tu as un rechargement automatique toutes les X minutes ?

PS: si on pouvait faire mémoriser nos coordonnées pour les commentaires ça serait top !
PS2: quand j'envoie mon commentaire, j'ai l'erreur "Le jeton CSRF est invalide. Veuillez renvoyer le formulaire.". Il faut que je le renvoie pour que ça passe.
gravatar Par Grégoire Marchal, le 28/08/2012 à 09:09
Pour la page d'accueil, pour définir le "Last-Modified" je récupère la date de modification du dernier post modifié, donc normalement ce problème ne devrait pas survenir... C'est bizarre.

Pour le PS2, le jeton CSRF est lui aussi mis en cache, ce qui provoque cette erreur... Je vais voir comment gérer ça.
Pour le PS1, là aussi le cache me met des bâtons dans le roues pour gérer ça... :)
gravatar Par Christophe, le 28/08/2012 à 11:01
Je me suis mal exprimé pour la page d'accueil : je parlais de la liste des articles, pas de l'encadré "Derniers commentaires" à gauche. Après avoir posté ce commentaire, quand je vais aller à l'accueil, dans la liste des articles, je vais voir un "2" au lieu de "3" dans la petite bulle à côté du titre de l'article.
gravatar Par Grégoire Marchal, le 28/08/2012 à 14:07
Oui c'est bien ce que j'avais compris.
On dirait que c'est le navigateur qui est à l'origine de ce comportement. Un F5 et l'information est mise à jour...
gravatar Par Grégoire Marchal, le 31/08/2012 à 17:05
Finalement, j'ai désactivé le cache sur la page d'accueil et les pages d'article. Les ESI ne fonctionnent pas comme je le pensais, ils n'ont pas l'air de marcher quand la page qui les contient est mise en cache par validation (c'est-à-dire avec le header Last-Modified). D'où le problème de CSRF...
gravatar Par Cethy, le 14/09/2012 à 10:12
Ce ne serait pas plus simple (et plus propre) d'utiliser un kernel.event et intercepter la requete ? Plutôt que de polluer ton action avec une logique de cache ?
Je ne me suis pas encore penché sur la problématique, mais c'est dans cette direction que je partirai avant tout.
gravatar Par Grégoire Marchal, le 17/09/2012 à 09:16
Effectivement, ça me gêne aussi que l'action embarque ce code sur le cache. Je n'avais pas trouvé de solution pour contourner ce problème, mais l'idée du kernel.event est une bonne piste, j'essayerai de la creuser, merci !

Commenter