Solución:
Aquí hay una solución para aquellos que buscan acelerar las solicitudes por segundo para cada usuario (dirección IP). Esta solución requiere de Google Guava library
. Vas a usar el LoadingCache
class para almacenar los recuentos de solicitudes y las direcciones IP de los clientes. También necesitará el javax.servlet-api
dependencia porque querrá utilizar una servlet filter
donde tiene lugar el recuento de solicitudes. Aquí está el código:
import javax.servlet.Filter;
@Component
public class requestThrottleFilter implements Filter {
private int MAX_REQUESTS_PER_SECOND = 5; //or whatever you want it to be
private LoadingCache<String, Integer> requestCountsPerIpAddress;
public requestThrottleFilter(){
super();
requestCountsPerIpAddress = CacheBuilder.newBuilder().
expireAfterWrite(1, TimeUnit.SECONDS).build(new CacheLoader<String, Integer>() {
public Integer load(String key) {
return 0;
}
});
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;
String clientIpAddress = getClientIP((HttpServletRequest) servletRequest);
if(isMaximumRequestsPerSecondExceeded(clientIpAddress)){
httpServletResponse.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
httpServletResponse.getWriter().write("Too many requests");
return;
}
filterChain.doFilter(servletRequest, servletResponse);
}
private boolean isMaximumRequestsPerSecondExceeded(String clientIpAddress){
int requests = 0;
try {
requests = requestCountsPerIpAddress.get(clientIpAddress);
if(requests > MAX_REQUESTS_PER_SECOND){
requestCountsPerIpAddress.put(clientIpAddress, requests);
return true;
}
} catch (ExecutionException e) {
requests = 0;
}
requests++;
requestCountsPerIpAddress.put(clientIpAddress, requests);
return false;
}
public String getClientIP(HttpServletRequest request) {
String xfHeader = request.getHeader("X-Forwarded-For");
if (xfHeader == null){
return request.getRemoteAddr();
}
return xfHeader.split(",")[0]; // voor als ie achter een proxy zit
}
@Override
public void destroy() {
}
}
Entonces, lo que esto básicamente hace es almacenar todas las solicitudes de direcciones IP en un LoadingCache
. Esto es como un mapa especial en el que cada entrada tiene un tiempo de vencimiento. En el constructor, el tiempo de caducidad se establece en 1 segundo. Eso significa que en la primera solicitud, una dirección IP más su recuento de solicitudes solo se almacena en LoadingCache durante un segundo. Se elimina automáticamente del mapa al expirar. Si durante ese segundo más solicitudes provienen de la dirección IP, entonces el isMaximumRequestsPerSecondExceeded(String clientIpAddress)
agregará esas solicitudes al recuento total de solicitudes, pero antes de eso, verifica si la cantidad máxima de solicitudes por segundo ya se ha excedido. Si ese es el caso, devuelve verdadero y el filtro devuelve una respuesta de error con el código de estado 429 que significa Demasiadas solicitudes.
De esta manera, solo se puede realizar una cantidad determinada de solicitudes por usuario por segundo.
EDITAR: asegúrese de dejar que Spring realice un escaneo de componentes en el paquete donde tiene guardado su Filtro o, de lo contrario, el Filtro no funcionará. Además, debido a que está anotado con @Component, el filtro funcionará para todos los puntos finales de forma predeterminada (/ *).
Si Spring detectó su filtro, debería ver algo como esto en el registro durante el inicio.
o.s.b.w.servlet.FilterRegistrationBean : Mapping filter:'requestThrottleFilter' to: [/*]
No tienes ese componente en Spring.
- Puede construirlo como parte de su solución. Cree un filtro y regístrelo en su contexto de primavera. El filtro debe verificar las llamadas entrantes y contar las solicitudes entrantes por usuario durante una ventana de tiempo. Usaría el algoritmo de token bucket ya que es el más flexible.
- Puede construir algún componente que sea independiente de su solución actual. Cree una puerta de enlace API que haga el trabajo. Puede extender la puerta de enlace de Zuul y, nuevamente, usar el algoritmo del depósito de tokens.
- Puede utilizar un componente ya integrado, como Mulesoft ESB, que puede actuar como puerta de enlace API y admite la limitación y el estrangulamiento de la velocidad. Yo nunca lo usé.
- Y finalmente, puede usar un administrador de API que tiene limitación y aceleración de velocidad y mucho más. Echa un vistazo a MuleSoft, WSO2, 3Scale, Kong, etc … (la mayoría tendrá un costo, algunos son de código abierto y tienen una edición comunitaria).
Spring no tiene limitación de velocidad lista para usar.
Hay un proyecto bucket4j-spring-boot-starter que usa la biblioteca bucket4j con el algoritmo token-bucket para limitar el acceso a la API REST. Puede configurarlo a través del archivo de propiedades de la aplicación. Existe una opción para limitar el acceso según la dirección IP o el nombre de usuario.
Como ejemplo, una configuración simple que permite un máximo de 5 solicitudes en 10 segundos independientemente del usuario:
bucket4j:
enabled: true
filters:
- cache-name: buckets
url: .*
rate-limits:
- bandwidths:
- capacity: 5
time: 10
unit: seconds
Si está usando Netflix Zuul, puede usar Spring Cloud Zuul RateLimit que usa diferentes opciones de almacenamiento: Consul, Redis, Spring Data y Bucket4j.