Pourquoi cela nous concerne : nous sommes Geonode et nous vendons des proxys ; les utilisateurs recourent donc constamment à nos services pour effectuer des requêtes HEAD afin de vérifier les liens, les tailles et la disponibilité à moindre coût. Il faut toutefois garder à l’esprit que la requête HEAD est distincte d’une requête GET « allégée », et la considérer comme telle conduit à des conclusions erronées mais apparemment fiables. Une URL qui renvoie un code 200 à une requête HEAD peut renvoyer un code 403 à une requête GET. Une ressource qui n’affiche aucune Content-Length avec une requête HEAD peut en afficher une avec une requête GET. Et une couche anti-bot peut interpréter une requête HEAD inhabituelle comme un signal en soi. La requête HEAD est excellente pour ce à quoi elle est destinée ; elle ne constitue toutefois qu’un piètre indicateur de « ce qui se passerait si je récupérais réellement cette ressource ».
La bonne façon de procéder
curl -I https://example.com
Le manuel de curl décrit la commande -I, --head
comme suit : « (HTTP FTP FILE) Récupère uniquement les en-têtes. Les serveurs HTTP disposent de la commande HEAD, que cette commande utilise pour ne récupérer que l’en-tête d’un document. Lorsqu’elle est utilisée sur une URL de type FTP ou FILE, curl affiche uniquement la taille du fichier et la date de dernière modification. »
Résultat :
HTTP/2 200
content-type: text/html; charset=UTF-8
content-length: 1256
last-modified: Thu, 17 Oct 2019 07:18:26 GMT
cache-control: max-age=604800
Voilà toute la réponse à la question posée en titre. Ce qui suit est la partie qui permet de gagner du temps.
Pourquoi « -X HEAD » est incorrect
Le manuel de curl aborde directement ce point dans la section « -X, --request » :
Cette option modifie uniquement le mot utilisé dans la requête HTTP ; elle ne change en rien le comportement de curl. Par exemple, si vous souhaitez effectuer une véritable requête HEAD, il ne suffit pas d’utiliser «
-X HEAD». Vous devez utiliser l’option «--head».
Le mécanisme : -X permute la chaîne de la méthode, et rien d’autre. curl continue de se comporter comme s’il effectuait une requête GET, ce qui signifie qu’il s’attend toujours à recevoir un corps de réponse. Le serveur, qui implémente correctement la méthode HEAD, envoie des en-têtes mais aucun corps de réponse. curl attend un contenu qui n’arrivera jamais, et la commande semble se bloquer jusqu’à ce qu’un délai d’expiration ou la fermeture de la connexion y mette fin.
Le manuel met également en garde contre un deuxième comportement d’-X qui peut piéger les utilisateurs en cas de redirections : « Si l’option --location est utilisée, la chaîne de méthode que vous définissez avec --request est utilisée pour toutes les requêtes ». Ainsi, -X POST -L renvoie une requête POST à chaque étape d’une chaîne de redirection, ce qui correspond rarement à l’intention de l’utilisateur.
Le principe général, énoncé par le manuel lui-même : « Normalement, vous n’avez pas besoin de cette option. Toutes sortes de requêtes GET, HEAD, POST et PUT sont plutôt invoquées à l’aide d’options de ligne de commande dédiées. » Utilisez -I pour HEAD, -d pour POST, -T pour PUT, et réservez -X aux méthodes véritablement inhabituelles telles que PROPFIND.
Qu'est-ce que la méthode HEAD, en réalité ?
La RFC 9110, au paragraphe 9.3.2, la définit en une seule phrase :
La méthode HEAD est identique à la méthode GET, à ceci près que le serveur NE DOIT PAS envoyer de contenu dans la réponse.
Et précise son objectif : « HEAD est utilisé pour obtenir des métadonnées sur la représentation sélectionnée sans transférer les données de cette représentation, souvent dans le but de tester des liens hypertextes ou de détecter des modifications récentes. »
Il s’agit là d’une contrainte stricte imposée aux serveurs — MUST NOT envoyer de contenu — et c’est la raison pour laquelle curl se bloque lorsqu’on lui demande d’en recevoir.
La règle relative aux en-têtes est délibérément moins stricte, et c’est là que les gens se trompent :
Le serveur DEVRAIT envoyer, en réponse à une requête HEAD, les mêmes champs d’en-tête que ceux qu’il aurait envoyés si la méthode de requête avait été GET. Cependant, un serveur PEUT omettre les champs d’en-tête dont la valeur n’est déterminée qu’au moment de la génération du contenu.
La RFC donne un exemple concret : les serveurs qui mettent en mémoire tampon des réponses dynamiques peuvent produire Content-Length et Vary lors d’une requête GET, qui ne sont « pas générés dans le cadre d’une réponse HEAD ». Il qualifie ces cas d’« incohérences mineures » et les considère comme « préférables à la génération puis à la suppression du contenu pour une requête HEAD, puisque la requête HEAD est généralement effectuée dans un souci d’efficacité ».
Ainsi, l’absence d’un Content-Length lors d’une requête HEAD n’est pas nécessairement un bug et n’a pas forcément de signification particulière. Il peut simplement s’agir d’un serveur refusant de calculer quelque chose qu’il n’aurait pu connaître qu’en affichant la page.
Il existe également une règle concernant les corps de requête qu’il est utile de connaître si vous développez des outils. Le contenu d’une requête HEAD « n’a pas de sémantique définie de manière générale, ne peut pas modifier la signification ou la cible de la requête, et pourrait amener certaines implémentations à rejeter la requête et à fermer la connexion en raison de son potentiel d’attaque par « request smuggling » ». La RFC stipule qu’un client « NE DOIT PAS générer de contenu dans une requête HEAD » en l’absence d’un accord préalable spécifique. N’envoyez pas de corps avec une requête HEAD.
À quoi sert la commande HEAD ?
Des cas réellement utiles, qui consistent tous à échanger un transfert complet contre quelques centaines d'octets.
Vérifier si une URL est valide :
curl -sI -o /dev/null -w '%{response_code}\n' https://example.com
Connaître la taille d'un fichier avant de le télécharger :
curl -sI https://example.com/large.iso | grep -i content-length
Suivre et signaler une chaîne de redirections :
curl -sIL -o /dev/null -w '%{num_redirects} hops -> %{url_effective}\n' https://example.com
Vérifier si un serveur prend en charge les requêtes de plage, ce qui permet de déterminer si un téléchargement interrompu peut être repris :
curl -sI https://example.com/file.zip | grep -i accept-ranges
Vérifier la fraîcheur d'un contenu sans le télécharger :
curl -sI https://example.com/data.json | grep -iE 'last-modified|etag'
Vérification groupée des liens, qui constitue l’utilisation classique et où l’économie de bande passante s’accumule :
while read -r url; do
code=$(curl -sIL -o /dev/null -w '%{response_code}' --max-time 10 "$url")
echo "$code $url"
done < urls.txt
Avec une bande passante limitée, l’économie est réelle : une vérification de lien qui transférerait 500 Ko par URL ne transfère à la place que quelques centaines d’octets. Sur dix mille URL, cela représente la différence entre cinq gigaoctets et quelques mégaoctets.
Quand la requête HEAD vous induit en erreur
Les modes de défaillance, qui justifient la mise en garde figurant en haut de cette page.
Le serveur rejette purement et simplement la requête HEAD. 405 Method Not Allowed
sur une URL où la requête GET fonctionne parfaitement. Peu courant pour le contenu statique, mais pas rare pour les API et les points de terminaison d’applications.
Le serveur traite la requête HEAD différemment. Codes d’état différents, en-têtes différents, parfois même un chemin d’exécution totalement différent au sein de l’application. La RFC autorise l’omission des en-têtes dérivés du contenu, et les implémentations varient quant à la manière dont elles appliquent cette règle.
Les caches et les CDN peuvent indexer les requêtes HEAD séparément. Les en-têtes de cache d’une requête HEAD peuvent correspondre à une entrée de cache différente de celle qu’une requête GET aurait trouvée ; une requête HEAD n’est donc pas un moyen fiable d’évaluer le comportement de mise en cache.
Les systèmes anti-bots réagissent différemment. Une requête HEAD provenant d’un client inconnu constitue en soi un signal, et la réponse obtenue peut ne pas être celle qu’une requête GET provenant d’un navigateur recevrait.
L’en-tête «Content-Length
» peut être absent ou erroné. Autorisé par la spécification, courant avec le contenu dynamique, et constitue une mauvaise base pour estimer la taille de téléchargement de tout contenu généré.
Les chaînes de redirection peuvent varier. Certains serveurs redirigent les requêtes GET et HEAD vers des destinations différentes, en particulier lorsque la négociation de contenu est impliquée.
Lorsque vous avez besoin de savoir ce que ferait une véritable requête, effectuez-en une et ignorez le corps :
curl -sS -o /dev/null -D - https://example.com
Une véritable requête GET, les en-têtes affichées sur stdout, le corps ignoré. Vous payez la bande passante et vous obtenez une réponse précise. Choisissez délibérément entre les deux : -I
lorsque vous recherchez la simplicité, -o /dev/null -D -
lorsque vous recherchez la précision.
Il existe une option intermédiaire pour les ressources volumineuses : demander un octet au lieu de la ressource entière :
curl -sS -r 0-0 -o /dev/null -D - https://example.com/large.iso
-r, --range
récupère « une plage d’octets (c’est-à-dire un document partiel) », donc 0-0
ne récupère que le premier octet. Il s’agit d’une véritable requête GET avec un comportement GET authentique, pour un coût en bande passante quasi nul. Mise en garde tirée du manuel : « De nombreux serveurs HTTP/1.1 n’ont pas cette fonctionnalité activée », il faut donc d’abord vérifier la présence de Accept-Ranges: bytes
et s’attendre à une réponse complète si celle-ci est absente.
Modèles à reproduire dans les scripts
Les commandes ci-dessus gagnent considérablement en utilité si on leur apporte un peu de structure.
Un vérificateur de liens qui rend compte avec honnêteté. La version naïve considère tout code de statut autre que 200 comme un lien rompu, ce qui génère de fausses alertes lors des redirections et sur les serveurs qui rejettent la requête HEAD. Celle-ci fait la distinction :
check() {
local url="$1" code
code=$(curl -sIL -o /dev/null --max-time 10 -w '%{response_code}' "$url")
case "$code" in
200) echo "OK $url" ;;
405) code=$(curl -sSL -o /dev/null --max-time 10 -w '%{response_code}' "$url")
echo "GET:$code $url" ;;
000) echo "TIMEOUT $url" ;;
*) echo "$code $url" ;;
esac
}
La branche « 405 » est importante : un serveur refusant la requête HEAD n’est pas un lien rompu, et la seule façon de le savoir est de réessayer avec une requête GET. 000 est la façon dont curl signale qu’aucune réponse HTTP n’est arrivée, ce qui permet de distinguer une défaillance réseau d’une erreur serveur.
Le parallélisme, avec prudence. La vérification des liens se prête naturellement au parallélisme, et la tentation est grande de la lancer à grande échelle. Résistez :
xargs -P 8 -I{} sh -c 'check "$1"' _ {} < urls.txt
Huit est une valeur par défaut raisonnable. La limite maximale ici est dictée par le respect dont il faut faire preuve envers le serveur d’autrui plutôt que par votre propre capacité ; un vérificateur de liens qui provoque un incident de limitation de débit coûte plus cher qu’il n’en fait gagner.
Définissez toujours un délai d’expiration. Une requête HEAD vers un hôte qui ne répond pas se bloque exactement aussi longtemps qu’une requête GET. --max-time 10 avec --connect-timeout 5 limite ce délai, et dans une boucle sur des milliers d’URL, c’est cette limite qui permet de mener la tâche à bien.
Enregistrez l’URL effective, pas seulement le statut. %{url_effective} après -L vous indique où un lien a réellement abouti, ce qui transforme « ce lien fonctionne » en « ce lien fonctionne et pointe désormais vers un autre endroit » — ce qui est généralement la découverte la plus intéressante.
Mettez vos résultats en cache. Vérifier à nouveau chaque URL à chaque exécution gaspille de la bande passante et de la bonne volonté. Enregistrez le statut et l’ETag ou Last-Modified, puis utilisez des requêtes conditionnelles lors des passages suivants afin que les ressources inchangées ne nécessitent qu’un 304 plutôt qu’une vérification complète.
HEAD via un proxy
Trois changements à noter.
curl -I -x http://user:pass@proxy.example.com:9000 https://example.com
L'CONNECTe apparaît en premier pour le HTTPS. curl établit un tunnel avant la requête HEAD, et dans le mode de sortie détaillé, la réponse du proxy à cette requête apparaît avant celle de la cible. --suppress-connect-headers la masque ; %{http_connect} affiche l'état du proxy séparément de celui de la cible.
L'économie de bande passante est l'essentiel. Avec un trafic facturé au volume, une requête HEAD ne coûte que quelques centaines d’octets, contre l’équivalent d’une page complète. Pour la validation des liens, la surveillance de la disponibilité et les contrôles de taille à grande échelle, cela fait la différence entre une opération abordable et une opération coûteuse. C’est l’une des rares optimisations véritablement importantes disponibles dans le cadre d’une tarification au gigaoctet.
Mais les blocages et les défis se comportent différemment. Une couche anti-bot qui renverrait une page de défi à une requête GET peut tout simplement refuser une requête HEAD, ou inversement. Si vous utilisez la requête HEAD pour vérifier si une cible est accessible, validez le résultat à l’aide d’une véritable requête GET sur un échantillon avant de vous y fier pour l’ensemble d’une liste. Il s’agit du modèle d’échec silencieux dont nous avons parlé dans Pourquoi il est important de tester les proxys : la requête aboutit, la réponse est erronée, et rien ne vous le signale.
Un détail concernant curl qu’il est utile de connaître : -G, --get se combine avec --head. Le manuel précise que lorsque -G est utilisé avec --head, « les données POST sont alors ajoutées à l’URL avec une requête HEAD » — ce qui est utile lorsque vous avez besoin de paramètres de requête constitués de paires clé-valeur dans une requête HEAD.
Bien interpréter la réponse
Tirer le meilleur parti des informations renvoyées.
Commencez par vérifier le statut. « 200
» : la page existe. « 301
» / «302
» : la page a été déplacée — ajoutez « -L
» pour y accéder. « 403
» : accès refusé. « 404
» : la page n’existe plus. « 405
» : le serveur n’accepte pas la méthode HEAD ; réessayez avec une requête GET. « 429
» : ralentissez la requête.
**Content-Length
** s’il est présent, en gardant à l’esprit que la spécification autorise son omission.
**Accept-Ranges: bytes
** indique que les téléchargements reprenables et les requêtes de plage sont disponibles.
**Last-Modified
et ETag
** permettent les requêtes conditionnelles. -z
envoie If-Modified-Since
— le manuel décrit cela comme une requête pour « un fichier qui a été modifié après l’heure et la date indiquées » — et --etag-compare
gère le côté « ETag
». Un 304 Not Modified
ne coûte presque rien et constitue la bonne méthode pour interroger une ressource de manière répétée.
**Content-Type
** vous indique ce que vous auriez reçu. text/html
, là où vous vous attendiez à du JSON, signifie généralement une erreur ou une page de connexion.
Pour une utilisation par des machines, évitez complètement l’analyse du texte :
curl -sI -o /dev/null -w '%{header_json}' https://example.com | jq
%{header_json}
renvoie tous les en-têtes de réponse au format JSON avec des noms en minuscules et des valeurs sous forme de tableaux, ce qui gère correctement les en-têtes répétés et rend inutile l’utilisation d’un analyseur syntaxique. Nous avons abordé cette fonctionnalité ainsi que les autres options d’inspection dans l’affichage des en-têtes de réponse avec curl.
Questions fréquentes
Comment envoyer une requête HEAD avec curl ?
curl -I https://example.com. La syntaxe complète est --head. N'utilisez pas -X HEAD : le manuel indique explicitement que cela « ne suffit pas » pour une requête HEAD correcte, car cela ne modifie que la chaîne de la méthode alors que curl continue d'attendre un corps de réponse.
Pourquoi « curl -X HEAD » se bloque-t-il ?
Parce que « -X » ne modifie que le mot dans la ligne de requête, et non le comportement de curl. curl attend toujours un corps de réponse, alors que le serveur n’en envoie correctement aucun, puisque la RFC 9110 exige qu’un serveur « NE DOIT PAS envoyer de contenu » dans une réponse HEAD. Utilisez plutôt « -I ».
Quelle est la différence entre HEAD et GET ?
HEAD est identique à GET, sauf que le serveur ne doit pas envoyer de corps de réponse. Cette méthode sert à obtenir des métadonnées sans transférer de contenu, généralement pour vérifier des liens ou tester l’actualité d’une page. Les serveurs doivent envoyer les mêmes en-têtes que pour GET, mais peuvent omettre ceux qui ne sont calculés qu’au moment de la génération du contenu.
La méthode HEAD renvoie-t-elle toujours les mêmes en-têtes que la méthode GET ?
Non. La spécification indique que les serveurs DEVRAIENT envoyer les mêmes en-têtes, mais PEUVENT omettre ceux « dont la valeur n’est déterminée qu’au moment de la génération du contenu » — elle cite Content-Length et Vary à titre d’exemples. Elle considère que ces légères incohérences sont préférables à la génération puis à la suppression d’un corps de requête.
Comment obtenir la taille d’un fichier sans le télécharger ?
curl -sI URL | grep -i content-length. Sachez que l’en-tête peut être absent pour le contenu généré dynamiquement, ce que la spécification autorise. Pour obtenir une réponse plus fiable à un coût quasi nul, demandez un seul octet avec -r 0-0 et lisez l’en-tête « Content-Range ».
Pourquoi une URL fonctionne-t-elle dans un navigateur mais renvoie-t-elle un code 405 à curl -I ?
Parce que le serveur n’accepte pas les requêtes HEAD sur ce point de terminaison. 405 Method Not Allowed est une réponse valide à une requête HEAD provenant d’un serveur qui gère correctement les requêtes GET. Réessayez avec une requête GET en supprimant le corps : curl -sS -o /dev/null -D - URL.
Puis-je envoyer une requête HEAD avec un corps ?
Vous ne devriez pas. La RFC 9110 stipule que le contenu d’une requête HEAD « n’a pas de sémantique généralement définie », ne peut pas modifier la signification de la requête et « pourrait amener certaines implémentations à rejeter la requête et à fermer la connexion en raison de son potentiel d’attaque par « request smuggling » ». Les clients NE DEVRAIENT PAS générer de contenu dans une requête HEAD.
La requête HEAD est-elle utile pour vérifier si un proxy fonctionne ?
En partie. Elle confirme la connectivité et renvoie le code d'état à moindre coût, ce qui en fait un bon test de fonctionnement. Elle ne permet toutefois pas de savoir si une requête GET réelle aboutirait, car les couches anti-bots traitent souvent ces deux types de requêtes différemment. Vérifiez à l'aide de requêtes GET réelles sur un échantillon avant de vous fier aux résultats HEAD pour l'ensemble d'une liste.
Conclusion
Deux commandes suffisent pour tout couvrir. curl -I URL pour une requête HEAD correcte, et curl -sS -o /dev/null -D - URL lorsque vous souhaitez obtenir les en-têtes qu’une véritable requête GET produirait. Ce qui ne fonctionne pas, c’est -X HEAD, et le manuel l’explique clairement : cela modifie le mot mais pas le comportement, donc curl attend un corps que le serveur n’est pas tenu d’envoyer.
Il vous appartient de choisir laquelle de ces deux options vous convient. La requête HEAD est nettement moins gourmande en données — quelques centaines d’octets contre une page entière —, ce qui en fait l’outil idéal pour la vérification des liens, la surveillance de la disponibilité et l’estimation de la taille, quel que soit le volume, et en particulier lorsque la bande passante est limitée. En revanche, c’est un outil inadapté pour prédire ce que renverrait une véritable requête, car les serveurs sont autorisés à omettre les en-têtes dérivés du contenu, peuvent rejeter purement et simplement les requêtes HEAD, et les acheminent souvent via une logique différente.
Et si vous avez besoin de la précision d’une requête GET sans consommer de bande passante, la requête «-r 0-0» est la solution intermédiaire sous-utilisée : une véritable requête GET qui ne récupère qu’un seul octet. Vérifiez d’abord la présence de l’en-tête «Accept-Ranges: bytes», car de nombreux serveurs vous fourniront de toute façon le fichier entier.
