Adding circuitBreaker filter in mvc throws IllegalArgumentException
i using java 21 spring boot 3.2.4 spring cloud dependencies 2023.0.1
Adding circuitBreaker filter in mvc throws IllegalArgumentException
An error occurs when the circuitBreaker filter is set as below and passes through the registered route.
spring-cloud-starter-circuitbreaker-reactor-resilience4j dependency is added
spring:
gateway.mvc:
routes:
- id: test-routes
predicates:
- name: Path
args:
patterns: |
/api-test/**,
/api-test2/**
filters:
- StripPrefix=1
- CircuitBreaker=myCircuitBreaker
uri: http://localhost:8081
Caused by: java.lang.IllegalArgumentException: A CircuitBreaker must have an id.
at org.springframework.util.Assert.hasText(Assert.java:240)
at org.springframework.cloud.circuitbreaker.resilience4j.Resilience4JCircuitBreakerFactory.create(Resilience4JCircuitBreakerFactory.java:125)
at org.springframework.cloud.gateway.server.mvc.filter.CircuitBreakerFilterFunctions.lambda$circuitBreaker$7(CircuitBreakerFilterFunctions.java:80)
at org.springframework.web.servlet.function.HandlerFilterFunction.lambda$andThen$0(HandlerFilterFunction.java:58)
at org.springframework.web.servlet.function.HandlerFilterFunction.lambda$ofRequestProcessor$3(HandlerFilterFunction.java:83)
at org.springframework.web.servlet.function.HandlerFilterFunction.lambda$andThen$0(HandlerFilterFunction.java:58)
at org.springframework.cloud.gateway.server.mvc.config.RouterFunctionHolderFactory.lambda$getRouterFunction$3(RouterFunctionHolderFactory.java:154)
at org.springframework.web.servlet.function.HandlerFilterFunction.lambda$andThen$0(HandlerFilterFunction.java:58)
at org.springframework.web.servlet.function.HandlerFilterFunction.lambda$ofRequestProcessor$3(HandlerFilterFunction.java:83)
at org.springframework.web.servlet.function.HandlerFilterFunction.lambda$andThen$1(HandlerFilterFunction.java:59)
at org.springframework.web.servlet.function.HandlerFilterFunction.lambda$andThen$1(HandlerFilterFunction.java:59)
at org.springframework.web.servlet.function.HandlerFilterFunction.lambda$andThen$1(HandlerFilterFunction.java:59)
at org.springframework.web.servlet.function.HandlerFilterFunction.lambda$andThen$1(HandlerFilterFunction.java:59)
at org.springframework.web.servlet.function.HandlerFilterFunction.lambda$apply$2(HandlerFilterFunction.java:70)
at org.springframework.web.servlet.function.support.HandlerFunctionAdapter.handle(HandlerFunctionAdapter.java:108)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014)
Did I miss anything?
I have the same issue. The yaml based configuration doesn't seem to register the circuitbreaker CircuitBreakerFilterFunctions. But doing via WebMvc.fn helps.
Reference: https://github.com/karthik20/spring-cloud-gateway-cb/blob/main/gateway/src/main/java/com/karthik/gateway/config/router/RouterConfig.java
@Bean
public RouterFunction<ServerResponse> routeConfig() {
return route("customer_route")
.route(path("/customer/**").or(path("/customer")), http("http://localhost:8080"))
.filter(circuitBreaker(config -> config.setId("customerCircuitBreaker")
.setStatusCodes("500")))
.build();
}
May I handle this issue? I have an idea to work on this.
Given Situation: yaml file fails to bind to the CircuitBreaker HandlerFilterFunction
// given properties.yaml
spring:
gateway.mvc:
routes:
- id: test-routes
predicates:
- name: Path
args:
patterns: |
/api-test/**,
/api-test2/**
filters:
- StripPrefix=1
- CircuitBreaker=myCircuitBreaker
uri: http://localhost:8081
// Thrown Exception
Caused by: java.lang.IllegalArgumentException: A CircuitBreaker must have an id.
at org.springframework.util.Assert.hasText(Assert.java:240)
at org.springframework.cloud.circuitbreaker.resilience4j.Resilience4JCircuitBreakerFactory.create(Resilience4JCircuitBreakerFactory.java:125)
at org.springframework.cloud.gateway.server.mvc.filter.CircuitBreakerFilterFunctions.lambda$cir
// Code
public static HandlerFilterFunction<ServerResponse, ServerResponse> circuitBreaker(CircuitBreakerConfig config) {
Set<HttpStatusCode> failureStatuses = config.getStatusCodes().stream()
.map(status -> HttpStatusHolder.valueOf(status).resolve()).collect(Collectors.toSet());
return (request, next) -> {
CircuitBreakerFactory<?, ?> circuitBreakerFactory = MvcUtils.getApplicationContext(request)
.getBean(CircuitBreakerFactory.class);
// TODO: cache
CircuitBreaker circuitBreaker = **circuitBreakerFactory.create(config.getId());**
findOperationmethod will matchHandlerFilterFuction(given Parameters)with proper Arguments
@Shortcut
public static HandlerFilterFunction<ServerResponse, ServerResponse> circuitBreaker(String id) {
return circuitBreaker(config -> config.setId(id));
}
public static HandlerFilterFunction<ServerResponse, ServerResponse> circuitBreaker(String id, URI fallbackUri) {
return circuitBreaker(config -> config.setId(id).setFallbackUri(fallbackUri));
}
public static HandlerFilterFunction<ServerResponse, ServerResponse> circuitBreaker(String id, String fallbackPath) {
return circuitBreaker(config -> config.setId(id).setFallbackPath(fallbackPath));
}
public static HandlerFilterFunction<ServerResponse, ServerResponse> circuitBreaker(
Consumer<CircuitBreakerConfig> configConsumer) {
CircuitBreakerConfig config = new CircuitBreakerConfig();
configConsumer.accept(config);
return circuitBreaker(config);
}
**@Shortcut
@Configurable
public static HandlerFilterFunction<ServerResponse, ServerResponse> circuitBreaker(CircuitBreakerConfig config) {**
Set<HttpStatusCode> failureStatuses = config.getStatusCodes().stream()
.map(status -> HttpStatusHolder.valueOf(status).resolve()).collect(Collectors.toSet());
return (request, next) -> {
CircuitBreakerFactory<?, ?> circuitBreakerFactory = MvcUtils.getApplicationContext(request)
.getBean(CircuitBreakerFactory.class);
// TODO: cache
CircuitBreaker circuitBreaker = circuitBreakerFactory.create(config.getId());
return circuitBreaker.run(() -> {
try {
ServerResponse serverResponse = next.handle(request);
// on configured status code, throw exception
if (failureStatuses.contains(serverResponse.statusCode())) {
throw new CircuitBreakerStatusCodeException(serverResponse.statusCode());
}
- Debugging shows that method with
@Configurable(which was added here #3172) has been matched but it should have been bound tocircuitBreaker(String id)
-
This is result of this line of code where
@Configurablewill always return by bypassing existing logic. -
My code changes:
- given HandlerFunctions, @Configurable will be processed the last as it will be matched. Also, Unordered Map tolist will introduce uncertain behaviors.
- Better Logging statements to observe which properties were failed to bind.
Hi all,
I was facing the same issue and tried with the latest version (2023.0.3), but it doesn't seem to be fixed Do you confirm ? Hope it will be part of the next update, thanks
I'm unable to replicate this using a simple project from start.spring.io plus the config in https://github.com/spring-cloud/spring-cloud-gateway/issues/3327#issuecomment-2115435008
If you'd like us to spend some time investigating, please take the time to provide a complete, minimal, verifiable sample (something that we can unzip attached to this issue or git clone, build, and deploy) that reproduces the problem.
If you would like us to look at this issue, please provide the requested information. If the information is not provided within the next 7 days this issue will be closed.