Gestion des Requêtes HTTP en PHP

Présentation

Le protocole HTTP (HyperText Transfer Protocol) est un standard de communication entre un client et un serveur, utilisé principalement sur le web, mais aussi pour les APIs (Application Programming Interface) et d'autres échanges de données. Il permet aux applications d'échanger des informations en suivant un modèle requête-réponse.

Lorsqu'un client, comme un navigateur web, une application mobile ou un assistant vocal, souhaite interagir avec un serveur, il envoie une requête HTTP spécifiant une méthode (GET, POST, etc.), une URL et éventuellement des données supplémentaires. Le serveur reçoit cette requête, la traite et renvoie une réponse indiquant le résultat de l'opération. Selon le type de requête, cette réponse peut contenir des informations demandées, comme une page HTML ou un fichier JSON, ou simplement un accusé de réception avec un code de statut HTTP (ex. : 200 OK ou 404 Not Found).

Chaque requête et réponse HTTP inclut également des en-têtes HTTP qui transmettent des informations supplémentaires sur l'échange. Ces en-têtes peuvent préciser le format des données, indiquer l'identité du client ou encore gérer l'authentification. Parmi les plus courants, l'en-tête User-Agent renseigne sur le type d'appareil ou de navigateur, Content-Type définit le format des données envoyées et Authorization permet d'ajouter un jeton d'authentification pour sécuriser l'accès à une API.

À chaque action sur un site ou une application en ligne, comme l'affichage d'une page, l'envoi d'un formulaire ou le chargement de données dynamiques, une ou plusieurs requêtes HTTP sont générées en arrière-plan.

Dans un scénario classique, un navigateur web tel que Chrome ou Firefox agit en tant que client et envoie une requête HTTP à un serveur web, qui lui répond avec une page HTML. Toutefois, HTTP n'est pas réservé aux navigateurs. Un serveur, grâce à des langages backend comme PHP, Python ou Node.js, peut également agir comme un client HTTP en envoyant des requêtes à d'autres services distants, notamment pour :

  • Récupérer des informations depuis une API externe.
    Exemple : Obtenir la température actuelle d'une ville via une API météo comme OpenWeatherMap.
  • Transmettre des données à une API externe.
    Exemple : Envoyer une requête à l'API de Stripe pour initier un paiement après une commande en ligne.
  • Permettre la communication entre plusieurs serveurs.
    Exemple : Demander des données utilisateur à un microservice dédié depuis un serveur principal.
  • Extraire des données à partir de pages web en utilisant des techniques de scraping (cette technique peut être interdite selon les conditions du site).
    Exemple : Collecter les prix d'un produit sur un site e-commerce pour une analyse."

Cependant, la gestion des requêtes HTTP par PHP nécessite des outils spécifiques, car contrairement à un navigateur qui traite automatiquement les requêtes et les réponses HTTP, un script PHP doit manuellement structurer, envoyer et interpréter ces requêtes.

Les APIs

Une API (Application Programming Interface) est une interface qui permet à deux logiciels ou systèmes de communiquer entre eux, en suivant un ensemble de règles et de formats bien définis. Elle rend possible l'échange, la transmission et le traitement des données entre deux entités distinctes.

