Skip to Content
BackendSpring Cloud Gateway

Spring Cloud Gateway

Passerelle (reverse proxy) réactive pour les microservices Spring Boot. Routing, filtres, découverte de services, dégradation et pièges courants.

Rôle dans une architecture microservices

Client → [Spring Cloud Gateway] → Service A / Service B / Service C

Le Gateway centralise :

  • le routage des requêtes vers les bons services
  • l’authentification et l’autorisation (jeton partagé)
  • le rate limiting
  • la résilience (circuit breaker)
  • la transformation des requêtes/réponses (headers, paths, bodies)

Pourquoi un seul gateway ? Sans gateway, chaque service client doit connaître l’adresse de chaque microservice. Avec un gateway, le client ne parle qu’à une seule URL. La découverte de services (Eureka, Consul) prend le relais pour les adresses réelles.

Dépendance Maven

<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <!-- Découverte de services (choisir l'un ou l'autre) --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <!-- ou Consul : --> <!-- <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-consul-discovery</artifactId> </dependency> -->

Note : utiliser spring-cloud-starter-webflux implicitement via le starter gateway. Ne pas ajouter spring-boot-starter-web (Tomcat) en même temps — les deux conteneurs entrent en conflit.

Configuration YAML — Routes

Syntaxe de base

spring: cloud: gateway: routes: - id: service-utilisateurs uri: http://localhost:8081 predicates: - Path=/api/utilisateurs/** filters: - StripPrefix=2
AttributRôle
idIdentifiant unique de la route (utile dans les logs)
uriDestination de la requête (URL absolue ou service nommé)
predicatesConditions d’activation de la route
filtersTransformations appliquées au passage

Predicates courants

predicates: # Correspondance de chemin (Ant-style) - Path=/api/v2/** # Méthode HTTP - Method=GET,POST # Header spécifique - Header=X-Request-Id # Interpolation dans le header - Header=X-Request-Id, (?i)req-[0-9a-f]+$ # Délai (ajoute du temps avant routage) - After=2026-01-01T00:00:00Z # Cookie - Cookie=locale,en # Coupure de poids (load balancing) - Weight=group1, 8

URI : statique vs découverte de services

# URI statique (en dur, pour dev ou services uniques) uri: http://localhost:8081 # URI avec préfixe lb: → load balancing via discovery uri: lb://service-utilisateurs # URI avec préfixe http(s): (discovery par nom ou URL explicite) uri: http://service-utilisateurs:8081

Le préfixe lb:// déclenche le LoadBalancer Spring Cloud, qui interroge Eureka/Consul pour obtenir la liste des instances disponibles.

Filtres intégrés

RewritePath

filters: # /api/utilisateurs/42 → /users/42 (au service aval) - RewritePath=/api/(?<segment>.*), /${segment}

AddRequestHeader / AddResponseHeader

filters: - AddRequestHeader=X-Gateway-Version, 2.1 - AddResponseHeader=X-Gateway-Processed, true

RequestRateLimiter (Sliding Window)

spring: cloud: gateway: routes: - id: service-public uri: lb://service-public predicates: - Path=/api/public/** filters: - name: RequestRateLimiter args: key-resolver: "#{@ipKeyResolver}" redis-rate-limiter.replenishRate: 10 redis-rate-limiter.burstCapacity: 20 redis-rate-limiter.requestedTokens: 1
// ipKeyResolver.java @Bean public KeyResolver ipKeyResolver() { return exchange -> Mono.just( exchange.getRequest().getRemoteAddress().getAddress().getHostAddress() ); }

Piège : RequestRateLimiter requiert Redis (par défaut). Sans Redis en production, le filtre échoue silencieusement ou ne s’applique pas.

CircuitBreaker

filters: - name: CircuitBreaker args: name: serviceUtilisateurs fallbackUri: forward:/fallback/utilisateurs
# Configuration des seuils circuit breaker spring: cloud: gateway: default-filters: - name: CircuitBreaker args: name: defaultCircuit fallbackUri: forward:/fallback routes: - id: service-utilisateurs uri: lb://service-utilisateurs predicates: - Path=/api/utilisateurs/** filters: - name: CircuitBreaker args: name: utilisateursCircuit fallbackUri: forward:/fallback/utilisateurs

Seuils par défaut : 5 échecs → circuit ouvert, puis HalfOpen après 10s. Ajuster via resilience4j.circuitbreaker.instances.*.

Retry

filters: - name: Retry args: retries: 3 statuses: BAD_GATEWAY, SERVICE_UNAVAILABLE methods: GET backoff: firstBackoff: 10ms maxBackoff: 500ms factor: 2

