Spring Cloud Gateway propose trois mécanismes de routage : statique, dynamique et automatique via l'intégration avec un annuaire de services. Cet article expliqeu comment le routage automatique fonctionne grâce à la découverte de services.
Configuration des itinéraires
Routage statique
Les itinéraires statiques sont définis dans les fichiers de configuration au démarrage de l'application. Ils offrent une correspondance rapide mais nécessitent une reconfiguration manuelle pour tout changement. Voici un exemple YAML :
Routage dynamique
Les itinéraires dynamiques peuvent être modifiés à chaud via l'API. Le code ci-dessous illustre l'utilisation de RouteDefinitionWriter pour gérer les règles en temps réel :
public void ajouterItineraire(String id, String uri, String condition) { RouteDefinition definition = new RouteDefinition(); definition.setId(id); definition.setUri(URI.create(uri)); definition.setPredicates(Collections.singletonList(new PredicateDefinition(condition))); writer.save(Mono.just(definition)).subscribe(); }
public void supprimerItineraire(String id) { writer.delete(Mono.just(id)).subscribe(); }
</div>Ici, `ajouterItineraire` enregistre une nouvelle règle, tandis que `supprimerItineraire` la retire. Les changements sont appliqués sans redémarrage.
**Routage automatique**
Le routage automatique s'appuie sur la découverte de services pour générer dynamiquement les itinéraires à partir des instances enregistrées. L'activation se fait via la configuration :
<div>```
spring:
cloud:
gateway:
discovery:
locator:
enabled: true
lower-case-service-id: true
Implémentation du routage dans le code source
Spring Cloud Gateway utilise Netty et Reactor pour traiter les requêtes. Le routage commence par la classe RoutePredicateHandlerMapping, qui évalue les conditions d'itinéraire pour chaque requête.
Voici une analyse simplifiée de RoutePredicateHandlerMapping avec des commentaires en français :
private final Map<String, RoutePredicateFactory> predicateFactories;
private final GatewayFilterHandlerFilter filterChain;
private final Map<String, Object> globalFilters;
private final RouteDefinitionLocator routeLocator;
public RoutePredicateHandlerMapping(List<RoutePredicateFactory> factories,
GatewayFilterHandlerFilter filterChain,
List<GlobalFilter> filters,
RouteDefinitionLocator routeLocator) {
this.predicateFactories = factories.stream()
.collect(Collectors.toMap(RoutePredicateFactory::name, Function.identity()));
this.filterChain = filterChain;
this.globalFilters = filters.stream()
.collect(Collectors.toMap(GlobalFilter::name, Function.identity()));
this.routeLocator = routeLocator;
setOrder(-1);
}
@Override
protected Object getHandlerInternal(ServerHttpRequest request) throws Exception {
List<RouteDefinition> definitions = this.routeLocator.getRouteDefinitions().collectList().block();
if (definitions == null) return null;
for (RouteDefinition def : definitions) {
RoutePredicateFactory factory = this.predicateFactories.get(def.getPredicate().getName());
RoutePredicate predicate = factory.apply(def.getPredicate().getArgs());
if (predicate.test(request)) {
Route route = new Route(def.getId(), def.getUri(), def.getFilters());
List<GatewayFilter> filters = new ArrayList<>(def.getFilters());
filters.addAll(globalFilters.values());
FilteringWebHandler handler = new FilteringWebHandler(new DefaultWebHandler(), new GatewayFilterChain(filters));
return new DefaultWebHandlerAdapter().handle(request, handler);
}
}
return null;
}
}
</div>Cette méthode parcourt toutes les règles, vérifie les conditions et applique les filtres avant de transférer la requête.
### Rôle de DiscoveryClientRouteDefinitionLocator
La clasce `DiscoveryClientRouteDefinitionLocator` convertit automatiquement les instances de services en itinéraires. Elle interroge l'annuaire via `DiscoveryClient` et génère des définitions de route :
<div>```
public class DiscoveryClientRouteDefinitionLocator implements RouteDefinitionLocator {
private final DiscoveryClient client;
private final Predicate<ServiceInstance> filter;
public DiscoveryClientRouteDefinitionLocator(DiscoveryClient client, Predicate<ServiceInstance> filter) {
this.client = client;
this.filter = filter;
}
@Override
public Flux<RouteDefinition> getRouteDefinitions() {
return Flux.fromIterable(client.getServices())
.flatMap(service -> Flux.fromIterable(client.getInstances(service)))
.filter(filter)
.map(this::creerDefinition);
}
private RouteDefinition creerDefinition(ServiceInstance instance) {
RouteDefinition def = new RouteDefinition();
def.setId(instance.getServiceId());
def.setUri(instance.getUri());
return def;
}
}