I have some microservices with Spring Cloud, Now I have to implement ratel-limit for the API on the cloud gateway based on the user name.
With the help of KeyResolver, I extract the user from the requester JWT token and implemented RedisRateLimiter with the same rate-limit value (consumer50tps) for all users as below, It's working fine.
Now the Requirement is I have to set rate-limit values based on the user subscription, Whereas for the same api/url different users get different limit values (consumer50tps or consumer100tps).
current code what is working fine for a single limitation value (consumer 50tps) for all Users.
@Bean
@Primary
public RedisRateLimiter consumer50tps() {
return new RedisRateLimiter(50, 50, 1);
}
@Bean
public RedisRateLimiter consumer100tps() {
return new RedisRateLimiter(100, 100, 1);
}
@Bean
public KeyResolver userKeyResolver1() {
return exchange -> {
// Implement logic to fetch JWT and get user name
};
}
@Bean
public RouteLocator appRouteConfig(RouteLocatorBuilder builder) {
return builder.routes()
.route(p -> p.path("/microservice-one/api1/**")
.filters(f -> f
.rewritePath("/microservice-one/(?<segment>.*)", "/${segment}")
.requestRateLimiter(r -> r.setRateLimiter(consumer50tps())
.setKeyResolver(userKeyResolver())
))
.uri("lb://microservice-one"))
.build();
}
Simple way we can do it, create a customFilter with the help of GatewayFilter as:
@Component
public class MyRateLimiterFilter implements GatewayFilter {
private final UserKeyResolver userKeyResolver;
private final ReactiveRedisTemplate<String, String> redisTemplate;
public MyRateLimiterFilter(ReactiveRedisTemplate<String, String> redisTemplate, UserKeyResolver userKeyResolver) {
this.redisTemplate = redisTemplate;
this.userKeyResolver = userKeyResolver;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return userKeyResolver.resolve(exchange)
.flatMap(key -> {
return redisTemplate.opsForValue().increment(key)
.flatMap(tokens -> {
if (tokens == 1) return redisTemplate.expire(key, Duration.ofMillis(1000)).thenReturn(tokens);
return Mono.just(tokens);
})
.flatMap(tokens -> {
int maxReqPerSec = 1;
// Implement rate limit based on user here, in this case "key" returns the user ID from JWT.
// You can inject the user from anywhere like user-service or read from any properties file and set the limit !
if(key.equals("consumer50tps")){
maxReqPerSec = 50;
}else if(key.equals("consumer100tps")){
maxReqPerSec = 100;
}
if (tokens <= maxReqPerSec) {
return chain.filter(exchange);
} else {
LOGGER.info("Exceeded request limit for the user: "+key + ", current number of request: "+tokens);
exchange.getResponse().setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
return exchange.getResponse().setComplete();
}
});
});
}
}
and then add this filter on RouteLocator on specific service or APIs as and enjoy it !!
@Bean
public RouteLocator appRouteConfig(RouteLocatorBuilder builder, MyRateLimiterFilter myRateLimiter) {
return builder.routes()
.route(p -> p.path("/microservice-one/**")
.filters(f -> f
.filter(myRateLimiter)
.rewritePath("/microservice-one/(?<segment>.*)", "/${segment}")
)
.uri("lb://microservice-one"))
.build();
}