marcosbarbero / spring-cloud-zuul-ratelimit

Rate limit auto-configure for Spring Cloud Netflix Zuul

Home Page:https://blog.marcosbarbero.com/spring-cloud-netflix-zuul-rate-limit/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Rest API return 500 when exceed limit

fathyelshemy opened this issue · comments

Hi ,
I have spring boot 1.5.x as zuul server and I add ratelimiter library with version 1.7.6 with the following config

zuul:
  semaphore.maxSemaphores: 800
  ratelimit:
    enabled: true
    repository: JPA
    add-response-headers: true
    behind-proxy: true
    default-policy-list:
      - limit: 5
        refresh-interval: 60
        type:
        - origin
    deny-request:
      response-status-code: 429

and follow doc and success to get headers for limit and when user exceed limit API return 500 in postman and the following exception in log console

2020-11-01 10:16:00.387 [http-nio-8085-exec-4] WARN  o.s.c.n.z.f.post.SendErrorFilter - Error during filtering
com.netflix.zuul.exception.ZuulException: 429
	at com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.support.RateLimitExceededException.<init>(RateLimitExceededException.java:13)
	at com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.filters.RateLimitPreFilter.lambda$run$0(RateLimitPreFilter.java:117)
	at java.util.ArrayList.forEach(ArrayList.java:1257)
	at com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.filters.RateLimitPreFilter.run(RateLimitPreFilter.java:82)
	at com.netflix.zuul.ZuulFilter.runFilter(ZuulFilter.java:117)
	at com.netflix.zuul.FilterProcessor.processZuulFilter(FilterProcessor.java:193)
	at com.netflix.zuul.FilterProcessor.runFilters(FilterProcessor.java:157)
	at com.netflix.zuul.FilterProcessor.preRoute(FilterProcessor.java:133)
	at com.netflix.zuul.ZuulRunner.preRoute(ZuulRunner.java:105)
	at com.netflix.zuul.http.ZuulServlet.preRoute(ZuulServlet.java:125)
	at com.netflix.zuul.http.ZuulServlet.service(ZuulServlet.java:74)
	at org.springframework.web.servlet.mvc.ServletWrappingController.handleRequestInternal(ServletWrappingController.java:157)
	at org.springframework.cloud.netflix.zuul.web.ZuulController.handleRequest(ZuulController.java:44)
	at org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter.handle(SimpleControllerHandlerAdapter.java:50)
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:967)
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:901)
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
	at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:872)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:661)
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:742)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.springframework.boot.web.filter.ApplicationContextHeaderFilter.doFilterInternal(ApplicationContextHeaderFilter.java:55)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at com.orange.combo.gatewayservice.config.httpLoggingFilter.RequestLogFilter.doFilter(RequestLogFilter.java:97)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.springframework.boot.actuate.trace.WebRequestTraceFilter.doFilterInternal(WebRequestTraceFilter.java:111)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:317)
	at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:127)
	at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:91)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
	at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:114)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
	at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:137)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
	at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:111)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
	at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:170)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
	at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
	at org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationProcessingFilter.doFilter(OAuth2AuthenticationProcessingFilter.java:176)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
	at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:116)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
	at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:66)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
	at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
	at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:56)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
	at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:214)
	at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:177)
	at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:347)
	at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:263)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:109)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:93)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.springframework.cloud.sleuth.instrument.web.TraceFilter.doFilter(TraceFilter.java:166)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at io.micrometer.spring.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:107)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:197)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.springframework.boot.actuate.autoconfigure.MetricsFilter.doFilterInternal(MetricsFilter.java:103)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at com.orange.combo.gatewayservice.config.apiFactoryFilter.HttpRequestCustomFilter.doFilter(HttpRequestCustomFilter.java:28)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198)
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:493)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140)
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81)
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342)
	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:800)
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:800)
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1471)
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
	at java.lang.Thread.run(Thread.java:748)

Hello @fathyelshemy ,thank you for submitting an issue!

Hi @fathyelshemy the line 1.x.x.RELEASE has reached its End Of Life a year ago. I recommend you to upgrade your application to the latest Spring Boot, Spring Cloud, and Spring Cloud RateLimit.

Hi @marcosbarbero I upgrade my spring boot app to 2.2.7 and still the same error it return 500 from postman put in log throw above exceptions and I debug a lot and Can't find the problem

Hi @fathyelshemy, did you also update this library to the latest version?

