har

HAR outils

Un fichier HAR est une transcription JSON de tout ce que votre navigateur a vu sur le réseau : comment il est structuré, ce que signifient ses sept phases de temps, et ce qu'il faut expurger avant de le partager.

2 outils

§01 GUIDE DU DOMAINE

Un HAR est une transcription, pas une capture de paquets

Un fichier HAR (HTTP Archive) est un unique document JSON décrivant ce qu’un navigateur a observé sur le réseau. Tout l’intéressant vit dans log.entries[] : un élément par paire requête/réponse, portant la méthode, l’URL, les en-têtes, les cookies, les tailles, une décomposition des temps phase par phase et — parfois — les corps. Le format est HAR 1.2, un brouillon du W3C jamais ratifié, et pourtant Chrome, Firefox, Safari, Charles et Fiddler émettent des dialectes suffisamment proches pour que les fichiers circulent entre eux.

Le modèle mental qui vous fait gagner des heures : le fichier est écrit par la pile réseau du navigateur lui-même, après coup — il contient le résumé de cette pile, pas le réseau. Aucun enregistrement de poignée de main TLS, aucune trame HTTP/2, aucun paquet DNS, seulement les durées que la pile a choisi de rapporter. Les requêtes parties avant l’ouverture de DevTools sont absentes, comme la navigation précédente à moins que Preserve log n’ait été actif, et les occurrences servies par le cache ou un service worker apparaissent comme des entrées qui n’ont jamais touché le réseau. Deux captures du « même » bug ne concordent pratiquement jamais.

Anatomie du fichier

log
├─ version "1.2" · creator{name,version} · browser
├─ pages[]     id, title, startedDateTime, pageTimings{onContentLoad,onLoad}
└─ entries[]   pageref, startedDateTime, time, connection, serverIPAddress
   ├─ request  method, url, httpVersion, headers[], cookies[],
   │           queryString[], postData{mimeType,text,params}
   ├─ response status, statusText, redirectURL, headers[], cookies[],
   │           content{size,compression,mimeType,text,encoding}
   ├─ cache    beforeRequest, afterRequest
   └─ timings  blocked, dns, connect, ssl, send, wait, receive

Deux détails piègent les gens. request.headers[] et request.cookies[] sont des représentations distinctes des mêmes cookies : un nettoyage qui ne parcourt que les en-têtes laisse donc le pot intact. Les clés commençant par _ (_initiator, _priority, _resourceType, _webSocketMessages) sont des extensions propres à l’éditeur : utiles dans Chrome, absentes ailleurs. Et presque tout champ numérique peut valoir -1indisponible, et non zéro — y compris bodySize, pageTimings et chaque phase de temps.

Les sept phases de temps

En millisecondes, dans l’ordre où elles se produisent.

PhaseMesureUne valeur élevée signifie souvent-1 quand
blockedMise en file avant que la requête ne quitte le clientLe plafond d’environ 6 par origine de HTTP/1.1 ; négociation de proxyJamais mise en file
dnsRésolution du nom de l’hôteRésolveur froid, longue chaîne de CNAMEConnexion réutilisée
connectPoignée de main TCP — inclut sslOrigine distante, RTT élevé, pas de keep-aliveConnexion réutilisée
sslNégociation TLS (comptée à l’intérieur de connect)Longue chaîne de certificats, pas de reprise de sessionHTTP simple, ou réutilisation
sendÉmission des octets de la requêteUn corps d’envoi volumineux
waitTTFB après le dernier octet de la requêteTraitement backend plus un RTT
receiveLecture du corps sur le réseauCharge utile non compressée ou surdimensionnée

entry.time est le temps total écoulé, en pratique blocked + dns + connect + send + wait + receive. Comme ssl est imbriqué dans connect, additionner les sept colonnes compte deux fois la poignée de main — l’erreur arithmétique la plus courante en analyse de HAR.

Reconstituer la cascade

Les deux axes sont récupérables depuis le JSON : la position horizontale est entry.startedDateTime moins le log.pages[].startedDateTime propriétaire ; la largeur est entry.time, découpée selon les phases ci-dessus. entry.connection montre quelles requêtes ont réutilisé une socket.

Ensuite, lisez la forme, pas les nombres. Un escalier — chaque requête démarrant quand la précédente se termine — est une chaîne de dépendances, HTML → JS → API → image, et aucun réglage serveur ne la corrige ; c’est la découverte qui doit avancer plus tôt. Un bloc dense dont le segment blocked grandit à mesure qu’on descend la liste est de la mise en file côté client, ce qui explique pourquoi HTTP/2 aplatit souvent une cascade sans qu’aucune réponse ne devienne plus rapide.

Choisir le bon outil

Collez d’abord la capture dans la Visionneuse HAR — également dans la sélection SRE. Elle aplatit log.entries[] en Method / Status / Type / Size / Time / URL avec un récapitulatif N requests · X KB · Y ms total, en colorant la cellule Status en rouge pour les 4xx/5xx et en orange pour les 3xx : le moyen le plus rapide de trouver l’anomalie. Métadonnées uniquement, jamais les corps.

La visionneuse donne des totaux par entrée, pas la répartition par phase. Pour les phases, interrogez le fichier brut avec le Chercheur JSONPath$..timings pour toutes les décompositions, ou $.log.entries[?(@.time>1000)].request.url pour les seules requêtes lentes. Si l’export refuse de s’analyser (téléchargement tronqué, collage avec retours à la ligne), passez-le dans le Formateur JSON pour obtenir la position exacte de l’erreur selon l’analyseur.

