Concevoir des contrats HTTP robustes pour des interfaces edge‑first et des backends PHP
Date Published

Un backend PHP peut renvoyer un JSON parfaitement valide tout en restant fragile à l’edge : statut ambigu, en-têtes oubliés, erreur métier confondue avec une panne réseau ou cache dont le comportement dépend d’un défaut de plateforme. Concevoir des contrats HTTP edge-first consiste à rendre explicites les règles que le navigateur, le CDN, le Worker, le proxy et l’origin devront tous appliquer.
Le point de départ est simple : HTTP est sans état. Chaque requête doit donc être compréhensible isolément, et chaque réponse doit porter, au-delà de son payload, les informations nécessaires à son interprétation, sa mise en cache et sa revalidation. Pour une équipe PHP, cette discipline réduit le couplage, améliore l’observabilité et évite que la logique métier soit réimplémentée dans les couches edge.
Contrats HTTP edge-first : la réponse courte
Un contrat HTTP robuste pour une interface edge-first spécifie au minimum la méthode, l’URI, les codes de statut, les en-têtes, les directives de cache et le comportement de revalidation. Côté PHP, utilisez des messages standardisés et immuables, traitez les réponses 4xx/5xx comme des réponses HTTP normales, et distinguez les erreurs de transport des erreurs métier.
Cette définition paraît élémentaire, mais elle impose un changement de perspective. Le contrat n’est pas seulement le schéma JSON exposé par une API. Il est l’ensemble des signaux HTTP qui permettent aux intermédiaires de prendre une décision correcte sans connaître le métier : servir une réponse, la conserver, la revalider, transmettre une erreur ou éviter de refaire un appel inutile à l’origin.
Dans une architecture edge-first, cette précision est particulièrement importante. Un CDN ou une plateforme d’exécution à l’edge peut répondre avant que la requête n’atteigne PHP. Cette capacité est utile seulement si l’origin produit des réponses dont la sémantique est stable, explicite et compatible avec les caches distribués.
Partir des sémantiques HTTP plutôt que de la connexion
La RFC 9110 rappelle qu’HTTP est un protocole sans état. Cela ne signifie pas qu’une application ne peut jamais connaître un utilisateur ; cela signifie que le serveur ne doit pas dépendre d’un état implicite de connexion pour comprendre la demande. Les éléments nécessaires à son traitement doivent être portés par la requête : méthode, URI, en-têtes pertinents, représentation et mécanisme d’authentification lorsque celui-ci est requis.
Pour un contrat d’API, cette règle aide à trancher de nombreuses décisions. Une ressource publique et stable peut être mise en cache si sa représentation et ses en-têtes le permettent. Une opération qui modifie un état doit exprimer clairement sa méthode et ses préconditions. Une réponse personnalisée ne doit pas être traitée comme une réponse publique par défaut.
Décrire une ressource, pas un échange de connexion
Un anti-modèle courant consiste à concevoir une API comme une succession de messages dépendants d’une session technique : « le client a déjà demandé ceci », « le serveur sait à quelle étape il se trouve » ou « cette réponse ne vaut que parce que la connexion reste ouverte ». Ce modèle se comporte mal lorsqu’un cache, un proxy ou une fonction edge intervient entre le client et PHP.
À l’inverse, une interface edge-first décrit une ressource et ses représentations. L’URI identifie le périmètre demandé ; la méthode exprime l’intention ; le statut indique le résultat ; les en-têtes précisent les conditions de réutilisation. Le corps JSON est alors une partie essentielle du contrat, mais pas sa totalité.
GET
doit exprimer une lecture d’une représentation, avec une politique de cache documentée.
POST
doit préciser l’opération créée ou déclenchée, ainsi que les statuts possibles.
PUT
,
PATCH
et
DELETE
doivent définir leurs préconditions et leurs effets attendus.
Les réponses doivent préciser ce qui relève du métier, de l’autorisation, de la validation ou d’une indisponibilité technique.
L’idempotence ne se réduit pas à choisir une méthode « correcte » dans une documentation. Elle doit être prise en compte dans le comportement réel de l’endpoint. Une reprise de requête par un client, un proxy ou une couche edge ne doit pas produire de surprise fonctionnelle lorsque l’opération est présentée comme idempotente.
Définir le contrat complet : méthode, URI, statut, en-têtes et corps
Un contrat HTTP exploitable par plusieurs équipes doit permettre de prédire le comportement sans lire le code PHP ni la configuration du CDN. Le plus efficace est d’adopter une fiche de contrat répétable pour chaque endpoint ou famille de ressources.
Définir la méthode et l’URI.
Indiquez la ressource visée, les paramètres de chemin, les paramètres de requête autorisés et leur influence sur la représentation.
Décrire les entrées.
Spécifiez les en-têtes attendus, le format du corps, les contraintes de validation et les mécanismes d’authentification applicables.
Énumérer les statuts.
Documentez les réponses de succès, les erreurs de validation, les refus d’accès, les absences de ressource et les cas de conflit pertinents.
Définir les en-têtes de sortie.
Incluez au minimum le type de contenu et les directives de cache. Ajoutez les en-têtes de revalidation quand ils font partie du comportement prévu.
Documenter la revalidation.
Expliquez ce que peut faire un intermédiaire lorsqu’une réponse n’est plus fraîche et si une version antérieure peut être servie temporairement.
Cette structure évite un problème fréquent : une OpenAPI riche sur le schéma JSON, mais muette sur les règles qui déterminent le comportement à l’edge. Or, un cache ne peut pas déduire de manière fiable vos intentions métier à partir d’un champ comme updatedAt ou isPublic. Il applique des règles HTTP et, notamment, les directives présentes dans les en-têtes.
Les en-têtes sont des données de produit
Les en-têtes sont parfois traités comme un détail d’infrastructure, ajouté en fin de chaîne par un framework, un middleware ou une configuration de proxy. Dans une interface edge-first, ils font partie de la promesse faite au consommateur. Cache-Control indique la stratégie de fraîcheur ; les en-têtes de type de contenu fixent la représentation ; les mécanismes de validation donnent un cadre à la revalidation.
Le manuel PHP illustre cette séparation : avec les streams HTTP natifs, les en-têtes de réponse sont accessibles séparément du corps via $http_response_er. Cette distinction technique doit devenir une distinction de conception. Tester uniquement le JSON reçu ne suffit pas si le client, un CDN ou un Worker décide aussi à partir des ers.
OpenAPI 3.1.2 fournit un support approprié pour documenter ces réponses, leurs statuts et leurs en-têtes. L’objectif n’est pas de confier toute la politique de plateforme au document, mais de faire de la spécification une référence commune entre produit, frontend, edge, backend PHP et exploitation.
Standardiser le transport HTTP côté PHP avec PSR-7 et PSR-18
La robustesse commence aussi dans le code qui appelle des services externes ou compose des requêtes vers un origin. PSR-7 définit des interfaces de messages HTTP en PHP. Elles structurent les requêtes et les réponses autour d’objets standardisés, avec une approche immuable : une modification produit un nouveau message plutôt que de muter silencieusement l’original.
Cette immutabilité est utile dans les applications comportant des middlewares, des politiques de trace, des mécanismes d’authentification et des adaptations de cache. Chaque couche peut enrichir ou transformer le message sans créer d’effets de bord difficiles à diagnostiquer. Elle facilite également les tests : le même contrat peut être exercé avec des implémentations interchangeables.
PSR-18 complète ce modèle en standardisant l’envoi des requêtes par des clients HTTP interchangeables. Votre domaine peut dépendre d’une interface de client plutôt que d’un SDK ou d’une bibliothèque précise. Cela réduit le couplage avec une implémentation HTTP et rend plus raisonnable le remplacement d’un client, l’usage d’un faux client en test ou la centralisation d’une politique de timeout.
Une séparation utile des responsabilités
Transport :
construire, envoyer et recevoir des messages HTTP compatibles avec les standards PHP.
Cache :
exposer et respecter les sémantiques HTTP de fraîcheur et de revalidation.
Métier :
décider si une commande est valide, si une ressource est accessible et quelle représentation doit être produite.
Cette séparation n’interdit pas au métier d’influencer la politique de cache. Une ressource peut être plus ou moins volatile selon sa nature. En revanche, le métier ne devrait pas devoir connaître la mécanique propre à un CDN pour exprimer cette décision. Il indique une politique à travers le contrat HTTP ; la couche edge applique les mécanismes de cache appropriés.
Le catalogue PHP-FIG, qui inclut notamment PSR-7, PSR-18 et PSR-6, confirme que les messages HTTP, les clients et le cache constituent des sujets d’interopérabilité majeurs dans l’écosystème PHP. S’aligner sur ces interfaces ne remplace pas une architecture, mais évite de faire dépendre l’architecture de détails de bibliothèque.
Ne pas transformer les 4xx et 5xx en exceptions de transport
Une erreur HTTP n’est pas nécessairement une erreur de transport. PSR-18 est clair sur ce point : un client ne doit pas considérer une réponse HTTP bien formée comme une erreur uniquement parce que son code de statut appartient à la famille 4xx ou 5xx. Les réponses 400 et 500 doivent être retournées normalement au code appelant.
Cette règle est essentielle pour préserver la sémantique du contrat. Une réponse 422 éventuelle, un 404 ou un 503 portent une information qui doit être lue, loggée et traitée à son niveau. Convertir indistinctement tous les statuts non 2xx en exception peut faire perdre le corps d’erreur, les en-têtes de diagnostic ou les directives de cache associées à la réponse.
Classer les échecs par nature
PSR-18 distingue les situations où la requête est invalide, où la réponse ne peut pas être parsée, et où le transport échoue, par exemple en cas de panne réseau ou de timeout. Ces catégories doivent rester distinctes des erreurs métier renvoyées par un service qui a effectivement reçu et traité la requête.
Erreur de requête :
le client ne peut pas envoyer une requête conforme à ce qui est attendu.
Erreur réseau ou timeout :
l’échange n’a pas abouti de manière exploitable ; il n’existe pas nécessairement de réponse HTTP à interpréter.
Erreur de parsing :
une réponse ne peut pas être analysée comme un message HTTP valide.
Réponse HTTP 4xx/5xx :
une réponse a été reçue ; son statut, ses en-têtes et son corps relèvent du contrat de l’API.
Cette taxonomie améliore les décisions de reprise. Une indisponibilité réseau peut justifier une stratégie adaptée à la nature de l’opération, tandis qu’une réponse 400 signale généralement une requête à corriger. Une réponse 500 est une réponse serveur et non la preuve qu’aucun traitement n’a eu lieu ; la prudence est donc nécessaire avant toute répétition automatique d’une opération qui pourrait avoir des effets.
Elle améliore aussi les tableaux de bord. Mélanger les échecs de connectivité avec les refus métier produit des alertes peu actionnables. Mesurer séparément les erreurs de transport, les réponses 5xx de l’origin et les réponses 4xx attendues donne une lecture bien plus fidèle de la santé technique et de l’usage de l’API.
Rendre le cache edge explicite avec Cache-Control
Une réponse sans Cache-Control n’est pas forcément une réponse qui ne sera jamais mise en cache. Cloudflare indique que des réponses dépourvues de cet en-tête peuvent tout de même être mises en cache selon le mécanisme de fraîcheur heuristique de RFC 9111, avec des TTL par défaut qui dépendent du code de statut. Compter sur cette situation revient à laisser une partie du contrat implicite.
Pour une API ou une interface web dont la performance dépend de l’edge, la politique doit être exprimée volontairement. Le backend PHP qui sert d’origin doit générer les directives adaptées à la ressource ; l’équipe plateforme doit ensuite vérifier que les règles du CDN ne les contredisent pas. Ce partage de responsabilités est plus fiable qu’une liste d’exceptions dans une configuration edge.
Choisir une politique de fraîcheur compréhensible
Quand l’objectif est de pouvoir servir temporairement une réponse devenue non fraîche pendant qu’une revalidation se déroule en arrière-plan, Cloudflare recommande la combinaison de max-age et de stale-while-revalidate. max-age établit la fenêtre de fraîcheur ; stale-while-revalidate encadre la période durant laquelle la réutilisation d’une réponse stale est acceptée pendant la revalidation.
Il faut cependant lire les interactions entre directives. Cloudflare documente que s-maxage, must-revalidate et proxy-revalidate peuvent empêcher la diffusion d’un contenu stale durant la revalidation à l’edge, conformément à la RFC 9111. Si votre objectif est précisément de conserver le comportement stale-while-revalidate à l’edge, l’emploi de s-maxage n’est donc pas neutre.
Il ne s’agit pas de bannir ces directives de manière universelle. Elles peuvent exprimer une exigence de fraîcheur plus stricte. Le vrai choix est produit et métier : peut-on servir une représentation légèrement ancienne pendant une courte période, ou faut-il imposer une revalidation avant toute réutilisation ? Le contrat doit répondre à cette question pour chaque catégorie de ressource sensible.
Inclure l’échec dans la conception
stale-if-error fait également partie des mécanismes à considérer lorsqu’une représentation antérieure peut être préférable à une indisponibilité lors d’un échec de l’origin. Mais cette décision ne convient pas à tous les contenus. Une donnée dont l’exactitude immédiate est critique ne doit pas recevoir la même politique qu’une page publique ou qu’un catalogue pouvant tolérer une légère ancienneté.
Le point important est de ne pas réduire le cache à un gain de performance. À l’edge, le cache est aussi une politique de disponibilité, de cohérence perçue et de charge origin. Les règles doivent être visibles dans le contrat, les tests et la spécification, plutôt que dissimulées dans un comportement par défaut.
Adapter le contrat aux mécanismes Cloudflare Workers et Fastly
Le contrat HTTP doit rester portable, mais l’équipe doit comprendre le mécanisme effectivement activé par la plateforme. Cloudflare indique que Workers Cache peut renvoyer des réponses mises en cache depuis l’edge sans exécuter le Worker, ce qui réduit la latence et l’usage CPU. Cette optimisation est d’autant plus efficace que l’origin fournit une réponse cohérente et explicitement cacheable.
Une limite importante concerne les API de cache. Les directives stale-while-revalidate et stale-if-error ne sont pas prises en charge avec cache.put et cache.match. Si le contrat repose sur ces comportements, il faut donc utiliser les mécanismes de Workers Caching appropriés plutôt que de supposer que l’API Cache seule reproduira la sémantique HTTP attendue.
Fastly porte une approche comparable, centrée sur HTTP. Son cache readthrough HTTP est présenté comme le mécanisme par défaut pour réutiliser les réponses backend et réduire les appels à l’origin. Sur Fastly Compute, l’envoi d’une requête vers un backend via send ou fetch déclenche le cache readthrough. Là encore, des en-têtes propres sur la réponse origin sont une condition pratique de résultats prévisibles.
Préserver la portabilité sans nier les différences
La bonne abstraction n’est pas « le cache fonctionne pareil partout ». Les plateformes disposent de mécanismes et de limites spécifiques. La bonne abstraction est : le contrat exprime d’abord des sémantiques HTTP standard, puis l’implémentation edge est validée pour vérifier qu’elle respecte cette intention.
Définissez la politique dans les réponses PHP et dans OpenAPI.
Testez le comportement réel sur la plateforme retenue, y compris en revalidation et lors d’un échec origin.
Documentez les écarts liés à une API de plateforme, comme les limites de
cache.put
et
cache.match
.
Évitez de déplacer la logique métier dans un Worker ou une fonction Compute uniquement pour compenser un contrat origin imprécis.
Cette méthode conserve la valeur d’une architecture edge-first sans rendre le backend PHP dépendant d’une seule plateforme. Elle rend aussi les migrations moins risquées : les en-têtes et statuts restent le langage commun entre l’origin, Cloudflare, Fastly et les autres intermédiaires HTTP.
Tester et documenter le contrat HTTP avant la mise en production
Un contrat n’est robuste que s’il est vérifié à plusieurs niveaux. Les tests unitaires PHP peuvent confirmer qu’un contrôleur ou une couche de réponse produit le bon statut, le bon corps et les bons en-têtes. Les tests d’intégration doivent ensuite vérifier l’échange avec un client HTTP conforme à l’interface attendue, sans confondre une réponse 4xx/5xx avec une exception de transport.
Les tests de bout en bout doivent enfin observer l’edge. L’objectif n’est pas uniquement de constater un cache hit ou miss, mais de vérifier les conséquences de la politique déclarée : réponse fraîche, revalidation, réponse stale éventuellement autorisée, comportement quand l’origin est indisponible et absence de mise en cache pour les ressources qui ne doivent pas être partagées.
Une checklist de revue pragmatique
La méthode et l’URI permettent-elles de comprendre l’opération sans état de connexion implicite ?
Chaque statut documenté correspond-il à un cas réel, avec un corps et des en-têtes lorsque nécessaire ?
Les consommateurs savent-ils distinguer une réponse HTTP d’un timeout, d’une panne réseau ou d’une erreur de parsing ?
Les règles
Cache-Control
sont-elles définies par catégorie de ressource plutôt que laissées au hasard ?
La stratégie de revalidation est-elle compatible avec les directives choisies, notamment si
stale-while-revalidate
est attendu ?
La configuration Cloudflare ou Fastly a-t-elle été testée avec les mécanismes réellement employés ?
La documentation OpenAPI décrit-elle les en-têtes de réponse et non le seul schéma JSON ?
La revue doit associer les rôles qui portent les conséquences de ces choix : développeurs PHP, responsables frontend, ingénieurs plateforme et responsables produit. Un TTL, un statut 503 ou une possibilité de servir une réponse stale ont des effets visibles pour l’utilisateur comme pour l’exploitation. Les décider collectivement évite les contrats techniquement corrects mais fonctionnellement inadaptés.
Enfin, observez ce que le système délivre réellement. Les journaux et la supervision doivent permettre de relier une requête à sa réponse HTTP, à son statut et, lorsque l’architecture le permet, au comportement du cache. Cette visibilité permet de détecter un en-tête manquant, une règle edge contradictoire ou une hausse d’erreurs réseau sans attribuer à tort le problème au domaine métier.
À retenir pour un backend PHP durable à l’edge
Des contrats HTTP solides donnent à l’edge des règles explicites au lieu de l’obliger à deviner l’intention de l’origin. Avec PSR-7 et PSR-18, un backend PHP peut standardiser ses messages et ses clients ; avec des statuts correctement interprétés, il peut séparer les réponses métier des pannes de transport ; avec Cache-Control, il peut rendre la fraîcheur et la revalidation auditables.
Commencez par un endpoint à forte lecture, décrivez dans OpenAPI ses statuts et ses en-têtes, puis testez son comportement réel derrière votre plateforme edge. Cette démarche progressive transforme les contrats HTTP edge-first en pratique d’équipe : plus prévisible pour les clients, plus portable entre les plateformes et plus simple à faire évoluer côté PHP.