Netflix Zuul
API Gateway pour microservices (Déprécié - Documentation Historique)
Introduction à Netflix Zuul
Qu'est-ce que Netflix Zuul ?
⚠️ ATTENTION - PRODUIT DÉPRÉCIÉ
Zuul 1 est officiellement déprécié. Cette documentation est fournie à titre historique et éducatif. Pour les nouveaux projets, utilisez Spring Cloud Gateway à la place.
Netflix Zuul était un service de routage et de filtrage d'API Gateway développé par Netflix. Il agissait comme un point d'entrée unique pour toutes les requêtes clientes vers les microservices, fournissant des fonctionnalités de routage dynamique, de surveillance, de résilience et de sécurité.
API Gateway] B --> C[Service A] B --> D[Service B] B --> E[Service C] style A fill:#FF9800,stroke:#E65100 style B fill:#4CAF50,stroke:#388E3C style C fill:#2196F3,stroke:#0D47A1 style D fill:#9C27B0,stroke:#4A148C style E fill:#FF5722,stroke:#E64A19
Fonctionnalités principales de Zuul :
- Routage dynamique : Redirection des requêtes vers les services appropriés
- Filtrage : Pré-filtres, filtres de routage, post-filtres
- Sécurité : Authentification, autorisation, validation
- Monitoring : Tracing des requêtes, métriques
- Résilience : Gestion des erreurs, fallbacks
- Load Balancing : Distribution de charge intégrée
Architecture avec Zuul
Structure de l'application
Navigateur/App] --> B[Zuul Gateway
Port: 8762] B --> C[Eureka Server
Port: 8761] C --> D[Service A
Port: 8080] C --> E[Service B
Port: 8081] C --> F[Service C
Port: 8082] style A fill:#FF9800,stroke:#E65100 style B fill:#4CAF50,stroke:#388E3C style C fill:#2196F3,stroke:#0D47A1 style D fill:#9C27B0,stroke:#4A148C style E fill:#FF5722,stroke:#E64A19 style F fill:#8BC34A,stroke:#33691E
Rôles des composants :
- Zuul Gateway : Point d'entrée unique pour toutes les requêtes
- Eureka Server : Serveur de découverte de services
- Services A, B, C : Microservices backend
- Client : Applications clientes (navigateurs, mobiles, etc.)
Services Backend
1 Service A - Utilisateurs
Structure du projet :
serviceA/
├── pom.xml
└── src/main/
├── java/com/example/users/
│ ├── UsersMicroserviceApplication.java
│ ├── controller/
│ │ └── UserController.java
│ ├── model/
│ │ └── User.java
└── resources/
└── application.properties
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Eureka Client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
# === CONFIGURATION SERVEUR ===
server.port=8080
spring.application.name=service-a
# === CONFIGURATION EUREKA ===
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
eureka.client.register-with-eureka=true
eureka.client.fetch-registry=true
eureka.instance.prefer-ip-address=true
package com.example.users.model;
public class User {
private Long id;
private String name;
private String email;
private String department;
//construtors
// Getters et Setters
package com.example.users.controller;
import com.example.users.model.User;
import org.springframework.web.bind.annotation.*;
import java.util.Arrays;
import java.util.List;
@RestController
@RequestMapping("/api/users")
@CrossOrigin(origins = "*")
public class UserController {
@GetMapping
public List<User> getAllUsers() {
return Arrays.asList(
new User(1L, "John Doe", "john@example.com", "IT"),
new User(2L, "Jane Smith", "jane@example.com", "HR")
);
}
@GetMapping("/{id}")
public User getUserById(@PathVariable Long id) {
return new User(id, "User " + id, "user" + id + "@example.com", "Department " + id);
}
@PostMapping
public User createUser(@RequestBody User user) {
user.setId(System.currentTimeMillis());
return user;
}
}
package com.example.users;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class UsersMicroserviceApplication {
public static void main(String[] args) {
SpringApplication.run(UsersMicroserviceApplication.class, args);
}
}
2 Service B - Produits
# === CONFIGURATION SERVEUR ===
server.port=8081
spring.application.name=service-b
# === CONFIGURATION EUREKA ===
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
eureka.client.register-with-eureka=true
eureka.client.fetch-registry=true
eureka.instance.prefer-ip-address=true
package com.example.products.model;
public class Product {
private Long id;
private String name;
private String description;
private Double price;
//construtors
// Getters et Setters
package com.example.products.controller;
import com.example.products.model.Product;
import org.springframework.web.bind.annotation.*;
import java.util.Arrays;
import java.util.List;
@RestController
@RequestMapping("/api/products")
@CrossOrigin(origins = "*")
public class ProductController {
@GetMapping
public List<Product> getAllProducts() {
return Arrays.asList(
new Product(1L, "Laptop", "High-performance laptop", 999.99),
new Product(2L, "Smartphone", "Latest smartphone model", 699.99)
);
}
@GetMapping("/{id}")
public Product getProductById(@PathVariable Long id) {
return new Product(id, "Product " + id, "Description " + id, 99.99 + id);
}
@PostMapping
public Product createProduct(@RequestBody Product product) {
product.setId(System.currentTimeMillis());
return product;
}
}
3 Service C - Commandes
# === CONFIGURATION SERVEUR ===
server.port=8082
spring.application.name=service-c
# === CONFIGURATION EUREKA ===
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
eureka.client.register-with-eureka=true
eureka.client.fetch-registry=true
eureka.instance.prefer-ip-address=true
package com.example.orders.model;
public class Order {
private Long id;
private Long userId;
private Long productId;
private Integer quantity;
private Double totalAmount;
//constructors
// Getters et Setters
package com.example.orders.controller;
import com.example.orders.model.Order;
import org.springframework.web.bind.annotation.*;
import java.util.Arrays;
import java.util.List;
@RestController
@RequestMapping("/api/orders")
@CrossOrigin(origins = "*")
public class OrderController {
@GetMapping
public List<Order> getAllOrders() {
return Arrays.asList(
new Order(1L, 1L, 1L, 2, 1999.98),
new Order(2L, 2L, 2L, 1, 699.99)
);
}
@GetMapping("/{id}")
public Order getOrderById(@PathVariable Long id) {
return new Order(id, 1L, 1L, 1, 999.99);
}
@PostMapping
public Order createOrder(@RequestBody Order order) {
order.setId(System.currentTimeMillis());
return order;
}
}
Zuul Gateway
1 Configuration de Base de Zuul
⚠️ RAPPEL - Zuul est déprécié
Pour les nouveaux projets, utilisez Spring Cloud Gateway à la place.
Dépendances Maven :
<dependencies>
<!-- Zuul -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<!-- Eureka Client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- Actuator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
Application principale :
package com.example.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
/**
* Application Zuul Gateway
*
* @EnableZuulProxy - Active Zuul comme proxy API Gateway
* @EnableDiscoveryClient - Active la découverte de services via Eureka
*/
@SpringBootApplication
@EnableZuulProxy // Active Zuul Proxy
@EnableDiscoveryClient // Active Eureka Client
public class ZuulGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulGatewayApplication.class, args);
}
}
2 Configuration de Routage
Configuration application.properties :
# === CONFIGURATION SERVEUR ===
server.port=8762
spring.application.name=zuul-gateway
# === CONFIGURATION EUREKA ===
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
eureka.client.register-with-eureka=true
eureka.client.fetch-registry=true
eureka.instance.prefer-ip-address=true
# === CONFIGURATION ZUUL - ROUTAGE DE BASE ===
# Routage par service name (discovery-based routing)
zuul.routes.service-a.path=/api/users/**
zuul.routes.service-a.serviceId=service-a
zuul.routes.service-b.path=/api/products/**
zuul.routes.service-b.serviceId=service-b
zuul.routes.service-c.path=/api/orders/**
zuul.routes.service-c.serviceId=service-c
# === CONFIGURATION ZUUL - OPTIONS AVANCÉES ===
# Désactiver les routes automatiques
zuul.ignored-services=*
# Timeout configuration
zuul.host.connect-timeout-millis=5000
zuul.host.socket-timeout-millis=10000
# Buffer configuration
zuul.host.max-total-connections=200
zuul.host.max-per-route-connections=20
# === CONFIGURATION DES FILTRES ===
# Désactiver les headers sensibles par défaut
zuul.sensitive-headers=
# === CONFIGURATION ACTUATOR ===
management.endpoints.web.exposure.include=routes,filters,health,info
management.endpoint.routes.enabled=true
management.endpoint.filters.enabled=true
3 Filtres Zuul
Filtres personnalisés :
package com.example.gateway.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
/**
* Filtre pré-routage pour le logging des requêtes
*
* Type: pre - Exécuté avant le routage
* Order: 1 - Ordre d'exécution
*/
@Component
public class PreLoggingFilter extends ZuulFilter {
private static final Logger logger = LoggerFactory.getLogger(PreLoggingFilter.class);
/**
* Type de filtre :
* - pre : Exécuté avant le routage
* - route : Exécuté pendant le routage
* - post : Exécuté après le routage
* - error : Exécuté en cas d'erreur
*/
@Override
public String filterType() {
return "pre";
}
/**
* Ordre d'exécution du filtre
* Plus le chiffre est petit, plus le filtre est exécuté tôt
*/
@Override
public int filterOrder() {
return 1;
}
/**
* Condition d'exécution du filtre
* true = toujours exécuté
*/
@Override
public boolean shouldFilter() {
return true;
}
/**
* Logique du filtre
*/
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
logger.info("Pre Filter: {} request to {}",
request.getMethod(),
request.getRequestURL().toString());
// Ajout d'un header personnalisé
ctx.addZuulRequestHeader("X-Request-Time", String.valueOf(System.currentTimeMillis()));
return null;
}
}
package com.example.gateway.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletResponse;
/**
* Filtre post-routage pour le logging des réponses
*/
@Component
public class PostLoggingFilter extends ZuulFilter {
private static final Logger logger = LoggerFactory.getLogger(PostLoggingFilter.class);
@Override
public String filterType() {
return "post";
}
@Override
public int filterOrder() {
return 1;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletResponse response = ctx.getResponse();
long startTime = (Long) ctx.get("startTime");
long duration = System.currentTimeMillis() - startTime;
logger.info("Post Filter: Response status {} in {}ms",
response.getStatus(),
duration);
return null;
}
}
package com.example.gateway.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.stereotype.Component;
/**
* Filtre pour mesurer le temps de traitement
*/
@Component
public class PreTimeFilter extends ZuulFilter {
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 0; // Exécuté en premier
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
ctx.set("startTime", System.currentTimeMillis());
return null;
}
}
package com.example.gateway.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
/**
* Filtre pour la gestion des erreurs
*/
@Component
public class ErrorFilter extends ZuulFilter {
private static final Logger logger = LoggerFactory.getLogger(ErrorFilter.class);
@Override
public String filterType() {
return "error";
}
@Override
public int filterOrder() {
return 1;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
Throwable throwable = ctx.getThrowable();
logger.error("Error Filter: {}", throwable.getMessage(), throwable);
return null;
}
}
4 Filtres de Sécurité
Filtres d'authentification :
package com.example.gateway.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Filtre d'authentification basique
*/
@Component
public class AuthFilter extends ZuulFilter {
private static final Logger logger = LoggerFactory.getLogger(AuthFilter.class);
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 2; // Exécuté après PreTimeFilter
}
@Override
public boolean shouldFilter() {
// Appliquer le filtre à toutes les requêtes sauf /api/public/**
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
return !request.getRequestURI().startsWith("/api/public/");
}
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
// Vérification du header d'authentification
String authorizationHeader = request.getHeader("Authorization");
if (authorizationHeader == null || !authorizationHeader.startsWith("Bearer ")) {
logger.warn("Unauthorized access to {}", request.getRequestURI());
// Bloquer la requête
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(401);
try {
HttpServletResponse response = ctx.getResponse();
response.getWriter().write("{\"error\": \"Unauthorized\"}");
response.setContentType("application/json");
} catch (Exception e) {
throw new ZuulException(e, 401, "Unauthorized");
}
return null;
}
logger.info("Authorized access to {}", request.getRequestURI());
return null;
}
}
5 Configuration Avancée
Configuration étendue :
# === CONFIGURATION ZUUL AVANCÉE ===
# === ROUTAGE PERSONNALISÉ ===
# Routage avec préfixe
zuul.routes.users.path=/api/v1/users/**
zuul.routes.users.serviceId=service-a
zuul.routes.users.strip-prefix=false
# Routage avec URL statique
zuul.routes.external.path=/api/external/**
zuul.routes.external.url=http://external-api.example.com
# Routage avec load balancing personnalisé
zuul.routes.loadbalanced.path=/api/balanced/**
zuul.routes.loadbalanced.serviceId=service-a
# === CONFIGURATION DES TIMEOUTS ===
# Timeout pour les connexions
zuul.host.connect-timeout-millis=10000
zuul.host.socket-timeout-millis=20000
# Timeout pour les routes spécifiques
zuul.routes.service-a.sensitive-headers=Cookie,Set-Cookie
zuul.routes.service-a.custom-sensitive-headers=true
# === CONFIGURATION DES HEADERS ===
# Headers à ignorer
zuul.ignored-headers=Access-Control-Allow-Credentials,Access-Control-Allow-Origin
# Headers sensibles (par défaut: Cookie,Set-Cookie,Authorization)
zuul.sensitive-headers=Cookie,Set-Cookie
# === CONFIGURATION DE LA SÉCURITÉ ===
# Désactiver les routes non sécurisées
zuul.ignored-patterns=/**/admin/**
# === CONFIGURATION DES FILTRES ===
# Désactiver les filtres par défaut
zuul.PreDecorationFilter.pre.disable=true
# === CONFIGURATION DES MÉTRIQUES ===
# Activer les métriques Hystrix
zuul.ribbon-isolation-strategy=THREAD
# === CONFIGURATION DU BUFFER ===
# Taille maximale du buffer
zuul.buffer-size=8192
# === CONFIGURATION DES CORS ===
# Activer CORS
zuul.allow-credentials=true
zuul.allowed-origins=*
zuul.allowed-methods=GET,POST,PUT,DELETE
zuul.allowed-headers=*
zuul.exposed-headers=Authorization,X-Total-Count
Utilisation et Tests
1 Exemples d'Utilisation
Requêtes via Zuul :
Accès aux services via Zuul :
# === REQUÊTES DIRECTES AUX SERVICES ===
# Service A - Utilisateurs
GET http://localhost:8080/api/users
GET http://localhost:8080/api/users/1
# Service B - Produits
GET http://localhost:8081/api/products
GET http://localhost:8081/api/products/1
# Service C - Commandes
GET http://localhost:8082/api/orders
GET http://localhost:8082/api/orders/1
# === REQUÊTES VIA ZUUL GATEWAY ===
# Mêmes requêtes via Zuul
GET http://localhost:8762/api/users
GET http://localhost:8762/api/users/1
GET http://localhost:8762/api/products
GET http://localhost:8762/api/products/1
GET http://localhost:8762/api/orders
GET http://localhost:8762/api/orders/1
# === REQUÊTES AVEC AUTHENTIFICATION ===
GET http://localhost:8762/api/users
Authorization: Bearer your-token-here
POST http://localhost:8762/api/users
Content-Type: application/json
Authorization: Bearer your-token-here
{
"name": "New User",
"email": "newuser@example.com",
"department": "IT"
}
2 Tests et Validation
Tests Postman/Curl :
# === TESTS AVEC CURL ===
# Test de routage basique
curl -X GET http://localhost:8762/api/users
# Test avec authentification
curl -X GET http://localhost:8762/api/users \
-H "Authorization: Bearer test-token"
# Test de création
curl -X POST http://localhost:8762/api/users \
-H "Content-Type: application/json" \
-H "Authorization: Bearer test-token" \
-d '{"name":"Test User","email":"test@example.com","department":"TEST"}'
# Test de routage vers différents services
curl -X GET http://localhost:8762/api/products
curl -X GET http://localhost:8762/api/orders
# === TESTS DES ENDPOINTS ACTUATOR ===
# Liste des routes
curl -X GET http://localhost:8762/actuator/routes
# Liste des filtres
curl -X GET http://localhost:8762/actuator/filters
# Health check
curl -X GET http://localhost:8762/actuator/health
Tableau Récapitulatif des Fonctionnalités
| Fonctionnalité | Zuul | Spring Cloud Gateway | État |
|---|---|---|---|
| Routage | Basé sur Servlet | Reactive (WebFlux) | ⚠️ Zuul / ✅ Gateway |
| Filtres | ZuulFilter | GatewayFilter | ⚠️ Legacy / ✅ Moderne |
| Load Balancing | Ribbon intégré | LoadBalancer réactif | ⚠️ Zuul / ✅ Gateway |
| Performance | Bloquant | Non-bloquant | ⚠️ Limité / ✅ Supérieur |
| Maintenance | Déprécié | Actif | ⚠️ Arrêté / ✅ Actif |
| Écosystème | Netflix OSS | Spring Cloud | ⚠️ Legacy / ✅ Intégré |
Bonnes Pratiques et Conseils
1 Configuration Optimale
Configuration recommandée pour Zuul :
# === CONFIGURATION ZUUL OPTIMALE ===
# Timeout raisonnable
zuul.host.connect-timeout-millis=5000
zuul.host.socket-timeout-millis=10000
# Buffer adapté
zuul.buffer-size=8192
# Connexions
zuul.host.max-total-connections=200
zuul.host.max-per-route-connections=20
# === CONFIGURATION DES ROUTES ===
# Routage sécurisé
zuul.routes.secure.path=/api/secure/**
zuul.routes.secure.serviceId=secure-service
zuul.routes.secure.sensitive-headers=Cookie,Set-Cookie,Authorization
# Routage public
zuul.routes.public.path=/api/public/**
zuul.routes.public.serviceId=public-service
zuul.routes.public.sensitive-headers=
# === CONFIGURATION DES HEADERS ===
# Headers à ignorer pour la sécurité
zuul.ignored-headers=Access-Control-Allow-Origin,Access-Control-Allow-Credentials
# Headers sensibles par défaut
zuul.sensitive-headers=Cookie,Set-Cookie,Authorization
Conclusion
Points Clés à Retenir
⚠️ Zuul est DÉPRÉCIÉ
Utilisez Spring Cloud Gateway pour les nouveaux projets !
Résumé historique :
- Zuul était l'API Gateway Netflix pour les microservices
- Utilisait des ZuulFilter pour le pré/post-traitement
- Fournissait du routage dynamique via Eureka
- Incluait des fonctionnalités de sécurité et de monitoring
- Déprécié au profit de Spring Cloud Gateway
⚠️ Points d'attention :
- Ne pas utiliser pour les nouveaux projets
- Migrer les projets existants vers Spring Cloud Gateway
- Maintenir les projets Zuul existants avec prudence
- Surveiller les vulnérabilités de sécurité