spring-cloud-gateway icon indicating copy to clipboard operation
spring-cloud-gateway copied to clipboard

Adding circuitBreaker filter in mvc throws IllegalArgumentException

Open bright-k opened this issue 1 year ago • 4 comments

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?

bright-k avatar Mar 29 '24 04:03 bright-k

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();
    }

karthik20 avatar Apr 01 '24 10:04 karthik20

May I handle this issue? I have an idea to work on this.

jivebreaddev avatar May 11 '24 01:05 jivebreaddev

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());**

image

  1. findOperation method will match HandlerFilterFuction(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());
					}
  1. Debugging shows that method with @Configurable(which was added here #3172) has been matched but it should have been bound to circuitBreaker(String id)

image

  1. This is result of this line of code where @Configurable will always return by bypassing existing logic. image

  2. My code changes:

image

  1. given HandlerFunctions, @Configurable will be processed the last as it will be matched. Also, Unordered Map tolist will introduce uncertain behaviors.
  2. Better Logging statements to observe which properties were failed to bind.

jivebreaddev avatar May 16 '24 14:05 jivebreaddev

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

Snorky35 avatar Jul 19 '24 13:07 Snorky35

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.

spencergibb avatar Sep 26 '24 21:09 spencergibb

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.

spring-cloud-issues avatar Oct 03 '24 21:10 spring-cloud-issues