@marcosbarbero
yes now my project works on spring 2.2.7 and JDK8 and the issue is still exists and after long invest I thing their is some issues in Filters because it go to filterchain.dofilter in LogFilterService.java and return with correct response in all cases and then disappear with 500 internal server error without any clear exceptions only com.netflix.zuul.exception.ZuulException: 429
so I'll put all my custom filters to try to discover whats the error

@Component
public class RequestLogFilter implements Filter {

  private static final Logger logger = LoggerFactory.getLogger("kibana-logger");
  private final String regex;

  public RequestLogFilter() {
    this.regex = Stream
            .of(".*/v2/api-docs", ".*/swagger-resources", ".*/swagger-ui.html", ".*/webjars", ".*/bot-management/getCampaign")
            .reduce((str1, str2) -> String.format("%s|%s", str1, str2))
            .map(str -> String.format("(%s).*", str))
            .orElse("");
  }

  @Override
  public void init(FilterConfig filterConfig) throws ServletException {

  }

  @Override
  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    final String path = ((HttpServletRequest) request).getServletPath();

    if (!path.matches(this.regex)) {
      request = new CustomHttpServletRequestWrapper((HttpServletRequest) request);
      response = new CustomHttpServletResponseWrapper((HttpServletResponse) response);

      if (((HttpServletRequest) request).getHeader("authorization") != null) {
        final String rolePrefix = "ROLE_";
        final String defaultRoleName = "ROLE_GUEST";
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        final String email = authentication.getName();
        final String roleName = authentication.getAuthorities().stream()
                .map(GrantedAuthority::getAuthority).filter(authority -> authority.startsWith(rolePrefix)).findFirst().orElse(defaultRoleName)
                .replaceFirst(rolePrefix, "");
        MDC.put("email", email);
        MDC.put("role", roleName);
      } else {
        MDC.remove("email");
        MDC.remove("role");
      }

      if (((HttpServletRequest) request).getHeader("host") != null) {
        final String host = ((HttpServletRequest) request).getHeader("host");
        MDC.put("host", host);
      }

      /* request info */
      final String method = ((HttpServletRequest) request).getMethod();
      final String url = ((HttpServletRequest) request).getRequestURL().toString();
      logger.info(String.format("REST, request, %s, %s", method, url));

      /* request headers */
      final List<String> requestHeaders = new LinkedList<>();
      final Enumeration<String> headerNames = ((HttpServletRequest) request).getHeaderNames();
      while (headerNames.hasMoreElements()) {
        final String headerName = headerNames.nextElement();
        if (!headerName.equals("authorization")) {
          final String headerValue = ((HttpServletRequest) request).getHeader(headerName);
          requestHeaders.add(String.format("%s: %s", headerName, headerValue));
        }
      }
      logger.info(String.format("REST, headers, %s", requestHeaders.toString()));

      /* request data */
      final String requestData = ((CustomHttpServletRequestWrapper) request).getBody().replaceAll("\n", "");
      final StringBuilder stringBuilder = new StringBuilder();
      if (request.getParameterMap().size() > 0) {
        stringBuilder.append("{");
        request.getParameterMap().forEach((key, value) -> stringBuilder.append(key).append(": ").append(value[0]).append(", "));
        stringBuilder.delete(stringBuilder.length() - 2, stringBuilder.length());
        stringBuilder.append("}");
      }
      logger.info(String.format("REST, data, %s, %s", requestData, stringBuilder.toString()));

      /* response */
      try {
         // --problem is hear
        chain.doFilter(request, response);
        response.flushBuffer();
      }finally {
        final String removingWhitespacesRegex = "\n";

        final Integer status = ((CustomHttpServletResponseWrapper) response).getStatus();
        final String contentType = response.getContentType();
        String responseData = ((CustomHttpServletResponseWrapper) response).getBody().replaceAll(removingWhitespacesRegex, "");
        if (status < 400)
          logger.info(String.format("REST, response, %d, %s, %s", status, contentType, responseData));
        else
          logger.error(String.format("REST, response, %d, %s, %s", status, contentType, responseData));
      }
    } else {
      chain.doFilter(request, response);
    }
  }

  @Override
  public void destroy() {

  }
}
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class HttpRequestCustomFilter implements Filter {

  @Override
  public void init(FilterConfig filterConfig) throws ServletException {

  }

  @Override
  public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    CustomHeadersHttpServletRequestWrapper request = new CustomHeadersHttpServletRequestWrapper((HttpServletRequest) servletRequest);
    String comboToken = request.getHeader("Combo-Token");
    if(Objects.nonNull(comboToken)) {
      request.addHeader("Authorization", request.getHeader("Combo-Token"));
    }
    filterChain.doFilter(request, servletResponse);
  }

  @Override
  public void destroy() {

  }
}
@Component
public class UserFilter extends ZuulFilter {

