Accueil >PHP >API REST en PHP >Méthodes HTTP et routage des requêtes

Méthodes HTTP et routage des requêtes

Dans une API REST, l’adresse dit avec quoi vous travaillez, et la méthode HTTP ce qui doit se passer. Ce que signifient les différentes méthodes, comment envoyer avec .htaccess toutes les requêtes de l’API vers un seul fichier PHP et comment les y répartir selon l’adresse et la méthode.

Les méthodes HTTP

  • GET – lit une ressource : GET /api/tasks renvoie la liste, GET /api/tasks/15 une seule tâche.
  • POST – crée un nouvel enregistrement dans la liste : POST /api/tasks. L’adresse du nouvel enregistrement est fixée par le serveur, qui la renvoie dans l’en-tête Location.
  • PUT – remplace tout l’enregistrement par ce qu’envoie le client. Ce qui manque dans le corps est supprimé ou remis à la valeur par défaut.
  • PATCH – ne modifie que les champs envoyés : {"done": true} marque la tâche comme faite sans toucher au titre.
  • DELETE – supprime la ressource.
  • HEAD – comme GET, mais ne renvoie que les en-têtes. OPTIONS – demande ce que permet l’adresse. Dans une API, c’est surtout le navigateur qui l’envoie, voir CORS : appeler l’API depuis JavaScript.

La norme HTTP (RFC 9110) classe les méthodes selon deux propriétés :

  • Les méthodes sûres ne modifient rien : GET, HEAD, OPTIONS. Un navigateur, un robot ou un cache peuvent les envoyer à tout moment. C’est pourquoi GET ne doit jamais rien supprimer ni enregistrer.
  • Les méthodes idempotentes ont le même effet qu’elles soient envoyées une fois ou dix fois : les méthodes sûres, PUT et DELETE. Si la connexion est coupée et que le client ne sait pas si la requête est arrivée, il peut la renvoyer. POST n’est pas idempotent – un POST envoyé deux fois crée deux enregistrements. PATCH peut être idempotent, mais ne l’est pas forcément.

L’API des tâches de l’exemple utilise GET, POST, PATCH et DELETE. Il n’y a pas de raison d’utiliser PUT : une tâche n’a que deux champs, et PATCH les gère tous les deux.

Méthode et corps de la requête en PHP

La méthode se trouve dans $_SERVER["REQUEST_METHOD"] (en majuscules). Le corps de la requête se lit de la même façon avec toutes les méthodes, par file_get_contents("php://input"). PHP ne remplit le tableau $_POST qu’avec la méthode POST – avec PUT ou PATCH, il reste vide, même si le client envoie des données de formulaire. PHP 8.4 a ajouté pour cela la fonction request_parse_body() ; une API en JSON n’en a pas besoin.

Un formulaire HTML ne connaît que GET et POST. Pour une API, cela n’a pas d’importance – elle est appelée par des programmes et par JavaScript (fetch envoie n’importe quelle méthode).

Toutes les requêtes vers un seul fichier

Des adresses comme /api/tasks/15 ne sont pas des fichiers. Apache doit les transmettre à un script qui décide, d’après l’adresse, ce qu’il faut faire. Il ne faut rien de plus que ce fichier .htaccess dans le dossier api :

Fichier api/.htaccess :

RewriteEngine On
RewriteRule ^ index.php [END]

La règle envoie tout ce qui arrive dans le dossier api vers index.php. Le drapeau END (Apache 2.4) arrête les tours de réécriture suivants, la règle ne tourne donc pas en boucle. Les autres fichiers de l’API (functions.php et les autres) ne peuvent ainsi jamais être exécutés directement depuis le navigateur. Le fonctionnement de la réécriture d’adresses est expliqué dans l’article mod_rewrite et URL lisibles de la série sur .htaccess et Apache.

L’adresse d’origine et les paramètres après le point d’interrogation sont conservés : le script les trouve dans $_SERVER["REQUEST_URI"] et $_GET.

Le routeur

Le fichier index.php compare la méthode et l’adresse à une table de routes et appelle la fonction qui traite la requête :

Fichier api/index.php :

<?php
// .htaccess envoie ici toutes les requêtes vers /api/…
require __DIR__."/functions.php";
require __DIR__."/tasks.php";
$base = "/api";   // adresse du dossier de l’API sur le site
$method = $_SERVER["REQUEST_METHOD"];
$path = substr(explode("?", $_SERVER["REQUEST_URI"], 2)[0], strlen($base));   // p. ex. "/tasks/15"
$routes = array(
  array("GET", '~^/tasks\z~', "list_tasks"),
  array("POST", '~^/tasks\z~', "create_task"),
  array("GET", '~^/tasks/(\d+)\z~', "get_task"),
  array("PATCH", '~^/tasks/(\d+)\z~', "update_task"),
  array("DELETE", '~^/tasks/(\d+)\z~', "delete_task"),
);
$allowed = array();
foreach($routes as list($m, $pattern, $function))
  {
    if(!preg_match($pattern, $path, $match)) continue;
    if($m === $method)
      {
        $function(...array_slice($match, 1));
        exit;
      }
    $allowed[] = $m;
  }
if($allowed)
  {
    header("Allow: ".implode(", ", $allowed));
    problem(405, "La méthode ".$method." n’est pas autorisée ici.");
  }
problem(404, "Cette adresse n’existe pas dans l’API.");
  • On retire de l’adresse la partie après le point d’interrogation et le /api initial. Si l’API se trouve ailleurs sur le site, il suffit de modifier $base.
  • Chaque route est une expression régulière. Le groupe (\d+) capture le numéro de la tâche et le passe à la fonction comme paramètre (sous forme de chaîne de caractères). Le motif se termine par \z et non par $ – celui-ci laisserait passer un caractère de fin de ligne à la fin, voir Bases des expressions régulières.
  • Seules les fonctions de la table peuvent être appelées. Le nom de la fonction ne vient jamais de l’adresse – le même principe que pour le routage par liste blanche.
  • Si l’adresse correspond, mais pas la méthode, le routeur renvoie 405 avec l’en-tête Allow. Si aucune route ne correspond, il renvoie 404.

La fonction list_tasks() et les autres se trouvent dans le fichier tasks.php de l’article API sur une base de données. Réponses à une méthode inconnue et à une adresse inconnue :

curl -i -X PUT http://localhost/api/tasks/1
HTTP/1.1 405 Method Not Allowed
Allow: GET, PATCH, DELETE
Content-Type: application/problem+json

{"title":"Method Not Allowed","status":405,"detail":"La méthode PUT n’est pas autorisée ici."}
curl -i http://localhost/api/users
HTTP/1.1 404 Not Found
Content-Type: application/problem+json

{"title":"Not Found","status":404,"detail":"Cette adresse n’existe pas dans l’API."}

Tester sans Apache

Pour les tests, le serveur intégré de PHP suffit. Dans le dossier du site, lancez-le avec la commande

php -S localhost:8000 api/index.php

Le fichier indiqué après l’adresse reçoit chaque requête, .htaccess n’est donc pas nécessaire. L’API répond alors sur http://localhost:8000/api/tasks. Le serveur intégré est destiné uniquement au développement – ne l’utilisez pas en production.