zuul速率限制在API响应上返回500内部服务器错误,在控制台日志上返回429 TOO_MANY_REQUESTS

问题描述

我尝试使用cloud-zuul-ratelimit 将传入请求限制为Zuul-netflix,但在邮递员响应中返回500,但在控制台日志中返回429 TOO_MANY_REQUESTS

2020-11-06T21:23:32.637Z (UTC+0),[http-nio-8085-exec-5] WARN  o.s.c.n.z.f.post.SendErrorFilter - Error during filtering
com.netflix.zuul.exception.ZuulException: 429 TOO_MANY_REQUESTS
    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:166)
    at org.springframework.cloud.netflix.zuul.web.ZuulController.handleRequest(ZuulController.java:45)
    at org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter.handle(SimpleControllerHandlerAdapter.java:52)
    at org.springframework.web.servlet.dispatcherServlet.dodispatch(dispatcherServlet.java:1040)
    at org.springframework.web.servlet.dispatcherServlet.doService(dispatcherServlet.java:943)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
    at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:660)
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:741)
    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:53)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at brave.servlet.TracingFilter.doFilter(TracingFilter.java:67)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at com.fathy.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.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:320)
    at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:126)
    at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:90)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:118)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:137)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:111)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:158)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationProcessingFilter.doFilter(OAuth2AuthenticationProcessingFilter.java:176)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.authentication.logout.logoutFilter.doFilter(logoutFilter.java:116)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:92)
    at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:77)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:56)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:215)
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:178)
    at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:358)
    at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:271)
    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:100)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    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.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    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.ExceptionLoggingFilter.doFilter(ExceptionLoggingFilter.java:50)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at brave.servlet.TracingFilter.doFilter(TracingFilter.java:84)
    at org.springframework.cloud.sleuth.instrument.web.LazyTracingFilter.doFilter(TraceWebServletAutoConfiguration.java:138)
    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.metrics.web.servlet.WebMvcmetricsFilter.doFilterInternal(WebMvcmetricsFilter.java:109)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    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:201)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at com.fathy.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:202)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:373)
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1590)
    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)

这是我的配置

server:
  port: ${PORT:8085}
  tomcat.max-threads: 800
  max-http-header-size: 102400

eureka:
  client:
    #    registerWithEureka: false
    fetchRegistry: true
    serviceUrl:
      defaultZone: http://127.0.0.1:8083/eureka/

zuul:
  semaphore.maxSemaphores: 800
  routes:
    user-management:
      path: /user-management/**
      serviceId: user-management-service
      sensitiveHeaders:
    bot-management:
      path: /bot-management/**
      serviceId: bot-management-service
      sensitiveHeaders:
    analytics:
      path: /analytics/**
      serviceId: analytics-service
    logging:
      path: /logging/**
      serviceId: logging-service
    gsma:
      path: /gsma/**
      serviceId: gsma-service
    accounting:
      path: /accounting/**
      serviceId: accounting-service
    content-management:
      path: /content-management/**
      serviceId: content-management-service
    dimelo:
      path: /dimelo/**
      serviceId: dimelo-service
  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


security:
  basic:
    enabled: false

ribbon:
  ReadTimeout: 60000
  ConnectionTimeout: 60000

hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 125000

spring:
  http:
    multipart:
      enabled: true
      max-file-size: -1
      max-request-size: -1
  datasource:
    url: jdbc:postgresql://localhost:5432/combo-deleted-user
    username: postgres
    password: postgres
    driver-class-name: org.postgresql.Driver
  jpa:
    show-sql: false
    generate-ddl: false
    hibernate:
      ddl-auto: validate
    properties:
      hibernate:
        dialect: org.hibernate.dialect.PostgresqlDialect
        default_schema: public
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest
    listener:
      simple:
        retry:
          enabled: true
          initial-interval: 1000
          max-attempts: 3
          max-interval: 10000
          multiplier: 2

feign:
  client:
    config:
      default:
        connectTimeout: 60000
        readTimeout: 60000

然后我尝试扩展ZuulFilter来检查令牌和声明

package com.fathy.gatewayservice.filters;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.fathy.gatewayservice.common.enums.OrangeErrorInfo;
import com.fathy.gatewayservice.entities.User;
import com.fathy.gatewayservice.repositories.UserRepository;
import com.fathy.gatewayservice.services.HttpErrorHandlingService;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.annotate.JsonSerialize;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationDetails;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.Map;
import java.util.Objects;

/**
 * A filter to check and forbid deleted accounts and invalidate tokens that haven't yet expired but holding claims that
 * are inconsistent with the current user's state
 *
 * @since 1.1
 */
@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));
  }
}

并使用BasicErrorController处理异常并抛出我的自定义异常

package com.fathy.gatewayservice.filters;

import com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.support.RateLimitExceededException;
import com.netflix.zuul.exception.ZuulException;
import com.fathy.gatewayservice.common.enums.OrangeErrorInfo;
import com.fathy.gatewayservice.services.HttpErrorHandlingService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.web.*;
import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController;
import org.springframework.boot.autoconfigure.web.servlet.error.ErrorViewResolver;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.web.firewall.RequestRejectedException;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.multipart.MultipartException;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import  com.fathy.gatewayservice.exceptions.ComboHttpRequestException;

/**
 * A custom error controller that extends Spring Boot default basic error controller
 * @since 2.1.4
 */
@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)
                throw new RateLimitExceededException();
        }
        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(RateLimitExceededException.class)
    public ResponseEntity<String> handleZuulError(RateLimitExceededException ex) {
        try {
            return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS)
                    .contentType(MediaType.APPLICATION_JSON).build();
        } catch (Exception e) {
            e.printstacktrace();
        }
        return ResponseEntity.badRequest().build();
    }
}

netflix.zuul.exception.Zuul异常命中自定义异常并抛出ExceptionHandler,但它在http响应中返回500 internal server error ,在控制台日志中返回429 TOO_MANY_REQUESTS

解决方法

暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!

如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。

小编邮箱:dio#foxmail.com (将#修改为@)