Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding circuitBreaker filter in mvc throws IllegalArgumentException #3327

Open
bright-k opened this issue Mar 29, 2024 · 3 comments · May be fixed by #3397
Open

Adding circuitBreaker filter in mvc throws IllegalArgumentException #3327

bright-k opened this issue Mar 29, 2024 · 3 comments · May be fixed by #3397

Comments

@bright-k
Copy link

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?

@karthik20
Copy link

karthik20 commented Apr 1, 2024

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

@jivebreaddev
Copy link

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

jivebreaddev pushed a commit to jivebreaddev/spring-cloud-gateway that referenced this issue May 13, 2024
findOperation method will not show any useful information when binding yaml properties to each class instances.

Such Logging will provide a mean to evaluate failure in binding yaml properties.

spring-cloud#3327
jivebreaddev pushed a commit to jivebreaddev/spring-cloud-gateway that referenced this issue May 13, 2024
…elds to resolve)

(RouterFunctionHolderFactory)

Given yaml Properties, CircuitBreaker(`CircuitConfig`) method passes binding test even when given value is `ID` not `CircuitConfig`

- operation method that is annotated with @configuration always pass binding test [findOperation] thereby creating wrong binding from property to method

Introducing Priority will resolve methods without @configuration to be processed first and @configuration will be matched the last.

spring-cloud#3327
jivebreaddev pushed a commit to jivebreaddev/spring-cloud-gateway that referenced this issue May 16, 2024
…g yaml properties to each class instances.

Such Logging will provide a mean to evaluate failure in binding yaml properties.

spring-cloud#3327
jivebreaddev pushed a commit to jivebreaddev/spring-cloud-gateway that referenced this issue May 16, 2024
Given yaml Properties, CircuitBreaker(`CircuitConfig`) method passes binding test even when given value is `ID` not `CircuitConfig`

- operation method that is annotated with @configuration always pass binding test [findOperation] thereby creating wrong binding from property to method

Introducing Sorting to the operations, it will resolve methods without @configuration to be processed first and @configuration will be matched the last.

spring-cloud#3327
@jivebreaddev
Copy link

jivebreaddev commented May 16, 2024

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 Gateway MVC: Retry filter not supported with YAML configuration #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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment