Blog // Exirel.me

HTTP Accept header

Par Florian Strzelecki - 21:45 - 25.04.2014

Tags : Web, HTTP, Hypermedia, Développement, HATEOAS, REST, Technique, Programmation, Bonne pratique, API

La RFC 2616 de l'IETF est une spécification du protocole HTTP/1.1. Ce document décrit notamment les headers des requêtes HTTP à disposition des clients, et en particulier le header "Accept", qui sera le sujet de cet article.

À quoi sert-il ?

En résumé : il permet la négociation de contenu dirigée par le client.

En utilisant Accept, le client indique au serveur HTTP quels types de contenus il est capable de gérer, éventuellement avec un ordre de préférence. Le serveur est alors invité à fournir le type de contenu le plus adapté pour répondre à la demande du client (mais ce n'est pas obligatoire).

Ce paramètre peut être utilisé avec d'autres headers, comme Accept-Language ou Accept-Encoding ; par exemple, le serveur Apache (qui documente sa méthode de négociation de contenu) utilise plusieurs headers pour déterminer la meilleure réponse possible.

Comment ça marche ?

Prenons un exemple avec une requête envoyée par Firefox (28.0), qui utilise ce header par défaut :

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

Ceci indique au serveur que le client attend, par ordre de préférence :

Pour le cas d'une balise img, le header sera différent, et contiendra plutôt image/png (j'ai d'ailleurs remarqué que l'URL n'est absolument pas prise en compte par le navigateur pour générer son header).

Le paramètre "q"

Le paramètre q pour "quality" indique au serveur le niveau de préférence (et d'une certaine façon, de support) du client pour un type donné. Dans notre exemple, le client indique qu'il peut gérer du XML, mais pas aussi bien que du HTML ou du xHTML (sans doute parce qu'un navigateur nous permet de lire des pages Web, et que le XML n'est pas vraiment fait pour nous) (ne riez pas) (du XML quoi...).

Sa valeur est comprise entre 1 et 0, avec une préférence décroissante. Il est donc possible de modifier l'ordre des types de contenus avec ce paramètre :

Accept: application/xml;q=0.9,application/json

Ce qui se traduit par application/json en priorité et application/xml en second, s'il n'y a rien d'autre.

Il est possible d'ajouter d'autres paramètres, mais le q est un nom réservé spécifique.

Autres paramètres

Imaginez un lecteur de flux RSS qui préfère la version 1.0 à la version 2.0, il pourrait l'indiquer de cette façon au serveur :

Accept: application/rss+xml;version=1.0, application/rss+xml;q=0.8;version=2.0

Ce qui se traduit par "Donne moi du RSS, de préférence en version 1.0, sinon du 2.0".

Bien sûr, il faudrait que le serveur soit capable d'interpréter ce header correctement, notamment le paramètre non standard version. Mais l'idée principale est là.

Accept et Content-Type

Attention à ne pas confondre avec le header "Content-Type", qui indique le format du contenu d'une requête ou d'une réponse, et pas le format attendu dans la réponse.

Par exemple, un client peut envoyer une requête POST de cette façon :

POST /action HTTP/1.1
Host: localhost
Accept: application/xml
Content-Type: application/json; charset=utf-8

{id: 7814}

Et la réponse sera alors :

HTTP/1.1 200 OK
Content-Type: application/xml; charset=utf-8

<result>1 action performed with success</result>

On peut alors imaginer que si le client avait demandé application/json, le serveur aurait répondu avec du contenu json.

Le Web et les APIs REST

Si vous vous intéressez un peu aux API et au concept REST, vous devez savoir qu'une réponse à une requête n'est jamais que la représentation d'une ressource, et qu'une ressource peut donc avoir plusieurs représentations.

REST est à la base du Web : nos navigateurs indiquent aux serveurs ce que nous souhaitons obtenir, à savoir, un format adapté à l'affichage sur nos écrans. Rien n'empêche alors d'imaginer des clients qui demandent un format adapté à la manipulation des données par une autre machine (c'est là tout l'intérêt d'une API, au passage).

C'est là que le header Accept devient très intéressant : plutôt que modifier l'URL d'une ressource pour en avoir une représentation alternative, nous pouvons très bien utiliser un simple header pour permettre au client d'initier une véritable négociation de contenu de la part du serveur.

Imaginez alors : il suffit de coder une fois le cœur fonctionnel d'un site web, et d'ajouter ensuite une couche de rendue, qui détermine le format en fonction du header Accept. Au lieu de séparer votre code, ou pire de le dupliquer, vous pourriez n'avoir qu'une seule et unique application, et de multiple moteur de rendu. C'est un concept assez basique, cette fois en poussant la logique encore un peu plus loin.

Cela semble prometteur... pourtant, cette technique n'est pas très employée, et certains framework n'implémente pas, voire, n'implémente plus la négociation de contenu via le header Accept.

Un point sur les navigateurs

Avant d'aller plus loin, il est bon de s'interroger sur le fonctionnement actuel des navigateurs : quels sont les valeurs répandues du header Accept ? Il serait dommage de baser tout un système de négociation de contenu qui ne pourrait pas fonctionner correctement à cause de l'historique des navigateurs.

J'ai effectué quelques tests et voici les résultats :

Bon, c'est plutôt une bonne nouvelle en ce qui concerne les navigateurs récents : aucun ne semble proposer au même niveau d'importance des formats fait pour les humains (HTML et xHTML) et ceux pour les machines (comme XML).

Mais il y a là matière à se faire avoir par l'historique du Web : dans un article de 2009, Kris Jordan signale les incongruités des anciennes versions des navigateurs, comme IE8 qui met au même niveau les images, les documents words, les powerpoints, les fichiers excels, et... tout le reste.

Il y a donc là un véritable problème, car vous ne pouvez pas sereinement employer le header Accept : certains clients pourraient amener à des comportements non-attendus (et des plus déroutants) avec leur mauvais usage du header Accept, rendant inopérant un si beau concept.

Mention spéciale à IE 8, sombre déchet de l'Histoire du Web, au moins autant qu'IE 6...

Négociation au cas par cas

Je ne pense pas qu'il faille s'arrêter là. Ce n'est pas parce que quelques vieux navigateurs s'amusent avec nos nerfs que nous devrions être réduits à utiliser une extension ".xml" ou ".json" pour servir des représentations différentes (je déteste avoir des URLs qui changent pour ça).

D'une part, ce qui compte c'est le document hypermedia, qui peut donc avoir un type très spécifique : si un client demande application/vdn.your.product il est plus qu'improbable qu'il s'agisse d'un simple navigateur. D'autre part, il suffit d'isoler les cas où c'est forcément un client spécifique, et de considérer tous les autres cas comme étant pour un navigateur.

Si le client demande à égalité text/html, application/xml+xhtml, et appliation/xml, c'est qu'il s'agit très probablement d'un navigateur.

De plus, si un client ne demande qu'un seul et unique type de contenu (en mettant de côté le joker */*), il est fort à parier qu'il ne s'agit pas d'un navigateur ; l'inverse étant vrai aussi, puisqu'un client ne demandant pas qu'un seul type est probablement un navigateur.

Enfin, de manière générale, si un client n'indique aucun type spécifique ni à votre API ni aux navigateurs modernes, c'est qu'il s'agit probablement d'une vieille version IE - et autant lui fournir du HTML ou du xHTML dans ce cas là.

En résumé

Le problème n'apparaît pas lorsque chaque type dispose d'un niveau de préférence différent : le problème ne se pose pas, puisqu'il suffit alors de suivre l'ordre, et le premier qui convient l'emporte.

Par contre, c'est une autre histoire lorsque plusieurs types sont tous au même niveau : est-ce un navigateur, ou est-ce un client qui demande réellement un type spécifique ?

Imaginons que vous obteniez une liste de types classés au même niveau de préférence (q non présent ou q=1.0), alors je conseillerais cette négociation de contenu :

Cela rend la négociation de contenu plus difficile, mais cela devrait fonctionner.

Exemples

C'est un navigateur (mode facile)

Facile : seul HTML et xHTML sont au niveau 1.0.

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

C'est un client pour une machine (mode facile)

Facile : un seul type est demandé.

Accept: application/xml
Accept: text/plain
Accept: application/json
Accept: text/csv

Facile aussi : le niveau pour les autres formats est différent, cela revient en réalité au premier cas.

Accept: application/xml, text/html; q=0.9
Accept: text/plain, text/html; q=0.9
Accept: application/json, text/html; q=0.9
Accept: text/csv, text/html; q=0.9

Enfin, tout aussi facile, c'est un type spécifique à votre format hypermedia.

Accept: application/vdn.your.custom.type

C'est un vieux navigateur (mode intermédiaire)

Relativement facile tout de même : tous sont au niveau 1.0, mais HTML et xHTML sont présents.

Accept: text/html,application/xhtml+xml,application/xml

Même chose avec des formats d'image, de vidéo, ou encore CSV : tant que HTML ou xHTML sont au même niveau, c'est qu'un navigateur est dans la partie (probablement Chrome pour image/webp).

Accept: text/html,application/xhtml+xml,text/csv,image/png,image/webp

C'est un vieux navigateur (mode difficile)

Là, c'est plus complexe, car IE 8 n'indique même pas qu'il souhaite du HTML ou du xHTML. La réponse : aucun autre cas ne convient, et il y a plus d'un seul type indiqué, c'est donc un navigateur (qui fait n'importe quoi).

Si je conseille de fonctionner ainsi, c'est parce qu'il est impossible de dire à quelqu'un de changer de navigateur pour visiter votre site, alors qu'il est tout à fait normal de lui indiquer qu'il a mal configuré sa requête HTTP pour son application.

Conclusion

Voilà, j'ai fait un rapide tour de ce header : il y aurait encore beaucoup à dire sur le sujet (notamment avec les autres header Accept-*, ou avec les types jokers comme text/* ou */*), mais je vais m'arrêter là pour le moment.

Le sujet est plus complexe qu'il n'y paraît, et je suis toujours un peu déçu de constater que ce header est très peu employé par les API dites "RESTful". Certes, l'historique des navigateurs Web n'aide pas franchement, mais je pense que c'est une option toujours disponible. Elle est très puissante, ce serait dommage de s'en passer pour si peu.

Lors de mes précédents articles sur les API et les versions, j'avais fait mention de l'usage de ce header, et c'est aussi un usage très puissant à votre disposition.

Enfin, je pense mener une expérimentation avec ce blog, en utilisant une négociation de contenu via header. Je vous tiens au courant !