Search code examples
spring-bootresponsecommithttp-status-codesrate-limiting

How to modify response status and body after committed spring boot


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


Solution

  • 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