L’anatomie d’une requête
Méthode, URL, en-têtes et corps, et ce que le serveur reçoit vraiment.
Dans la leçon précédente, vous avez envoyé une requête en tapant une URL dans un navigateur. Le navigateur a rempli tout le reste pour vous. Cette leçon démonte une requête pour que vous sachiez ce qu’est chaque pièce, car chaque test que vous écrirez fixera certaines de ces pièces exprès et vérifiera la réaction du serveur.
Une requête HTTP a quatre parties :
- une méthode, qui dit quel type d’action vous voulez
- une URL, qui dit sur quelle chose, sur quel serveur, l’action doit porter
- des en-têtes, qui portent des informations supplémentaires sur la requête
- un corps facultatif, qui porte les données que vous envoyez
À quoi ressemble une requête sur le réseau
HTTP/1.1 est un protocole texte. Avant tout chiffrement, une requête n’est que quelques lignes de texte brut. Voici la requête que curl envoie pour https://api.testingapis.com/api/echo?color=red. Vous pouvez la voir vous-même en lançant curl avec -v (verbeux) : les lignes qui commencent par > sont ce qui est parti.
GET /api/echo?color=red HTTP/1.1
Host: api.testingapis.com
User-Agent: curl/8.18.0
Accept: */*La première ligne est la ligne de requête : la méthode, le chemin avec sa query string, et la version du protocole. Chaque ligne suivante est un en-tête, un nom et une valeur séparés par deux-points. Une ligne vide termine les en-têtes. S’il y avait un corps, il viendrait après cette ligne vide. Les versions plus récentes de HTTP (HTTP/2 et HTTP/3) envoient les mêmes informations dans un format binaire, mais les parties sont identiques, et tous les outils vous les montrent encore sous cette forme texte.
Voir ce que le serveur a reçu
Le plus simple pour comprendre les requêtes est d’en envoyer une à un serveur qui vous dit ce qu’il a reçu. /api/echo fait exactement cela. Il accepte n’importe quelle méthode et répond avec une description de votre requête. Envoyez celle-ci :
/api/echo?color=red&size=10Voici la réponse pour curl, réduite aux champs intéressants. Si vous l’envoyez depuis la page, ce seront les en-têtes de votre navigateur.
{
"method": "GET",
"url": "http://api.testingapis.com/api/echo?color=red&size=10",
"path": "/api/echo",
"queryString": "?color=red&size=10",
"query": { "color": "red", "size": "10" },
"headers": {
"Accept": "*/*",
"Host": "api.testingapis.com",
"User-Agent": "curl/8.18.0",
"Accept-Encoding": "gzip, br",
"Cf-Ipcountry": "IN",
"X-Forwarded-For": "203.0.113.7, 172.71.198.178, 10.30.107.46",
"X-Forwarded-Proto": "https"
},
"contentType": null,
"contentLength": 0,
"body": null
}Deux choses méritent un second regard. Le serveur a vu l’URL en http://, pas en https://. Et il a reçu des en-têtes que vous n’avez jamais envoyés, comme Cf-Ipcountry et X-Forwarded-For. Votre requête est passée par un CDN et un répartiteur de charge avant d’atteindre l’application, et chacun a réécrit ou ajouté quelque chose. La première adresse de X-Forwarded-For est la vôtre (celle ci-dessus est un exemple). C’est normal dans les systèmes en production, et c’est pour cela qu’un point d’accès d’écho est utile : il montre ce qui est arrivé, pas ce que vous croyez avoir envoyé.
La méthode
La méthode est le verbe. GET demande des données. POST envoie des données pour créer quelque chose. PUT, PATCH et DELETE modifient ou suppriment des choses. La barre d’adresse d’un navigateur ne sait envoyer que GET, d’où le besoin d’autres outils pour le reste. Les méthodes ont leur propre leçon, Les méthodes HTTP, donc pour l’instant il suffit de savoir que la même URL peut faire des choses différentes selon la méthode.
L’URL
Décomposez cette URL en ses parties :
https://api.testingapis.com/api/Products?category=books&limit=5| Partie | Valeur | Signification |
|---|---|---|
| Schéma | https | Comment se connecter. https, c’est HTTP dans une connexion chiffrée. |
| Hôte | api.testingapis.com | Quel serveur. Il est aussi envoyé dans l’en-tête Host. |
| Chemin | /api/Products | Quelle ressource sur ce serveur. |
| Query string | category=books&limit=5 | Des paramètres en plus, après le ?, séparés par &. |
Les URL peuvent aussi porter un port (:8080 après l’hôte) et un fragment (#section à la fin). Vous verrez souvent des ports sur les environnements de test et locaux. Les fragments n’atteignent jamais le serveur : le navigateur les garde.
Paramètres de chemin et paramètres de requête
Les valeurs peuvent voyager dans le chemin ou dans la query string. Dans /api/Products/1, le 1 est un paramètre de chemin. Il identifie un produit précis, et la documentation écrit le chemin /api/Products/{id}. Dans /api/Products?limit=5, limit est un paramètre de requête. Les paramètres de requête servent en général à filtrer, trier ou paginer les résultats, et sont en général facultatifs. Ce partage est une convention, pas une règle : consultez la documentation de chaque API.
Les chemins sont souvent sensibles à la casse, et les noms de paramètres de requête le sont généralement aussi. Cela dépend du framework du serveur, alors faites un test rapide sur toute nouvelle API : envoyez /api/products au lieu de /api/Products et regardez ce qui se passe.
L’encodage pourcent
Certains caractères ont un rôle dans une URL. ? commence la query string, & sépare les paramètres, # commence le fragment, / sépare les segments du chemin, et les espaces ne sont pas autorisés du tout. Pour envoyer l’un d’eux comme donnée, vous le remplacez par % suivi de la valeur hexadécimale de son octet. Une espace devient %20 et & devient %26. On appelle cela l’encodage pourcent, ou encodage d’URL.
Imaginons que vous vouliez chercher "red shoes" et filtrer sur une étiquette dont la valeur est littéralement le texte a&b. Encodé, ça marche :
/api/echo?q=red%20shoes&tag=a%26b{
"queryString": "?q=red%20shoes&tag=a%26b",
"query": { "q": "red shoes", "tag": "a&b" }
}Envoyez maintenant l’étiquette sans encoder l’esperluette :
/api/echo?tag=a&b{
"queryString": "?tag=a&b",
"query": { "tag": "a", "b": "" }
}Le serveur voit maintenant deux paramètres : tag avec la valeur a, et un paramètre vide nommé b. Ni erreur, ni avertissement. La requête a "marché" et les données sont fausses. C’est une vraie famille de bugs dans les clients qui construisent des URL en collant des chaînes, et c’est pourquoi les champs de recherche sont un bon endroit où taper des esperluettes, des signes plus, des barres obliques et du texte non anglais pendant les tests. L’encodeur d’URL de ce site fait la conversion si vous en avez besoin à la main.
Les en-têtes
Les en-têtes sont des paires nom-valeur qui décrivent la requête. Les noms ne sont pas sensibles à la casse : content-type et Content-Type veulent dire la même chose. La plupart des clients ajoutent plusieurs en-têtes automatiquement. Ceux que vous fixerez ou vérifierez le plus souvent sont ceux-ci :
| En-tête | Ce qu’il dit | Exemple |
|---|---|---|
Content-Type | Le format du corps que vous envoyez. | application/json |
Accept | Les formats que vous acceptez de recevoir. | application/json |
User-Agent | Quel client a envoyé la requête. | curl/8.18.0 |
Authorization | Des identifiants, comme un token. | Bearer abc123 |
Host | Quel site sur le serveur. Les clients le remplissent à partir de l’URL. | api.testingapis.com |
Accept compte quand une API peut répondre dans plusieurs formats. /api/formats/negotiateconnectez-vous pour voir renvoie le même utilisateur en JSON par défaut, et en XML si vous le demandez :
/api/formats/negotiateComparez le XML avec le JSON que vous obtenez sans l’en-tête. Les deux portent les sept mêmes champs, écrits à la manière de chaque format : la liste roles devient des éléments role répétés, et le manager nul devient un élément vide marqué xsi:nil. Quand une API propose plusieurs formats, testez-les tous et ne supposez pas qu’ils correspondent. Quand cette leçon a été écrite, il manquait trois champs dans la version XML de cet utilisateur.
Authorization est traité en détail dans Authentification et autorisation. Une chose à savoir dès maintenant : ne collez jamais de vrais tokens dans des captures d’écran, des rapports de bug ou des collections partagées. Ce sont des mots de passe.
Le corps
Les requêtes GET n’ont normalement pas de corps. POST, PUT et PATCH en ont généralement un, parce qu’elles envoient des données au serveur. Le corps peut être dans n’importe quel format, et l’en-tête Content-Type dit au serveur comment le lire. Pour la plupart des API actuelles, le corps est du JSON. Ceci envoie un petit objet JSON au point d’accès d’écho avec curl :
curl -X POST https://api.testingapis.com/api/echo \
-H "Content-Type: application/json" \
-d '{"title":"Dune","year":1965}'{
"method": "POST",
"path": "/api/echo",
"query": {},
"contentType": "application/json",
"contentLength": 28,
"body": "{\"title\":\"Dune\",\"year\":1965}",
"json": { "title": "Dune", "year": 1965 }
}Le serveur a lu l’en-tête, vu application/json, et analysé les 28 octets en un objet. Vous pouvez envoyer la même requête d’ici :
/api/echo{"title":"Dune","year":1965}Quand Content-Type et le corps ne concordent pas
Envoyez maintenant le même genre de corps avec curl, mais sans l’en-tête :
curl -X POST https://api.testingapis.com/api/echo \
-d '{"title":"Dune"}'{
"contentType": "application/x-www-form-urlencoded",
"contentLength": 16,
"body": "{\"title\":\"Dune\"}",
"json": null,
"form": { "{\"title\":\"Dune\"}": "" }
}L’option -d de curl utilise par défaut application/x-www-form-urlencoded, le format des formulaires HTML. Le serveur a cru l’en-tête, a essayé de lire le corps comme un formulaire, et s’est retrouvé avec un seul champ dont le nom est tout le texte JSON. Aucune erreur n’est revenue. Beaucoup de vrais serveurs se comportent ainsi, et d’autres renvoient 415 Unsupported Media Type. Un Content-Typeabsent ou faux est l’une des raisons les plus fréquentes pour lesquelles une requête "a l’air juste" et échoue quand même : c’est la première chose à vérifier quand un serveur ignore vos données.
Si l’en-tête est bon mais le JSON cassé, un bon serveur le dit. Voici une partie de la réponse d’écho pour le corps {"title":"Dune",}, qui a une virgule finale :
"json": null,
"jsonError": "The JSON object contains a trailing comma at the end which is not supported in this mode. Change the reader options. LineNumber: 0 | BytePositionInLine: 16."Exercice
Utilisez /api/echo pour répondre à chaque question. Regardez la réponse à chaque fois au lieu de deviner.
- Envoyez
/api/echo?name=Zoë&city=São Paulodepuis la barre d’adresse de votre navigateur. Qu’est-ce que le navigateur a changé dans l’URL avant de l’envoyer, et en quoi le serveur l’a-t-il décodée ? - Envoyez
/api/echo?id=1&id=2. Comment le serveur représente-t-il un paramètre qui apparaît deux fois ? Les frameworks ne sont pas d’accord sur ce point, ce qui en fait une bonne question pour toute API que vous testez. - Envoyez un POST avec un corps JSON et
Content-Type: text/plain. Le serveur a-t-il quand même analysé le JSON ? - Essayez
/api/echo/user-agentdepuis un navigateur et depuis curl. Pourquoi une API se soucierait-elle de celui que vous avez utilisé ?