0

I implemented a rate limiter with Filter.class. However, we encountered that we should not limit successful requests. So, I needed status code of the response. When I get status code in Filter chain, it always returns 200. That means request not processed. When I trigger chain.doFilter status is set but response is in the committed state means read-only. However, I need to return 429 response for the rate limit responses

I tried OncePerRequestFilter.class, lots of wrappers that I forget. I expect to set response body via response status

Euxinos
  • 23
  • 6
  • If the response has been send you cannot modify the status (obviously) as the client already received it. – M. Deinum Nov 10 '22 at 07:59
  • Thanks for your answer, And yes I know that. This question was like self-answer by me. You can do it before committed and via response status code. – Euxinos Nov 10 '22 at 08:05

1 Answers1

0

I came upon lots of answers. However it was like that.

So I removed Filter usage. I use ResponseBodyAdvice globally.

@ControllerAdvice
public class RequestLimitAdvice implements ResponseBodyAdvice<Object> {
    private final LoadingCache<String, Integer> requestCountsPerClient;

    private static final List<Integer> statusCodes = Arrays.asList(HttpStatus.BAD_REQUEST.value(), HttpStatus.UNAUTHORIZED.value(),
            HttpStatus.FORBIDDEN.value(), HttpStatus.NOT_FOUND.value(), HttpStatus.METHOD_NOT_ALLOWED.value(),
            HttpStatus.NOT_ACCEPTABLE.value(), HttpStatus.REQUEST_TIMEOUT.value(), HttpStatus.CONFLICT.value(),
            HttpStatus.INTERNAL_SERVER_ERROR.value(), HttpStatus.NOT_IMPLEMENTED.value(), HttpStatus.SERVICE_UNAVAILABLE.value(),
            HttpStatus.GATEWAY_TIMEOUT.value(), HttpStatus.HTTP_VERSION_NOT_SUPPORTED.value());

    public RequestLimitAdvice() {
        requestCountsPerClient = Caffeine.newBuilder().
                expireAfterWrite(1, TimeUnit.MINUTES).build(key -> 0);
    }

    @Override
    public boolean supports(@NotNull MethodParameter returnType, @NotNull Class<? extends HttpMessageConverter<?>> converterType) {
        return true;
    }

    @Override
    public Object beforeBodyWrite(Object body, @NotNull MethodParameter returnType, @NotNull MediaType selectedContentType,
                                  @NotNull Class<? extends HttpMessageConverter<?>> selectedConverterType,
                                  @NotNull ServerHttpRequest request, @NotNull ServerHttpResponse response) {

        if (response instanceof ServletServerHttpResponse) {
            ServletServerHttpResponse httpServletResponse = (ServletServerHttpResponse) (response);

            if (statusCodes.contains(httpServletResponse.getServletResponse().getStatus())) {
                String client = getClient(((ServletServerHttpRequest) request).getServletRequest());

                if (isMaximumRequestsPerSecondExceeded(client)) {

                    response.setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
                    return Response.notOk(Translator.toLocale("http.status.too_many_requests"), EErrorCode.TOO_MANY_REQUESTS).getError();

                }
            }
        }

        return body;
    }

So in ResponseBodyAdvice, thanks to set of response status code and modify enable of response body. I could make what I wanted.

From the answers of

Euxinos
  • 23
  • 6