Après une mise en place un peu laborieuse, voici un petit
récapitulatif de ce que j'ai fait pour faire fonctionner Varnish avec
Symfony 2.
La configuration utilisée : Ubuntu 11.10, Varnish 3.0.0-4, Symfony 2.0
Installation
Ouvrir le terminal et taper :
sudo apt-get install varnish
Configuration de Varnish
Ouvrir et modifier le fichier de configuration de Varnish, par exemple :
sudo vi /etc/varnish/default.vcl
Surcharger
les méthodes vcl_recv (réception des données), vcl_hash (construction
de la clé unique qui correspondra à une entrée de cache donnée) et
vcl_fetch :
sub vcl_recv {
set req.http.Surrogate-Capability = "abc=ESI/1.0";
if (req.restarts == 0) {
if (req.http.x-forwarded-for) {
set req.http.X-Forwarded-For =
req.http.X-Forwarded-For + ", " + client.ip;
} else {
set req.http.X-Forwarded-For = client.ip;
}
}
if (req.request != "GET" &&
req.request != "HEAD" &&
req.request != "PUT" &&
req.request != "POST" &&
req.request != "TRACE" &&
req.request != "OPTIONS" &&
req.request != "DELETE") {
/* Non-RFC2616 or CONNECT which is weird. */
return (pipe);
}
if (req.request != "GET" && req.request != "HEAD") {
/* We only deal with GET and HEAD by default */
return (pass);
}
if (req.http.Authorization || req.http.Cookie) {
/* Not cacheable by default */
#return (pass);
}
return (lookup);
}
set req.http.Surrogate-Capability = "abc=ESI/1.0";
permet de dire à Varnish de prendre en charge le langage ESI, qui est
un langage de balisage qui s'insère dans le code HTML, et déclenche des
traitement de construction de la page.
sub vcl_hash {
### these 2 entries are the default ones used for vcl. Below we add our own.
hash_data(req.url);
hash_data(req.http.host);
if( req.http.Cookie ~ "locale" ) {
hash_data(regsub( req.http.Cookie, "^.*?locale=([^;]*);*.*$", "\1" ));
}
return (hash);
}
if( req.http.Cookie ~ "locale" ) {
hash_data(regsub( req.http.Cookie, "^.*?locale=([^;]*);*.*$", "\1" ));
}
permet de gérer un cache par langue, dans le cas où la
langue n'est pas spécifiée dans l'URL. Par défaut, Varnish prend pour
hash l'URL de la page, donc si votre langue est en session, vous
obtiendrez le même contenu pour toutes vos langues. Mettre la locale en
cookie, puis ici ajouter sa valeur au hash règle ce problème.
Nous verrons plus bas comment mettre votre locale en cookie.
sub vcl_fetch {
if (beresp.http.surrogate-control ~ "ESI/1.0") {
unset beresp.http.surrogate-control;
set beresp.do_esi = true;
}
if (beresp.ttl <= 0s ||
beresp.http.Set-Cookie ||
beresp.http.Vary == "*") {
/*
* Mark as "Hit-For-Pass" for the next 2 minutes
*/
set beresp.ttl = 120 s;
return (hit_for_pass);
}
return (deliver);
}
Après avoir modifié ce fichier, redémarrez Varnish :
sudo service varnish restart
Configuration de Symfony2
app/config/config.yml
framework:
templating: { engines: ['twig'] }
session:
default_locale: fr
auto_start: false # Pour ne pas générer le PHPSESSID à chaque page (cache multi user)
esi: true # Pour utiliser les tags ESI dans les templates Twig
app/config/routing.yml
_internal:
resource: "@FrameworkBundle/Resources/config/routing/internal.xml"
prefix: /_internal
-> pour la prise en charge des routes internes de Symfony2 dans les tags ESI
web/app.php
//$kernel = new AppCache($kernel);
-> On commente cette ligne pour passer par Varnish et
non plus par le système de cache de Symfony2. (voir s’il y a une
possibilité de gérer ça autrement car ce n'est pas très propre !)
Mise en place des blocs et pages cachées dans Symfony2
Pour cacher un bloc, il faut que celui-ci soit rendu par une action :
{% render "NomDeBundle:Test:monAction" with {'locale': app.session.locale}, {'standalone': true} %}
La locale sert à gérer le cache par langue, du
fait que ce paramètre se retrouve dans l’url interne et donc dans le
hash utilisé par Varnish.
Si la langue n’est pas encore dans le cookie, le le hash ne contiendra
pas la locale ni fr, ni en, mais sera vide. Il est donc mieux de faire :
{% if app.session.locale %}
{% render "NomDeBundle:Test:monAction" with {'locale': app.session.locale}, {'standalone': true} %}
{% else %}
{% render "NomDeBundle:Test:monAction" with {'locale': app.session.defaultLocale}, {'standalone': true} %}
{% endif %}
L’option
'standalone': true va faire que ce bloc deviendra un tag ESI.
Le tag ESI ressemblera à :
<esi:include src="http://example.com/1.html" onerror="continue"/>
Dans l’action qui rend le bloc :
/**
* Renders the user cart block on the top right of the page
* ESI block
*
* @Extra\Template("NomDeBundle:Test:esi_header_top.html.twig")
*/
public function cartBlockAction() {
$response = new Response();
$response->setContent($this->renderView('NomDeBundle:Test:esi_header_top.html.twig', array()));
$response->setSharedMaxAge(600); # en secondes, donc environ 10 minutes
$response->setPublic();
return $response;
}
-> On ajoute les bons headers à la réponse, où
setSharedMaxAge est le temps en secondes de la durée de vie du cache.
-> Même fonctionnement pour les pages complètes.
Pour ne pas mettre en cache les blocs utilisant par exemple la session, mettre les headers adéquats dans la réponse :
$response->setMaxAge(0);
$response->setPrivate();
Cookie de langue
Comme on l'a
vu plus haut, si votre locale est dans votre session et non dans l'URL,
il faut créer un cookie contenant la locale, au moment où vous changez
votre locale ; dans une action changeLocaleAction par exemple.
Lorsque vous vous apprêtez à retourner l'objet Response :
$response->headers->setCookie(
new Cookie(
'locale',
$votreLocale,
time() + 600));
Pour faire vos tests
Créer un fichier de test qui fait un CURL sur une URL passée en paramètre GET avec le header “Surrogate-Capability:abc=ESI/1.0”
-> celà permet de voir les balises ESI dans le code source (par exemple en inspectant avec Firebug).
Par exemple
web/testvarnish.php
<?php
$url = $_GET["url"];
$ret = curl_init($url);
curl_setopt($ret, CURLOPT_HTTPHEADER, array(
'Surrogate-Capability:abc=ESI/1.0'
));
echo curl_exec($ret);
?>
Enfin, vous pouvez tester les performances avec un outil tel que JMeter.