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 CLe 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-webfluximplicitement via le starter gateway. Ne pas ajouterspring-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| Attribut | Rôle |
|---|---|
id | Identifiant unique de la route (utile dans les logs) |
uri | Destination de la requête (URL absolue ou service nommé) |
predicates | Conditions d’activation de la route |
filters | Transformations 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, 8URI : 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:8081Le 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, trueRequestRateLimiter (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 :
RequestRateLimiterrequiert 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/utilisateursSeuils par défaut : 5 échecs → circuit ouvert, puis
HalfOpenaprès 10s. Ajuster viaresilience4j.circuitbreaker.instances.*.
Retry
filters:
- name: Retry
args:
retries: 3
statuses: BAD_GATEWAY, SERVICE_UNAVAILABLE
methods: GET
backoff:
firstBackoff: 10ms
maxBackoff: 500ms
factor: 2Filtres 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/routesBadges d’erreur HTTP
Le Gateway retourne directement les codes de statut des services aval, sauf si un filtre les modifie. Patterns courants :
| Code | Signification dans un contexte Gateway |
|---|---|
404 | Aucune route ne correspond à la requête |
403 | Filtre d’autorisation a bloqué |
429 | Rate limit dépassé (un filtre l’a ajouté) |
502 | Service aval injoignable (DNS, réseau) |
503 | Circuit breaker ouvert, service temporairement hors-ligne |
504 | Dé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, true2. 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: 10s5. 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 -
RewritePathconfiguré 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/healthet/actuator/gateway/routesactivées - Logging
DEBUGsurorg.springframework.cloud.gatewaypour le debug de routage - Pas de
spring-boot-starter-webajouté (conflit de conteneurs)