Du thème du magazine à la pratique des projets
Pages de services et techniques pertinentes pour l'article
Un appel REST est en théorie simple : requête envoyée, réponse reçue, terminé. En pratique, les intégrations en production échouent rarement à cause d’une « mauvaise URL », mais à cause de cas limites en exploitation : timeouts sporadiques, problèmes DNS ou TLS de courte durée, systèmes en aval surchargés, ou 429 (Trop de requêtes), parce qu’un API-Gateway bride le débit. C’est précisément là que se distingue un prototype de démonstration d’une intégration exploitable en continu.
Ce billet montre comment tu peux, avec le RESTClient dans Delphi, établir des chemins de communication robustes : définitions de timeout claires, retries ciblés uniquement là où ils sont sûrs d’un point de vue fonctionnel et technique, et un comportement de backoff qui respecte les rate limits au lieu de les aggraver. L’accent n’est pas mis sur le « beau code », mais sur le comportement sous charge, la capacité de debug, une classification précise des erreurs et la question de savoir quand l’effort supplémentaire est réellement justifié.
Pourquoi timeouts, retries et 429 apparaissent ensemble en environnements réels
Dans les réseaux d’entreprise, les appels REST ne passent que rarement « directement sur Internet ». On trouve typiquement des chaînes de proxy, de la terminaison TLS, des API-Gateways, des WAF (Web Application Firewall) et plusieurs sauts internes. Chaque maillon peut avoir ses propres timeouts et limites. Un timeout côté client peut signifier :
- Le serveur n’a pas répondu (surcharge, deadlock, blocage en aval).
- La réponse est arrivée, mais trop tard (trajet dégradé, perte de paquets, congestion).
- Tu t’es toi‑même exclu : timeouts trop courts ou thread UI/main bloquant.
Parallèlement, des retries « naïfs » provoquent souvent davantage de problèmes : si un serveur est déjà à sa limite, les retries augmentent la charge et transforment un petit goulot en panne majeure. Pour un statut 429, c’est encore plus manifeste : un rate limit est une instruction explicite pour envoyer moins ou revenir plus tard. Un client sans backoff se comporte comme un générateur de DoS, mais involontairement.
La robustesse ne vient donc pas d’un « retry partout », mais d’un modèle de décision cohérent : quelles erreurs sont transitoires, quelles erreurs sont permanentes, quelles requêtes sont retryables (idempotentes), et comment piloter les temps d’attente pour que ton système reste stable.
Configurer les timeouts proprement : que signifie exactement « timeout » pour le RESTClient dans Delphi ?
Un piège fréquent : « timeout » n’est pas une seule chose. Selon la pile, il existe plusieurs phases. Même si les composants Delphi-REST encapsulent beaucoup de détails, il faut garder le modèle en tête :
- Connect-Timeout : temps jusqu’à l’établissement de la connexion TCP (incl. DNS/TLS selon l’implémentation).
- Read/Response-Timeout : temps jusqu’à l’arrivée d’octets depuis le serveur ou jusqu’à ce que la réponse soit complète.
- Timeout global : plafond pour l’appel complet, y compris les retries.
En pratique, un timeout trop court est au moins aussi dangereux qu’un timeout trop long : tu crées des erreurs artificielles qui seront retriées et génèreront de la charge. À l’inverse, un timeout trop long bloque des threads workers, des slots de queue ou la réactivité de l’UI. Pour l’exploitation et l’administration, il est important que les timeouts soient configurables (par exemple par endpoint) et qu’ils soient consignés dans les logs.
Recommandation pratique : deux niveaux plutôt qu’un seul chiffre
Pour les appels REST dans les logiciels métiers, deux niveaux se sont avérés efficaces :
- Call-Timeout (par requête) : une limite supérieure réaliste, adaptée au cas d’utilisation.
- Timeout de job (global) : si vous avez un traitement par lots ou un job de synchronisation, limitez la durée totale et interrompez proprement.
Ainsi vous évitez qu’une seule réponse d’API attende indéfiniment, et en même temps qu’un job nocturne, à cause de nombreux réessais, tourne « jusqu’à midi ».
Prendre la décision sur les réessais : pas technique, mais fonctionnel
La question de savoir si un réessai est autorisé n’est pas purement technique. Le concept clé est la idempotence : une requête est idempotente si son exécution multiple a le même effet que son exécution unique. Exemples typiques : GET est idempotent, PUT souvent aussi (si vous définissez complètement l’objet cible), DELETE généralement également. POST est souvent non idempotent (p. ex. « créer un nouvel ordre »).
Pourquoi est-ce décisif ? Un Timeout peut signifier que le serveur a quand même traité la requête, mais que la réponse n’est plus arrivée chez le client. Si vous répétez alors aveuglément un POST, vous générez des doublons. En exploitation, c’est une « erreur fantôme » classique : dans l’application il est indiqué « Timeout », dans le backend il y a des enregistrements en double.
La base fiable : réessayer uniquement les opérations clairement réessayables
Une règle robuste, éprouvée en intégrations :
- GET : réessayable en cas d’erreurs transitoires.
- PUT/DELETE : réessayable si votre API le définit clairement sur le plan fonctionnel (p. ex. l’ID de ressource est stable) et si le serveur implémente correctement l’idempotence.
- POST : réessayable uniquement si vous disposez d’une stratégie de clé d’idempotence (ID de requête fonctionnellement unique empêchant les doublons côté serveur) ou si le POST est sémantiquement idempotent (rare, mais possible).
Si vous ne contrôlez pas l’API, c’est le point où, en tant que lead technique, vous devez prendre une décision : soit vous acceptez « pas de réessai pour les POST » (et concevez de meilleurs messages d’erreur/mécanismes de resynchronisation), soit vous négociez avec le fournisseur d’API une clé d’idempotence ou un modèle dédupliquable.
429 Too Many Requests : respecter les limites de débit plutôt que « réessayer à l’aveugle »
HTTP 429 n’est pas une « erreur gênante », mais un mécanisme de régulation. En environnements d’entreprise, le 429 provient souvent de :
- API-Gateway avec des limites Token-Bucket/Leaky-Bucket (limitation de débit).
- APIs cloud avec des limites par locataire par minute/heure.
- Services internes qui se protègent contre les pics de charge.
Pour le client, cela signifie : réessayer oui, mais de façon contrôlée. Deux points sont importants :
- Examiner l’en-tête Retry-After, s’il est présent (secondes ou date HTTP).
- Utiliser un backoff si aucun Retry-After n’est fourni ou si vous combinez avec du jitter.
Le piège le plus courant : traiter le 429 comme un 500 (« erreur serveur, réessayer immédiatement »). Cela renforce la limitation. Mieux : 429 est un signal pour attendre activement et, si nécessaire, réduire le niveau de parallélisme.
Backoff avec jitter : pourquoi sans hasard tout s’effondre de façon synchrone
Le backoff exponentiel signifie que vous augmentez le temps d’attente après chaque échec (p. ex. 200 ms, 400 ms, 800 ms …). Jitter est une composante aléatoire qui empêche de nombreux clients de retenter en même temps. Sans jitter, il se passe souvent ceci en pratique : une limite est atteinte, 50 clients reçoivent 429, tous attendent exactement 1 seconde puis envoient de nouveau simultanément. Résultat : encore des 429, et vous avez un « Thundering Herd ».
Une approche pragmatique est le « Full Jitter » ou « Equal Jitter » : vous calculez une fenêtre de backoff puis choisissez un délai d’attente aléatoire à l’intérieur de cette fenêtre. Cela peut sembler un détail, mais en exploitation cela fait la différence entre une récupération stable et un bourdonnement permanent.
Un modèle propre : encapsuler les appels REST au lieu de répandre des boucles de retry partout
Si vous implémentez des retries/backoff « ad hoc » à chaque point d’appel, le comportement devient vite incohérent : un endpoint réessaie de manière agressive, un autre pas du tout, le journal est lacunaire, et les administrateurs ne voient que des « erreurs sporadiques ». Cela devient robuste lorsque vous définissez un chemin d’appel central :
- Un wrapper autour du RESTClient/RESTRequest, qui applique une Policy (Timeout, Retry, Backoff).
- Un objet de résultat unifié : code d’état, durée, compteur de tentatives, le cas échéant dernière Exception.
- Journalisation standardisée (Request-ID/Correlation-ID, Endpoint, méthode HTTP, en-têtes pertinents).
C’est là que le code supplémentaire est vraiment rentable : vous obtenez un comportement reproductible, de meilleurs logs, et vous pouvez configurer des policies par système cible sans restructurer l’application.
Matrice de décision de Policy (brève et pratique)
Pour la plupart des intégrations, une matrice simple, mise en œuvre dans le wrapper, suffit :
- Retry pour : erreurs réseau/coupures de connexion, 408, 429, 502, 503, 504 (selon le contrat API).
- Pas de retry pour : 400/401/403/404 (généralement configuration/authentification/erreur de requête), 409/422 (conflits métier/validation), ainsi que pour POST sans Idempotency-Key.
- Max. tentatives : garder bas (souvent 2–4 tentatives suffisent), privilégier plutôt un meilleur monitoring.
- Max. backoff : limiter (p. ex. quelques secondes à une minute), sinon vous bloquez trop de workers.
Important : ces règles ne sont pas universelles. Un 404 peut, en cas de consistance éventuelle, être transitoire ; un 409 peut être transitoire dans des stratégies de locking. La différence est : il s’agit alors d’une dérogation consciente, pas d’un comportement aléatoire.
Cas limite concret : Timeout après POST – a-t-il été sauvegardé ou pas ?
C’est le classique qu’on reproduit rarement proprement dans le débogueur : vous envoyez un POST (p. ex. « créer un ticket »), votre client reçoit un Read-Timeout, et l’utilisateur clique « encore ». Dans le backend, le ticket existe déjà. Sans contre-mesure, des doublons ou des incohérences apparaissent.
Cela ne devient robuste qu’avec l’une des trois stratégies suivantes :
- Idempotency-Key : vous générez pour chaque opération métier un identifiant de requête unique (p. ex. GUID), l’envoyez en en-tête, et le serveur garantit un traitement dédupliqué.
- Client-seitige Deduplizierung : vous stockez des « pending requests » avec un identifiant propre localement et, après un timeout, effectuez un contrôle d’état (p. ex. un GET sur la clé métier). C’est plus complexe et pas toujours possible.
- Kein Retry : vous signalez clairement que le statut est inconnu et mettez en place un processus de resynchronisation manuel/automatique (p. ex. rapprochement ultérieur).
Si vous développez des intégrations pour l’exploitation, « statut inconnu » est une catégorie valide. N’essayez pas de coder l’incertitude hors du système. Consignez-la dans les logs, rendez-la visible et prévoyez un chemin de rapprochement.
Conception du backoff en pratique : limites, parallélisme et annulation
Un backoff n’est pas simplement un « sleep ». Il faut le placer dans le contexte de votre application :
- Parallelität : si vous avez 20 threads et qu’ils attendent tous, 20 threads sont bloqués. Pour des services, c’est souvent acceptable ; pour des applications desktop, moins.
- Cancel : un utilisateur annule, le service s’arrête, un job est terminé. L’attente du backoff doit être annulable, sinon les processus d’arrêt/shutdown se bloquent.
- Fairness : plusieurs endpoints ne doivent pas se priver mutuellement. Les rate-limits sont souvent appliqués par token ou par endpoint ; votre wrapper doit pouvoir contrôler par système cible.
Une approche propre est : implémenter le backoff dans une fonction qui attend par petits intervalles en vérifiant un flag d’annulation (p. ex. Event/Token). Ce n’est pas du luxe : c’est précisément ce point qui décide si un Windows- et Linux-Services s’arrête proprement ou « reste bloqué » dans la console du Service Control Manager.
Durée maximale et „Budget“ par appel
Une implémentation robuste du retry ne fonctionne pas seulement avec des « max tries », mais aussi avec un budget temporel. Exemple : vous autorisez au maximum 10 secondes de temps total pour l’appel, retries inclus. Ainsi, une tentative unique ne peut pas soudainement bloquer 30 secondes parce qu’un timeout est mal réglé. Pour les admins et l’exploitation, cela est très utile, car cela limite les pics de latence et stabilise les files d’attente.
Debugging et diagnostic d’exploitation : sans bons logs, les Retries sont des amplificateurs d’erreurs invisibles
Les réessais sans journalisation sont dangereux, car vous n’entendez au final que «ça prend parfois du temps». Si vous voulez être robuste, vous avez besoin de logs qui n’affichent pas seulement des exceptions mais fournissent du contexte :
- Correlation-ID : un ID de requête que vous générez par appel et conservez pour chaque réessai.
- Numéro de tentative et délai (backoff).
- Statut HTTP et en-têtes sélectionnés (en particulier Retry-After, RateLimit-Header si présents).
- Durée par tentative et durée totale.
- Endpoint (hôte + chemin), mais pas de données sensibles dans les logs (Tokens, données personnelles).
Pour les responsables techniques, c’est aussi le levier pour ajuster les seuils : vous voyez si les timeouts surviennent «toujours à 3 secondes» (probablement trop court) ou si des 429 arrivent par vagues (concurrence trop élevée, backoff insuffisant ou absence de limites côté client).
Pièges typiques dans les logs
- Trop de payload : consigner intégralement les corps JSON semble utile, mais explose avec des fichiers/pièces jointes et crée des problèmes de confidentialité. Mieux : hash/taille, Content-Type, et, si nécessaire, journalisation de debug ciblée via un feature flag.
- Pas de distinction entre timeout et annulation : un appel interrompu n’est pas une erreur au même titre qu’un timeout. Séparez-les, sinon les administrateurs poursuivront des erreurs fantômes.
- Le réessai masque la cause initiale : si la tentative 1 rencontre une erreur TLS et que la tentative 2 réussit, vous voulez quand même savoir qu’il y a eu un problème TLS intermittent. C’est un signal précoce.
Rate limiting côté client : quand vous devez contrôler la charge vous-même
Le 429 est la réponse du serveur. Dans de nombreux scénarios, il est toutefois judicieux de limiter déjà côté client avant de provoquer des 429. C’est surtout pertinent si vous :
- avez des jobs par lots (p. ex. synchronisation de données la nuit) et l’API n’autorise que X requêtes par minute.
- utilisez plusieurs workers/threads et lancez les requêtes en parallèle.
- avez plusieurs instances de processus en cours (p. ex. terminal server ou plusieurs services).
Concrètement : vous implémentez un petit rate limiter (p. ex. token bucket) par système cible ou par clé API. Cela réduit les 429, stabilise le débit et rend les durées d’exécution plus prévisibles. Pour l’exploitation et la planification de capacité, c’est souvent plus utile qu’un «encore un réessai».
Important : Rate limiter et backoff se complètent
Le rate limiter vous maintient en dessous du seuil en fonctionnement normal. Le backoff est la réaction lorsque vous recevez malgré tout des 429 ou une surcharge temporaire. Celui qui n’a que du backoff finit par «se frotter au mur» et ralentir. Celui qui n’a que des rate limiters réagit mal aux limites surprises ou aux quotas partagés (p. ex. lorsque plusieurs systèmes utilisent la même clé API).
Sécurité et conformité : les réessais ne doivent pas masquer les problèmes d’authentification
Dans les entreprises, l’authentification et l’autorisation sont souvent la source la plus fréquente d’«erreurs» après un déploiement : tokens expirés, credentials client mal configurées, exceptions de proxy manquantes. Les réessais n’apportent rien ici et peuvent même être nuisibles, car ils remplissent les logs et déclenchent des mécanismes de verrouillage (p. ex. blocage de compte, limites de taux sur les endpoints d’auth).
Règle pratique : ne pas retenter les 401/403 (sauf si vous avez une gestion explicite de Token-Refresh). Si vous implémentez un Token-Refresh, séparez-le clairement du mécanisme de retry : d’abord renouveler le token, puis renvoyer une seule fois. Et consignez explicitement qu’un refresh a eu lieu.
Quand l’effort en vaut la peine — et quand non
Les réessais robustes et le backoff ne sont pas une fin en soi. Ils sont particulièrement utiles si au moins un des points suivants est vrai :
- L’intégration est critique pour l’entreprise (p. ex. enregistrement des commandes, expédition, facturation).
- L’API est externe ou exploitée en mode « best effort » en interne et vous n’en avez pas le contrôle total.
- Vous avez des pics de charge (p. ex. fenêtres de jobs, clôture mensuelle) et vous souhaitez traverser ces périodes de manière stable.
- Vous exploitez un service/daemon et devez pouvoir l’arrêter de façon prévisible et propre.
Ils ont moins d’intérêt si vous n’avez que des « GETs de confirmation » dans l’interface et que l’utilisateur clique de toute façon à nouveau, ou si vous travaillez dans un environnement interne très stable sans quotas et où les erreurs sont immédiatement visibles. Même dans ces cas, des timeouts et un logging soignés restent presque toujours pertinents.
Checklist pragmatique pour l’exploitation en production du client Delphi-REST
- Timeouts : configurables par endpoint, choisis de manière réaliste, budget global défini.
- Retry-Policy : dépendante de la méthode HTTP et de l’idempotence, pas universelle.
- 429-Handling : respecter Retry-After, backoff avec jitter, surveiller le parallélisme.
- Abbruchpfad : pouvoir interrompre l’attente de backoff (arrêt du service, annulation par l’utilisateur).
- Logging : Correlation-ID, tentative, délai, durée, statut/en-têtes – sans secrets.
- Optionnel : limiteur de débit côté client pour exploitation par lot / en parallèle.
Conclusion : la robustesse est un comportement, pas un bloc catch-all d’exception
Avec le RESTClient dans Delphi vous obtenez rapidement des appels REST fonctionnels. Ce n’est toutefois qu’en production que cela devient robuste : définissez consciemment les timeouts, sécurisez les réessais au plan métier (idempotence !), et respectez les limites 429 avec backoff et jitter. Le code n’est pas compliqué, mais il doit être centralisé, configurable et correctement observable. C’est à ce moment-là que l’effort paie : moins de tickets « sporadiques », un meilleur diagnostic en exploitation et des intégrations qui tiennent la charge.
Si vous souhaitez appliquer proprement une politique de réessai/backoff à des applications Delphi existantes ou dimensionner celle-ci pour une nouvelle intégration : prendre contact.
Pour ce sujet, les timeouts du Restclient Delphi et la stratégie de réessai Delphi sont également importants. L’article replace ces aspects de manière compréhensible et montre ce qui compte au quotidien.
Discuter d’un projet ou d’un chantier de modernisation avec Net-Base.
Étape suivante
Lorsque le sujet devient un projet réel, l'architecture, l'existant et l'exploitation doivent être examinés ensemble dès le départ.
Nous n'intervenons pas seulement sur des questions ponctuelles, mais aussi lorsque des fragments de code source, des problématiques liées aux systèmes legacy ou des concepts de portail doivent se transformer en un projet d'entreprise robuste.
- L'état des lieux, l'état cible et les risques techniques sont évalués conjointement.
- REST, l’accès aux données, les portails et le déploiement ne sont pas reportés à des phases ultérieures.
- Vous identifiez tôt quelle voie est viable économiquement et opérationnellement.