Varnish : header Vary:User-Agent et gestion du cache en fonction du device

Si votre site n’est pas full responsive, et qu’en fonction du type de device ( mobile ou sur desktop) l’affichage et le contenu est différent pour une même url il faut donc gérer des caches différents.
De plus Varnish se base sur le contenu du header Vary pour gérer différents cache. Si votre application ou votre serveur web rajoute un header Vary: User-Agent , alors Varnish va géré un cache par version différente du User-Agent. Il faut savoir qu’il y a énormément de version, et qu’en plus cette version dépend également du système d’exploitation de l’utilisateur (Windows / Linux / Mac / Android …) , donc autant dire qu’il n’y aura pour une même page plus de 1000 objets différents. Tout ceci sera totalement inutile pour vos utilisateurs.

Pour gérer ces deux contraintes il faut donc normaliser le header Vary.

Créer le fichier mobiledetect.vcl avec le code suivant :

sub mobiledetect {
   unset req.http.X-User-Agent;
   set req.http.X-User-Agent = "desktop";
   if (req.http.User-Agent ~ "(?i)(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino"
      ||
      req.http.User-Agent ~ "(?i)^1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-"
      ) {
      set req.http.X-User-Agent = "mobile";
   }
}

sub vcl_backend_response {
   set beresp.http.X-User-Agent = bereq.http.X-User-Agent;

   if (bereq.http.X-User-Agent) {
      if (!beresp.http.Vary) {
         set beresp.http.Vary = "X-User-Agent";
      } elseif (beresp.http.Vary ~ "User-agent") {

         set beresp.http.Had-User-Agent = "yes";
         set beresp.http.Vary = regsub(beresp.http.Vary, ",? *User-agent*", "");
         set beresp.http.Vary = regsub(beresp.http.Vary, "^, *", "");
         if (beresp.http.Vary == "") {
            set beresp.http.Vary = "X-User-Agent";
         } else {
            set beresp.http.Vary = beresp.http.Vary + ", X-User-Agent";
         }
      } elseif (beresp.http.Vary !~ "X-User-Agent") {
         set beresp.http.Vary = beresp.http.Vary + ", X-User-Agent";
      }
   }
}

sub vcl_deliver {
   # Reset User-Agent is present in request
   if ((resp.http.X-User-Agent) && (resp.http.Vary)) {

      if (resp.http.Had-User-Agent) {

         set resp.http.Vary = regsub(resp.http.Vary, "X-User-Agent", "User-Agent");
      } else {
         set resp.http.Vary = regsub(resp.http.Vary, "X-User-Agent", "");
      }
      set resp.http.Vary = regsub(resp.http.Vary, ",$", "");
      if (resp.http.Vary == "") {
         unset resp.http.Vary;
      }
   }
}

La fonction mobiledetect  va gérer deux version du User-agent pour différencier les mobiles des PC. Elle est basé sur le code fourni par ce site Detect Mobile Browsers

Ensuite, le reste du code va normaliser le header Vary si ce dernier contenait User-agent, afin de gérer un nombre de cache restreint, puis il va remettre le header Vary d’origine pour le retourner au client. Explication en image :

Varnish cache normalisation du header Vary:User-Agent

Pour faire fonctionner ce système il faut inclure ce fichier dans le fichier de conf de Varnish include « /etc/varnish/mobiledetect.vcl »;
puis il faut appeler ce script via un call mobiledetect;  dans la fonction vcl_recv

Attention, comme de nombreux exemples le montre, il n’est pas nécessaire de rajouter la variable X-User-Agent ou même le Vary, au hash. Varnish utiliseen effet indépendamment le Vary et vcl_hash(). Référence

Explication en image sur la façon dont Varnish gère la clé de cache Varnish Cache : gestion de la clé avec vcl_hash et Vary

Cette méthode est également à appliquer pour toutes les valeurs différentes que peut prendre le header Vary (Cookies, Accept-Language). Voir l’explication donnée par Varnish