  private final UserRepository userRepository;
  private final HttpErrorHandlingService errorHandlingService;

  @Value("${token.validitySeconds}")
  private long validitySeconds;

  private static final Logger LOGGER = LoggerFactory.getLogger("kibana-logger");

  public UserFilter(UserRepository deletedUserRepository, HttpErrorHandlingService errorHandlingService) {
    this.userRepository = deletedUserRepository;
    this.errorHandlingService = errorHandlingService;
  }


  @Override
  public String filterType() {
    return FilterConstants.PRE_TYPE; //Executed before the request is routed
  }

  @Override
  public int filterOrder() {
    return 0;
  }

  @Override
  public boolean shouldFilter() {
    return true; //Indicates that run() method should be invoked
  }

  @Override
  @SuppressWarnings("unchecked")
  public Object run() {
    //A wrapper around the request, shared by all filters and is unique to each request
    RequestContext requestContext = RequestContext.getCurrentContext();
    final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
    final String email = authentication.getName();
    final User user = userRepository.findByEmail(email);
    try {
      //Set Response Body here
      if(Objects.nonNull(user)) {
        if(user.isDeleted())
          buildResponse(requestContext, HttpStatus.FORBIDDEN, OrangeErrorInfo.FORBIDDEN_USER, "User is deleted");
        else {
          //The JWT details are only available for secured routes (i.e. routes requiring access token)
          if(authentication.getDetails() instanceof OAuth2AuthenticationDetails) {
            final Map<String, Object> jwtClaims = (Map<String, Object>)((OAuth2AuthenticationDetails)authentication.getDetails())
                    .getDecodedDetails();
            if(isInvalidToken(user.getTokenLastUpdated(), (Long)jwtClaims.get("exp")))
              buildResponse(requestContext, HttpStatus.UNAUTHORIZED, OrangeErrorInfo.EXPIRED_CREDENTIALS, "COMBO token expired");
          }
        }
      }
    } catch (IOException e) {
      LOGGER.error("Error while serializing object to json");
    }
    return null;
  }

  /**
   * builds a response depending on the request context and client error
   * @param requestContext the current request context
   * @param httpStatus the response HTTP status code
   * @param orangeErrorInfo the custom error code and message (ODI standardized)
   * @param errorMessage the custom error description message
   * @throws IOException if converting the response body object to JSON has failed (propagated)
   */
  private void buildResponse(RequestContext requestContext, HttpStatus httpStatus, OrangeErrorInfo orangeErrorInfo,
                             String errorMessage) throws IOException {
    requestContext.setSendZuulResponse(false);
    requestContext.setResponseStatusCode(httpStatus.value());
    requestContext.getResponse().setContentType(MediaType.APPLICATION_JSON_VALUE);

    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.setSerializationInclusion(JsonSerialize.Inclusion.NON_NULL);
    requestContext.setResponseBody(errorHandlingService.buildErrorResponseBody(
            requestContext.getRequest(), orangeErrorInfo, errorMessage));
  }

  /**
   * computes the creation date of the token using the validity duration defined in different environments
   * (creation date = expiration date - validity duration) and checks whether this date is earlier that the token's
   * last updated date
   *
   * @param tokenLastUpdated the date where the token was last modified/updated
   * @param expiration the token expiration date in seconds
   * @return true if the creation date is earlier than last updated date
   */
  private boolean isInvalidToken(Date tokenLastUpdated, Long expiration) {
    Instant expirationDateTime = Instant.ofEpochSecond(expiration);
    Instant tokenLastUpdatedDateTime = tokenLastUpdated.toInstant();
    Duration duration = Duration.ofSeconds(validitySeconds);
    return expirationDateTime.minus(duration).isBefore(tokenLastUpdatedDateTime.truncatedTo(ChronoUnit.SECONDS));
  }
}
@Controller
public class CustomErrorController extends BasicErrorController {

    @Autowired
    private HttpErrorHandlingService httpErrorHandlingService;

    public CustomErrorController(ErrorAttributes errorAttributes, ServerProperties errorProperties, List<ErrorViewResolver> errorViewResolvers) {
        super(errorAttributes, errorProperties.getError(), errorViewResolvers);
    }

