From 8599aee0cabb93db513ad4781bb7388baa3ef05b Mon Sep 17 00:00:00 2001 From: qnnn <65326092+qnnn@users.noreply.github.com> Date: Thu, 23 Jan 2025 23:59:24 +0800 Subject: [PATCH 1/2] Support for reloading httpClient connectTimeout Configuration Signed-off-by: Nan Chiu <1543393961@qq.com> Signed-off-by: qnnn <65326092+qnnn@users.noreply.github.com> --- .../cloud/gateway/filter/NettyRoutingFilter.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/NettyRoutingFilter.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/NettyRoutingFilter.java index bf4f6ad713..2350d4d253 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/NettyRoutingFilter.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/NettyRoutingFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2020 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -254,7 +254,8 @@ protected Mono getHttpClientMono(Route route, ServerWebExchange exch * @return the configured HttpClient. */ protected HttpClient getHttpClient(Route route, ServerWebExchange exchange) { - Object connectTimeoutAttr = route.getMetadata().get(CONNECT_TIMEOUT_ATTR); + Object connectTimeoutAttr = route.getMetadata().get(CONNECT_TIMEOUT_ATTR) != null + ? route.getMetadata().get(CONNECT_TIMEOUT_ATTR) : properties.getConnectTimeout(); if (connectTimeoutAttr != null) { Integer connectTimeout = getInteger(connectTimeoutAttr); return this.httpClient.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectTimeout); From 93fa29611326a2df5a80e049ab51092fa1aaf0d2 Mon Sep 17 00:00:00 2001 From: qnnn <65326092+qnnn@users.noreply.github.com> Date: Fri, 24 Jan 2025 01:43:21 +0800 Subject: [PATCH 2/2] add unit test testConnectTimeoutConfigurationReloadWorks. Signed-off-by: qnnn <65326092+qnnn@users.noreply.github.com> --- .../filter/NettyRoutingFilterTests.java | 43 ++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/NettyRoutingFilterTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/NettyRoutingFilterTests.java index ace0bcc292..b96bb9fa49 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/NettyRoutingFilterTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/NettyRoutingFilterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2020 the original author or authors. + * Copyright 2013-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,18 +16,26 @@ package org.springframework.cloud.gateway.filter; +import java.util.Collections; +import java.util.HashMap; + +import io.netty.channel.ChannelOption; import org.hamcrest.Matchers; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import reactor.core.publisher.Mono; import reactor.netty.DisposableServer; +import reactor.netty.http.client.HttpClient; +import reactor.netty.http.client.HttpClientConfig; import reactor.netty.http.server.HttpServer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.context.environment.EnvironmentChangeEvent; +import org.springframework.cloud.gateway.route.Route; import org.springframework.cloud.gateway.route.RouteLocator; import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; import org.springframework.cloud.gateway.test.BaseWebClientTests; @@ -35,6 +43,8 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.MapPropertySource; import org.springframework.test.util.TestSocketUtils; import org.springframework.test.web.reactive.server.WebTestClient; @@ -48,6 +58,12 @@ class NettyRoutingFilterTests extends BaseWebClientTests { @Autowired private ApplicationContext context; + @Autowired + private NettyRoutingFilter nettyRoutingFilter; + + @Autowired + private RouteLocator routeLocator; + @BeforeAll public static void beforeAll() { port = TestSocketUtils.findAvailableTcpPort(); @@ -88,6 +104,30 @@ void testCaseInsensitiveScheme() { } } + @Test + void testConnectTimeoutConfigurationReloadWorks() { + ConfigurableEnvironment environment = (ConfigurableEnvironment) this.context.getEnvironment(); + HashMap map = new HashMap<>(); + environment.getPropertySources().addFirst(new MapPropertySource("mock", map)); + map.put("spring.cloud.gateway.httpclient.connect-timeout", "1000"); + this.context.publishEvent(new EnvironmentChangeEvent(this.context, + Collections.singleton("spring.cloud.gateway.httpclient.connect-timeout"))); + Route route = routeLocator.getRoutes() + .filter(r -> r.getId().equals("refreshable_configuration_test")).blockLast(); + assertThat(route).isNotNull(); + HttpClient httpClient = nettyRoutingFilter.getHttpClient(route, null); + HttpClientConfig configuration = httpClient.configuration(); + assertThat(configuration.options().get(ChannelOption.CONNECT_TIMEOUT_MILLIS)).isEqualTo(1000); + + map.put("spring.cloud.gateway.httpclient.connect-timeout", "5000"); + this.context.publishEvent(new EnvironmentChangeEvent(this.context, + Collections.singleton("spring.cloud.gateway.httpclient.connect-timeout"))); + httpClient = nettyRoutingFilter.getHttpClient(route, null); + configuration = httpClient.configuration(); + assertThat(configuration.options().get(ChannelOption.CONNECT_TIMEOUT_MILLIS)).isEqualTo(5000); + environment.getPropertySources().remove("mock"); + } + @SpringBootConfiguration @EnableAutoConfiguration @Import(PermitAllSecurityConfiguration.class) @@ -98,6 +138,7 @@ public RouteLocator routes(RouteLocatorBuilder builder) { return builder.routes() .route(p -> p.path("/mockexample").filters(f -> f.prefixPath("/httpbin")).uri("http://example.com")) .route(p -> p.path("/issue").uri("HTTP://127.0.0.1:" + port)) + .route("refreshable_configuration_test", p -> p.path("/refresh").uri("http://example.com")) .build(); }