# Dépannage : trafic

> Routes, délais, flux, amonts, gRPC, débit, cache, corps, appel externe et politiques : le symptôme, sa cause et quoi faire.

Dépannage - Depuis la version 0.1.0 - Flexway v1.7.1 - https://flexway.io/docs/operations-troubleshooting-traffic/

> Guide d'exploitation : configurer, déployer et observer la passerelle. Ce que fait la fonctionnalité côté produit : [guide produit](https://flexway.io/docs/product-overview/) ; l'appeler : [guide développeur](https://flexway.io/docs/developer-overview/).

## Routage et contrat

| Symptôme | Cause | Action |
| --- | --- | --- |
| `404` sur une route qu'on croit exposée | Elle n'est pas dans le contrat. « Servi = déclaré » | L'ajouter au contrat, pas à la configuration |
| `400` « Query parameter ... is not declared » | Fermé par défaut : le contrat ne déclare pas ce paramètre | Le déclarer dans le contrat, pas contourner la passerelle |
| `422` `invalid_request` | La requête ne respecte pas le schéma du contrat | L'extension `errors` nomme le champ et la règle |
| Un en-tête n'arrive pas à l'amont | Il n'est pas déclaré dans le contrat - il est retiré, pas refusé | Le déclarer comme paramètre `in: header` |
| Refus « segment … has two parameters side by side » ou « a '{' that is never closed » | Un segment de chemin que le routeur ne sait pas lire | Mettre un texte entre les deux paramètres, fermer l'accolade |
| Refus « segments … rank the same and can match the same text » | Deux segments mixtes de même rang lisent le même texte (`a{x}` et `{x}a`) | Rendre l'un plus littéral, ou renommer un chemin |
| `404` sur `/reports/2026.pdf` alors que `/reports/{year}.pdf` est au contrat | Passerelle antérieure à `1.6.0` : le segment mixte était pris pour un littéral | Mettre à jour ([Servir sa première API](https://flexway.io/docs/operations-first-api/)) |

## Délais, flux et amonts

| Symptôme | Cause | Action |
| --- | --- | --- |
| Un flux arrive d'un coup à la fin | Un intermédiaire bufferise - pas la passerelle | Vérifier l'edge et tout proxy entre le client et elle |
| Un flux s'arrête au milieu, journal `read budget exhausted` | L'amont s'est tu plus longtemps que `server.timeouts.read` | Faire émettre un battement (`: keepalive`) à l'amont, ou relever `read` |
| `504` `upstream_timeout` | L'amont n'a pas répondu dans le budget de l'opération | Lire `flexway_upstream_duration_seconds` ; relever `x-flexway-timeout` sur **cette** opération, pas le défaut global |
| `504` `upstream_timeout` « budget was spent before its upstream could be called » | Un `external-call` de la phase requête a consommé tout le budget de l'opération | Raccourcir son `timeout`, ou relever `x-flexway-timeout` de l'opération |
| `503` `upstream_unavailable` avec `Retry-After` | L'amont refuse les connexions : il est arrêté | Vérifier l'amont ; la passerelle a déjà repris une fois si la méthode le permettait |
| `502` `upstream_error` | L'amont a répondu quelque chose qui n'est pas du HTTP exploitable | Chercher côté amont, pas côté passerelle |
| `503` avec `Flexway-Degraded: circuit-open` | L'amont échoue ; le coupe-circuit répond à sa place | Réparer l'amont ; un appel d'essai passe après `cooldown` |
| `503` avec `Flexway-Degraded: upstream-saturated` | `max_in_flight` atteint sur cet amont | Relever le plafond, ou trouver pourquoi l'amont ralentit |
| `503` `overloaded` avec `Flexway-Degraded: shedding`, `flexway_shed_total{reason="in_flight"}` qui monte | La réplique est à son plafond de requêtes en vol (`server.in_flight`, dérivé de sa limite mémoire s'il n'est pas écrit) : elle refuse l'excédent au lieu d'être tuée (OOM) | Ajouter des répliques, ou plus de CPU et de mémoire à chacune ; la limite en vigueur est dans `/config` (`server.in_flight`) et au journal de démarrage |

## gRPC

| Symptôme | Cause | Action |
| --- | --- | --- |
| Refus au chargement « google.api.http ... not supported at this version » | Variable sur plusieurs segments, joker ou verbe dans une règle REST | Une variable par segment (`/v1/shelves/{shelf}/books/{book}`), ou la méthode en gRPC seulement |
| Refus au chargement « is a gRPC method and relays to upstream » | La méthode tombe sur un amont HTTP (le `default`) | Nommer son amont dans `flexway.v1.operation.upstream`, déclaré `protocol: grpc` |
| Client gRPC : `UNIMPLEMENTED` | La méthode n'est pas dans le jeu servi - jeu périmé | Reconstruire le `.binpb` : il est relu sans redémarrage |
| REST : `upstream_status` | L'amont gRPC a répondu une erreur : c'est sa réponse, pas un échec de la passerelle | Lire `grpc.status` et `grpc.details` du problème |

## Débit et quotas

| Symptôme | Cause | Action |
| --- | --- | --- |
| `429` `rate_limited` | Le seau de l'appelant est vide | Lire `Retry-After` ; relever `rps`/`burst` de la classe, ou changer la classe de l'opération |
| Tous les anonymes semblent partager un seau | La passerelle voit l'adresse du bord - ou, derrière un load balancer cloud sans proxy protocol, celle du load balancer - pas celle du client | Déclarer le bord dans `server.trusted_proxies` ; lire `x-forwarded-for` dans le journal d'accès du bord, et activer le proxy protocol au load balancer et au bord ([Poser des quotas](https://flexway.io/docs/operations-quotas/) du guide) |

## Cache de réponses

| Symptôme | Cause | Action |
| --- | --- | --- |
| `Flexway-Cache: miss` à chaque requête | L'amont interdit de garder (`no-store`, `no-cache`, `private`, `Set-Cookie`), répond sans `Content-Length`, au-delà de `server.cache.max_entry_bytes`, ou avec un statut autre que `200`, `203`, `204`, `301`, `404` | Lire les en-têtes de la réponse de l'amont ; `flexway_cache_lookups_total` par opération |
| Aucun `Flexway-Cache` sur une opération cachée | Contournement : appelant identifié sur un cache public, condition `when` fausse, méthode autre que `GET`/`HEAD` | `result="bypass"` dans `flexway_cache_lookups_total` ; un cache par appelant se déclare `scope: subject` à l'opération |
| Démarrage refusé : `response cache declaration(s) refused` | Un cache sur un flux, un WebSocket, gRPC, l'inférence, une écriture, ou public sur une opération authentifiée | Le message nomme l'opération et la porte (`x-flexway-cache` ou la politique) |

## Corps et statut de réponse

| Symptôme | Cause | Action |
| --- | --- | --- |
| `502` `response_too_large` | Une opération à politique de corps a reçu une réponse au-delà de `server.body.max_bytes` | Relever `server.body.max_bytes` (rechargeable), ou paginer côté amont |
| `502` `upstream_error` sur une opération transformée, journal `an upstream answer could not be transformed` | L'amont a répondu autre chose que du JSON lisible, compressé malgré `Accept-Encoding: identity`, ou sans un champ projeté avec `on_missing: fail` | Le journal nomme la cause et le champ ; corriger l'amont, ou passer à `on_missing: omit` |
| Démarrage refusé : `body policy attachment(s) refused` | Une politique de corps atteint un flux, une réponse non JSON, gRPC ou l'inférence ; ou elle retire un champ requis, garde un champ non déclaré, masque avec un type refusé | Le message nomme l'opération et la politique ; l'attacher plus étroitement ou `!nom` |
| `flexway_policy_nonconformant_total` monte | Un corps servi ne tient plus le schéma de son statut (échantillon) - l'amont a changé, ou le statut n'est pas déclaré | Le journal `departs from its schema` donne le champ et le mot-clé ; la réponse est servie quand même |
| Refus au chargement « `body` is read by the conditions of transform-body, redact and map-status only » | Une autre politique lit `body` dans sa condition ou un gabarit | Lire le corps dans une politique de corps, ou s'en passer |

## Appel externe

| Symptôme | Cause | Action |
| --- | --- | --- |
| `403` (ou autre `4xx`) avec le `code` d'une politique `external-call` | Le service externe a refusé : `deny.when` vaut vrai sur sa réponse | Attendu ; le motif est chez le service, jamais recopié dans le problème |
| `504`, `503` ou `502` sans que l'amont soit appelé, journal `external call` `failed_closed` | Le service externe n'a pas répondu à temps, est injoignable, a son circuit ouvert ou a rendu une réponse illisible pour `deny.when` / `set_headers` - et la politique est `fail-closed` | Le journal donne la politique et la cause ; `flexway_policy_external_calls_total{outcome="failed_closed"}` ; réparer le service, ou `fail-open` si la décision peut manquer |
| Un en-tête attendu d'un `external-call` manque, journal `external call` en `WARN` | `fail-open` a absorbé une panne du service | `flexway_policy_external_calls_total{outcome="failed_open"}` ; le journal donne la cause |
| Démarrage refusé : `external-call attachment(s) refused` | `timeout` au-delà du budget d'une opération, `send.body` sur un flux, phase `response` sur un WebSocket, gRPC ou l'inférence | Le message nomme l'opération et la politique |

## Politiques

| Symptôme | Cause | Action |
| --- | --- | --- |
| Refus au démarrage « is protected - the gateway alone writes it » | Une politique écrit un en-tête que seule la passerelle pose (identité, trace, tenant, `X-Forwarded-*`) | Choisir un autre nom d'en-tête |
| L'amont reçoit `X-User-Roles: [seller admin]` | Passerelle antérieure à `1.6.0` : une liste rendue à la manière de Go | Mettre à jour : une liste arrive `seller,admin` ; `{{ auth.claims.roles.join(' ') }}` pour un autre séparateur ([Enrichir un flux](https://flexway.io/docs/operations-policies/)) |
| Refus au démarrage `undeclared reference to 'x'` ou `found no matching overload` dans une valeur | Une fonction que le langage n'a pas, ou `join` sur un séparateur qui n'est pas du texte | Seules les fonctions de CEL et `join` existent ; corriger l'expression |
| Refus au démarrage « on_missing is required » | Une valeur est une expression `{{ … }}` sans dire quoi faire si elle manque | Ajouter `on_missing: skip`, `empty` ou `fail` |
| Refus au démarrage « two … policies at the same level » | Deux `cors` (ou `rate-limit`, `rewrite-path`) au même niveau | En garder une, ou déplacer l'autre vers un niveau plus précis |
| `500` `internal_error`, journal `a policy value is missing` | `on_missing: fail` et la valeur manque pour cet appelant | Ajouter une condition `when`, ou passer à `skip` |
| Une politique n'agit pas | Le journal de démarrage dit `NOT applied` : son type vient d'une version ultérieure | Lire `effective_chains` dans `/config` |