    @Override
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        Exception ex = ((Exception)request.getAttribute("javax.servlet.error.exception"));
        if(Objects.nonNull(ex)) {
            if(ex.getCause() instanceof MultipartException)
                throw new ComboHttpRequestException("The submitted value for content-type header isn't supported", request,
                        OrangeErrorInfo.INVALID_HEADER_VALUE);
            else if(ex instanceof RequestRejectedException)
                throw new ComboHttpRequestException(ex.getMessage(), request);
            else if(ex instanceof ZuulException)
                if(new RateLimitExceededException().getMessage().contains(ex.getMessage()))
                    throw new ZuulRateLimitException("The application has made too many calls and has exceeded the rate limit for this service",request,OrangeErrorInfo.TOO_MANY_REQUESTS);
        }
        return super.error(request);
    }

    @ExceptionHandler(ComboHttpRequestException.class)
    public ResponseEntity<String> handleError(ComboHttpRequestException ex) {
        try {
            return ResponseEntity.badRequest()
                    .contentType(MediaType.APPLICATION_JSON)
                    .body(httpErrorHandlingService.buildErrorResponseBody(String.valueOf(ex.getRequest().getAttribute("javax.servlet.error.request_uri")),
                            ex.getRequest().getMethod(),
                            ex.getOrangeErrorInfo(),
                            ex.getMessage()));
        } catch (IOException e) {
            e.printStackTrace();
        }
        return ResponseEntity.badRequest().build();
    }

    @ExceptionHandler(ZuulRateLimitException.class)
    public ResponseEntity<String> handleZuulError(ZuulRateLimitException ex){
        try {

            return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS)
                    .contentType(MediaType.APPLICATION_JSON)
                    .body(httpErrorHandlingService.buildErrorResponseBody(String.valueOf(ex.getRequest().getAttribute("javax.servlet.error.request_uri")),
                            ex.getRequest().getMethod(),
                            ex.getOrangeErrorInfo(),
                            ex.getMessage()));
        } catch (IOException e) {
            e.printStackTrace();
        }
        return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS).build();

    }

go in those filters and return com.netflix.zuul.exception.ZuulException: 429 TOO_MANY_REQUESTS successfully but return 500 in postman

and all of them is working fine before add spring-cloud-zuul-ratelimit and I follow doc to the letter
please help

what's on line 97 of RequestLogFilter?

do you mean

// --problem is hear

it I am right this is spelling mistake i mean
// -- problem is here ( it goes in filters and fire uncatched exception)

chain.doFilter(request, response);

it return correct response for logging (429 TOO_MANY_REQUESTS) but for logging only and in postman it return 500

I'm sorry I missed your comment, you shared a lot of code.
I think it will be easier if you can share a project reproducing the problem

here you are full service and I add config yml file inside project called gateway-service.yml
https://github.com/fathyelshemy/zuul-service.git

Thanks.
I'm looking into it and I couldn't understand how you're loading this gateway-service.yml to the application context, can you point it to me?
Also, the project doesn't run as a standalone, I would need a runnable project to investigate further

sorry I'll update repo to make it as standalone project and notify u once I finished thanks

Hi Marco,
I update the repo with same case to simulate here you are the repo zuul-service

to simulate the issue you have to call http://localhost:5050/welcome 4 time It will return 500 in postman and 429 in console logs

I just debugged your sample application, the problem is in your RequestLogFilter, as you are using javax.servlet.Filter instead of a ZuulFilter you must catch the exception/status code returned by the RateLimitPreFilter (ZuulFilter) and send an error response.

Something like this:

response.sendError(theStatusCodeReturnedByRateLimitLibrary);
return;

Unless you have a strong reason to use servlet filters I would migrate it to a ZuulFilter to make the transition seamless

thanks for your advice but I can't catch RateLimitExceededException in RequestLogFilter I don't why If you can help I'll be thankful to you

and I already made my last option to migrate to ZuulFilter and I implement what I did and push to the zuul-service

and I can't log body of response in case of error fired from zuulExceptions.

I see, but you want to log every request that gives an exception?
Wouldn't it be better to leave this feature to an access_log instead?

Don't get it, please explain more what should I leave
I need log to every exception throw by zuul-service and I am sure that every exception thrown by any service throw zuul I log if successfully

I would just use the access_log on any proxy/load-balancer to log the request body if you need to add it to the zuul service itself I wouldn't mix servlet filters with zuul filters.
Regardless of your approach, it has nothing to do with this library is trying to solve. As you are adding servlet filters you have to deal with the burden of how ZuulFilters and Servlet Filters works

Thanks a lot for your support I handle logging issues using ZuulFilters.

but I have small question about exceptions that throw com.netflix.zuul.exception.ZuulException: 429 TOO_MANY_REQUESTS
how can prevent this exception only from print its stack trace on console

This library relies on Spring Boot's logging feature if you want to completely disable the logging you can just set the logging level to off, something like this:

logging.level.com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit=off