Pour la colonne Status, les Codes HTTP transforment un simple nombre en nom, classe et signification d’une ligne, filtrables par code ou mot-clé — la façon rapide de trancher entre 400 et 422. Ils s’arrêtent à la signification, pas au comportement de redirection : leur ligne 301 dit seulement que la ressource a été déplacée définitivement. Pour lire une chaîne, ajoutez ceci qui vient du protocole lui-même — 301 et 302 autorisent un client à réécrire un POST en GET et à abandonner le corps, tandis que 307 et 308 ne le permettent pas. La comparaison complète est la matrice de redirections des outils HTTP, qui couvre aussi le côté en-têtes. Sans capture, avec seulement une URL, les En-têtes HTTP la chargent côté serveur depuis api.sitekits.dev et renvoient le statut, le nombre de sauts de redirection et tous les en-têtes de réponse — corps jamais récupéré, URL jamais conservée. Décodez le jeton porteur d’un 401 avec le Décodeur JWT pour voir si exp était dépassé ; rejouez-le avec le Testeur REST API, qui émet depuis votre navigateur droit vers la cible, si bien que le CORS s’applique comme dans votre application.

Pièges

Un HAR brut est un identifiant

Cookies de session, Authorization: Bearer …, x-api-key, URL signées, corps de connexion et toute réponse que votre session pouvait lire s’y trouvent. Le Nettoyeur HAR s’exécute entièrement dans votre navigateur — rien n’est téléversé — en remplaçant les en-têtes sensibles, les paramètres correspondant à token|key|secret|password|passwd|pwd|auth|session|sig|signature, et les valeurs entières de postData.text / content.text par [REDACTED], tout en rapportant combien il en a attrapé. Faites-le même si votre navigateur a proposé un export « assaini » — ce que ceux-là retirent varie selon la version.

L’expurgation par motifs n’est pas une preuve

Les noms d’en-têtes sont comparés à une liste fixe, les noms de paramètres à une expression régulière : tout ce qui est inhabituel survit donc — secrets dans les segments de chemin d’une URL (/v1/reset/9f3c…), tableaux structurés cookies[], serverIPAddress (l’IP réelle de votre origine), noms d’hôtes internes. Parcourez la sortie, collez toute URL suspecte dans le Parseur d’URL pour une chaîne de requête décodée, et cherchez authorization et set-cookie avant que le fichier ne quitte votre machine. Même réflexe pour les outils de confidentialité et la sélection sécurité.

content.size et bodySize ne mesurent pas la même chose

content.size est la longueur du corps décodé ; bodySize est le nombre d’octets réellement reçus, content.compression enregistrant l’économie. Les réponses issues du cache rapportent bodySize: 0, si bien qu’un total en Ko calculé depuis content.size surestime le coût réseau d’un site compressé.

FAQ
Pourquoi le temps total de mon HAR ne correspond-il pas au temps de chargement que j'ai mesuré ?
Parce que les requêtes se chevauchent. Additionner entry.time sur log.entries[] compte deux fois chaque milliseconde pendant laquelle deux requêtes étaient en vol : la somme représente donc généralement plusieurs fois le temps réel. Pour le temps réel, lisez log.pages[].pageTimings.onLoad, ou prenez l'intervalle entre le startedDateTime le plus ancien et le startedDateTime le plus récent augmenté de son time.
Que signifie réellement une phase wait longue dans une entrée HAR ?
wait est le temps jusqu'au premier octet mesuré après l'envoi du dernier octet de la requête : il couvre donc le traitement propre du serveur plus un aller-retour réseau. Un wait épais avec un receive fin désigne le backend — requête lente, cache froid, saut entre régions. La forme inverse, wait fin et receive épais, signifie que la réponse était simplement volumineuse ou que le lien était lent.
J'ai expurgé les en-têtes Cookie de mon HAR — pourquoi les cookies de session sont-ils toujours dans le fichier ?
Parce que le HAR stocke les cookies deux fois. request.headers[] contient la ligne Cookie brute, tandis que request.cookies[] et response.cookies[] contiennent les mêmes valeurs analysées en objets nom/valeur : toute passe qui ne parcourt que les en-têtes laisse donc le pot intact — y compris le Nettoyeur HAR de ce site, qui expurge les valeurs d'en-têtes, les paramètres de requête et de POST et les deux corps, mais ne touche pas aux tableaux cookies[]. Cherchez « cookies » dans le fichier avant de le joindre, et profitez-en pour vérifier serverIPAddress et tout secret logé dans un segment de chemin d'URL.
Pourquoi certaines entrées HAR affichent-elles dns -1 et connect -1 ?
Ces phases n'ont pas eu lieu pour cette requête. En HAR 1.2, -1 signifie que la valeur est sans objet ou indisponible, ce que vous obtenez quand la requête a emprunté une connexion keep-alive existante et n'a donc eu besoin ni de résolution DNS, ni de poignée de main TCP, ni de négociation TLS. Traiter -1 comme zéro est sans conséquence ; le moyenner comme s'il s'agissait d'une vraie mesure ne l'est pas.
Pourquoi les corps de requête et de réponse manquent-ils dans mon HAR ?
postData.text et content.text sont des champs optionnels, et DevTools omet couramment les charges utiles volumineuses ou binaires pour garder l'export manipulable. Quand un corps est présent, il peut être en Base64 plutôt qu'en texte brut, ce que signale content.encoding valant base64. Un corps absent signifie que l'exportateur ne l'a pas enregistré, non que la réponse était vide.