Imagine un restaurant :

  1. La requête : Le client (ton application) passe une commande auprès du serveur (l'API).
  2. Le traitement : Le serveur (l'API) transmet la commande à la cuisine (son backend).
  3. La réponse : Une fois le plat prêt, le serveur (l'API) le rapporte au client (ton application).

L'API joue donc le rôle d'intermédiaire standardisé entre deux parties qui ne se connaissent pas directement.

Différents types d'APIs :

  • API locale : Permet à une application de communiquer avec des composants internes du système ou des bibliothèques installées localement.
    Exemple : Un jeu vidéo utilise DirectX sous Windows pour afficher des graphismes et jouer des sons. DirectX est une API locale fournie par le système d'exploitation.
  • API web : Accessible via le réseau (HTTP/HTTPS). Elle permet à une application d'interroger un serveur à distance.
    Exemple : Une application météo utilise une API web pour récupérer la température actuelle en envoyant une requête vers https://api.weather.com.
  • API RESTful : Un type d'API web structuré autour du protocole HTTP, des méthodes standards (GET, POST, etc.) et de ressources accessibles par des URLs.
    Exemple : L'API GitHub permet de récupérer des infos sur des dépôts ou des utilisateurs via des requêtes du type GET /users/octocat.
  • API SOAP : API web plus ancienne et plus stricte, utilisant le format XML pour structurer les données. Nécessite une structure très formelle.
    Exemple : Une banque utilise une API SOAP pour transmettre des données de transaction de manière sécurisée entre ses serveurs.
  • API GraphQL : Une alternative aux APIs REST, le client peut demander exactement les données qu'il souhaite, ni plus ni moins, via une seule requête.
    Exemple : Une application mobile de réseaux sociaux utilise GraphQL pour récupérer en une seule requête les infos d'un utilisateur, ses derniers posts, et ses commentaires.

Les APIs RESTful

Une API RESTful est un type d'API Web qui suit les principes de l'architecture REST (Representational State Transfer).

Caractéristiques principales :

  • Utilise des endpoints (URL) pour représenter les ressources.
  • Les données sont généralement échangées au format JSON (ou parfois XML).
  • Fonctionne sans conservation d'état (stateless) côté serveur, ce qui signifie que chaque requête est indépendante et contient toutes les informations nécessaires pour être traitée, sans dépendre d'un état stocké côté serveur.
  • Repose sur les méthodes HTTP standard (GET, POST, PUT, DELETE, etc.).
  • Est simple à comprendre et couramment employée dans les applications Web modernes.

Exemples d'endpoints REST courants :

  • GET /users : Récupère la liste des utilisateurs.
  • GET /users/{id} : Récupère les détails d'un utilisateur.
  • POST /users : Crée un nouvel utilisateur.
  • PUT /users/{id} : Met à jour complètement un utilisateur.
  • PATCH /users/{id} : Met à jour partiellement un utilisateur.
  • DELETE /users/{id} : Supprime un utilisateur.

Jusqu'à présent, nous avons principalement utilisé les méthodes GET et POST, car ce sont celles que les formulaires HTML supportent nativement. Cependant, comme on peut le voir avec les endpoints RESTful (PUT, PATCH, DELETE), d'autres méthodes HTTP sont couramment utilisées pour interagir avec des APIs web.

Envoyer une requête HTTP

En PHP, plusieurs méthodes existent pour envoyer des requêtes HTTP. L'une des solutions les plus puissantes et flexibles est l'utilisation de l'extension cURL (Client for URLs). Cette extension permet de faire des requêtes HTTP de manière simple et robuste, avec un contrôle précis sur les options de la requête, telles que la méthode HTTP, les en-têtes personnalisés, ainsi que l'authentification.

Initialiser une session

La première étape pour utiliser cURL est d'initialiser une session cURL avec la fonction curl_init(). Cette fonction retourne un handle de session cURL (un identifiant de connexion pour maintenir une session HTTP ouverte entre un client et un serveur) que vous utiliserez ensuite pour exécuter la requête.

Il est possible de fournir directement l'URL cible dès l'appel à curl_init(). Cela permet de spécifier immédiatement l'adresse de la ressource que l'on souhaite interroger.


                    // Initialiser la session cURL avec l'URL cible.
                    $ch = curl_init('https://api.exemple.com/users');
                

Exécuter la requête et récupérer la réponse

Une fois les options de la requête configurées, il faut exécuter la requête à l'aide de la fonction curl_exec().

Par défaut, le résultat de cette exécution, c'est-à-dire la réponse HTTP renvoyée par le serveur, est affiché directement dans la sortie standard. C'est à dire, dans le navigateur si vous exécutez le script via un serveur web, ou dans le terminal si le script est lancé en ligne de commande.

Cela signifie que la variable de retour ne contiendra pas le contenu de la réponse, mais simplement true ou false, selon que la requête s'est exécutée correctement ou non.

Exemple sans configuration spécifique :


                    // Initialiser la session cURL avec l'URL cible.
                    $ch = curl_init('https://api.exemple.com/users');

                    // Exécuter la requête.
                    $response = curl_exec($ch);
                

Dans ce cas, la réponse du serveur s'affichera automatiquement à l'écran, et $response vaudra true (ou false si une erreur s'est produite).

Récupérer la réponse dans une variable

Si vous souhaitez stocker le contenu de la réponse dans une variable (par exemple pour l'analyser ou l'utiliser dans le reste du script), vous devez demander à cURL d'adopter ce comportement, en activant l'option CURLOPT_RETURNTRANSFER.


                    // Initialiser la session cURL avec l'URL cible.
                    $ch = curl_init('https://api.exemple.com/users');

                    // Demander à cURL de retourner la réponse au lieu de l'afficher.
                    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

                    // Exécuter la requête.
                    $response = curl_exec($ch);
                

Grâce à cette option, $response contiendra le contenu brut de la réponse HTTP (par exemple du JSON), que vous pourrez ensuite manipuler dans votre code.

Gérer les erreurs

Lors de l'exécution d'une requête avec curl_exec(), il est possible qu'une erreur se produise (ex. : URL invalide, serveur injoignable, problème SSL, etc.).

Pour détecter et diagnostiquer ces erreurs, deux fonctions peuvent être utilisées :

  • curl_errno() : Renvoie le code d'erreur numérique de la dernière opération cURL. Si aucune erreur ne s'est produite, cette fonction renvoie 0. Sinon, elle retourne un code spécifique (ex. : 6 = nom de domaine introuvable). Vous trouverez la liste complète des erreurs sur dans la documentation officielle de cURL.
  • curl_error($ch) : Renvoie un message d'erreur lisible correspondant à curl_errno(). Si aucune erreur ne s'est produite, elle renvoie une chaîne vide.

                    // Initialiser la session cURL avec l'URL cible.
                    $ch = curl_init('https://api.exemple.com/users');

                    // Demander à cURL de retourner la réponse au lieu de l'afficher.
                    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

                    // Exécuter la requête.
                    $response = curl_exec($ch);

                    // Vérifier s'il y a une erreur.
                    if (curl_errno($ch)) 
                    {
                        $code = curl_errno($ch);
                        $msg = curl_error($ch);

                        // Afficher le message d'erreur.
                        echo "Erreur cURL ($code) : $msg";
                    } 
                

Fermer la session

Une fois la requête exécutée et la réponse traitée, il est important de fermer la session cURL pour libérer les ressources et éviter des fuites de mémoire.


                    // Initialiser la session cURL avec l'URL cible.
                    $ch = curl_init('https://api.exemple.com/users');

                    // Demander à cURL de retourner la réponse au lieu de l'afficher.
                    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

                    // Exécuter la requête.
                    $response = curl_exec($ch);

                    // Récupérer le code d'erreur cURL (0 s'il n'y a pas eu d'erreur).
                    $erreurCode = curl_errno($ch);

                    // Si une erreur a eu lieu, récupérer le message d'erreur correspondant.
                    $erreurMsg = curl_error($ch);

                    // Fermer la session cURL.
                    curl_close($ch);

                    // Vérifier s'il y a une erreur cURL.
                    if ($erreurCode) 
                    {
                        // Afficher le message d'erreur.
                        echo "Erreur cURL ($erreurCode) : $erreurMsg";
                    } 
                

Identifier le type de contenu d'une réponse HTTP

Après avoir exécuté une requête, il peut être utile d'obtenir certaines informations techniques sur le déroulement de l'échange avec le serveur. Pour cela, cURL propose la fonction curl_getinfo().

Cette fonction permet de récupérer un ensemble d'informations sur :

  • le comportement de la requête : URL finale atteinte, durée totale, nombre de redirections suivies, etc.
  • la réponse HTTP : code de statut (ex. : 200, 404), type de contenu retourné (JSON, image, etc.), taille des données échangées, etc.

Ces données sont particulièrement utiles pour :

  • déboguer une requête ou comprendre pourquoi elle a échoué,
  • adapter dynamiquement le traitement de la réponse selon son contenu,
  • collecter des statistiques sur les performances du serveur ou le poids des échanges.

L'une des utilisations les plus courantes est la récupération du type MIME de la réponse, à l'aide de l'option CURLINFO_CONTENT_TYPE.

La réponse retournée est toujours une chaîne de caractères brute, quelle que soit sa nature réelle (JSON, image, PDF, etc.). Identifier le type de contenu permet de savoir comment cette chaîne doit être interprétée. Elle pourra, selon le cas, être décodée ou manipulée comme un flux binaire, afin de la rendre exploitable dans l'application.


                    // Initialiser la session cURL avec l'URL cible.
                    $ch = curl_init('https://api.exemple.com/users/focan/pic');

                    // Retourner le contenu sous forme de chaîne.
                    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

                    // Exécuter la requête.
                    $response = curl_exec($ch);

                    // Récupérer le code d'erreur cURL (0 s'il n'y a pas eu d'erreur).
                    $erreurCode = curl_errno($ch);

                    // Si une erreur a eu lieu, récupérer le message d'erreur correspondant.
                    $erreurMsg = curl_error($ch);

                    // Récupérer le code HTTP de la réponse retournée par le serveur (ex. : 200, 404, 500).
                    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);

                    // Vérifier le type MIME retourné.
                    $contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);

                    // Fermer la session cURL.
                    curl_close($ch);

                    // Vérifier s'il y a une erreur cURL.
                    if ($erreurCode) 
                    {
                        // Afficher le message d'erreur.
                        echo "Erreur cURL ($code) : $msg";
                    } 
                    else 
                    {
                        // Si le fichier est une image au format jpeg, l'enregistrer.
                        if ($contentType === 'image/jpeg') 
                        {
                            // Générer un nom de fichier unique.
                            $nomFichier = $filename = date('Ymd') . '_' . uniqid() . '.jpg';

                            // Monter le chemin de destination.
                            $cheminCompletFichierDestination = __DIR__ . DIRECTORY_SEPARATOR . 'images' . DIRECTORY_SEPARATOR . '$nomFichier.jpg';

                            // Enregistrer l'image.
                            file_put_contents($cheminCompletFichierDestination, $response);

                            echo "Image enregistrée avec succès dans le fichier : $nomFichier";
                        } 
                        else 
                        {
                            echo "Type de contenu inattendu : $contentType";
                        }
                    }
                

Exercices - Part 01

Exo-gestion-des-requetes-http-01 : Découverte de cURL

  • Objectif : Manipuler l'envoi de requêtes HTTP avec cURL, comprendre et exploiter les différents types de contenus retournés par une API externe (texte, JSON, image, audio), organiser son code de manière modulaire, et afficher dynamiquement les données dans une page web à partir d'une documentation technique.
  • Instructions :
    1. Explorer la documentation de l'API de citations de la série Kaamelott : Se rendre sur le site web KAAMELOTT'S API et parcourir la documentation pour comprendre son fonctionnement et les différents endpoints (URL) qu'elle propose.
    2. Initialiser le projet : Créer les dossiers et fichiers en respectant la structure suivante :
      
                                      📁 exo-gestion-des-requetes-http-01/
                                      ├── 📁 fonctions/
                                      │   ├── 📄 gestionRequeteHttp.php
                                      │   └── 📄 gestionApiKaamelott.php
                                      ├── 📁 template/
                                      │   ├── 📄 header.php
                                      │   └── 📄 footer.php
                                      ├── 📄 audio.php
                                      ├── 📄 index.php
                                      ├── 📄 citations.php
                                      ├── 📄 citation-aleatoire.php
                                      ├── 📄 personnage-citations.php
                                      ├── 📄 personnage-citation-aleatoire.php
                                      └── 📄 portrait.php
                                      
    3. Créer le template :
      • Ajouter le code HTML de base dans header.php, incluant une navigation permettant d'accéder à chaque page du projet.
      • Ajouter dans footer.php le code de fin de structure HTML. Vous pouvez y insérer les informations de votre choix (crédits, lien vers la doc, etc.).
      • Inclure le header et le footer dans chaque page du projet.
      • Dans chaque fichier, ajouter dans la balise <main> un titre dans une balise <h1>, correspondant à la fonction de la page. Exemple : pour citations.php, le titre sera Afficher toutes les citations.
      • Vérifier que la navigation fonctionne bien en testant les liens du menu dans le navigateur.
    4. Afficher toutes les citations dans le fichier citations.php :
      • Identifier dans la documentation le endpoint à utiliser pour obtenir toutes les citations.
      • Utiliser cURL pour envoyer la requête HTTP (le code PHP doit toujours précéder la structure HTML). Pour cette API, toutes les requêtes se font en GET, ce qui tombe bien puisque c'est justement la méthode utilisée par défaut par cURL. Il n'est donc pas nécessaire de configurer la méthode de la requête dans cet exercice.
      • Stocker les éventuelles erreurs dans une variable et les afficher sous le titre, dans le code HTML.
      • Analyser le type de contenu de la réponse pour déterminer la manière de traiter les données reçues (texte, JSON, image, etc.).
      • Si la réponse est au format JSON, utiliser json_decode() en passant true en second argument pour obtenir un tableau associatif.
      • Afficher temporairement le résultat avec print_r() pour s'assurer du bon fonctionnement de la requête.
      • Une fois la structure confirmée, supprimer l'appel à print_r().
      • Ajouter une boucle foreach() après le titre afin de parcourir les citations reçues et de les afficher sous forme de liste, en accompagnant chaque citation de son auteur.
    5. Afficher une citation aléatoire dans le fichier citation-aleatoire.php :
      • Copier la logique utilisée pour envoyer une requête dans le fichier citations.php, et collez-la dans citation-aleatoire.php.
      • Modifier le endpoint pour interroger celui permettant de récupérer une seule citation aléatoire (cf. documentation de l'API).
      • Affichez temporairement le contenu retourné (comme pour la page précédente) afin de vérifier que la réponse contient bien une citation unique.
      • Même si cette méthode fonctionne, elle n'est pas très maintenable. Cela signifie que si vous devez modifier la logique d'appel de l'API, vous devrez le faire dans toutes les pages concernées. Pour éviter ces répétitions (copier/coller), il est préférable de centraliser cette logique dans une fonction réutilisable à appeler à chaque fois que l'on souhaite effectuer une requête HTTP.
    6. Factoriser l'envoi de requêtes HTTP dans une fonction dédiée :
      • Dans les fichiers citations.php et citation-aleatoire.php, vous avez jusqu'ici codé manuellement la logique permettant d'envoyer des requêtes HTTP avec cURL.
      • Plutôt que de recopier ce même code dans chaque nouvelle page du projet, vous allez le déplacer dans une fonction générique, réutilisable partout.
      • Dans le fichier gestionRequeteHttp.php, créez une fonction nommée envoyerRequeteGet() qui prendra en paramètre l'URL du endpoint (c'est-à-dire l'adresse de la ressource à appeler).
      • Cette fonction devra encapsuler toute la logique de la requête :
        • Initialiser cURL avec l'URL ;
        • Configurer les options nécessaires ;
        • Exécuter la requête ;
        • Gérer les erreurs cURL si besoin ;
        • Récupérer le type MIME de la réponse (ex. : JSON, image, audio...) ;
        • Retourner un tableau associatif contenant :
          • reponse : le contenu brut renvoyé par le serveur,
          • contentType : le type MIME de la réponse,
          • erreur : le message d'erreur en cas d'échec, ou null sinon.
      • Importez ce fichier dans les deux pages concernées avec require_once.
      • Remplacez ensuite la logique actuelle par un appel à envoyerRequeteGet(), en lui passant l'URL du bon endpoint.
      • Testez que tout fonctionne comme avant (affichage de la réponse, gestion d'erreurs, décodage JSON, etc.) avant de poursuivre.
    7. Créer les premières fonctions d'accès à l'API dans gestionApiKaamelott.php :
      • Maintenant que la fonction envoyerRequeteGet() est en place et fonctionne correctement dans vos premières pages, vous pouvez aller plus loin dans la structuration du projet.
      • Dans le fichier gestionApiKaamelott.php, créez des fonctions dédiées pour chaque type de requête que vous souhaitez effectuer.
      • Par exemple :
        • obtenirCitations() pour récupérer toutes les citations.
        • obtenirCitationAleatoire() pour récupérer une citation aléatoire.
      • Chaque fonction devra simplement appeler envoyerRequeteGet() en lui transmettant l'URL du bon endpoint.
      • Cette nouvelle couche permet de :
        • rendre votre code plus clair et plus lisible (on appelle une fonction au nom explicite plutôt que de passer une URL directement),
        • faciliter la maintenance : si l'URL change, il suffit de la modifier dans une seule fonction,
        • éviter les répétitions : chaque appel à l'API a sa propre fonction, que vous pouvez réutiliser autant que nécessaire.
      • Ce fichier jouera un rôle central pour toutes les futures fonctionnalités : vous y ajouterez par la suite des fonctions pour récupérer les citations par personnage, les portraits, les fichiers audio, etc.
    8. Afficher toutes les citations d'un personnage (personnage-citations.php) :
      • Dans le fichier gestionApiKaamelott.php, ajouter une fonction nommée obtenirPersoCitations(string $personnage).
      • Elle retournera l'appel à la fonction envoyerRequeteGet() avec le endpoint approprié.
      • Dans le fichier personnage-citations.php :
        • Inclure le fichier gestionApiKaamelott.php avec require_once.
        • Créer une variable contenant un nom de personnage, par exemple "Perceval".
        • Appeler obtenirPersoCitations() en lui passant ce nom.
        • Décoder la réponse si elle est de type application/json.
        • Parcourir le tableau des citations avec une boucle foreach() et afficher toutes les citations dans une balise <ul>.
        • Afficher un message d'erreur si la requête échoue.
    9. Afficher une citation aléatoire d'un personnage (personnage-citation-aleatoire.php) :
      • Dans le fichier gestionApiKaamelott.php, ajouter une fonction nommée obtenirPersoCitationAleatoire(string $personnage).
      • Elle appellera envoyerRequeteGet() avec le endpoint approprié.
      • Dans personnage-citation-aleatoire.php :
        • Inclure le fichier gestionApiKaamelott.php.
        • Définir un personnage (par exemple "Perceval") dans une variable.
        • Appeler obtenirPersoCitationAleatoire() en lui passant ce nom.
        • Si la réponse est correcte et de type JSON, la décoder et afficher la citation dans une balise <p>.
        • Comme il ne s'agit que d'une seule citation, aucun foreach() n'est nécessaire.
        • Afficher un message d'erreur si la requête échoue.
    10. Afficher le portrait d'un personnage (portrait.php) :
      • Dans le fichier gestionApiKaamelott.php, ajoutez une fonction nommée obtenirPersoPortrait(string $personnage).
      • Elle devra appeler envoyerRequeteGet() en lui transmettant l'URL correspondant au portrait du personnage.
      • Dans le fichier portrait.php :
        • Inclure le fichier gestionApiKaamelott.php avec require_once.
        • Créer une variable contenant le nom d'un personnage (ex. : "Arthur").
        • Appeler obtenirPersoPortrait() avec ce nom.
        • Si aucune erreur n'est survenue, vous allez pouvoir afficher directement l'image dans la page HTML.
        • Pour cela, il est nécessaire de convertir les données binaires de l'image en base64, via base64_encode(), puis d'utiliser une balise <img> avec un src encodé.
        • Exemple de balise HTML :
          
                                                          <img src="data:<?=$type?>;base64,<?=$imageBase64?>" alt="Portrait du personnage">
                                                          <!--
                                                              En dur, cela afficherait quelque chose dans ce style :
                                                              <img src="data:image/jpeg;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA" alt="Portrait du personnage">
                                                          -->
                                                      
        • L'encodage en base64 permet d'inclure directement une image dans le code HTML sans devoir la sauvegarder sur le serveur. Cela simplifie l'affichage de contenus reçus via une requête HTTP tout en gardant la page autonome.
        • Afficher un message d'erreur en cas d'échec.
    11. Écouter un fichier audio Kaamelott sur la page audio.php :
      • Dans le fichier gestionApiKaamelott.php, ajoutez une fonction nommée obtenirAudioFichier(string $nomDuFichier).
      • Elle devra appeler envoyerRequeteGet() en lui transmettant l'URL correspondant au fichier audio.
      • Dans le fichier audio.php :
        • Inclure le fichier gestionApiKaamelott.php avec require_once.
        • Créer une variable contenant le nom d'un fichier audio (ex. : "cest_pas_faux1.mp3").
        • Appeler obtenirAudioFichier() avec ce nom.
        • Si aucune erreur n'est survenue, vous pourrez intégrer ce fichier audio dans une balise <audio> HTML.
        • Pour cela, encodez d'abord les données binaires du fichier MP3 en base64 via base64_encode().
        • Utilisez ensuite la balise suivante pour l'afficher dans le navigateur :
          
                                                      <audio controls>
                                                          <source src="data:<?=$type?>;base64,<?=$audioBase64?>" type="<?=$type?>">
                                                          Votre navigateur ne supporte pas la lecture audio.
                                                      </audio>
                                                      <!--
                                                          En dur, cela afficherait quelque chose dans ce style :
                                                          <source src="data:audio/mp3;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA" type="audio/mp3">
                                                      -->
                                                      
        • Pourquoi utiliser base64 ici aussi ? : comme pour les images, cela permet de lire un fichier audio directement sans avoir à le sauvegarder sur le serveur. La source audio est intégrée au code HTML lui-même, ce qui rend le projet plus simple à déployer.
        • Afficher un message d'erreur si la requête échoue.

Exo-gestion-des-requetes-http-02

  • Objectif : Apprendre à exploiter les données reçues depuis un formulaire HTML, en particulier via un champ de type <select>, afin de personnaliser les requêtes envoyées à une API en fonction des choix de l'utilisateur.
  • Instructions :
    1. Créer une copie du projet : Copiez le dossier de l'exercice précédent et renommez-le exo-gestion-des-requetes-http-02.
    2. Ajouter un formulaire sur la page personnage-citations.php :
      • Le formulaire HTML devra contenir un champ <select> avec 10 personnages au choix. Chaque <option> doit avoir un attribut value représentant le nom du personnage :
        
                                                <form method="GET">
                                                <label for="personnage">Choisissez un personnage :</label>
                                                <select name="personnage" id="personnage" required>
                                                    <option value="Arthur">Arthur</option>
                                                    <option value="Perceval">Perceval</option>
                                                    <!-- etc. -->
                                                </select>
                                                <button type="submit">Valider</button>
                                                </form>
                                                
      • Une fois le formulaire soumis, récupérez la valeur sélectionnée en PHP via :
        
                                                <?php
                                                $personnage = $_GET['personnage'] ?? null;
                                                ?>
                                                
    3. Lier dynamiquement le choix utilisateur à l'appel API :
      • Modifiez personnage-citations.php pour n'appeler obtenirPersoCitations() que si un personnage a été sélectionné.
      • Utilisez la valeur récupérée via le formulaire comme argument de la fonction.
      • Testez que les citations du bon personnage s'affichent correctement.
    4. Centraliser le formulaire :
      • Créez un dossier includes/ à la racine du projet. Ce dossier a pour but de regrouper tous les morceaux de code HTML réutilisables (comme les formulaires, composants d'interface, bannières, etc.). Cela permet de mieux organiser le code, d'éviter les duplications et de faciliter la maintenance du projet.
      • Créez-y un fichier form.php et déplacez-y le formulaire.
      • Dans personnage-citations.php, remplacez le formulaire par :
        
                                                require __DIR__ . '/includes/form.php';
                                            
      • Faites de même dans personnage-citation-aleatoire.php et personnage-portrait.php en adaptant l'appel aux fonctions obtenirPersoCitationAleatoire() et obtenirPersoPortrait() selon le personnage sélectionné.
    5. Ajouter un formulaire de sélection dans audio.php :
      • Comme pour les personnages, proposez 10 fichiers audio à choisir dans un formulaire avec <select> :
        
                                                <form method="GET">
                                                <label for="audio">Choisissez un fichier audio :</label>
                                                <select name="audio" id="audio" required>
                                                    <option value="cest_pas_faux1">cest_pas_faux1.mp3</option>
                                                    <option value="joie_de_vivre">joie_de_vivre.mp3</option>
                                                    <!-- etc. -->
                                                </select>
                                                <button type="submit">Valider</button>
                                                </form>
                                                
      • Tout comme pour les pages impliquant le choix d'un personnage, adaptez le code pour que l'appel à la fonction obtenirAudioFichier() ne soit effectué que si le formulaire a été soumis et qu'un fichier audio a bien été sélectionné, sans oublier de lui passer cette valeur en argument.
    6. Créer des fonctions pour centraliser les options disponibles :
      • Dans le fichier gestionApiKaamelott.php, ajoutez deux fonctions : obtenirListePersonnages() et obtenirListeFichiersAudio().
      • Ces fonctions doivent retourner respectivement :
        • un tableau contenant les noms de 10 personnages, pour obtenirListePersonnages() ;
        • un tableau contenant les noms de 10 fichiers audio, pour obtenirListeFichiersAudio().
      • Ces fonctions permettent de centraliser les listes de personnages et de fichiers audio. Cela évite les répétitions, permet une mise à jour rapide, et garantit une meilleure organisation du code.
    7. Rendre le formulaire dynamique :
      • Transformer le fichier form.php pour qu'il s'adapte automatiquement selon la page qui l'inclut :
        • Appeler obtenirListePersonnages() ou obtenirListeFichiersAudio() avant d'inclure le formulaire ;
        • Passer cette liste dans une variable (par exemple $options) pour qu'elle soit utilisée dans le formulaire ;
        • Dans le formulaire, utilisez une boucle foreach() pour générer automatiquement chaque balise <option> à partir du tableau retourné par la fonction. Pour chaque option, utilisez la clé du tableau comme valeur dans l'attribut value, et affichez le nom du personnage ou du fichier audio comme texte visible.
      • Dans les fichiers personnage-citations.php, personnage-citation-aleatoire.php, personnage-portrait.php et audio.php, vous devez maintenant adapter le traitement côté PHP.
      • La valeur envoyée par le formulaire est une clé numérique (index du tableau). Vous devez utiliser cette clé pour retrouver la vraie valeur (le nom du personnage ou du fichier audio) dans le tableau retourné par obtenirListePersonnages() ou obtenirListeFichiersAudio().
      • Une fois la valeur récupérée, vous pourrez l'utiliser comme argument dans l'appel à la fonction réalisant la requête HTTP (ex. : obtenirPersoCitations(), obtenirAudioFichier(), etc.).
  • Exo-gestion-des-requetes-http-03

    • Objectif : Réaliser une interface unique, interactive et évolutive, permettant à l'utilisateur d'interagir avec plusieurs types de données issus d'une API (citations, portraits, fichiers audio) via un formulaire dynamique à plusieurs étapes.
    • Instructions :
      1. Créer une copie du projet précédent :
      2. Copiez le dossier exo-gestion-des-requetes-http-02 et renommez-le exo-gestion-des-requetes-http-03.
      3. Créer une interface à étapes dans index.php :
        • Cette version de index.php doit afficher un formulaire permettant d'abord de choisir une action (ex : afficher une citation, un portrait...).
        • Si l'action choisie nécessite un argument supplémentaire (comme un personnage ou un fichier audio), le formulaire doit s'adapter automatiquement pour proposer une nouvelle liste déroulante.
        • En résumé :
          • Étape 1 : Choix de l'action
          • Étape 2 (Si besoin) : Choix d'un argument (personnage ou fichier audio)
          • Étape 3 : Affichage de la réponse
      4. Utiliser une seule page pour tout gérer :
        • Dans index.php, créez un formulaire contenant un seul champ <select> dynamique.
        • Ce champ doit afficher soit :
          • la liste des actions disponibles (si aucune action n'a encore été choisie),
          • la liste des personnages (si l'action choisie nécessite un personnage),
          • ou la liste des fichiers audio (si l'action choisie nécessite un fichier audio).
        • Utilisez obtenirListeActions(), obtenirListePersonnages() ou obtenirListeFichiersAudio() pour remplir dynamiquement les options du formulaire.
        • Un champ <input type="hidden"> permet de conserver le choix de l'action lorsqu'un second champ est nécessaire.
      5. Utiliser match() pour déterminer l'état du formulaire :
        • Déterminez dynamiquement quelle liste d'options afficher à l'aide d'un match() sur la valeur de la dernière sélection.
        • Chaque cas retourne un tableau contenant :
          • id : identifiant logique (action, perso, audio)
          • label : texte à afficher pour le champ
          • options : le tableau des options à afficher
      6. Exécuter dynamiquement la bonne requête :
        • Utilisez tenterExecutionRequete() pour :
          • Déterminer automatiquement quelle fonction appeler
          • Identifier si un argument est requis
          • Extraire la bonne valeur (personnage ou audio) à partir de l'index sélectionné
        • Ce fonctionnement repose sur ReflectionFunction pour détecter si la fonction attend un paramètre ou non.
      7. Afficher dynamiquement la réponse :
        • Utilisez le champ contentType pour adapter l'affichage selon le type de donnée retourné :
          • JSON : décoder avec json_decode() et afficher les citations dans des balises <p>
          • Image : encoder avec base64_encode() et afficher avec une balise <img>
          • Audio : même encodage, puis afficher avec une balise <audio>
      8. Ajouter un bouton de retour :
        • Ajoutez un lien "Retour" permettant de revenir à l'étape de sélection d'action initiale si l'utilisateur est dans une étape secondaire (sélection de personnage ou audio).

Les En-têtes HTTP

Les en-têtes HTTP sont des informations envoyées avec chaque requête et réponse HTTP. Ils permettent au client et au serveur de partager des détails sur la nature et le traitement des données transmises.

Un en-tête se présente sous la forme d'une paire clé-valeur et peut contenir des indications sur le format des données, la gestion du cache, l'authentification ou encore les règles de sécurité.

Lorsqu'un client, comme un navigateur ou une application, envoie une requête à un serveur, il inclut des en-têtes pour préciser, par exemple, la nature du contenu souhaité, la langue préférée ou des informations d'identification.

De son côté, le serveur répond avec ses propres en-têtes pour indiquer la nature du contenu retourné, la durée de validité des données, ou encore définir des cookies.

Certains types d'en-têtes sont exclusivement envoyés par le client dans une requête, d'autres uniquement par le serveur dans une réponse, tandis que d'autres encore peuvent être utilisés par les deux parties, selon le contexte.

Voici quelques en-têtes pouvant être configuré par le client

Content-Type

Indique le type MIME des données envoyées ou reçues, afin que le serveur ou le client sache comment interpréter le contenu.

Valeurs courantes :

  • application/json : Format JSON pour les requêtes et réponses.
  • multipart/form-data : Utilisé pour les formulaires HTML avec fichiers.
  • application/x-www-form-urlencoded : Format classique utilisé par les formulaires HTML (clé=valeur&clé2=valeur2).

Il est également possible de spécifier le jeu de caractères utilisé pour l'encodage du texte, comme UTF-8, en l'ajoutant dans l'en-tête :


                Content-Type: application/json; charset=UTF-8
                

Ce type d'information permet notamment au serveur de bien gérer les caractères spéciaux (accents, emojis, etc.).

Content-Length

Indique la taille (en octets) du contenu envoyé ou reçu.

Valeurs courantes :

  • 0 : Aucun contenu.
  • 24567 : Taille du contenu en octets.

Referer

Indique l'URL de la page d'où provient la requête.

Valeurs courantes :

  • https://www.exemple.com/page1 : Le client a cliqué sur un lien depuis cette page.
  • about:blank : Requête générée sans origine spécifique.

User-Agent

Informe le serveur sur le client (navigateur, système d'exploitation, etc.).

Valeurs courantes :

  • Mozilla/5.0 (Windows NT 10.0; Win64; x64) : Navigateur sur Windows 10.
  • curl/7.64.1 : Requête effectuée via cURL.

Accept

Indique au serveur les types de contenu que le client est capable de traiter (et parfois préférer). Cela permet au serveur d'adapter le format de la réponse (HTML, JSON, XML, etc.) selon ce que le client attend.

Valeurs courantes :

  • text/html : Demande une page web en HTML.
  • application/json : Demande une réponse au format JSON.
  • image/png : Indique qu'une image PNG est attendue.

Exemple :


                Accept: text/html, application/json
                

Lorsque vous indiquez plusieurs valeurs dans un en-tête Accept (ou un de ses dérivés), l'ordre des valeurs implique une priorité implicite. Les options situées à gauche sont considérées comme préférées. Toutefois, le serveur reste libre d'ignorer cet ordre s'il juge qu'une autre option est plus appropriée à traiter.

Pour indiquer explicitement vos préférences, vous pouvez utiliser un paramètre nommé q (pour "quality factor"). Ce paramètre permet d'associer un poids (compris entre 0 et 1) à chaque option. Plus la valeur est élevée, plus la préférence est forte. Si aucun q n'est précisé, la valeur par défaut est q=1.0.

Les en-têtes suivants peuvent tous être accompagnés de ce paramètre :

Le serveur utilisera alors ces priorités pour choisir la meilleure réponse possible, en tenant compte des capacités disponibles de son côté.

Exemple :


                Accept: text/html, application/json;q=0.8
                

Ici, le client indique préférer recevoir une réponse au format HTML (q n'étant pas précisé, il vaut 1.0 par défaut) et ensuite au format JSON. Le serveur, s'il en a la possibilité, adaptera sa réponse en fonction de ces préférences.

Accept-Encoding

Ce champ d'en-tête permet au client d'indiquer au serveur quels types de compression il est capable de décompresser. Cela permet de réduire la taille des données transférées, ce qui améliore les performances et réduit le temps de chargement.

Le serveur, s'il prend en charge l'un des encodages proposés, renverra la réponse compressée avec un en-tête Content-Encoding indiquant la méthode utilisée.

Valeurs courantes :

  • gzip : Compression très répandue, souvent utilisée pour HTML, CSS, JS.
  • deflate : Similaire à gzip, mais sans métadonnées.
  • br (Brotli) : Plus performant que gzip, adapté aux sites modernes.
  • identity : Spécifie l'absence de compression.

Exemple :


                Accept-Encoding: br;q=1.0, gzip;q=0.9, deflate;q=0.8
                

Le client indique ici qu'il préfère recevoir les réponses encodées avec br (Brotli), mais accepte également gzip (avec une priorité légèrement inférieure) et deflate en dernier recours. Le serveur choisira le type de compression qu'il supporte en fonction de cet ordre de préférence.

Accept-Language

Indique les langues préférées du client, classées par ordre de préférence. Le serveur peut ainsi adapter le contenu de sa réponse (s'il propose plusieurs versions linguistiques).

Exemple :


                Accept-Language: fr, en;q=0.9, it;q=0.8
                

Accept-Charset

Ce champ d'en-tête permet au client d'indiquer au serveur les encodages de caractères qu'il est capable de comprendre. Cela concerne principalement la manière dont le texte est représenté (ex. : UTF-8, ISO-8859-1).

Le serveur peut alors adapter le jeu de caractères utilisé dans sa réponse, si nécessaire.

Encodages courants :

  • UTF-8 : L'encodage universel recommandé. Il prend en charge la quasi-totalité des langues et symboles du monde.
  • ISO-8859-1 (aussi appelé Latin-1) : Encodage occidental historique, souvent utilisé par défaut dans les anciens navigateurs.
  • UTF-16 : Moins courant sur le Web, utilisé dans certains environnements (comme Windows).
  • US-ASCII : Ancien format limité aux caractères anglais de base (sans accents).

Exemple :


                Accept-Charset: UTF-8, ISO-8859-1;q=0.8
                

Configurer les en-têtes avec cURL

Après avoir initialisé la session cURL, vous pouvez personnaliser le comportement de la requête à l'aide de la fonction curl_setopt().

Nous avons déjà vu un premier exemple avec CURLOPT_RETURNTRANSFER qui permet de stocker la réponse dans une variable au lieu de l'afficher directement.

Mais cette fonction permet tout un tas d'autres options comme la configuration des en-têtes HTTP, le type de méthode utilisée (GET, POST, PUT, etc.), le corps de la requête, ou encore des options de sécurité.

Chaque appel à curl_setopt() prend trois arguments :

  • Le handle cURL (la ressource créée avec curl_init())
  • Une constante représentant l'option à modifier
  • La valeur à associer à cette option

L'option CURLOPT_HTTPHEADER permet d'ajouter des en-têtes personnalisés à la requête HTTP.


                // Ajouter les en-têtes "Accept", "Content-Type" et "Authorization".
                // "Accept" indique que le client préfère une réponse JSON, mais accepte aussi HTML si nécessaire.
                // "Content-Type" précise que les données envoyées sont au format JSON.
                curl_setopt($ch, CURLOPT_HTTPHEADER, [
                    "Accept: application/json;q=1.0, text/html;q=0.8",
                    "Content-Type: application/json",
                ]);
                

S'authentifier auprès d'une API

La majorité des API n'offrent pas un accès libre à leurs données. Pour garantir la sécurité, contrôler l'accès, ou limiter la charge de leurs serveurs, elles exigent que chaque requête soit authentifiée. Cette authentification permet d'identifier le client qui fait la demande, de restreindre ou d'autoriser certaines opérations, et d'appliquer des quotas d'utilisation (nombre de requêtes par jour, par heure, etc.).

Voici quelques cas d'usage concrets :

  • Une API bancaire vérifie que seule la personne connectée accède à ses relevés.
  • Une API de gestion d'utilisateurs réserve certaines actions aux administrateurs (ajout, suppression, etc.).
  • Une API publique (cinéma, météo, traduction...) impose une inscription pour obtenir une clé d'accès unique, afin de limiter les abus et suivre l'activité.

Transmettre une clé d'authentification (API key)

Pour accéder à certaines API, il est nécessaire de transmettre une clé d'identification (appelée API key ou token). Cette clé est fournie après inscription sur le site du service concerné.

Elle peut être transmise de différentes façons :

  • Dans l'URL (en tant que paramètre GET) :
    
                            https://api.exemple.com/data?apikey=abc123
                            
  • Dans un en-tête HTTP personnalisé comme X-API-Key :
    
                            X-API-Key: abc123456789
                            
  • Dans l'en-tête Authorization (standard recommandé) :
    Authorization: Bearer abc123456789

Le choix dépend entièrement de l'API : certaines exigent un paramètre GET, d'autres un en-tête personnalisé, ou encore l'en-tête Authorization. Consultez toujours la documentation officielle pour connaître la méthode attendue.

Exemples d'API connues :

  • OMDb API : clé transmise dans l'URL (?apikey=VOTRE_CLE)
  • NewsAPI : clé envoyée dans l'en-tête X-Api-Key
  • Spotify Web API : utilise un jeton Bearer dans l'en-tête Authorization après une authentification OAuth2

Authentification Basic

Le schéma Basic permet d'envoyer un identifiant et un mot de passe dans l'en-tête Authorization, encodés en base64 au format username:password.

Authorization: Basic dXNlcm5hbWU6bW90ZGVwYXNz

Voici comment le faire en PHP :


                $username = 'Claudy';
                $password = 'F0c4n';
                $token = base64_encode("$username:$password");
                curl_setopt($ch, CURLOPT_HTTPHEADER, [
                "Authorization: Basic $token"
                ]);
                

Le base64 n'est qu'un encodage, pas un chiffrement. Il ne protège pas vos identifiants. Ce type d'authentification doit absolument être utilisé avec une connexion HTTPS.

Authentification Bearer

Le schéma Bearer repose sur l'envoi d'un jeton sécurisé dans l'en-tête Authorization. Ce jeton permet au serveur d'authentifier le client sans avoir besoin de renvoyer ses identifiants à chaque requête.


                Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
                

Le jeton peut être temporaire ou permanent. Les jetons permanents sont souvent utilisés côté serveur pour effectuer des appels récurrents en arrière-plan.

Le jeton est fourni par l'API après une étape d'authentification. Cela peut se faire via un formulaire de connexion, un endpoint comme /login ou /token, ou via un service OAuth2 (OAuth2 est un protocole d'autorisation permettant à un utilisateur de déléguer l'accès à ses données sans transmettre son mot de passe. C'est ce que l'on retrouve dans les boutons "Se connecter avec Google", "Se connecter avec GitHub", etc.).

Comme pour l'authentification Basic, le jeton Bearer doit être transmis exclusivement via une connexion HTTPS, sans quoi il pourrait être intercepté.

Envoyer des données avec cURL

Jusqu'ici, nous avons effectué des requêtes HTTP de type GET (le mode par défaut de cURL), qui servent uniquement à récupérer des données. Cependant, il est tout à fait possible d'envoyer d'autres types de requêtes comme POST, PUT, ou PATCH pour transmettre des données au serveur.

Voici les principales options à connaître pour spécifier la méthode HTTP :

  • CURLOPT_HTTPGET : Force la méthode HTTP GET.
    
                            curl_setopt($ch, CURLOPT_HTTPGET, true);
                            
    Même si GET est utilisé par défaut, il peut être utile de le spécifier manuellement, notamment lorsqu'on souhaite changer dynamiquement le type de requête (par exemple, dans un script ou une fonction).
  • CURLOPT_POST : Spécifie que la méthode sera POST (envoi de données).
    
                            curl_setopt($ch, CURLOPT_POST, true);
                            
  • CURLOPT_CUSTOMREQUEST : Permet de définir manuellement une méthode HTTP (utile pour PUT, PATCH, DELETE, etc.).
    
                            curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PATCH');
                            

Envoyer des données dans le corps de la requête

Une fois la méthode configurée, vous pouvez envoyer les données avec l'option CURLOPT_POSTFIELDS. Les données envoyées peuvent prendre plusieurs formes selon ce que le serveur attend :

  • Pour une API REST avec les méthodes POST,PUT ou PATCH (Content-Type: application/json) :
    
                            // Préparer les données à envoyer au format JSON.
                            $donnees = [
                                'texte' => 'Aller à la piscine',
                                'fait' => false
                            ];
    
                            // Spécifier les données à envoyer dans le corps de la requête.
                            // On encode le tableau PHP en JSON, car c'est le format attendu par l'API.
                            curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($donnees));
    
                            // Indiquer dans les en-têtes que le contenu envoyé est au format JSON.
                            // Cela permet au serveur de comprendre et de parser correctement les données reçues.
                            curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
                            
  • Équivalent à l'envoi d'un formulaire HTML avec la méthode POST (Content-Type: application/x-www-form-urlencoded) :
    
                            // Préparer les données à envoyer.
                            $donnees = [
                                'nom' => 'Focan',
                                'email' => 'focan@exemple.com'
                            ];
    
                            // Ajouter les données dans le corps de la requête.
                            // S'agissant d'un simple tableau associatif (ne contenant pas de fichier en valeur), 
                            // cURL formatera automatiquement le tableau pour qu'il soit identique à celui envoyé par les formulaires HTML (POST).
                            // C'est à dire que les données envoyée peuvent être récupérée avec "$_POST" côté serveur.
                            curl_setopt($ch, CURLOPT_POSTFIELDS, $donnees);
    
                            // De plus, il est inutile d'ajouter manuellement l'en-tête Content-Type,
                            // cURL s'en charge et génère la bonne valeur ("application/x-www-form-urlencoded").
                            
  • Équivalent à l'envoi d'un formulaire HTML avec la méthode POST et un champ <input type="file"> (Content-Type: multipart/form-data) :
    
                            // Chemin vers le fichier image à envoyer.
                            $cheminFichierImage = __DIR__ . DIRECTORY_SEPARATOR . 'images' . DIRECTORY_SEPARATOR . 'poney.jpg';
    
                            // Préparer les données à envoyer, y compris un fichier.
                            // On utilise la classe "CURLFile" pour indiquer à cURL qu'il s'agit d'un fichier à uploader.
                            $donnees = [
                                'nom' => 'Focan',
                                'photo' => new CURLFile($cheminFichierImage)
                            ];
    
                            // Ajouter les données dans le corps de la requête.
                            // cURL détecte automatiquement la présence d'un fichier et construit une requête "multipart/form-data".
                            curl_setopt($ch, CURLOPT_POSTFIELDS, $donnees);
    
                            // Des plus, il est inutile d'ajouter manuellement l'en-tête Content-Type,
                            // cURL s'en charge et génère la bonne valeur ("multipart/form-data") avec les bons délimiteurs (boundary).
                            
    Dans ce cas, il ne faut pas définir manuellement l'en-tête Content-Type. En effet, lorsque vous utilisez un tableau avec un objet CURLFile, cURL se charge de construire automatiquement la requête au format multipart/form-data et de générer la bonne frontière (boundary) pour séparer les différentes parties. Si vous définissez vous-même un Content-Type dans ce contexte, vous risquez de casser la structure attendue par le serveur, ce qui empêchera l'upload de fonctionner correctement.

Exercices - Part 02

Exo-gestion-des-requetes-http-04

  • Objectif : Créer une interface permettant à un utilisateur d'interagir avec une API REST (Todolist) en envoyant des requêtes HTTP via cURL.
  • Instructions :
    1. Créer un nouveau dossier de projet :
      • Créez un dossier nommé exo-gestion-des-requetes-http-04.
      • Organisez votre projet avec la structure suivante :
        
                                                📁 exo-gestion-des-requetes-http-04/
                                                ├── 📁 fonctions/
                                                │   ├── 📄 gestionRequeteHttp.php
                                                │   └── 📄 gestionApiTodolist.php
                                                ├── 📁 template/
                                                │   ├── 📄 header.php
                                                │   └── 📄 footer.php
                                                ├── 📄 index.php
                                                ├── 📄 creer-liste.php
                                                ├── 📄 gerer-liste.php
                                                
      • Vous pouvez réutiliser le fichier gestionRequeteHttp.php si vous l'avez réalisé dans les exercices précédents.
    2. Consulter la documentation de l'API :
      • Rendez-vous sur : https://php-exo-curl.cvmdev.be.
      • Lisez bien la documentation : elle décrit les différentes routes disponibles (GET, POST, PUT, PATCH, DELETE), le format des données attendues, les en-têtes requis, etc.
      • Vous aurez besoin de votre clé privée (Bearer token), disponible dans votre profil utilisateur.
    3. Créer les fonctions de communication avec l'API :
      • Dans le fichier gestionApiTodolist.php, créez les fonctions suivantes :
        • creerTodolist() : crée une nouvelle liste
        • supprimerTodolist() : supprime une liste
        • recupererToutesLesTodolists() : récupère toutes les listes
        • recupererToutesLesTaches() : récupère les tâches d'une liste
        • ajouterTache() : ajoute une tâche à une liste
        • actualiserTache() : modifie une tâche (texte ou état)
        • supprimerTache() : supprime une tâche
      • Chaque fonction doit appeler l'API via une méthode HTTP appropriée (GET, POST, etc.), transmettre les bons paramètres, et gérer la réponse (erreur, succès...)
    4. Créer une interface d'utilisation :
      • index.php :
        • Cette page est la porte d'entrée de l'interface.
        • Elle doit afficher toutes les todolists de l'utilisateur (via recupererToutesLesTodolists()).
        • Chaque liste doit apparaître dans un petit formulaire avec deux boutons :
          • Modifier redirige vers gerer-liste.php avec le nom de la liste en paramètre d'URL.
          • Supprimer supprime la liste via l'API et recharge la page avec les listes restantes.
        • Proposer un bouton ou lien pour accéder à la création d'une nouvelle todolist (creer-liste.php).
        • Si une erreur survient (erreur cURL ou serveur), afficher un message explicite à l'utilisateur.
      • creer-liste.php :
        • Afficher un formulaire permettant de créer une nouvelle todolist (champ de texte + bouton).
        • Le formulaire doit envoyer une requête en GET avec le nom de la liste en paramètre todolistNom.
        • Une fois la liste créée avec succès, l'utilisateur est automatiquement redirigé vers gerer-liste.php pour y ajouter des tâches.
        • En cas d'erreur (nom déjà utilisé, nom invalide, etc.), afficher un message d'erreur.
      • gerer-liste.php :
        • Cette page permet de gérer les tâches d'une liste donnée (passée en paramètre d'URL).
        • Afficher le nom de la liste, ainsi qu'un formulaire pour ajouter une nouvelle tâche (champ texte + bouton).
        • Afficher toutes les tâches existantes, avec pour chacune :
          • Un champ texte (prérempli),
          • Une case à cocher fait,
          • Un bouton pour actualiser la tâche,
          • Un bouton pour supprimer la tâche.
        • Toutes les actions (ajout, modification, suppression) doivent être gérées via des requêtes GET, et rediriger après traitement pour éviter la duplication à l'actualisation.
        • Afficher un message d'erreur clair en cas d'échec (ex : tâche inexistante, liste non trouvée, etc.).

Gestion des requêtes côté serveur

Une fois la requête envoyée par le client, c'est au serveur de la réceptionner, de l'interpréter et d'y répondre correctement. Cela implique plusieurs étapes. Il faut authentifier l'utilisateur si besoin, vérifier les en-têtes reçus, s'assurer que la méthode HTTP utilisée est bien celle attendue, valider les données, et enfin construire une réponse structurée et cohérente. PHP offre des outils simples pour effectuer ces vérifications étape par étape, tout en conservant un contrôle précis sur le comportement du serveur.

  1. Vérifier l'authentification (si applicable) :
    
                            // Récupère tous les en-têtes HTTP envoyés par le client (navigateur, cURL, etc.).
                            // Les en-têtes peuvent ne pas être présents dans "$_SERVER"
                            // à cause de restrictions imposées par certains serveurs ou proxys.
                            // La fonction prédéfinie "getallheaders()" est plus fiable dans ce cas.
                            // Elle renvoie un tableau associatif contenant tous les en-têtes tels qu'ils ont été reçus,
                            // avec leurs noms normalisés (ex : ['Authorization' => 'Bearer ...']).
                            $headers = getallheaders();
    
                            // Vérifier si l'en-tête "Authorization" est présent
                            // et si sa valeur correspond bien à notre jeton d'accès privé.
                            if (!isset($headers['Authorization']) || $headers['Authorization'] !== 'Bearer mon-token-secret') 
                            {
                                // Si le jeton est absent ou incorrect, on retourne une erreur
                                // Le code 400 signifie : utilisateur non authentifié ou identifiants invalides.
                                http_response_code(401); 
                                echo json_encode(['erreur' => 'Authentification requise']);
                            }
                            
  2. Vérifier que la méthode HTTP utilisée par le client est bien celle attendue par le endpoint (URL) actuel :
    
                            // Récupérer la méthode de la requête.
                            $methode = $_SERVER['REQUEST_METHOD'];
    
                            // Vérifier que la méthode est bien celle attendue par le endpoint (URL) actuel.
                            if ($methode !== 'POST') 
                            {
                                // La fonction prédéfinie "http_response_code" permet de configurer le code HTTP retourné au client.
                                // Le code 405 signifie : méthode non autorisée
                                http_response_code(405);
    
                                // Formater la réponse au client au format JSON à l'aide la fonction prédéfinie "json_encode".
                                $reponse = json_encode(['erreur' => 'Méthode non autorisée, utilisez POST']);
    
                                // Informer le client qu'une erreur est survenue.
                                // Dès qu'un contenu est affiché (via "echo", "die", "print_r", etc.),
                                // le texte affiché sera envoyé comme corps de réponse.
                                echo $reponse;
    
                                // Termine immédiatement l'exécution du script. 
                                // Cela garantit que rien d'autre (code ou texte parasite) ne soit ajouté à la réponse, 
                                // évitant ainsi de corrompre la réponse et de provoquer des erreurs côté client.
                                exit;
                            }
                            
  3. Vérifier et valider le type de contenu envoyé par le client :
    
                            // Récupérer le type de contenu configuré par le client via l'en-tête "Content-Type".
                            // Ne pas oublier de récupérer les en-têtes si ça n'a pas été fait en amont : "$headers = getallheaders()";
                            $contentType = $headers['Content-Type'] ?? '';
    
                            // Vérifier que le type de contenu est au format attendu (en l'occurrence, un JSON).
                            if ($contentType !== 'application/json') 
                            {
                                // Le code 415 signifie : Type de média non supporté.
                                http_response_code(415);
    
                                // Répondre au client qu'une erreur est survenue.
                                echo json_encode(['erreur' => 'Type de contenu attendu : application/json']);
                                exit;
                            }
    
                            // Malherueusement, on ne peut pas uniquement se fier aux en-têtes fournis par le client.
                            // Il faut donc s'assurer que les données envoyées par le client sont bien au format attendu.
    
                            // Récupère les données brutes du corps de la requête.
                            // Contrairement aux formulaires HTML classiques (encodés en "application/x-www-form-urlencoded" ou "multipart/form-data"),
                            // les requêtes JSON nécessitent "file_get_contents()" avec "php://input" pour lire directement le contenu envoyé par le client.
                            $donneesBrutes = file_get_contents('php://input');
    
                            // Éviter les attaques par débordement.
                            // Pour cet exemple, on limite le poids des données à 1 Ko
                            if (strlen($donneesBrutes) > 1024) 
                            {
                                // Le code 413 signifie : corps de la requête (payload) trop volumineux.
                                http_response_code(413);
    
                                // Répondre au client qu'une erreur est survenue.
                                echo json_encode(['erreur' => 'Données trop volumineuses']);
                                exit;
                            }
    
                            // Tenter de convertir le JSON en un tableau exploitable par PHP.
                            $donnees = json_decode($donneesBrutes, true);
    
                            // Vérifier que la dernière utilisation de "json_decode" n'a pas rencontré d'erreur.
                            if (json_last_error() !== JSON_ERROR_NONE) 
                            {
                                // Le code 400 signifie : Requête invalide.
                                http_response_code(400);
    
                                // Répondre au client qu'une erreur est survenue.
                                echo json_encode(['erreur' => 'JSON invalide']);
                                exit;
                            }
                            
  4. Vérifier la validité des champs :
    
                            if (!isset($donnees['nom']) || !is_string($donnees['nom']) || empty(trim($donnees['nom']))) 
                            {
                                // Répondre au client qu'une erreur est survenue.
                                http_response_code(400);
                                echo json_encode(['erreur' => 'Le champ "nom" est requis et doit être une chaîne non vide']);
                                exit;
                            }
    
                            if (!isset($donnees['age']) || !filter_var($donnees['age'], FILTER_VALIDATE_INT) || $donnees['age'] < 0) 
                            {
                                // Répondre au client qu'une erreur est survenue.
                                http_response_code(400);
                                echo json_encode(['erreur' => 'Le champ "age" doit être un entier positif']);
                                exit;
                            }
    
                            // Répondre au client que tout est ok.
                            http_response_code(200);
                            echo json_encode(['message' => 'Utilisateur ajouté avec succès']);
                            exit;
                            

En-têtes HTTP côté serveur

Avant d'envoyer une réponse au client, le serveur peut, lui aussi, définir des en-têtes HTTP pour transmettre des informations supplémentaires sur le contenu, le comportement de la réponse ou les règles de communication à suivre.

Certains en-têtes, comme Content-Type ou Content-Length, sont utilisés aussi bien côté client que côté serveur. D'autres, en revanche, sont propres aux réponses serveur.

Voici un aperçu des en-têtes les plus fréquents, de leur utilité et des valeurs qu'ils peuvent contenir.

Transfer-Encoding

Cet en-tête indique comment le serveur transmet les données au client, en particulier si elles sont envoyées en plusieurs fragments plutôt qu'en un seul bloc. Il est souvent utilisé pour le streaming (envoi continu) ou quand la taille totale des données n'est pas connue à l'avance.

Valeurs courantes :

  • chunked : Les données sont divisées en petits morceaux (chunks), utile pour les flux en direct ou les réponses générées dynamiquement.

Exemple : Le serveur envoie une réponse fragmentée.


                    Transfer-Encoding: chunked
                

Content-Encoding

Cet en-tête précise si le contenu de la réponse a été compressé ou modifié avant d'être envoyé. Il permet de réduire la taille des données pour accélérer le transfert.

Valeurs courantes :

  • gzip : Compression courante pour les fichiers HTML, CSS ou JavaScript.
  • compress : Ancienne méthode de compression, rarement utilisée aujourd'hui.
  • deflate : Compression légère, similaire à gzip mais moins courante.
  • br : Compression moderne (Brotli), très efficace pour les sites web.
  • identity : Aucune compression, les données sont envoyées telles quelles.

Exemple : Le serveur compresse la réponse avec gzip.


                    Content-Encoding: gzip
                

Cache-Control

Indique au client (navigateur ou proxy) comment mettre en cache la réponse pour optimiser les performances.

Valeurs courantes :

  • public : Tout le monde (navigateurs, proxys) peut mettre la réponse en cache.
  • private : Seuls les navigateurs des utilisateurs peuvent la mettre en cache.
  • no-store : Interdit toute mise en cache pour des données sensibles.
  • max-age=86400 : La réponse reste valide en cache pendant 24 heures (86400 secondes).

Exemple : Autorise le cache public pendant 7 jours.


                    Cache-Control: public, max-age=604800
                

Set-Cookie

Permet au serveur d'envoyer un cookie au client pour stocker des informations, comme une session utilisateur.

Valeurs courantes :

  • sessionId=abc123; HttpOnly : Un cookie inaccessible aux scripts pour plus de sécurité.
  • user=JohnDoe; Secure : Un cookie utilisable uniquement en HTTPS.

Exemple : Définit un cookie sécurisé avec des restrictions strictes.


                    Set-Cookie: userId=42; Secure; HttpOnly; SameSite=Strict
                

Strict-Transport-Security

Force le navigateur à utiliser HTTPS pour toutes les futures connexions au serveur, renforçant la sécurité.

Valeurs courantes :

  • max-age=31536000 : Applique cette règle pendant 1 an.
  • includeSubDomains : Étend la règle à tous les sous-domaines.

Exemple : Active HTTPS pendant 1 an pour le domaine et ses sous-domaines.


                    Strict-Transport-Security: max-age=31536000; includeSubDomains
                

Content-Security-Policy

Limite les sources autorisées pour les scripts, images, etc., afin de prévenir les attaques comme le XSS (injections de scripts malveillants).

Valeurs courantes :

  • default-src 'self' : N'autorise que les ressources du même domaine.
  • script-src 'self' https://trusted.cdn.com : Accepte les scripts du domaine et d'un CDN de confiance.

Exemple : Restreint les scripts et images au domaine actuel.


                    Content-Security-Policy: default-src 'self'; script-src 'self'; img-src 'self'
                

Configurer les en-têtes côté serveur avec PHP

Pour configurer les en-têtes côté serveur, on utilise la fonction native header() de PHP. Elle permet d'ajouter, modifier ou supprimer des en-têtes HTTP avant que le corps de la réponse ne soit envoyé au client.

Les en-têtes HTTP doivent être envoyés avant d'afficher du contenu dans la réponse, quel que soit le client (navigateur, cURL, Postman, etc.). Si un affichage a déjà eu lieu, PHP déclenche une erreur du type headers already sent.

Dans l'exemple suivant, on indique au client que la réponse sera au format JSON et que sa mise en cache est interdite :


                header('Content-Type: application/json');
                header('Cache-Control: no-store');
                

Notez que la fonction header() permet aussi de réaliser des redirections


                // Effectue une redirection externe vers la page "/connexion.php".
                // Cela signifie que le serveur envoie un en-tête HTTP "Location" au client,
                // qui va alors initier une nouvelle requête vers cette URL.
                // La redirection est dite "externe" car elle est visible côté client (l'URL change dans la barre d'adresse du navigateur).
                header('Location: /connexion.php');

                // Toujours interrompre l'exécution du script PHP après une redirection.
                exit;
                

Le CORS

Le CORS (Cross-Origin Resource Sharing) est un mécanisme utilisé par les navigateurs web pour contrôler les requêtes HTTP effectuées entre des origines différentes (par exemple, entre deux domaines distincts).

Il permet à un serveur de déclarer explicitement quelles origines sont autorisées à accéder à ses ressources, via des en-têtes HTTP spécifiques.

Si aucune autorisation n'est définie, le navigateur bloque l'accès à la réponse, afin de limiter les risques liés aux interactions entre sites.

Le mécanisme CORS ne concerne que les requêtes HTTP effectuées depuis un navigateur web. Les requêtes exécutées côté serveur, comme celles écrites en PHP, ne sont pas soumises à cette restriction, elles peuvent appeler librement des ressources sur d'autres domaines.

Cependant, le CORS doit être pensé lorsque l'on développe une API ou un serveur web qui doit être consulté par des applications exécutées dans un navigateur.

Dans ce contexte, comprendre et configurer correctement CORS permet de permettre ou restreindre l'accès à votre API de manière sécurisée et maîtrisée.

Pourquoi le CORS a-t-il été créé ?

Le CORS a été conçu pour répondre aux limitations de la politique de même origine (Same-Origin Policy), une règle de sécurité mise en œuvre par les navigateurs.

Cette règle empêche les scripts exécutés dans une page web d'accéder à des ressources situées sur une autre origine (c'est-à-dire un domaine, un port ou un protocole différent), sauf si cette origine est explicitement autorisée.

Ce fonctionnement protège l'utilisateur contre certaines attaques, mais rend aussi plus complexe la communication entre services répartis sur plusieurs domaines (comme une application web qui interagit avec une API distante).

Le CORS permet de lever ces restrictions de manière encadrée, en laissant au serveur le soin de définir quelles origines externes sont autorisées à accéder à ses ressources.

Comment fonctionne le CORS ?

Lorsque le navigateur effectue une requête HTTP vers une origine différente, il applique les règles du mécanisme CORS pour déterminer si la réponse peut être accessible côté client. Le comportement du navigateur dépend du type de requête effectuée.

Les requêtes "simples"

Une requête est considérée comme simple si elle respecte toutes les conditions suivantes (selon la spécification CORS) :

  • La méthode est GET, POST ou HEAD
  • Les en-têtes utilisés sont uniquement :
    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type avec l'une des valeurs suivantes :
      • application/x-www-form-urlencoded
      • multipart/form-data
      • text/plain
  • Aucune authentification spéciale (Authorization, etc.)
  • Aucun envoi de credentials (credentials: include)

Dans ce cas, le navigateur envoie la requête directement, sans pré-vérification.

Le serveur doit simplement répondre avec l'en-tête :


                    Access-Control-Allow-Origin: https://exemple.com
                

Sans cet en-tête, le navigateur bloque l'accès à la réponse, même si le serveur a bien répondu.

Les requêtes "complexes"

Dès que la requête ne respecte pas l'un des critères ci-dessus, elle est considérée comme complexe.

C'est le cas, par exemple, si :

  • La méthode est PUT, DELETE, PATCH, etc.
  • Un en-tête personnalisé est utilisé (ex. : Authorization, X-Custom-Header)
  • Le Content-Type a une valeur non reconnue comme simple (application/json, etc.)

Dans ce cas, le navigateur effectue automatiquement une requête de pré-vérification (preflight), en utilisant la méthode OPTIONS.

Cette requête contient des en-têtes comme :


                    Access-Control-Request-Method: PUT
                    Access-Control-Request-Headers: Authorization
                

Elle permet au navigateur de demander au serveur s'il accepte la requête envisagée.

Le serveur doit alors répondre avec les en-têtes suivants :


                    Access-Control-Allow-Origin: https://exemple.com
                    Access-Control-Allow-Methods: OPTIONS, PUT
                    Access-Control-Allow-Headers: Authorization
                

Si ces informations ne sont pas présentes ou ne correspondent pas, le navigateur bloque la requête réelle.

La pré-vérification n'est envoyé qu'une seule fois pour un ensemble donné de paramètres (méthode, en-têtes, etc.) ; le navigateur peut le mettre en cache pendant un certain temps, selon la réponse du serveur (Access-Control-Max-Age).

Implications et limitations du CORS

Gestion des cookies

Le CORS introduit certaines limitations et considérations importantes :

Par défaut, les cookies, sessions, et autres informations d'identification ne sont pas envoyés dans les requêtes cross-origin. Pour qu'ils le soient, plusieurs conditions doivent être réunies des deux côtés (client et serveur) :

Côté client : La requête doit explicitement indiquer l'envoi des credentials, avec l'option :


                    fetch(url, {
                        credentials: "include"
                    });
                

Côté serveur

: Le serveur doit inclure les en-têtes Access-Control-Allow-Credentials et Access-Control-Allow-Origin. De plus, ce dernier ne doit pas être défini sur *. Il doit indiquer une origine précise. Par exemple :


                    Access-Control-Allow-Credentials: true
                    Access-Control-Allow-Origin: https://mon-site.com
                

Gestion des autorisations

Il est courant d'utiliser l'en-tête Authorization dans une requête HTTP pour transmettre un jeton d'accès (Bearer) ou une authentification HTTP basique (Basic).

Contrairement à ce que pourrait laisser penser le nom credentials, l'envoi de l'en-tête Authorization n'est pas lié à l'option credentials: "include" dans fetch().

Cela signifie qu'il est tout à fait possible d'envoyer un en-tête Authorization dans une requête CORS, même si credentials vaut omit ou same-origin (valeur par défaut). De plus, cela reste compatible avec une configuration serveur utilisant Access-Control-Allow-Origin: *, tant que l'en-tête est autorisé dans Access-Control-Allow-Headers.

Exemple côté client :


                    fetch("https://api.exemple.com/data", {
                        method: "GET",
                        headers: {
                            "Authorization": "Bearer abc123"
                        }
                    });
                

Côté serveur - Pour que cette requête soit acceptée dans un contexte cross-origin, le serveur doit répondre à la pré-vérification (OPTIONS) avec :


                    Access-Control-Allow-Headers: Authorization
                    Access-Control-Allow-Origin: *
                

Cela fonctionne tant qu'il n'y a pas d'envoi de cookies ou de session, et que credentials: "include" n'est pas utilisé.

Bonnes pratiques CORS

  • Toujours spécifier une origine précise (Access-Control-Allow-Origin) si votre serveur gère des données sensibles. Évitez le joker * dans ce cas.
  • N'utilisez Access-Control-Allow-Credentials: true que si nécessaire, et jamais avec * comme origine.
  • Autorisez explicitement les en-têtes personnalisés (comme Authorization) via Access-Control-Allow-Headers.
  • Pour permettre l'envoi de cookies ou de sessions, assurez-vous que :
    • Le client utilise credentials: "include"
    • Le serveur envoie Access-Control-Allow-Credentials: true
    • L'origine soit spécifiée (pas *)
  • Évitez de tout ouvrir "en test" (ex : *, toutes les méthodes, tous les en-têtes) sans contrôle, cela peut entraîner des failles de sécurité.
  • Testez les requêtes CORS dans un navigateur (via fetch, Postman ou outils de dev) pour comprendre les erreurs éventuelles, les navigateurs bloquent sans toujours afficher des messages explicites.