问题描述
我尝试使用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 (将#修改为@)