HTTP/2 promettait un Web beaucoup plus rapide et Cloudflare a déployé l’accès HTTP/2 à tous nos clients il y a très longtemps. Mais une caractéristique de HTTP/2, la hiérarchisation, n’était pas à la hauteur des attentes qu'il soulevait. Non pas parce qu'elle était fondamentalement défaillante, mais à cause de la façon dont les navigateurs l'ont implémenté.

Aujourd'hui, Cloudflare propose une modification de la hiérarchisation HTTP/2 qui donne à nos serveurs le contrôle des décisions de hiérarchisation qui rendent réellement le Web beaucoup plus rapide.

Historiquement, le navigateur contrôlait comment et quand le contenu Web était chargé. Aujourd'hui, nous introduisons un changement radical dans ce modèle pour tous les forfaits payants, ce qui donne directement le contrôle au propriétaire du site. Les clients peuvent activer l'option « hiérarchisation HTTP/2 améliorée » dans l'onglet Vitesse du tableau de bord Cloudflare : cette option remplace les paramètres par défaut du navigateur par un système de planification amélioré qui permet aux visiteurs de profiter d’un Web nettement plus rapide (nous avons constaté une vitesse 50 % plus rapide à plusieurs reprises). Avec Cloudflare Workers, les propriétaires de sites peuvent aller plus loin et personnaliser pleinement l'expérience en fonction de leurs besoins spécifiques.

Historique

Les pages Web sont constituées de dizaines (parfois des centaines) de ressources différentes qui sont chargées et assemblées par le navigateur dans le contenu final affiché. Cela inclut le contenu visible avec lequel l'utilisateur interagit (HTML, CSS, images), ainsi que la logique d'application (JavaScript) du site lui-même, les annonces, les analyses permettant de suivre l'utilisation du site et les balises de repérage markéting. Le séquencement du mode de chargement de ces ressources peut avoir un impact significatif sur le temps nécessaire à l'utilisateur pour voir le contenu et interagir avec la page.

