Careeroza
Gateway Filters
Built-in Filters
Filters change the request before it goes to the service, or the response before it goes back. default-filters apply to every route:
spring:
cloud:
gateway:
default-filters:
- AddResponseHeader=X-Powered-By, ShopEase-Gateway
- RemoveResponseHeader=X-Internal-Debug
- DedupeResponseHeader=Access-Control-Allow-Origin
routes:
- id: catalog-public
uri: lb://product-service
predicates:
- Path=/catalog/**
filters:
# /catalog/7 -> /api/products/7
- RewritePath=/catalog/(?<segment>.*), /api/products/$\{segment}
- AddRequestHeader=X-Source, public-catalog
- id: order-service
uri: lb://order-service
predicates:
- Path=/api/orders/**
filters:
- name: Retry
args:
retries: 2
methods: GET # never blindly retry POST /orders!
statuses: BAD_GATEWAY,SERVICE_UNAVAILABLE| Filter | Does |
|---|---|
AddRequestHeader / AddResponseHeader | Add a header |
RewritePath | Change the path with a regex |
StripPrefix=1 | Drop the first path segment: /shop/api/x → /api/x |
Retry | Retry failed calls (safe methods only) |
CircuitBreaker | Resilience4j circuit breaker with fallbackUri |
RequestRateLimiter | Rate limit with Redis (next topic) |
TokenRelay | Pass the OAuth2 token to the service (advance level) |
A Custom Global Filter
A GlobalFilter runs for every route. This one makes sure each request has an X-Correlation-Id (so you can follow one order across all service logs) and logs how long the call took:
@Component
@Slf4j
class CorrelationIdFilter implements GlobalFilter, Ordered {
static final String HEADER = "X-Correlation-Id";
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String incoming = exchange.getRequest().getHeaders().getFirst(HEADER);
String id = (incoming != null) ? incoming : UUID.randomUUID().toString();
ServerWebExchange withId = exchange.mutate()
.request(r -> r.header(HEADER, id))
.build();
withId.getResponse().getHeaders().add(HEADER, id);
long start = System.nanoTime();
return chain.filter(withId).then(Mono.fromRunnable(() -> {
long ms = (System.nanoTime() - start) / 1_000_000;
log.info("{} {} -> {} in {} ms [{}]",
exchange.getRequest().getMethod(),
exchange.getRequest().getPath(),
withId.getResponse().getStatusCode(), ms, id);
}));
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE; // run first
}
}Example
curl -i localhost:8080/api/products/7Example output
HTTP/1.1 200 OK
X-Correlation-Id: 3f2b9c1e-6a4d-4c8e-9a51-0b7e2d4f8c13
X-Powered-By: ShopEase-Gateway
{"id":7,"name":"boAt Rockerz 450","price":1499.00,"stock":120,"category":"Audio"}
# gateway log
INFO c.s.g.CorrelationIdFilter : GET /api/products/7 -> 200 OK in 23 ms [3f2b9c1e-6a4d-4c8e-9a51-0b7e2d4f8c13]How a Request Flows Through Filters
request -> [CorrelationIdFilter] -> [Security] -> [RateLimiter] -> [RewritePath] -> service
|
response <- [CorrelationIdFilter] <- ...........................................<- +
(code inside .then(...) runs on the way back)Tip: Code written before chain.filter(exchange) is the "pre" part (request side); code in .then(...) is the "post" part (response side). Never call blocking code (JDBC, Thread.sleep) inside a gateway filter.