Filtres personnalisés

GateFilter (API GatewayFilter)

@Component public class AuditLogFilter implements GlobalFilter, Ordered { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { long debut = System.currentTimeMillis(); String methode = exchange.getRequest().getMethod().name(); String path = exchange.getRequest().getURI().getPath(); return chain.filter(exchange).then(Mono.fromRunnable(() -> { long duree = System.currentTimeMillis() - debut; System.out.printf("[%s] %s %s → %d ms%n", LocalDateTime.now(), methode, path, duree); })); } @Override public int getOrder() { return -1; // s'exécute tôt } }

WebFilter (filtre au niveau du serveur web)

Plus adapté quand on a besoin du contexte HTTP complet (sessions, cookies).

@Component public class CorsFilter implements WebFilter { @Override public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) { ServerHttpRequest request = exchange.getRequest(); ServerHttpResponse response = exchange.getResponse(); response.getHeaders().add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "*"); response.getHeaders().add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, "GET,POST,PUT,DELETE,OPTIONS"); response.getHeaders().add(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, "*"); if (request.getMethod() == HttpMethod.OPTIONS) { response.setStatusCode(HttpStatus.OK); return response.setComplete(); } return chain.filter(exchange); } }

Actuator pour le diagnostic

# Voir les routes actives curl http://localhost:8080/actuator/gateway/routes

Badges d’erreur HTTP

Le Gateway retourne directement les codes de statut des services aval, sauf si un filtre les modifie. Patterns courants :

CodeSignification dans un contexte Gateway
404Aucune route ne correspond à la requête
403Filtre d’autorisation a bloqué
429Rate limit dépassé (un filtre l’a ajouté)
502Service aval injoignable (DNS, réseau)
503Circuit breaker ouvert, service temporairement hors-ligne
504Délai d’attente du service aval dépassé

Retour d’erreur structuré

@Component public class GatewayExceptionHandler implements WebExceptionHandler { private static final ObjectMapper mapper = new ObjectMapper(); @Override public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) { ServerHttpResponse response = exchange.getResponse(); response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR); response.getHeaders().setContentType(MediaType.APPLICATION_JSON); Map<String, Object> body = Map.of( "error", Map.of( "code", "GATEWAY_ERROR", "status", 502, "message", "Service aval injoignable" ) ); try { byte[] bytes = mapper.writeValueAsBytes(body); DataBuffer buffer = response.bufferFactory().wrap(bytes); return response.writeWith(Mono.just(buffer)); } catch (Exception e) { return response.setComplete(); } } }

Pièges courants

1. Ordre des filtres

Les filtres s’exécutent en ordre de déclaration (premier → dernier). L’ordre avant le routage modifie la requête avant l’envoi au service aval. L’ordre après le routage modifie la réponse avant retour au client.

filters: # Exécuté AVANT le routage (la requête arrive modifiée au service) - RewritePath=/api/(?<segment>.*), /${segment} # Exécuté APRES le routage (la réponse repart modifiée) - AddResponseHeader=X-Processed, true

2. RoundRobin par défaut

Le load balancer Spring Cloud utilise RoundRobin sans configuration. Pas de poids, pas de santé — seulement la rotation. Utiliser RoundRobin avec des préférences personnalisées uniquement si les instances ont des capacités différentes.

3. Pas de sessions HTTP

Spring Cloud Gateway est basé sur WebFlux (réactif). Pas de HttpSession. Si le service aval attend une session, la passerelle ne la gère pas. Passer les identifiants dans les headers ou les cookies.

4. Délais par défaut trop courts

# Par défaut, le timeout est de 2s pour certaines opérations # En production, augmenter : spring: cloud: gateway: discovery: locator: enabled: true httpclient: connect-timeout: 3000 response-timeout: 10s

5. Filtre qui consomme le body

Certains filtres (ex. ModifyRequestBody) consomment le DataBuffer du body. Après consommation, le corps n’est plus lisible. Ne jamais enchaîner deux filtres qui lisent/modifient le même body sans les coordonner.

Checklist de déploiement

  • URI avec préfixe lb:// pour la découverte de services
  • RewritePath configuré pour correspondre au chemin attendu par le service aval
  • Redis disponible pour le rate limiting (si utilisé)
  • Timeout HTTP adapté au temps de réponse attendu
  • Circuit breaker configuré par route critique
  • Sondes /actuator/health et /actuator/gateway/routes activées
  • Logging DEBUG sur org.springframework.cloud.gateway pour le debug de routage
  • Pas de spring-boot-starter-web ajouté (conflit de conteneurs)