Un navigateur est essentiellement un moteur de traitement HTML qui parcourt le document HTML et suit les instructions dans l'ordre, du début à la fin de l’HTML, en construisant la page au fur et à mesure. Les références aux feuilles de style (CSS) indiquent au navigateur comment styliser le contenu de la page et le navigateur retarde l'affichage du contenu jusqu'à ce qu'il ait chargé la feuille de style (afin de savoir comment styliser le contenu qu'il va afficher). Les scripts référencés dans le document peuvent avoir divers comportements. Si le script est marqué comme « asynchrone » ou « différé », le navigateur peut continuer à traiter le document et simplement exécuter le code de script chaque fois que les scripts sont disponibles. Si les scripts ne sont pas marqués comme étant asynchrones ou différés, le navigateur DOIT arrêter le traitement du document jusqu'à ce que le script soit téléchargé et exécuté avant de continuer. Ces scripts sont appelés scripts de « blocage » car ils empêchent le navigateur de continuer à traiter le document jusqu'à ce qu'ils aient été chargés et exécutés.

Le document HTML est divisé en deux parties. La <tête> du document se trouve au début et contient les feuilles de style, les scripts et autres instructions nécessaires au navigateur pour afficher le contenu. Le <corps> du document vient après la tête et contient le contenu réel de la page affiché dans la fenêtre du navigateur (bien que les scripts et les feuilles de style puissent également figurer dans le corps). Tant que le navigateur n'atteint pas le corps du document, rien ne peut être affiché pour l'utilisateur et la page reste vide. Il est donc important de traverser rapidement la tête du document. « HTML5 rocks » propose un excellent tutoriel sur le fonctionnement des navigateurs, si vous souhaitez approfondir les détails.

Généralement, il revient au navigateur de déterminer l’ordre de chargement des différentes ressources nécessaires à la création de la page et à la poursuite du traitement du document. Dans le cas d’HTTP/1.x, le navigateur est limité dans le nombre de ressources qu’il peut demander à un serveur à la fois (généralement 6 connexions et une seule ressource à la fois par connexion), de sorte que la commande est strictement contrôlée par le navigateur en fonction de la manière dont les ressources sont demandées. Avec HTTP/2, les choses changent de manière importante. Le navigateur peut demander toutes les ressources en même temps (au moins dès qu’il en a connaissance) et fournit des instructions détaillées au serveur sur la manière dont les ressources doivent être livrées.

Commande optimale de ressources

Pour la plupart des phases du cycle de chargement des pages, il y a une commande optimale des ressources qui permettra une expérience utilisateur plus rapide (et la différence entre optimal et non peut être importante, autant qu'une amélioration de 50 % ou plus).

Comme décrit ci-dessus, au début du cycle de chargement de la page, avant que le navigateur ne puisse restituer le contenu, il est bloqué sur le CSS et bloque JavaScript dans la section <tête> du HTML. Pendant cette phase du cycle de chargement, il est préférable d’utiliser 100 % de la bande passante de la connexion pour télécharger les ressources de blocage et de les télécharger une par une dans l’ordre dans lequel elles sont définies dans le HTML. Cela permet au navigateur d’analyser et d’exécuter chaque élément pendant le téléchargement de la prochaine ressource de blocage, permettant ainsi le téléchargement et l’exécution en chaîne.

Les scripts mettent le même temps à télécharger lorsqu'ils sont téléchargés en parallèle ou l'un après l'autre. Cependant, en les téléchargeant de manière séquentielle, le premier script peut être traité et exécuté pendant le téléchargement du second.

Une fois que le contenu bloquant le rendu a été chargé, les choses deviennent un peu plus intéressantes et le chargement optimal peut dépendre du site spécifique ou même des priorités de l’entreprise (contenu utilisateur, annonces, analyse, etc.). Les polices (en particulier) peuvent être difficiles, du fait que le navigateur ne découvre les polices dont il a besoin qu'une fois les feuilles de style appliquées au contenu sur le point d'être affiché. Par conséquent, lorsque le navigateur connaît une police, il est nécessaire d'afficher le texte déjà prêt à être affiché à l'écran. Un retard dans le chargement de la police se transforme en points avec du texte vide à l'écran (ou du texte affiché avec une police incorrecte).

En général, il y a des compromis à prendre en compte :

  • Les polices personnalisées et les images visibles dans la partie visible de la page (fenêtre) doivent être chargées le plus rapidement possible. Ils ont un effet directe sur l'expérience visuelle de l'utilisateur lors du chargement de la page.
  • JavaScript non bloquant doit être téléchargé en série par rapport aux autres ressources JavaScript, afin que l'exécution de chacune puisse être mise en chaine avec les téléchargements. JavaScript peut inclure une logique applicative orientée vers l’utilisateur, de même que des balises de repérage et de marketing d'analyse et leur retard peuvent entraîner une chute des métriques suivies par l'entreprise.
  • Les images bénéficient du téléchargement en parallèle. Les premiers octets d'un fichier image contiennent les dimensions des images pouvant être nécessaires à la mise en page du navigateur et le téléchargement progressif des images en parallèle peut paraître visuellement complet avec environ 50 % des octets transférés.

En étudiant les avantages et inconvénients, une stratégie qui fonctionne bien dans la plupart des cas est la suivante :

  • Les polices personnalisées se téléchargent séquentiellement et divisent la bande passante disponible avec des images visibles.
  • Les images visibles se téléchargent en parallèle, divisant la part « d'images » de la bande passante entre eux.
  • Lorsqu'il n'y a plus de polices ou d'images visibles en attente :
  • Les scripts non bloquants se téléchargent séquentiellement et divisent la bande passante disponible en images non visibles
  • Les images non visibles se téléchargent en parallèle, divisant la part « d'images » de la bande passante.

Ainsi, le contenu visible par l'utilisateur est chargé le plus rapidement possible, la logique applicative est retardée le moins possible et les images non visibles sont chargées de manière à ce que la mise en page puisse être achevée le plus rapidement possible.

Exemple

À titre d'illustration, nous utiliserons une page de catégorie de produit simplifiée à partir d'un site de commerce électronique typique. Dans cet exemple, la page a :

  • Le fichier HTML de la page elle-même, représenté par une case bleue.
  • 1 feuille de style externe (fichier CSS), représentée par une case verte.
  • 4 scripts externes (JavaScript), représentés par des cases oranges. 2 des scripts bloquent au début de la page et 2 sont asynchrones. Les cases de script de blocage utilisent une nuance orange plus sombre.
  • 1 police Web personnalisée, représentée par une case rouge.
  • 13 images, représentées par des cases violettes. Le logo de la page et 4 des images du produit sont visibles dans la fenêtre d'affichage. 8 images du produit nécessitent un défilement pour être vues. Les 5 images visibles utilisent un violet plus foncé.

Par souci de commodité, nous supposerons que toutes les ressources ont la même taille et qu’elles prennent chacune une seconde à télécharger sur la connexion du visiteur. Il faut au total 20 secondes pour tout charger, mais le MODE de chargement peut avoir un impact considérable sur l'expérience.

Voici à quoi ressemblerait le chargement optimal décrit dans le navigateur lorsque les ressources se chargent :

  • La page est vide pendant les 4 premières secondes lors du chargement des scripts HTML, CSS et de blocage, qui utilisent tous 100 % de la connexion.
  • Au bout de 4 secondes, l'arrière-plan et la structure de la page sont affichés sans texte ni image.
  • Une seconde plus tard, à 5 secondes, le texte de la page est affiché.
  • À partir de 5 à 10 secondes, les images se chargent. Elles sont floues mais deviennent nettes très rapidement. À environ 7 secondes, il est presque impossible de la distinguer de la version finale.
  • Au bout de 10 secondes, tout le contenu visuel de la fenêtre a été chargé.
  • Au cours des deux secondes qui suivent, le JavaScript asynchrone est chargé et exécuté, exécutant toute logique non critique (analyses, balises marketing, etc.).
  • Pendant les 8 dernières secondes, les autres images du produit sont chargées, afin d’être prêtes au moment où l'utilisateur parcours la page.

Hiérarchisation du navigateur actuel

Tous les moteurs de navigateur actuels implémentent différentes stratégies de hiérarchisation, dont aucune n'est optimale.

Microsoft Edge et Internet Explorer ne prennent pas en charge la hiérarchisation. Tout dépend donc de la configuration par défaut de HTTP/2, qui consiste à tout charger en parallèle, en divisant la bande passante de manière égale entre tous les éléments. Microsoft Edge envisage d'utiliser le moteur de navigateur Chromium dans les prochaines versions de Windows, ce qui contribuera à améliorer la situation. Dans notre page d'exemple, cela signifie que le navigateur est coincé dans la tête pendant la majeure partie du temps de chargement car les images ralentissent le transfert des scripts et feuilles de style bloquants.

Visuellement, il est très pénible de regarder un écran vide pendant 19 secondes avant l’affichage du contenu, suivi d’un délai d’une seconde pour l’affichage du texte. Soyez patient lorsque vous regardez l’évolution du processus car, pendant les 19 secondes d’écran vide, il peut sembler que rien ne se passe (même si ce n’est pas le cas) :

Safari charge toutes les ressources en parallèle, en divisant la bande passante en fonction de l'importance que Safari accorde à chacune d’elles (les ressources bloquant le rendu comme les scripts et les feuilles de style étant plus importantes que les images). Les images se chargent en parallèle, mais également en même temps que le contenu bloquant le rendu.

Bien que similaire à Edge (qui tout se télécharge en même temps), en allouant plus de bande passante aux ressources bloquant le rendu, Safari peut afficher le contenu bien avant :

  • Au bout de 8 secondes environ, le chargement de la feuille de style et des scripts est terminé pour que la page puisse commencer à s'afficher. Les images se chargeant en parallèle, elles peuvent également être rendues dans leur état partiel (flou pour les images progressives). C'est toujours deux fois plus lent que le cas optimal mais beaucoup mieux que ce que nous avons vu avec Edge.
  • Au bout de 11 secondes environ, la police est chargée pour que le texte puisse être affiché et que davantage de données d'image soient téléchargées, ce qui rend les images un peu plus nettes. Ceci est comparable à l'expérience après environ 7 secondes pour un cas de chargement optimal.
  • Pendant les 9 secondes restantes de la charge, les images deviennent plus nettes à mesure que plus de leurs données sont téléchargées jusqu'à ce qu'elles soient finalement terminées à 20 secondes.

Firefox crée un arbre de dépendance qui regroupe les ressources, puis programme les groupes pour se charger l'un après l'autre ou pour partager la bande passante entre eux. Au sein d'un groupe donné, les ressources partagent la bande passante et sont téléchargées simultanément. Les images sont planifiées pour se charger après les feuilles de style bloquant le rendu et pour se charger en parallèle, mais les scripts et les feuilles de style bloquant le rendu se chargent également en parallèle et ne profitent pas des avantages du traitement en chaîne.

Dans notre exemple, cela s'avère être une expérience légèrement plus rapide qu'avec Safari car les images sont retardées jusqu'à la fin des feuilles de style :

  • Au bout de 6 secondes, le contenu de la page initiale est rendu avec les versions d'arrière-plan et floues des images du produit (comparé à 8 secondes pour Safari et 4 secondes pour le cas optimal).
  • À 8 secondes, la police est chargée et le texte peut être affiché avec des versions légèrement plus nettes des images du produit (comparé à 11 secondes pour Safari et 7 secondes dans le cas Optimal).
  • Pendant les 12 secondes restantes du chargement, les images du produit deviennent plus nettes à mesure que le contenu restant se charge.

Chrome (et tous les navigateurs basés sur Chromium) hiérarchise les ressources dans une liste. Cela fonctionne vraiment bien pour le contenu bloquant le rendu qui tire profit du chargement dans l’ordre mais fonctionne moins bien pour les images. Chaque image est chargée à 100 % avant de commencer l'image suivante.

En pratique, c’est presque aussi intéressant que le cas de chargement optimal, la seule différence étant que les images se chargent une à la fois et non en parallèle :

  • Jusqu'à 5 secondes, l'expérience de Chrome est identique à celle du cas optimal : l'arrière-plan s'affiche à 4 secondes et le contenu du texte à 5 secondes.
  • Pendant les 5 secondes suivantes, les images visibles se chargent une par une jusqu'à ce qu'elles soient toutes terminées après 10 secondes (par rapport au cas optimal où elles sont légèrement floues à 7 secondes et deviennent plus nettes pendant les 3 secondes restantes).
  • Une fois la partie visuelle de la page terminée au bout de 10 secondes (identique au cas optimal), les 10 secondes restantes sont consacrées à l’exécution des scripts asynchrones et au chargement des images masquées (comme avec le cas de chargement optimal).

Comparaison visuelle

Sur le plan visuel, l’impact peut être assez fort, même s’ils prennent tous le même temps pour charger techniquement tout le contenu :

Hiérarchisation côté serveur

La hiérarchisation HTTP/2 est demandée par le client (navigateur) et il appartient au serveur de décider de ce qu’il faut faire en fonction de la demande. Un bon nombre de serveurs ne sont pas disposés à faire quoi que ce soit avec la hiérarchisation mais pour ceux qui le sont, ils respectent tous la demande du client. Une autre option serait de décider de la meilleure hiérarchisation à utiliser côté serveur, en tenant compte de la demande du client.

Conformément à la spécification, la hiérarchisation HTTP/2 est un arbre de dépendance qui nécessite une connaissance approfondie de toutes les requêtes en vol pour pouvoir hiérarchiser les ressources les unes par rapport aux autres. Cela permet des stratégies incroyablement complexes, mais difficile à mettre en œuvre côté navigateur ou côté serveur, comme en témoignent les différentes stratégies de navigateur et les différents niveaux de prise en charge du serveur. Pour faciliter la gestion de la hiérarchisation, nous avons développé un schéma de hiérarchisation plus simple qui offre toute la flexibilité nécessaire pour une planification optimale.

Le schéma de hiérarchisation de Cloudflare comprend 64 « niveaux ». À l'intérieur de chaque niveau de priorité, il existe des groupes de ressources qui déterminent le mode de partage de connexion entre eux :

Toutes les ressources d'un niveau de priorité supérieur sont transférées avant de passer au niveau de priorité immédiatement inférieur.

Dans un niveau de priorité donné, il existe 3 différents groupes de « concurrence » :

  • 0 : Toutes les ressources du groupe « 0 » de concurrence sont envoyées séquentiellement dans l'ordre dans lequel elles ont été demandées, en utilisant 100 % de la bande passante. Ce n’est que lorsque toutes les ressources du groupe « 0 » de concurrence ont été téléchargées que d'autres groupes du même niveau seront pris en compte.
  • 1 : Toutes les ressources du groupe « 1 » de concurrence sont envoyées séquentiellement dans l’ordre dans lequel elles ont été demandées. La bande passante disponible est divisée de manière égale entre le groupe « 1 » de concurrence et le groupe « n » de concurrence.
  • n : Les ressources du groupe « n » de concurrence sont envoyées en parallèle, ce qui divise la bande passante disponible pour le groupe entre les ressources de ce dernier.

Concrètement, le groupe « 0 » de concurrence est utile pour les contenus critiques devant être traités de manière séquentielle (scripts, CSS, etc.). Le groupe « 1 » de concurrence est utile pour les contenus moins importants pouvant partager la bande passante avec d'autres ressources. Cependant, lorsque les ressources elles-mêmes continuent de bénéficier du traitement séquentiel (scripts asynchrones, images non progressives, etc.). Le groupe « n » de concurrence est utile pour les ressources bénéficiant d'un traitement en parallèle (images progressives, vidéo, audio, etc.).

Hiérarchisation par défaut de Cloudflare

Lorsqu'elle est activée, la hiérarchisation améliorée implémente la planification « optimale » des ressources décrite ci-dessus. Les hiérarchisations spécifiques appliquées ressemblent à ceci :

Ce système de hiérarchisation permet d'envoyer le contenu bloquant le rendu en série, suivi par les images visibles en parallèle, puis le reste du contenu de la page avec un certain niveau de partage pour équilibrer l'application et le chargement du contenu. L'avertissement «* Si détectable » est dû au fait que tous les navigateurs ne font pas la différence entre les divers types de feuilles de style et scripts mais ce sera toujours beaucoup plus rapide dans tous les cas. 50 % plus rapide par défaut n'est pas inhabituel, en particulier pour les visiteurs d’Edge et Safari :

Personnalisation de la hiérarchisation avec Workers

Plus rapide par défaut, c'est bien, mais là où les choses deviennent vraiment intéressantes, c'est également parce qu’il est possible de configurer la hiérarchisation avec Cloudflare Workers, ce qui permet aux sites de remplacer la hiérarchisation par défaut des ressources ou d'implémenter leurs propres systèmes de hiérarchisation complets.

Si un Worker ajoute un en-tête « cf-priority » à la réponse, les serveurs périphériques Cloudflare utiliseront la priorité et la concurrence spécifiées pour cette réponse. Le format de l'en-tête est <priorité>/<concurrence>, donc quelque chose comme réponse.headers.set ('cf-priority', « 30/0 »); définirait la priorité sur 30 avec une concurrence de 0 pour la réponse donnée. De même, « 30/1 » définirait la concurrence à 1 et « 30/n » définirait la concurrence à n.

Avec ce niveau de flexibilité, un site peut ajuster la hiérarchisation des ressources en fonction de ses besoins. Augmenter la priorité de certains scripts asynchrones importants, par exemple, ou augmenter la priorité des hero headers avant que le navigateur n'ait identifié leur présence dans la fenêtre d'affichage.

Pour aider à éclairer les décisions en matière de hiérarchisation, l’exécution de Workers expose également les informations de hiérarchisation demandées par le navigateur dans l’objet requête transmis à l’écouteur d'événement de récupération de Worker (request.cf.requestPriority). La priorité demandée entrante est une liste d'attributs délimitée par des points-virgules, qui ressemble à ceci : « poids = 192 ; exclusif = 0 ; groupe = 3 ; groupe-poids = 127 ».

  • poids : Le poids demandé par le navigateur pour la hiérarchisation HTTP/2.
  • exclusif : Indicateur exclusif HTTP/2 demandé par le navigateur (1 pour les navigateurs basés sur Chromium, 0 pour les autres).
  • groupe : ID de flux HTTP/2 pour le groupe de demandes (différent de zéro pour Firefox).
  • groupe-poids : Poids HTTP/2 pour le groupe de demandes (différent de zéro pour Firefox).

Ce n'est que le début

La possibilité de régler et de contrôler la hiérarchisation des réponses est le constituant de base dont bénéficieront de nombreux travaux futurs. Nous allons en plus mettre en œuvre nos propres optimisations avancées. Mais, en l'exposant dans Workers, nous l'avons également ouvert aux sites et aux chercheurs pour qu'ils expérimentent différentes stratégies de hiérarchisation. Avec Apps Marketplace, il est également possible pour les entreprises de créer de nouveaux services d'optimisation en plus de la plate-forme Workers et de les ouvrir à d'autres sites.

Si vous êtes sur un plan Pro ou supérieur, allez sur l'onglet Vitesse dans le tableau de bord Cloudflare et activez « Hiérarchisation HTTP / 2 améliorée » pour accélérer votre site.