From 1233357509a41c71bf1112c2a9f7b8f87cf2b062 Mon Sep 17 00:00:00 2001 From: Abel Salgado Romero Date: Fri, 2 Aug 2024 15:28:34 +0200 Subject: [PATCH 01/90] Remove PRAGMA HTTP header from cache This header is deprecated and is unnecessary when using 'Cache-Control'. Currently SCG always returns 'Cache-Control' already. --- .../factory/cache/ResponseCacheManager.java | 2 + ...emoveHeadersAfterCacheExchangeMutator.java | 51 ++++++++ ...esponseCacheGatewayFilterFactoryTests.java | 19 +++ .../LocalResponseCacheGlobalFilterTests.java | 20 ++++ ...veHeaderAfterCacheExchangeMutatorTest.java | 110 ++++++++++++++++++ 5 files changed, 202 insertions(+) create mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/postprocessor/RemoveHeadersAfterCacheExchangeMutator.java create mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/cache/postprocessor/RemoveHeaderAfterCacheExchangeMutatorTest.java diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/ResponseCacheManager.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/ResponseCacheManager.java index 7bbc4b12..e6f115f1 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/ResponseCacheManager.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/ResponseCacheManager.java @@ -34,6 +34,7 @@ import org.springframework.cloud.gateway.filter.factory.cache.LocalResponseCache import org.springframework.cloud.gateway.filter.factory.cache.LocalResponseCacheProperties.RequestOptions; import org.springframework.cloud.gateway.filter.factory.cache.keygenerator.CacheKeyGenerator; import org.springframework.cloud.gateway.filter.factory.cache.postprocessor.AfterCacheExchangeMutator; +import org.springframework.cloud.gateway.filter.factory.cache.postprocessor.RemoveHeadersAfterCacheExchangeMutator; import org.springframework.cloud.gateway.filter.factory.cache.postprocessor.SetCacheDirectivesByMaxAgeAfterCacheExchangeMutator; import org.springframework.cloud.gateway.filter.factory.cache.postprocessor.SetMaxAgeHeaderAfterCacheExchangeMutator; import org.springframework.cloud.gateway.filter.factory.cache.postprocessor.SetResponseHeadersAfterCacheExchangeMutator; @@ -80,6 +81,7 @@ public class ResponseCacheManager { this.ignoreNoCacheUpdate = isSkipNoCacheUpdateActive(requestOptions); this.afterCacheExchangeMutators = List.of(new SetResponseHeadersAfterCacheExchangeMutator(), new SetStatusCodeAfterCacheExchangeMutator(), + new RemoveHeadersAfterCacheExchangeMutator(HttpHeaders.PRAGMA), new SetMaxAgeHeaderAfterCacheExchangeMutator(configuredTimeToLive, Clock.systemDefaultZone(), ignoreNoCacheUpdate), new SetCacheDirectivesByMaxAgeAfterCacheExchangeMutator()); diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/postprocessor/RemoveHeadersAfterCacheExchangeMutator.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/postprocessor/RemoveHeadersAfterCacheExchangeMutator.java new file mode 100644 index 00000000..13d9e818 --- /dev/null +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/postprocessor/RemoveHeadersAfterCacheExchangeMutator.java @@ -0,0 +1,51 @@ +/* + * Copyright 2013-2020 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.gateway.filter.factory.cache.postprocessor; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.cloud.gateway.filter.factory.cache.CachedResponse; +import org.springframework.web.server.ServerWebExchange; + +/** + * Removes one or more HTTP headers from response. Assumes it is run after + * {@link SetResponseHeadersAfterCacheExchangeMutator}. + * + * @author Abel Salgado Romero + */ +public class RemoveHeadersAfterCacheExchangeMutator implements AfterCacheExchangeMutator { + + private static final Log LOGGER = LogFactory.getLog(RemoveHeadersAfterCacheExchangeMutator.class); + + private final String[] httpHeader; + + public RemoveHeadersAfterCacheExchangeMutator(String... httpHeaders) { + this.httpHeader = httpHeaders; + } + + @Override + public void accept(ServerWebExchange exchange, CachedResponse cachedResponse) { + for (String header : httpHeader) { + var previousValue = exchange.getResponse().getHeaders().remove(header); + if (previousValue != null) { + LOGGER.debug("HTTP Header value found in response, removing HTTP header " + header); + } + } + } + +} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/cache/LocalResponseCacheGatewayFilterFactoryTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/cache/LocalResponseCacheGatewayFilterFactoryTests.java index 1803ae0b..b14be476 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/cache/LocalResponseCacheGatewayFilterFactoryTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/cache/LocalResponseCacheGatewayFilterFactoryTests.java @@ -351,6 +351,25 @@ public class LocalResponseCacheGatewayFilterFactoryTests extends BaseWebClientTe .jsonPath("$.headers." + CUSTOM_HEADER, "2"); } + @Test + void shouldNotReturnPragmaHeaderInNonCachedAndCachedResponses() { + String uri = "/" + UUID.randomUUID() + "/cache/headers"; + + testClient.get() + .uri(uri) + .header("Host", "www.localresponsecache.org") + .exchange() + .expectHeader() + .doesNotExist(HttpHeaders.PRAGMA); + + testClient.get() + .uri(uri) + .header("Host", "www.localresponsecache.org") + .exchange() + .expectHeader() + .doesNotExist(HttpHeaders.PRAGMA); + } + void assertNonVaryHeaderInContent(String uri, String varyHeader, String varyHeaderValue, String nonVaryHeader, String nonVaryHeaderValue, String expectedNonVaryResponse) { testClient.get() diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/cache/LocalResponseCacheGlobalFilterTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/cache/LocalResponseCacheGlobalFilterTests.java index 54ebd154..28d63d0b 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/cache/LocalResponseCacheGlobalFilterTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/cache/LocalResponseCacheGlobalFilterTests.java @@ -30,6 +30,7 @@ import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; import org.springframework.cloud.gateway.test.BaseWebClientTests; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; +import org.springframework.http.HttpHeaders; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.ActiveProfiles; @@ -123,6 +124,25 @@ public class LocalResponseCacheGlobalFilterTests { .isEqualTo("1"); } + @Test + void shouldNotReturnPragmaHeaderInNonCachedAndCachedResponses() { + String uri = "/" + UUID.randomUUID() + "/global-cache/headers"; + + testClient.get() + .uri(uri) + .header("Host", "www.localresponsecache.org") + .exchange() + .expectHeader() + .doesNotExist(HttpHeaders.PRAGMA); + + testClient.get() + .uri(uri) + .header("Host", "www.localresponsecache.org") + .exchange() + .expectHeader() + .doesNotExist(HttpHeaders.PRAGMA); + } + @EnableAutoConfiguration @SpringBootConfiguration @Import(DefaultTestConfig.class) diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/cache/postprocessor/RemoveHeaderAfterCacheExchangeMutatorTest.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/cache/postprocessor/RemoveHeaderAfterCacheExchangeMutatorTest.java new file mode 100644 index 00000000..5217a779 --- /dev/null +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/cache/postprocessor/RemoveHeaderAfterCacheExchangeMutatorTest.java @@ -0,0 +1,110 @@ +/* + * Copyright 2013-2020 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.gateway.filter.factory.cache.postprocessor; + +import java.time.Duration; +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import org.springframework.cloud.gateway.filter.factory.cache.CachedResponse; +import org.springframework.http.CacheControl; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.mock.http.server.reactive.MockServerHttpRequest; +import org.springframework.mock.http.server.reactive.MockServerHttpResponse; +import org.springframework.mock.web.server.MockServerWebExchange; +import org.springframework.web.server.ServerWebExchange; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.http.HttpHeaders.CACHE_CONTROL; +import static org.springframework.http.HttpHeaders.CONTENT_TYPE; +import static org.springframework.http.HttpHeaders.EXPIRES; +import static org.springframework.http.HttpHeaders.PRAGMA; + +/** + * @author Abel Salgado Romero + */ +class RemoveHeaderAfterCacheExchangeMutatorTest { + + private static final String HTTP_HEADER_TO_REMOVE = "X-To-Remove"; + + @Test + void onlyHeaderToRemoveFromResponseIsRemoved() { + final ServerWebExchange inputExchange = setupExchange(Map.of(HTTP_HEADER_TO_REMOVE, "A-Value")); + + final var mutator = new RemoveHeadersAfterCacheExchangeMutator(HTTP_HEADER_TO_REMOVE); + CachedResponse cachedResponse = new CachedResponse.Builder(HttpStatus.OK).build(); + + mutator.accept(inputExchange, cachedResponse); + + assertThat(inputExchange.getResponse().getHeaders()).doesNotContainKey(HTTP_HEADER_TO_REMOVE) + .containsEntry(CACHE_CONTROL, List.of("max-age=60")) + .containsEntry(CONTENT_TYPE, List.of("application/octet-stream")) + .hasSize(2); + } + + @Test + void multipleHeadersToRemoveFromResponseAreRemoved() { + final Map headers = Map.of(HTTP_HEADER_TO_REMOVE, "A-Value", PRAGMA, "void", EXPIRES, "0"); + final ServerWebExchange inputExchange = setupExchange(headers); + + final var mutator = new RemoveHeadersAfterCacheExchangeMutator(HTTP_HEADER_TO_REMOVE, PRAGMA, EXPIRES); + CachedResponse cachedResponse = new CachedResponse.Builder(HttpStatus.OK).build(); + + mutator.accept(inputExchange, cachedResponse); + + assertThat(inputExchange.getResponse().getHeaders()).doesNotContainKey(HTTP_HEADER_TO_REMOVE) + .doesNotContainKey(PRAGMA) + .doesNotContainKey(EXPIRES) + .containsEntry(CACHE_CONTROL, List.of("max-age=60")) + .containsEntry(CONTENT_TYPE, List.of("application/octet-stream")) + .hasSize(2); + } + + @Test + void headersAreNotModifiedIfHeaderToRemoveIsEmpty() { + final ServerWebExchange inputExchange = setupExchange(Map.of()); + + final var mutator = new RemoveHeadersAfterCacheExchangeMutator(HTTP_HEADER_TO_REMOVE); + CachedResponse cachedResponse = new CachedResponse.Builder(HttpStatus.OK).build(); + + mutator.accept(inputExchange, cachedResponse); + + assertThat(inputExchange.getResponse().getHeaders()).containsEntry(CACHE_CONTROL, List.of("max-age=60")) + .containsEntry(CONTENT_TYPE, List.of("application/octet-stream")) + .hasSize(2); + } + + private ServerWebExchange setupExchange(Map headersToAdd) { + HttpHeaders responseHeaders = new HttpHeaders(); + responseHeaders.setCacheControl(CacheControl.maxAge(Duration.ofSeconds(60))); + responseHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM); + headersToAdd.forEach((k, v) -> responseHeaders.set(k, v)); + + MockServerHttpRequest httpRequest = MockServerHttpRequest.get("https://this").build(); + MockServerWebExchange inputExchange = MockServerWebExchange.from(httpRequest); + MockServerHttpResponse httpResponse = inputExchange.getResponse(); + httpResponse.setStatusCode(HttpStatus.OK); + httpResponse.getHeaders().putAll(responseHeaders); + + return inputExchange; + } + +} From f586c961c0128c8dc03cbeb3909d0b8e950d838c Mon Sep 17 00:00:00 2001 From: spencergibb Date: Wed, 4 Sep 2024 16:06:37 -0400 Subject: [PATCH 02/90] Removes commons lang3 --- .../PathRoutePredicatePathContainerAttrBenchMarkTests.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/PathRoutePredicatePathContainerAttrBenchMarkTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/PathRoutePredicatePathContainerAttrBenchMarkTests.java index e7f47220..41055e5c 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/PathRoutePredicatePathContainerAttrBenchMarkTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/PathRoutePredicatePathContainerAttrBenchMarkTests.java @@ -22,7 +22,6 @@ import java.util.List; import java.util.Random; import java.util.function.Predicate; -import org.apache.commons.lang3.RandomStringUtils; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; @@ -47,8 +46,10 @@ public class PathRoutePredicatePathContainerAttrBenchMarkTests { static { predicates = new LinkedList<>(); - PATH_PATTERN_PREFIX = String.format("/%s/%s/", RandomStringUtils.random(20, true, false), - RandomStringUtils.random(10, true, false)); + Random random = new Random(); + String path1 = String.format("%1$" + 20 + "s", random.nextInt()).replace(' ', '0'); + String path2 = String.format("%1$" + 10 + "s", random.nextInt()).replace(' ', '0'); + PATH_PATTERN_PREFIX = String.format("/%s/%s/", path1, path2); for (int i = 0; i < ROUTES_NUM; i++) { PathRoutePredicateFactory.Config config = new PathRoutePredicateFactory.Config() .setPatterns(Collections.singletonList(PATH_PATTERN_PREFIX + i)) From 4676b39c606f24a5e24d4b2fb210ec120bd28ce8 Mon Sep 17 00:00:00 2001 From: Abel Salgado Romero Date: Thu, 5 Sep 2024 10:24:21 +0200 Subject: [PATCH 03/90] Remove EXPIRES HTTP header from cache This header conflicts and can cause confusion when 'Cache-Control + max-age' are set. And currently SCG always sets 'max-age' in 'Cache-Control' via 'SetMaxAgeHeaderAfterCacheExchangeMutator'. Closes #3488 --- .../filter/factory/cache/ResponseCacheManager.java | 2 +- ...LocalResponseCacheGatewayFilterFactoryTests.java | 13 +++++++++++-- .../cache/LocalResponseCacheGlobalFilterTests.java | 13 +++++++++++-- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/ResponseCacheManager.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/ResponseCacheManager.java index e6f115f1..1a5471f5 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/ResponseCacheManager.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/ResponseCacheManager.java @@ -81,7 +81,7 @@ public class ResponseCacheManager { this.ignoreNoCacheUpdate = isSkipNoCacheUpdateActive(requestOptions); this.afterCacheExchangeMutators = List.of(new SetResponseHeadersAfterCacheExchangeMutator(), new SetStatusCodeAfterCacheExchangeMutator(), - new RemoveHeadersAfterCacheExchangeMutator(HttpHeaders.PRAGMA), + new RemoveHeadersAfterCacheExchangeMutator(HttpHeaders.PRAGMA, HttpHeaders.EXPIRES), new SetMaxAgeHeaderAfterCacheExchangeMutator(configuredTimeToLive, Clock.systemDefaultZone(), ignoreNoCacheUpdate), new SetCacheDirectivesByMaxAgeAfterCacheExchangeMutator()); diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/cache/LocalResponseCacheGatewayFilterFactoryTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/cache/LocalResponseCacheGatewayFilterFactoryTests.java index b14be476..1325e75e 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/cache/LocalResponseCacheGatewayFilterFactoryTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/cache/LocalResponseCacheGatewayFilterFactoryTests.java @@ -353,6 +353,15 @@ public class LocalResponseCacheGatewayFilterFactoryTests extends BaseWebClientTe @Test void shouldNotReturnPragmaHeaderInNonCachedAndCachedResponses() { + shouldNotReturnHeader(HttpHeaders.PRAGMA); + } + + @Test + void shouldNotReturnExpiresHeaderInNonCachedAndCachedResponses() { + shouldNotReturnHeader(HttpHeaders.EXPIRES); + } + + private void shouldNotReturnHeader(String header) { String uri = "/" + UUID.randomUUID() + "/cache/headers"; testClient.get() @@ -360,14 +369,14 @@ public class LocalResponseCacheGatewayFilterFactoryTests extends BaseWebClientTe .header("Host", "www.localresponsecache.org") .exchange() .expectHeader() - .doesNotExist(HttpHeaders.PRAGMA); + .doesNotExist(header); testClient.get() .uri(uri) .header("Host", "www.localresponsecache.org") .exchange() .expectHeader() - .doesNotExist(HttpHeaders.PRAGMA); + .doesNotExist(header); } void assertNonVaryHeaderInContent(String uri, String varyHeader, String varyHeaderValue, String nonVaryHeader, diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/cache/LocalResponseCacheGlobalFilterTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/cache/LocalResponseCacheGlobalFilterTests.java index 28d63d0b..f5f4c668 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/cache/LocalResponseCacheGlobalFilterTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/cache/LocalResponseCacheGlobalFilterTests.java @@ -126,6 +126,15 @@ public class LocalResponseCacheGlobalFilterTests { @Test void shouldNotReturnPragmaHeaderInNonCachedAndCachedResponses() { + shouldNotReturnHeader(HttpHeaders.PRAGMA); + } + + @Test + void shouldNotReturnExpiresHeaderInNonCachedAndCachedResponses() { + shouldNotReturnHeader(HttpHeaders.EXPIRES); + } + + private void shouldNotReturnHeader(String header) { String uri = "/" + UUID.randomUUID() + "/global-cache/headers"; testClient.get() @@ -133,14 +142,14 @@ public class LocalResponseCacheGlobalFilterTests { .header("Host", "www.localresponsecache.org") .exchange() .expectHeader() - .doesNotExist(HttpHeaders.PRAGMA); + .doesNotExist(header); testClient.get() .uri(uri) .header("Host", "www.localresponsecache.org") .exchange() .expectHeader() - .doesNotExist(HttpHeaders.PRAGMA); + .doesNotExist(header); } @EnableAutoConfiguration From 4aa7c08867e0d223f429278042d799732345f62f Mon Sep 17 00:00:00 2001 From: tungj Date: Fri, 7 Jun 2024 17:38:16 +0800 Subject: [PATCH 04/90] Fix example code to compile Fixes gh-3427 --- .../writing-custom-predicates-and-filters.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/modules/ROOT/pages/spring-cloud-gateway-server-mvc/writing-custom-predicates-and-filters.adoc b/docs/modules/ROOT/pages/spring-cloud-gateway-server-mvc/writing-custom-predicates-and-filters.adoc index aa12f127..fdabf35d 100644 --- a/docs/modules/ROOT/pages/spring-cloud-gateway-server-mvc/writing-custom-predicates-and-filters.adoc +++ b/docs/modules/ROOT/pages/spring-cloud-gateway-server-mvc/writing-custom-predicates-and-filters.adoc @@ -94,7 +94,7 @@ import org.springframework.web.servlet.function.ServerResponse; class SampleHandlerFilterFunctions { public static HandlerFilterFunction instrument(String requestHeader, String responseHeader) { return (request, next) -> { - ServerRequest modified = ServerRequest.from(request).header(requestHeader, generateId()); + ServerRequest modified = ServerRequest.from(request).header(requestHeader, generateId()).build(); ServerResponse response = next.handle(modified); response.headers().add(responseHeader, generateId()); return response; @@ -147,7 +147,7 @@ import org.springframework.web.servlet.function.ServerRequest; class SampleBeforeFilterFunctions { public static Function instrument(String header) { - return request -> ServerRequest.from(request).header(header, generateId());; + return request -> ServerRequest.from(request).header(header, generateId()).build(); } } ---- From 3bb69ec39ca8493c5856897551ea19e035f2f27d Mon Sep 17 00:00:00 2001 From: qnnn <65326092+qnnn@users.noreply.github.com> Date: Sat, 20 Jul 2024 02:02:16 +0800 Subject: [PATCH 05/90] Fix potential memory leak when using ReadBodyRoutePredicateFactory Fixes gh-3465 --- .../filter/RemoveCachedBodyFilter.java | 24 ++----------------- .../handler/RoutePredicateHandlerMapping.java | 2 ++ .../support/ServerWebExchangeUtils.java | 22 +++++++++++++++++ 3 files changed, 26 insertions(+), 22 deletions(-) diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/RemoveCachedBodyFilter.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/RemoveCachedBodyFilter.java index 5307cb98..e3b15fe8 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/RemoveCachedBodyFilter.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/RemoveCachedBodyFilter.java @@ -16,37 +16,17 @@ package org.springframework.cloud.gateway.filter; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import reactor.core.publisher.Mono; +import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; import org.springframework.core.Ordered; -import org.springframework.core.io.buffer.PooledDataBuffer; import org.springframework.web.server.ServerWebExchange; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.CACHED_REQUEST_BODY_ATTR; - public class RemoveCachedBodyFilter implements GlobalFilter, Ordered { - private static final Log log = LogFactory.getLog(RemoveCachedBodyFilter.class); - @Override public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - return chain.filter(exchange).doFinally(s -> { - Object attribute = exchange.getAttributes().remove(CACHED_REQUEST_BODY_ATTR); - if (attribute != null && attribute instanceof PooledDataBuffer) { - PooledDataBuffer dataBuffer = (PooledDataBuffer) attribute; - if (dataBuffer.isAllocated()) { - if (log.isTraceEnabled()) { - log.trace("releasing cached body in exchange attribute"); - } - // ensure proper release - while (!dataBuffer.release()) { - // release() counts down until zero, will never be infinite loop - } - } - } - }); + return chain.filter(exchange).doFinally(s -> ServerWebExchangeUtils.clearCachedRequestBody(exchange)); } @Override diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/RoutePredicateHandlerMapping.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/RoutePredicateHandlerMapping.java index fb12f36b..eb0006a8 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/RoutePredicateHandlerMapping.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/RoutePredicateHandlerMapping.java @@ -24,6 +24,7 @@ import org.springframework.cloud.gateway.config.GatewayProperties; import org.springframework.cloud.gateway.config.GlobalCorsProperties; import org.springframework.cloud.gateway.route.Route; import org.springframework.cloud.gateway.route.RouteLocator; +import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; import org.springframework.core.env.Environment; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.reactive.handler.AbstractHandlerMapping; @@ -99,6 +100,7 @@ public class RoutePredicateHandlerMapping extends AbstractHandlerMapping { }) .switchIfEmpty(Mono.empty().then(Mono.fromRunnable(() -> { exchange.getAttributes().remove(GATEWAY_PREDICATE_ROUTE_ATTR); + ServerWebExchangeUtils.clearCachedRequestBody(exchange); if (logger.isTraceEnabled()) { logger.trace("No RouteDefinition found for [" + getExchangeDesc(exchange) + "]"); } diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ServerWebExchangeUtils.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ServerWebExchangeUtils.java index 0ce96e64..36ee17d9 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ServerWebExchangeUtils.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ServerWebExchangeUtils.java @@ -40,6 +40,7 @@ import org.springframework.core.io.buffer.DataBufferFactory; import org.springframework.core.io.buffer.DataBufferUtils; import org.springframework.core.io.buffer.DefaultDataBuffer; import org.springframework.core.io.buffer.NettyDataBuffer; +import org.springframework.core.io.buffer.PooledDataBuffer; import org.springframework.http.HttpStatus; import org.springframework.http.server.reactive.AbstractServerHttpResponse; import org.springframework.http.server.reactive.ServerHttpRequest; @@ -379,6 +380,27 @@ public final class ServerWebExchangeUtils { .flatMap(function); } + /** + * clear the request body in a ServerWebExchange attribute. The attribute is + * {@link #CACHED_REQUEST_BODY_ATTR}. + * @param exchange the available ServerWebExchange. + */ + public static void clearCachedRequestBody(ServerWebExchange exchange) { + Object attribute = exchange.getAttributes().remove(CACHED_REQUEST_BODY_ATTR); + if (attribute != null && attribute instanceof PooledDataBuffer) { + PooledDataBuffer dataBuffer = (PooledDataBuffer) attribute; + if (dataBuffer.isAllocated()) { + if (log.isTraceEnabled()) { + log.trace("releasing cached body in exchange attribute"); + } + // ensure proper release + while (!dataBuffer.release()) { + // release() counts down until zero, will never be infinite loop + } + } + } + } + private static ServerHttpRequest decorate(ServerWebExchange exchange, DataBuffer dataBuffer, boolean cacheDecoratedRequest) { if (dataBuffer.readableByteCount() > 0) { From da77ec780af4576a6a4e823c4c4621d909a749f4 Mon Sep 17 00:00:00 2001 From: ohprettyhak Date: Mon, 1 Jul 2024 23:04:58 +0900 Subject: [PATCH 06/90] Updates HeaderRoutePredicateFactory to use getValuesAsList Fixes gh-3447 --- .../predicate/HeaderRoutePredicateFactory.java | 5 +---- .../HeaderRoutePredicateFactoryTests.java | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/HeaderRoutePredicateFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/HeaderRoutePredicateFactory.java index e96e9bc6..97d8c43d 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/HeaderRoutePredicateFactory.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/HeaderRoutePredicateFactory.java @@ -17,7 +17,6 @@ package org.springframework.cloud.gateway.handler.predicate; import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.function.Predicate; import java.util.regex.Pattern; @@ -59,9 +58,7 @@ public class HeaderRoutePredicateFactory extends AbstractRoutePredicateFactory values = exchange.getRequest() - .getHeaders() - .getOrDefault(config.header, Collections.emptyList()); + List values = exchange.getRequest().getHeaders().getValuesAsList(config.header); if (values.isEmpty()) { return false; } diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/HeaderRoutePredicateFactoryTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/HeaderRoutePredicateFactoryTests.java index 92be2a3a..e4a51451 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/HeaderRoutePredicateFactoryTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/HeaderRoutePredicateFactoryTests.java @@ -92,6 +92,14 @@ public class HeaderRoutePredicateFactoryTests extends BaseWebClientTests { assertThat(predicate.toString()).contains("Header: myheader regexp=myregexp"); } + @Test + public void headerRouteHandlesCommaSeparatedValues() { + testClient.get().uri("/get").header("X-Example-Header", "value1, value2 ,exact_match,value3").exchange() + .expectStatus().isOk().expectHeader() + .valueEquals(HANDLER_MAPPER_HEADER, RoutePredicateHandlerMapping.class.getSimpleName()).expectHeader() + .valueEquals(ROUTE_ID_HEADER, "header_test_comma_separated"); + } + @EnableAutoConfiguration @SpringBootConfiguration @Import(DefaultTestConfig.class) @@ -103,8 +111,10 @@ public class HeaderRoutePredicateFactoryTests extends BaseWebClientTests { @Bean RouteLocator queryRouteLocator(RouteLocatorBuilder builder) { return builder.routes() - .route("header_exists_dsl", r -> r.header("X-Foo").filters(f -> f.prefixPath("/httpbin")).uri(uri)) - .build(); + .route("header_exists_dsl", r -> r.header("X-Foo").filters(f -> f.prefixPath("/httpbin")).uri(uri)) + .route("header_test_comma_separated", r -> r.header("X-Example-Header", "exact_match") + .filters(f -> f.prefixPath("/httpbin")).uri(uri)) + .build(); } } From f444ad5cf226c2663564daa1b5ac4225dec7f4c4 Mon Sep 17 00:00:00 2001 From: spencergibb Date: Thu, 26 Sep 2024 16:24:35 -0400 Subject: [PATCH 07/90] formatting --- .../HeaderRoutePredicateFactoryTests.java | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/HeaderRoutePredicateFactoryTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/HeaderRoutePredicateFactoryTests.java index e4a51451..6792ed97 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/HeaderRoutePredicateFactoryTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/HeaderRoutePredicateFactoryTests.java @@ -94,10 +94,16 @@ public class HeaderRoutePredicateFactoryTests extends BaseWebClientTests { @Test public void headerRouteHandlesCommaSeparatedValues() { - testClient.get().uri("/get").header("X-Example-Header", "value1, value2 ,exact_match,value3").exchange() - .expectStatus().isOk().expectHeader() - .valueEquals(HANDLER_MAPPER_HEADER, RoutePredicateHandlerMapping.class.getSimpleName()).expectHeader() - .valueEquals(ROUTE_ID_HEADER, "header_test_comma_separated"); + testClient.get() + .uri("/get") + .header("X-Example-Header", "value1, value2 ,exact_match,value3") + .exchange() + .expectStatus() + .isOk() + .expectHeader() + .valueEquals(HANDLER_MAPPER_HEADER, RoutePredicateHandlerMapping.class.getSimpleName()) + .expectHeader() + .valueEquals(ROUTE_ID_HEADER, "header_test_comma_separated"); } @EnableAutoConfiguration @@ -111,10 +117,12 @@ public class HeaderRoutePredicateFactoryTests extends BaseWebClientTests { @Bean RouteLocator queryRouteLocator(RouteLocatorBuilder builder) { return builder.routes() - .route("header_exists_dsl", r -> r.header("X-Foo").filters(f -> f.prefixPath("/httpbin")).uri(uri)) - .route("header_test_comma_separated", r -> r.header("X-Example-Header", "exact_match") - .filters(f -> f.prefixPath("/httpbin")).uri(uri)) - .build(); + .route("header_exists_dsl", r -> r.header("X-Foo").filters(f -> f.prefixPath("/httpbin")).uri(uri)) + .route("header_test_comma_separated", + r -> r.header("X-Example-Header", "exact_match") + .filters(f -> f.prefixPath("/httpbin")) + .uri(uri)) + .build(); } } From eb1de2a37ca8eb540858c21475c746e313691e3d Mon Sep 17 00:00:00 2001 From: John Gray Date: Tue, 9 Apr 2024 11:44:26 -0400 Subject: [PATCH 08/90] Defer instantiation of TimeoutExcpetions. By deferring instantiation of TimeoutExcpetions on each request, this prevents an agent, like Dynatrace, from thinking all requests are throwing TimeoutExceptions. Fixes gh-2600 Fixes gh-3344 --- .../cloud/gateway/filter/NettyRoutingFilter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 4d342892..d09931ee 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 @@ -191,7 +191,7 @@ public class NettyRoutingFilter implements GlobalFilter, Ordered { if (responseTimeout != null) { responseFlux = responseFlux .timeout(responseTimeout, - Mono.error(new TimeoutException("Response took longer than timeout: " + responseTimeout))) + Mono.defer(() -> Mono.error(new TimeoutException("Response took longer than timeout: " + responseTimeout)))) .onErrorMap(TimeoutException.class, th -> new ResponseStatusException(HttpStatus.GATEWAY_TIMEOUT, th.getMessage(), th)); } From 726b9efd9b3de383e97bb8cf640d58dc0e6c6618 Mon Sep 17 00:00:00 2001 From: spencergibb Date: Thu, 26 Sep 2024 16:38:46 -0400 Subject: [PATCH 09/90] formatting --- .../cloud/gateway/filter/NettyRoutingFilter.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 d09931ee..bf4f6ad7 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 @@ -191,7 +191,8 @@ public class NettyRoutingFilter implements GlobalFilter, Ordered { if (responseTimeout != null) { responseFlux = responseFlux .timeout(responseTimeout, - Mono.defer(() -> Mono.error(new TimeoutException("Response took longer than timeout: " + responseTimeout)))) + Mono.defer(() -> Mono + .error(new TimeoutException("Response took longer than timeout: " + responseTimeout)))) .onErrorMap(TimeoutException.class, th -> new ResponseStatusException(HttpStatus.GATEWAY_TIMEOUT, th.getMessage(), th)); } From f2aabac5d062ec44b33ac925265626c08d2837a8 Mon Sep 17 00:00:00 2001 From: Jens Mallien Date: Tue, 24 Sep 2024 14:26:18 +0200 Subject: [PATCH 10/90] Adds a missing Shortcut annotation to dedupeResponseHeader Fixes gh-3536 --- .../cloud/gateway/server/mvc/filter/FilterFunctions.java | 1 + 1 file changed, 1 insertion(+) diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/FilterFunctions.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/FilterFunctions.java index d21c08f5..c7cb910f 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/FilterFunctions.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/FilterFunctions.java @@ -66,6 +66,7 @@ public interface FilterFunctions { return ofResponseProcessor(AfterFilterFunctions.addResponseHeader(name, values)); } + @Shortcut static HandlerFilterFunction dedupeResponseHeader(String name) { return ofResponseProcessor(AfterFilterFunctions.dedupeResponseHeader(name)); } From c4b8f71330e30e26590d2dca1a87e107eb035650 Mon Sep 17 00:00:00 2001 From: Benjamin Bargeton Date: Mon, 22 Apr 2024 15:12:17 +0200 Subject: [PATCH 11/90] Only copy body request content if body is present This avoids to have a "Transfer-Encoding: chunked" with an empty body if downstream exchanges are negotiated in HTTP 1.1 Fixes gh-3325 Fixes gh-3368 --- .../mvc/handler/RestClientProxyExchange.java | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/RestClientProxyExchange.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/RestClientProxyExchange.java index 37493b6b..006905b9 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/RestClientProxyExchange.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/RestClientProxyExchange.java @@ -19,6 +19,7 @@ package org.springframework.cloud.gateway.server.mvc.handler; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.io.UncheckedIOException; import org.springframework.cloud.gateway.server.mvc.common.MvcUtils; import org.springframework.http.client.ClientHttpResponse; @@ -36,11 +37,22 @@ public class RestClientProxyExchange implements ProxyExchange { @Override public ServerResponse exchange(Request request) { - return restClient.method(request.getMethod()) - .uri(request.getUri()) - .headers(httpHeaders -> httpHeaders.putAll(request.getHeaders())) - .body(outputStream -> copyBody(request, outputStream)) - .exchange((clientRequest, clientResponse) -> doExchange(request, clientResponse), false); + var requestSpec = restClient.method(request.getMethod()) + .uri(request.getUri()) + .headers(httpHeaders -> httpHeaders.putAll(request.getHeaders())); + if (isBodyPresent(request)) { + requestSpec.body(outputStream -> copyBody(request, outputStream)); + } + return requestSpec.exchange((clientRequest, clientResponse) -> doExchange(request, clientResponse), false); + } + + private static boolean isBodyPresent(Request request) { + try { + return !request.getServerRequest().servletRequest().getInputStream().isFinished(); + } + catch (IOException e) { + throw new UncheckedIOException(e); + } } private static int copyBody(Request request, OutputStream outputStream) throws IOException { From 915eb7ab3b0049bb7814386e67b183fe92b973e6 Mon Sep 17 00:00:00 2001 From: spencergibb Date: Thu, 26 Sep 2024 16:52:18 -0400 Subject: [PATCH 12/90] polishing --- .../gateway/server/mvc/handler/RestClientProxyExchange.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/RestClientProxyExchange.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/RestClientProxyExchange.java index 006905b9..27a02569 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/RestClientProxyExchange.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/RestClientProxyExchange.java @@ -37,9 +37,9 @@ public class RestClientProxyExchange implements ProxyExchange { @Override public ServerResponse exchange(Request request) { - var requestSpec = restClient.method(request.getMethod()) - .uri(request.getUri()) - .headers(httpHeaders -> httpHeaders.putAll(request.getHeaders())); + RestClient.RequestBodySpec requestSpec = restClient.method(request.getMethod()) + .uri(request.getUri()) + .headers(httpHeaders -> httpHeaders.putAll(request.getHeaders())); if (isBodyPresent(request)) { requestSpec.body(outputStream -> copyBody(request, outputStream)); } From 66a3e410725d954f208c83f57a05b79f1ed9fe60 Mon Sep 17 00:00:00 2001 From: Benjamin Bargeton Date: Wed, 17 Apr 2024 21:36:59 +0200 Subject: [PATCH 13/90] Remove HTTP/2 :status pseudo header if added by downstream HTTP client Fixes gh-3326 Fixes gh-3369 --- .../GatewayServerMvcAutoConfiguration.java | 9 ++++ ...emoveHttp2StatusResponseHeadersFilter.java | 44 +++++++++++++++++++ ...atewayServerMvcAutoConfigurationTests.java | 3 ++ 3 files changed, 56 insertions(+) create mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/RemoveHttp2StatusResponseHeadersFilter.java diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/GatewayServerMvcAutoConfiguration.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/GatewayServerMvcAutoConfiguration.java index a3e94fac..20e5ff68 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/GatewayServerMvcAutoConfiguration.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/GatewayServerMvcAutoConfiguration.java @@ -39,6 +39,7 @@ import org.springframework.cloud.gateway.server.mvc.filter.HttpHeadersFilter.Res import org.springframework.cloud.gateway.server.mvc.filter.RemoveContentLengthRequestHeadersFilter; import org.springframework.cloud.gateway.server.mvc.filter.RemoveHopByHopRequestHeadersFilter; import org.springframework.cloud.gateway.server.mvc.filter.RemoveHopByHopResponseHeadersFilter; +import org.springframework.cloud.gateway.server.mvc.filter.RemoveHttp2StatusResponseHeadersFilter; import org.springframework.cloud.gateway.server.mvc.filter.TransferEncodingNormalizationRequestHeadersFilter; import org.springframework.cloud.gateway.server.mvc.filter.WeightCalculatorFilter; import org.springframework.cloud.gateway.server.mvc.filter.XForwardedRequestHeadersFilter; @@ -163,6 +164,14 @@ public class GatewayServerMvcAutoConfiguration { return new RemoveContentLengthRequestHeadersFilter(); } + @Bean + @ConditionalOnMissingBean + @ConditionalOnProperty(prefix = GatewayMvcProperties.PREFIX, + name = "remove-http2-status-response-headers-filter.enabled", matchIfMissing = true) + public RemoveHttp2StatusResponseHeadersFilter removeHttp2StatusResponseHeadersFilter() { + return new RemoveHttp2StatusResponseHeadersFilter(); + } + @Bean @ConditionalOnMissingBean @ConditionalOnProperty(prefix = GatewayMvcProperties.PREFIX, diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/RemoveHttp2StatusResponseHeadersFilter.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/RemoveHttp2StatusResponseHeadersFilter.java new file mode 100644 index 00000000..36551378 --- /dev/null +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/RemoveHttp2StatusResponseHeadersFilter.java @@ -0,0 +1,44 @@ +/* + * Copyright 2013-2023 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.gateway.server.mvc.filter; + +import org.springframework.core.Ordered; +import org.springframework.http.HttpHeaders; +import org.springframework.web.servlet.function.ServerResponse; + +// jdk http client add the :status pseudo-header to the response. +// Copying this header to an upstream HTTP/2 connection will cause a protocol error. +public class RemoveHttp2StatusResponseHeadersFilter implements HttpHeadersFilter.ResponseHttpHeadersFilter, Ordered { + + @Override + public int getOrder() { + return 1000; + } + + @Override + public HttpHeaders apply(HttpHeaders input, ServerResponse serverResponse) { + if (input.containsKey(":status")) { + + HttpHeaders filtered = new HttpHeaders(); + filtered.addAll(input); + filtered.remove(":status"); + return filtered; + } + return input; + } + +} diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/GatewayServerMvcAutoConfigurationTests.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/GatewayServerMvcAutoConfigurationTests.java index e8c139e7..0cdce409 100644 --- a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/GatewayServerMvcAutoConfigurationTests.java +++ b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/GatewayServerMvcAutoConfigurationTests.java @@ -28,6 +28,7 @@ import org.springframework.cloud.gateway.server.mvc.filter.ForwardedRequestHeade import org.springframework.cloud.gateway.server.mvc.filter.RemoveContentLengthRequestHeadersFilter; import org.springframework.cloud.gateway.server.mvc.filter.RemoveHopByHopRequestHeadersFilter; import org.springframework.cloud.gateway.server.mvc.filter.RemoveHopByHopResponseHeadersFilter; +import org.springframework.cloud.gateway.server.mvc.filter.RemoveHttp2StatusResponseHeadersFilter; import org.springframework.cloud.gateway.server.mvc.filter.TransferEncodingNormalizationRequestHeadersFilter; import org.springframework.cloud.gateway.server.mvc.filter.WeightCalculatorFilter; import org.springframework.cloud.gateway.server.mvc.filter.XForwardedRequestHeadersFilter; @@ -46,6 +47,7 @@ public class GatewayServerMvcAutoConfigurationTests { "spring.cloud.gateway.mvc.remove-content-length-request-headers-filter.enabled=false", "spring.cloud.gateway.mvc.remove-hop-by-hop-request-headers-filter.enabled=false", "spring.cloud.gateway.mvc.remove-hop-by-hop-response-headers-filter.enabled=false", + "spring.cloud.gateway.mvc.remove-http2-status-response-headers-filter.enabled=false", "spring.cloud.gateway.mvc.transfer-encoding-normalization-request-headers-filter.enabled=false", "spring.cloud.gateway.mvc.weight-calculator-filter.enabled=false", "spring.cloud.gateway.mvc.x-forwarded-request-headers-filter.enabled=false") @@ -55,6 +57,7 @@ public class GatewayServerMvcAutoConfigurationTests { assertThat(context).doesNotHaveBean(RemoveContentLengthRequestHeadersFilter.class); assertThat(context).doesNotHaveBean(RemoveHopByHopRequestHeadersFilter.class); assertThat(context).doesNotHaveBean(RemoveHopByHopResponseHeadersFilter.class); + assertThat(context).doesNotHaveBean(RemoveHttp2StatusResponseHeadersFilter.class); assertThat(context).doesNotHaveBean(TransferEncodingNormalizationRequestHeadersFilter.class); assertThat(context).doesNotHaveBean(WeightCalculatorFilter.class); assertThat(context).doesNotHaveBean(XForwardedRequestHeadersFilter.class); From 22c9f2059b795f73387c8ce8f8f0f1ce001f7e08 Mon Sep 17 00:00:00 2001 From: Thomas Carlier Date: Thu, 26 Sep 2024 22:49:36 +1000 Subject: [PATCH 14/90] Update README.adoc --- docs/src/main/asciidoc/README.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/README.adoc b/docs/src/main/asciidoc/README.adoc index d5ff430d..934e4b48 100644 --- a/docs/src/main/asciidoc/README.adoc +++ b/docs/src/main/asciidoc/README.adoc @@ -20,7 +20,7 @@ image::https://codecov.io/gh/spring-cloud/spring-cloud-gateway/branch/main/graph [[building]] = Building -include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/main/docs/modules/ROOT/partials/contributing.adoc[] +include::https://github.com/spring-cloud/spring-cloud-build/blob/main/docs/modules/ROOT/partials/building.adoc[] [[contributing]] = Contributing From f253bf7d34662ca4459e68914104d22a8c3bb969 Mon Sep 17 00:00:00 2001 From: spencergibb Date: Fri, 27 Sep 2024 11:59:00 -0400 Subject: [PATCH 15/90] polishing --- docs/src/main/asciidoc/README.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/README.adoc b/docs/src/main/asciidoc/README.adoc index 934e4b48..c3ef9823 100644 --- a/docs/src/main/asciidoc/README.adoc +++ b/docs/src/main/asciidoc/README.adoc @@ -20,7 +20,7 @@ image::https://codecov.io/gh/spring-cloud/spring-cloud-gateway/branch/main/graph [[building]] = Building -include::https://github.com/spring-cloud/spring-cloud-build/blob/main/docs/modules/ROOT/partials/building.adoc[] +include::https:///raw.githubusercontent.com/spring-cloud/spring-cloud-build/main/docs/modules/ROOT/partials/building.adoc[] [[contributing]] = Contributing From 0ce24854718e2574bab61e7925eec5626d395bb8 Mon Sep 17 00:00:00 2001 From: Abimael Sergio Date: Wed, 1 May 2024 11:36:42 -0300 Subject: [PATCH 16/90] Improve documentation, add reference of HttpClientCustomizer. Fixes gh-2623 Fixes gh-3381 --- docs/modules/ROOT/nav.adoc | 1 + .../spring-cloud-gateway/http-client.adoc | 49 +++++++++++++++++++ .../spring-cloud-gateway/tls-and-ssl.adoc | 1 - 3 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 docs/modules/ROOT/pages/spring-cloud-gateway/http-client.adoc diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc index d1e78782..edcb0484 100644 --- a/docs/modules/ROOT/nav.adoc +++ b/docs/modules/ROOT/nav.adoc @@ -50,6 +50,7 @@ ** xref:spring-cloud-gateway/global-filters.adoc[] ** xref:spring-cloud-gateway/httpheadersfilters.adoc[] ** xref:spring-cloud-gateway/tls-and-ssl.adoc[] +** xref:spring-cloud-gateway/http-client.adoc[] ** xref:spring-cloud-gateway/configuration.adoc[] ** xref:spring-cloud-gateway/route-metadata-configuration.adoc[] ** xref:spring-cloud-gateway/http-timeouts-configuration.adoc[] diff --git a/docs/modules/ROOT/pages/spring-cloud-gateway/http-client.adoc b/docs/modules/ROOT/pages/spring-cloud-gateway/http-client.adoc new file mode 100644 index 00000000..0f972f90 --- /dev/null +++ b/docs/modules/ROOT/pages/spring-cloud-gateway/http-client.adoc @@ -0,0 +1,49 @@ +[[http-client]] += The HttpClientCustomizer + +The HttpClientCustomizer interface in spring-cloud-gateway allows for the customization of the HTTP client used by the gateway. It provides a single method, customize, which takes an HttpClient as an argument and returns a customized version of it. + +This interface is useful for scenarios where you need to configure specific settings or behaviors for the HTTP client, such as setting timeouts, adding custom headers, or enabling specific features. By implementing this interface, you can provide a custom implementation that meets your specific requirements. + +Here's an example of how you might use the HttpClientCustomizer interface: + +.MyHttpClientCustomizer.java +[source,java] +---- +import org.springframework.cloud.gateway.config.HttpClientCustomizer; +import reactor.netty.http.client.HttpClient; + +public class MyHttpClientCustomizer implements HttpClientCustomizer { + + @Override + public HttpClient customize(HttpClient httpClient) { + // Customize the HTTP client here + return httpClient.tcpConfiguration(tcpClient -> { + // Set the connect timeout to 5 seconds + tcpClient.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000); + // Set the read timeout to 10 seconds + tcpClient.option(ChannelOption.SO_TIMEOUT, 10000); + return tcpClient; + }); + } +} +---- + +In this example, the MyHttpClientCustomizer class implements the HttpClientCustomizer interface and overrides the customize method. Within this method, the HTTP client is customized by setting the connect timeout to 5 seconds and the read timeout to 10 seconds. + +To use this customizer, you would need to register it with the Spring Cloud Gateway configuration: + +.GatewayConfiguration.java +[source,java] +---- +@Configuration +public class GatewayConfiguration { + + @Bean + public HttpClientCustomizer myHttpClientCustomizer() { + return new MyHttpClientCustomizer(); + } +} +---- + +By registering the customizer as a bean, it will be automatically applied to the HTTP client used by the gateway. \ No newline at end of file diff --git a/docs/modules/ROOT/pages/spring-cloud-gateway/tls-and-ssl.adoc b/docs/modules/ROOT/pages/spring-cloud-gateway/tls-and-ssl.adoc index 012f1cf3..ab1f0e68 100644 --- a/docs/modules/ROOT/pages/spring-cloud-gateway/tls-and-ssl.adoc +++ b/docs/modules/ROOT/pages/spring-cloud-gateway/tls-and-ssl.adoc @@ -68,4 +68,3 @@ spring: close-notify-flush-timeout-millis: 3000 close-notify-read-timeout-millis: 0 ---- - From 92159e92131d6fc36b28f2b50f65af4109463e33 Mon Sep 17 00:00:00 2001 From: buildmaster Date: Sun, 29 Sep 2024 13:37:46 +0000 Subject: [PATCH 17/90] Bumping versions --- README.adoc | 241 +--------------------------------------------------- 1 file changed, 1 insertion(+), 240 deletions(-) diff --git a/README.adoc b/README.adoc index 9a06cafb..32d1ae73 100644 --- a/README.adoc +++ b/README.adoc @@ -27,246 +27,7 @@ image::https://codecov.io/gh/spring-cloud/spring-cloud-gateway/branch/main/graph [[building]] = Building -:spring-cloud-build-branch: main - -Spring Cloud is released under the non-restrictive Apache 2.0 license, -and follows a very standard Github development process, using Github -tracker for issues and merging pull requests into main. If you want -to contribute even something trivial please do not hesitate, but -follow the guidelines below. - -[[sign-the-contributor-license-agreement]] -== Sign the Contributor License Agreement - -Before we accept a non-trivial patch or pull request we will need you to sign the -https://cla.pivotal.io/sign/spring[Contributor License Agreement]. -Signing the contributor's agreement does not grant anyone commit rights to the main -repository, but it does mean that we can accept your contributions, and you will get an -author credit if we do. Active contributors might be asked to join the core team, and -given the ability to merge pull requests. - -[[code-of-conduct]] -== Code of Conduct -This project adheres to the Contributor Covenant https://github.com/spring-cloud/spring-cloud-build/blob/main/docs/src/main/asciidoc/code-of-conduct.adoc[code of -conduct]. By participating, you are expected to uphold this code. Please report -unacceptable behavior to spring-code-of-conduct@pivotal.io. - -[[code-conventions-and-housekeeping]] -== Code Conventions and Housekeeping -None of these is essential for a pull request, but they will all help. They can also be -added after the original pull request but before a merge. - -* Use the Spring Framework code format conventions. If you use Eclipse - you can import formatter settings using the - `eclipse-code-formatter.xml` file from the - https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/main/spring-cloud-dependencies-parent/eclipse-code-formatter.xml[Spring - Cloud Build] project. If using IntelliJ, you can use the - https://plugins.jetbrains.com/plugin/6546[Eclipse Code Formatter - Plugin] to import the same file. -* Make sure all new `.java` files to have a simple Javadoc class comment with at least an - `@author` tag identifying you, and preferably at least a paragraph on what the class is - for. -* Add the ASF license header comment to all new `.java` files (copy from existing files - in the project) -* Add yourself as an `@author` to the .java files that you modify substantially (more - than cosmetic changes). -* Add some Javadocs and, if you change the namespace, some XSD doc elements. -* A few unit tests would help a lot as well -- someone has to do it. -* If no-one else is using your branch, please rebase it against the current main (or - other target branch in the main project). -* When writing a commit message please follow https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html[these conventions], - if you are fixing an existing issue please add `Fixes gh-XXXX` at the end of the commit - message (where XXXX is the issue number). - -[[checkstyle]] -== Checkstyle - -Spring Cloud Build comes with a set of checkstyle rules. You can find them in the `spring-cloud-build-tools` module. The most notable files under the module are: - -.spring-cloud-build-tools/ ----- -└── src -    ├── checkstyle -    │   └── checkstyle-suppressions.xml <3> -    └── main -    └── resources -    ├── checkstyle-header.txt <2> -    └── checkstyle.xml <1> ----- -<1> Default Checkstyle rules -<2> File header setup -<3> Default suppression rules - -[[checkstyle-configuration]] -=== Checkstyle configuration - -Checkstyle rules are *disabled by default*. To add checkstyle to your project just define the following properties and plugins. - -.pom.xml ----- - -true <1> - true - <2> - true - <3> - - - - - <4> - io.spring.javaformat - spring-javaformat-maven-plugin - - <5> - org.apache.maven.plugins - maven-checkstyle-plugin - - - - - - <5> - org.apache.maven.plugins - maven-checkstyle-plugin - - - - ----- -<1> Fails the build upon Checkstyle errors -<2> Fails the build upon Checkstyle violations -<3> Checkstyle analyzes also the test sources -<4> Add the Spring Java Format plugin that will reformat your code to pass most of the Checkstyle formatting rules -<5> Add checkstyle plugin to your build and reporting phases - -If you need to suppress some rules (e.g. line length needs to be longer), then it's enough for you to define a file under `${project.root}/src/checkstyle/checkstyle-suppressions.xml` with your suppressions. Example: - -.projectRoot/src/checkstyle/checkstyle-suppresions.xml ----- - - - - - - ----- - -It's advisable to copy the `${spring-cloud-build.rootFolder}/.editorconfig` and `${spring-cloud-build.rootFolder}/.springformat` to your project. That way, some default formatting rules will be applied. You can do so by running this script: - -```bash -$ curl https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/main/.editorconfig -o .editorconfig -$ touch .springformat -``` - -[[ide-setup]] -== IDE setup - -[[intellij-idea]] -=== Intellij IDEA - -In order to setup Intellij you should import our coding conventions, inspection profiles and set up the checkstyle plugin. -The following files can be found in the https://github.com/spring-cloud/spring-cloud-build/tree/main/spring-cloud-build-tools[Spring Cloud Build] project. - -.spring-cloud-build-tools/ ----- -└── src -    ├── checkstyle -    │   └── checkstyle-suppressions.xml <3> -    └── main -    └── resources -    ├── checkstyle-header.txt <2> -    ├── checkstyle.xml <1> -    └── intellij -       ├── Intellij_Project_Defaults.xml <4> -       └── Intellij_Spring_Boot_Java_Conventions.xml <5> ----- -<1> Default Checkstyle rules -<2> File header setup -<3> Default suppression rules -<4> Project defaults for Intellij that apply most of Checkstyle rules -<5> Project style conventions for Intellij that apply most of Checkstyle rules - -.Code style - -image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/main/docs/modules/ROOT/assets/images/intellij-code-style.png[Code style] - -Go to `File` -> `Settings` -> `Editor` -> `Code style`. There click on the icon next to the `Scheme` section. There, click on the `Import Scheme` value and pick the `Intellij IDEA code style XML` option. Import the `spring-cloud-build-tools/src/main/resources/intellij/Intellij_Spring_Boot_Java_Conventions.xml` file. - -.Inspection profiles - -image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/main/docs/modules/ROOT/assets/images/intellij-inspections.png[Code style] - -Go to `File` -> `Settings` -> `Editor` -> `Inspections`. There click on the icon next to the `Profile` section. There, click on the `Import Profile` and import the `spring-cloud-build-tools/src/main/resources/intellij/Intellij_Project_Defaults.xml` file. - -.Checkstyle - -To have Intellij work with Checkstyle, you have to install the `Checkstyle` plugin. It's advisable to also install the `Assertions2Assertj` to automatically convert the JUnit assertions - -image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/main/docs/modules/ROOT/assets/images/intellij-checkstyle.png[Checkstyle] - -Go to `File` -> `Settings` -> `Other settings` -> `Checkstyle`. There click on the `+` icon in the `Configuration file` section. There, you'll have to define where the checkstyle rules should be picked from. In the image above, we've picked the rules from the cloned Spring Cloud Build repository. However, you can point to the Spring Cloud Build's GitHub repository (e.g. for the `checkstyle.xml` : `https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/main/spring-cloud-build-tools/src/main/resources/checkstyle.xml`). We need to provide the following variables: - -- `checkstyle.header.file` - please point it to the Spring Cloud Build's, `spring-cloud-build-tools/src/main/resources/checkstyle-header.txt` file either in your cloned repo or via the `https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/main/spring-cloud-build-tools/src/main/resources/checkstyle-header.txt` URL. -- `checkstyle.suppressions.file` - default suppressions. Please point it to the Spring Cloud Build's, `spring-cloud-build-tools/src/checkstyle/checkstyle-suppressions.xml` file either in your cloned repo or via the `https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/main/spring-cloud-build-tools/src/checkstyle/checkstyle-suppressions.xml` URL. -- `checkstyle.additional.suppressions.file` - this variable corresponds to suppressions in your local project. E.g. you're working on `spring-cloud-contract`. Then point to the `project-root/src/checkstyle/checkstyle-suppressions.xml` folder. Example for `spring-cloud-contract` would be: `/home/username/spring-cloud-contract/src/checkstyle/checkstyle-suppressions.xml`. - -IMPORTANT: Remember to set the `Scan Scope` to `All sources` since we apply checkstyle rules for production and test sources. - -[[duplicate-finder]] -== Duplicate Finder - -Spring Cloud Build brings along the `basepom:duplicate-finder-maven-plugin`, that enables flagging duplicate and conflicting classes and resources on the java classpath. - -[[duplicate-finder-configuration]] -=== Duplicate Finder configuration - -Duplicate finder is *enabled by default* and will run in the `verify` phase of your Maven build, but it will only take effect in your project if you add the `duplicate-finder-maven-plugin` to the `build` section of the projecst's `pom.xml`. - -.pom.xml -[source,xml] ----- - - - - org.basepom.maven - duplicate-finder-maven-plugin - - - ----- - -For other properties, we have set defaults as listed in the https://github.com/basepom/duplicate-finder-maven-plugin/wiki[plugin documentation]. - -You can easily override them but setting the value of the selected property prefixed with `duplicate-finder-maven-plugin`. For example, set `duplicate-finder-maven-plugin.skip` to `true` in order to skip duplicates check in your build. - -If you need to add `ignoredClassPatterns` or `ignoredResourcePatterns` to your setup, make sure to add them in the plugin configuration section of your project: - -[source,xml] ----- - - - - org.basepom.maven - duplicate-finder-maven-plugin - - - org.joda.time.base.BaseDateTime - .*module-info - - - changelog.txt - - - - - - - ----- - +Unresolved directive in - include::https:///raw.githubusercontent.com/spring-cloud/spring-cloud-build/main/docs/modules/ROOT/partials/building.adoc[] [[contributing]] = Contributing From a1cb4eb05020f68b89d31fc32a910ae4066298e1 Mon Sep 17 00:00:00 2001 From: Jivebread Date: Thu, 16 May 2024 22:45:48 +0900 Subject: [PATCH 18/90] Fixes missing circuit breaker id 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. Fixes gh-3327 Fixes gh-3397 --- .../gateway/server/mvc/config/RouterFunctionHolderFactory.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/RouterFunctionHolderFactory.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/RouterFunctionHolderFactory.java index e75b259f..f612d07d 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/RouterFunctionHolderFactory.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/RouterFunctionHolderFactory.java @@ -22,6 +22,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Comparator; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; @@ -244,6 +245,7 @@ public class RouterFunctionHolderFactory { String operationName, Map operationArgs) { return operations.getOrDefault(operationName, Collections.emptyList()) .stream() + .sorted(Comparator.comparing(OperationMethod::isConfigurable)) .map(operationMethod -> new NormalizedOperationMethod(operationMethod, operationArgs)) .filter(opeMethod -> matchOperation(opeMethod, operationArgs)) .findFirst(); From ffb497e74c8937135eb6b4302bccbeb2854c99a3 Mon Sep 17 00:00:00 2001 From: spencergibb Date: Wed, 9 Oct 2024 13:12:59 -0400 Subject: [PATCH 19/90] Fixes missing aliasfor values --- .../mvc/config/NormalizedOperationMethod.java | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/NormalizedOperationMethod.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/NormalizedOperationMethod.java index 9e0b6216..d8f31d4a 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/NormalizedOperationMethod.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/NormalizedOperationMethod.java @@ -28,6 +28,8 @@ import org.springframework.cloud.gateway.server.mvc.invoke.OperationParameter; import org.springframework.cloud.gateway.server.mvc.invoke.OperationParameters; import org.springframework.cloud.gateway.server.mvc.invoke.reflect.DefaultOperationMethod; import org.springframework.cloud.gateway.server.mvc.invoke.reflect.OperationMethod; +import org.springframework.core.annotation.MergedAnnotation; +import org.springframework.core.annotation.MergedAnnotations; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -52,8 +54,9 @@ public class NormalizedOperationMethod implements OperationMethod { } public boolean isConfigurable() { - Configurable annotation = delegate.getMethod().getAnnotation(Configurable.class); - return annotation != null && delegate.getParameters().getParameterCount() == 1; + MergedAnnotation configurable = MergedAnnotations.from(delegate.getMethod()) + .get(Configurable.class); + return configurable.isPresent() && delegate.getParameters().getParameterCount() == 1; } @Override @@ -72,8 +75,10 @@ public class NormalizedOperationMethod implements OperationMethod { private Map normalizeArgs(Map operationArgs) { if (hasGeneratedKey(operationArgs)) { - Shortcut shortcut = getMethod().getAnnotation(Shortcut.class); - if (shortcut != null) { + MergedAnnotation shortcutMergedAnnotation = MergedAnnotations.from(delegate.getMethod()) + .get(Shortcut.class); + if (shortcutMergedAnnotation.isPresent()) { + Shortcut shortcut = shortcutMergedAnnotation.synthesize(); String[] fieldOrder = getFieldOrder(shortcut); return switch (shortcut.type()) { case DEFAULT -> { From 71ba49a620521591261607caaa5b4711057aadc5 Mon Sep 17 00:00:00 2001 From: spencergibb Date: Wed, 9 Oct 2024 13:13:51 -0400 Subject: [PATCH 20/90] Fixes missing arguments while binding parameters --- .../gateway/server/mvc/config/RouterFunctionHolderFactory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/RouterFunctionHolderFactory.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/RouterFunctionHolderFactory.java index f612d07d..f5e5122d 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/RouterFunctionHolderFactory.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/RouterFunctionHolderFactory.java @@ -274,7 +274,7 @@ public class RouterFunctionHolderFactory { Map args = new HashMap<>(); if (operationMethod.isConfigurable()) { OperationParameter operationParameter = operationMethod.getParameters().get(0); - Object config = bindConfigurable(operationMethod, args, operationParameter); + Object config = bindConfigurable(operationMethod, operationArgs, operationParameter); args.put(operationParameter.getName(), config); } else { From d40ef5c09dbf77060866f6dc012592efe3bf3668 Mon Sep 17 00:00:00 2001 From: spencergibb Date: Wed, 9 Oct 2024 13:15:03 -0400 Subject: [PATCH 21/90] Adds debug logging for finding predicates and filters Fixes gh-3396 --- .../server/mvc/config/RouterFunctionHolderFactory.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/RouterFunctionHolderFactory.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/RouterFunctionHolderFactory.java index f5e5122d..e10a06b0 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/RouterFunctionHolderFactory.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/RouterFunctionHolderFactory.java @@ -16,13 +16,14 @@ package org.springframework.cloud.gateway.server.mvc.config; +import java.util.Arrays; import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.Comparator; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; @@ -234,6 +235,11 @@ public class RouterFunctionHolderFactory { if (handlerFilterFunction != null) { operationHandler.accept(handlerFilterFunction); } + if (log.isDebugEnabled()) { + log.debug(LogMessage.format("Yaml Properties matched Operations name: %s, args: %s, params: %s", + normalizedName, opMethod.getNormalizedArgs().toString(), + Arrays.toString(opMethod.getParameters().stream().toArray()))); + } } else { throw new IllegalArgumentException(String.format("Unable to find operation %s for %s with args %s", From df2beb9d2a0b74fae6427b348a80524a0a707bff Mon Sep 17 00:00:00 2001 From: spencergibb Date: Wed, 9 Oct 2024 13:16:08 -0400 Subject: [PATCH 22/90] Adds shortcut field order to CircuitBreakerFilterFunctions See gh-3327 --- .../server/mvc/filter/CircuitBreakerFilterFunctions.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/CircuitBreakerFilterFunctions.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/CircuitBreakerFilterFunctions.java index 54ae1709..ba522460 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/CircuitBreakerFilterFunctions.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/CircuitBreakerFilterFunctions.java @@ -68,7 +68,7 @@ public abstract class CircuitBreakerFilterFunctions { return circuitBreaker(config); } - @Shortcut + @Shortcut("id") @Configurable public static HandlerFilterFunction circuitBreaker(CircuitBreakerConfig config) { Set failureStatuses = config.getStatusCodes() From c3ea4bb1508f6a7ec0c3baa477087be338410f70 Mon Sep 17 00:00:00 2001 From: spencergibb Date: Wed, 9 Oct 2024 13:16:27 -0400 Subject: [PATCH 23/90] Adds shortcut field order to RetryFilterFunctions --- .../cloud/gateway/server/mvc/filter/RetryFilterFunctions.java | 3 ++- .../application-propertiesbeandefinitionregistrartests.yml | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/RetryFilterFunctions.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/RetryFilterFunctions.java index ac8358f0..8a550ce2 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/RetryFilterFunctions.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/RetryFilterFunctions.java @@ -48,6 +48,7 @@ public abstract class RetryFilterFunctions { private RetryFilterFunctions() { } + @Shortcut public static HandlerFilterFunction retry(int retries) { return retry(config -> config.setRetries(retries)); } @@ -58,7 +59,7 @@ public abstract class RetryFilterFunctions { return retry(config); } - @Shortcut + @Shortcut({ "retries", "series", "methods" }) @Configurable public static HandlerFilterFunction retry(RetryConfig config) { RetryTemplateBuilder retryTemplateBuilder = RetryTemplate.builder(); diff --git a/spring-cloud-gateway-server-mvc/src/test/resources/application-propertiesbeandefinitionregistrartests.yml b/spring-cloud-gateway-server-mvc/src/test/resources/application-propertiesbeandefinitionregistrartests.yml index d27c4c76..5270b8cb 100644 --- a/spring-cloud-gateway-server-mvc/src/test/resources/application-propertiesbeandefinitionregistrartests.yml +++ b/spring-cloud-gateway-server-mvc/src/test/resources/application-propertiesbeandefinitionregistrartests.yml @@ -34,6 +34,7 @@ spring.cloud.gateway.mvc: - HttpbinUriResolver= - TokenRelay - AddRequestHeader=X-Test,listRoute2 + - Retry=3,SERVER_ERROR - id: listRoute3 uri: lb://httpbin predicates: From 6a1b21f58bbaff0ae8d9d31d69062c7228536e92 Mon Sep 17 00:00:00 2001 From: Jens Mallien Date: Tue, 30 Jul 2024 13:16:53 +0200 Subject: [PATCH 24/90] Adds flushing after each buffer write in RestClientProxyExchange in case of text/event-stream Fixes gh-3410 Fixes gh-3486 --- .../gateway/server/mvc/common/HttpUtils.java | 79 +++++++++++++++++ ...ClientHttpRequestFactoryProxyExchange.java | 4 +- .../mvc/handler/RestClientProxyExchange.java | 3 +- .../server/mvc/common/HttpUtilsTests.java | 88 +++++++++++++++++++ 4 files changed, 172 insertions(+), 2 deletions(-) create mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/HttpUtils.java create mode 100644 spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/common/HttpUtilsTests.java diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/HttpUtils.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/HttpUtils.java new file mode 100644 index 00000000..9cd20c9f --- /dev/null +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/HttpUtils.java @@ -0,0 +1,79 @@ +/* + * Copyright 2013-2023 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.gateway.server.mvc.common; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.util.Assert; +import org.springframework.util.StreamUtils; + +import static org.springframework.http.MediaType.TEXT_EVENT_STREAM; + +public abstract class HttpUtils { + + private static final int BUFFER_SIZE = 16384; + + private HttpUtils() { + throw new AssertionError("Must not instantiate utility class."); + } + + public static int copyResponseBody(ClientHttpResponse clientResponse, InputStream inputStream, + OutputStream outputStream) throws IOException { + Assert.notNull(clientResponse, "No ClientResponse specified"); + Assert.notNull(inputStream, "No InputStream specified"); + Assert.notNull(outputStream, "No OutputStream specified"); + + int transferredBytes; + + if (TEXT_EVENT_STREAM.equals(clientResponse.getHeaders().getContentType())) { + transferredBytes = copyResponseBodyWithFlushing(inputStream, outputStream); + } + else { + transferredBytes = StreamUtils.copy(inputStream, outputStream); + } + + return transferredBytes; + } + + private static int copyResponseBodyWithFlushing(InputStream inputStream, OutputStream outputStream) + throws IOException { + int readBytes; + var totalReadBytes = 0; + var buffer = new byte[BUFFER_SIZE]; + + while ((readBytes = inputStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, readBytes); + outputStream.flush(); + if (totalReadBytes < Integer.MAX_VALUE) { + try { + totalReadBytes = Math.addExact(totalReadBytes, readBytes); + } + catch (ArithmeticException e) { + totalReadBytes = Integer.MAX_VALUE; + } + } + } + + outputStream.flush(); + + return totalReadBytes; + } + +} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/ClientHttpRequestFactoryProxyExchange.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/ClientHttpRequestFactoryProxyExchange.java index ccda73d4..056653c1 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/ClientHttpRequestFactoryProxyExchange.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/ClientHttpRequestFactoryProxyExchange.java @@ -20,6 +20,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; +import org.springframework.cloud.gateway.server.mvc.common.HttpUtils; import org.springframework.cloud.gateway.server.mvc.common.MvcUtils; import org.springframework.http.client.ClientHttpRequest; import org.springframework.http.client.ClientHttpRequestFactory; @@ -54,7 +55,8 @@ public class ClientHttpRequestFactoryProxyExchange implements ProxyExchange { InputStream inputStream = MvcUtils.getAttribute(request.getServerRequest(), MvcUtils.CLIENT_RESPONSE_INPUT_STREAM_ATTR); // copy body from request to clientHttpRequest - StreamUtils.copy(inputStream, httpServletResponse.getOutputStream()); + HttpUtils.copyResponseBody(clientHttpResponse, inputStream, + httpServletResponse.getOutputStream()); } return null; }); diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/RestClientProxyExchange.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/RestClientProxyExchange.java index 27a02569..91fce3f7 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/RestClientProxyExchange.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/RestClientProxyExchange.java @@ -21,6 +21,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.UncheckedIOException; +import org.springframework.cloud.gateway.server.mvc.common.HttpUtils; import org.springframework.cloud.gateway.server.mvc.common.MvcUtils; import org.springframework.http.client.ClientHttpResponse; import org.springframework.util.StreamUtils; @@ -71,7 +72,7 @@ public class RestClientProxyExchange implements ProxyExchange { InputStream inputStream = MvcUtils.getAttribute(request.getServerRequest(), MvcUtils.CLIENT_RESPONSE_INPUT_STREAM_ATTR); // copy body from request to clientHttpRequest - StreamUtils.copy(inputStream, httpServletResponse.getOutputStream()); + HttpUtils.copyResponseBody(clientResponse, inputStream, httpServletResponse.getOutputStream()); } return null; }); diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/common/HttpUtilsTests.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/common/HttpUtilsTests.java new file mode 100644 index 00000000..839232ca --- /dev/null +++ b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/common/HttpUtilsTests.java @@ -0,0 +1,88 @@ +/* + * Copyright 2013-2024 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.gateway.server.mvc.common; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.junit.jupiter.api.Test; + +import org.springframework.http.MediaType; +import org.springframework.mock.http.client.MockClientHttpResponse; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * @author Jens Mallien + */ +public class HttpUtilsTests { + + @Test + public void copyResponseBodyForJson() throws IOException { + MockClientHttpResponse mockResponse = new MockClientHttpResponse(new byte[0], 200); + mockResponse.getHeaders().setContentType(MediaType.APPLICATION_JSON); + + InputStream inputStream = mock(InputStream.class); + when(inputStream.transferTo(any())).thenReturn(3L); + OutputStream outputStream = mock(OutputStream.class); + + int result = HttpUtils.copyResponseBody(mockResponse, inputStream, outputStream); + + assertThat(result).isEqualTo(3); + verify(outputStream, times(1)).flush(); + } + + @Test + public void copyResponseBodyForTextEventStream() throws IOException { + MockClientHttpResponse mockResponse = new MockClientHttpResponse(new byte[0], 200); + mockResponse.getHeaders().setContentType(MediaType.TEXT_EVENT_STREAM); + + InputStream inputStream = mock(InputStream.class); + when(inputStream.read(any())) + .thenReturn(1) + .thenReturn(1) + .thenReturn(1) + .thenReturn(-1); + OutputStream outputStream = mock(OutputStream.class); + + int result = HttpUtils.copyResponseBody(mockResponse, inputStream, outputStream); + + assertThat(result).isEqualTo(3); + verify(outputStream, times(4)).flush(); + } + + @Test + public void copyResponseBodyWithoutContentType() throws IOException { + MockClientHttpResponse mockResponse = new MockClientHttpResponse(new byte[0], 200); + + InputStream inputStream = mock(InputStream.class); + when(inputStream.transferTo(any())).thenReturn(3L); + OutputStream outputStream = mock(OutputStream.class); + + int result = HttpUtils.copyResponseBody(mockResponse, inputStream, outputStream); + + assertThat(result).isEqualTo(3); + verify(outputStream, times(1)).flush(); + } + +} From 5b73fdb5fe46555b1918c5173050bcc47c016c6a Mon Sep 17 00:00:00 2001 From: spencergibb Date: Mon, 14 Oct 2024 13:12:38 -0400 Subject: [PATCH 25/90] Refactored HttpUtils to AbstractProxyExchange This allows the streaming media types and the buffer size to be configured with new properties GatewayMvcProperties.streamingBufferSize and GatewayMvcProperties.streamingMediaTypes. See gh-3486 --- .../GatewayServerMvcAutoConfiguration.java | 5 +-- ...pUtils.java => AbstractProxyExchange.java} | 21 ++++++------ .../mvc/config/GatewayMvcProperties.java | 32 +++++++++++++++++++ ...ClientHttpRequestFactoryProxyExchange.java | 15 +++++++-- .../mvc/handler/RestClientProxyExchange.java | 17 +++++++--- ...s.java => AbstractProxyExchangeTests.java} | 29 +++++++++++------ 6 files changed, 90 insertions(+), 29 deletions(-) rename spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/{HttpUtils.java => AbstractProxyExchange.java} (71%) rename spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/common/{HttpUtilsTests.java => AbstractProxyExchangeTests.java} (75%) diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/GatewayServerMvcAutoConfiguration.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/GatewayServerMvcAutoConfiguration.java index 20e5ff68..9baad4ce 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/GatewayServerMvcAutoConfiguration.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/GatewayServerMvcAutoConfiguration.java @@ -88,8 +88,9 @@ public class GatewayServerMvcAutoConfiguration { @Bean @ConditionalOnMissingBean(ProxyExchange.class) - public RestClientProxyExchange restClientProxyExchange(RestClient.Builder restClientBuilder) { - return new RestClientProxyExchange(restClientBuilder.build()); + public RestClientProxyExchange restClientProxyExchange(RestClient.Builder restClientBuilder, + GatewayMvcProperties properties) { + return new RestClientProxyExchange(restClientBuilder.build(), properties); } @Bean diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/HttpUtils.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/AbstractProxyExchange.java similarity index 71% rename from spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/HttpUtils.java rename to spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/AbstractProxyExchange.java index 9cd20c9f..9cc4606b 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/HttpUtils.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/AbstractProxyExchange.java @@ -20,21 +20,21 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import org.springframework.cloud.gateway.server.mvc.config.GatewayMvcProperties; +import org.springframework.cloud.gateway.server.mvc.handler.ProxyExchange; import org.springframework.http.client.ClientHttpResponse; import org.springframework.util.Assert; import org.springframework.util.StreamUtils; -import static org.springframework.http.MediaType.TEXT_EVENT_STREAM; +public abstract class AbstractProxyExchange implements ProxyExchange { -public abstract class HttpUtils { + private final GatewayMvcProperties properties; - private static final int BUFFER_SIZE = 16384; - - private HttpUtils() { - throw new AssertionError("Must not instantiate utility class."); + protected AbstractProxyExchange(GatewayMvcProperties properties) { + this.properties = properties; } - public static int copyResponseBody(ClientHttpResponse clientResponse, InputStream inputStream, + protected int copyResponseBody(ClientHttpResponse clientResponse, InputStream inputStream, OutputStream outputStream) throws IOException { Assert.notNull(clientResponse, "No ClientResponse specified"); Assert.notNull(inputStream, "No InputStream specified"); @@ -42,7 +42,7 @@ public abstract class HttpUtils { int transferredBytes; - if (TEXT_EVENT_STREAM.equals(clientResponse.getHeaders().getContentType())) { + if (properties.getStreamingMediaTypes().contains(clientResponse.getHeaders().getContentType())) { transferredBytes = copyResponseBodyWithFlushing(inputStream, outputStream); } else { @@ -52,11 +52,10 @@ public abstract class HttpUtils { return transferredBytes; } - private static int copyResponseBodyWithFlushing(InputStream inputStream, OutputStream outputStream) - throws IOException { + private int copyResponseBodyWithFlushing(InputStream inputStream, OutputStream outputStream) throws IOException { int readBytes; var totalReadBytes = 0; - var buffer = new byte[BUFFER_SIZE]; + var buffer = new byte[properties.getStreamingBufferSize()]; while ((readBytes = inputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, readBytes); diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/GatewayMvcProperties.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/GatewayMvcProperties.java index 9bea9ddd..ac553b7d 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/GatewayMvcProperties.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/GatewayMvcProperties.java @@ -18,6 +18,7 @@ package org.springframework.cloud.gateway.server.mvc.config; import java.time.Duration; import java.util.ArrayList; +import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; @@ -26,6 +27,7 @@ import jakarta.validation.constraints.NotNull; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.core.style.ToStringCreator; +import org.springframework.http.MediaType; @ConfigurationProperties(GatewayMvcProperties.PREFIX) public class GatewayMvcProperties { @@ -51,6 +53,18 @@ public class GatewayMvcProperties { private HttpClient httpClient = new HttpClient(); + /** + * Mime-types that are streaming. + */ + private List streamingMediaTypes = Arrays.asList(MediaType.TEXT_EVENT_STREAM, + new MediaType("application", "stream+json"), new MediaType("application", "grpc"), + new MediaType("application", "grpc+protobuf"), new MediaType("application", "grpc+json")); + + /** + * Buffer size for streaming media mime-types. + */ + private int streamingBufferSize = 16384; + public List getRoutes() { return routes; } @@ -71,11 +85,29 @@ public class GatewayMvcProperties { return httpClient; } + public List getStreamingMediaTypes() { + return streamingMediaTypes; + } + + public void setStreamingMediaTypes(List streamingMediaTypes) { + this.streamingMediaTypes = streamingMediaTypes; + } + + public int getStreamingBufferSize() { + return streamingBufferSize; + } + + public void setStreamingBufferSize(int streamingBufferSize) { + this.streamingBufferSize = streamingBufferSize; + } + @Override public String toString() { return new ToStringCreator(this).append("httpClient", httpClient) .append("routes", routes) .append("routesMap", routesMap) + .append("streamingMediaTypes", streamingMediaTypes) + .append("streamingBufferSize", streamingBufferSize) .toString(); } diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/ClientHttpRequestFactoryProxyExchange.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/ClientHttpRequestFactoryProxyExchange.java index 056653c1..5c17dd2d 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/ClientHttpRequestFactoryProxyExchange.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/ClientHttpRequestFactoryProxyExchange.java @@ -20,19 +20,28 @@ import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; -import org.springframework.cloud.gateway.server.mvc.common.HttpUtils; +import org.springframework.cloud.gateway.server.mvc.common.AbstractProxyExchange; import org.springframework.cloud.gateway.server.mvc.common.MvcUtils; +import org.springframework.cloud.gateway.server.mvc.config.GatewayMvcProperties; import org.springframework.http.client.ClientHttpRequest; import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.client.ClientHttpResponse; import org.springframework.util.StreamUtils; import org.springframework.web.servlet.function.ServerResponse; -public class ClientHttpRequestFactoryProxyExchange implements ProxyExchange { +public class ClientHttpRequestFactoryProxyExchange extends AbstractProxyExchange { private final ClientHttpRequestFactory requestFactory; + @Deprecated public ClientHttpRequestFactoryProxyExchange(ClientHttpRequestFactory requestFactory) { + super(new GatewayMvcProperties()); + this.requestFactory = requestFactory; + } + + public ClientHttpRequestFactoryProxyExchange(ClientHttpRequestFactory requestFactory, + GatewayMvcProperties properties) { + super(properties); this.requestFactory = requestFactory; } @@ -55,7 +64,7 @@ public class ClientHttpRequestFactoryProxyExchange implements ProxyExchange { InputStream inputStream = MvcUtils.getAttribute(request.getServerRequest(), MvcUtils.CLIENT_RESPONSE_INPUT_STREAM_ATTR); // copy body from request to clientHttpRequest - HttpUtils.copyResponseBody(clientHttpResponse, inputStream, + ClientHttpRequestFactoryProxyExchange.this.copyResponseBody(clientHttpResponse, inputStream, httpServletResponse.getOutputStream()); } return null; diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/RestClientProxyExchange.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/RestClientProxyExchange.java index 91fce3f7..46e9d032 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/RestClientProxyExchange.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/RestClientProxyExchange.java @@ -21,18 +21,26 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.UncheckedIOException; -import org.springframework.cloud.gateway.server.mvc.common.HttpUtils; +import org.springframework.cloud.gateway.server.mvc.common.AbstractProxyExchange; import org.springframework.cloud.gateway.server.mvc.common.MvcUtils; +import org.springframework.cloud.gateway.server.mvc.config.GatewayMvcProperties; import org.springframework.http.client.ClientHttpResponse; import org.springframework.util.StreamUtils; import org.springframework.web.client.RestClient; import org.springframework.web.servlet.function.ServerResponse; -public class RestClientProxyExchange implements ProxyExchange { +public class RestClientProxyExchange extends AbstractProxyExchange { private final RestClient restClient; + @Deprecated public RestClientProxyExchange(RestClient restClient) { + super(new GatewayMvcProperties()); + this.restClient = restClient; + } + + public RestClientProxyExchange(RestClient restClient, GatewayMvcProperties properties) { + super(properties); this.restClient = restClient; } @@ -60,7 +68,7 @@ public class RestClientProxyExchange implements ProxyExchange { return StreamUtils.copy(request.getServerRequest().servletRequest().getInputStream(), outputStream); } - private static ServerResponse doExchange(Request request, ClientHttpResponse clientResponse) throws IOException { + private ServerResponse doExchange(Request request, ClientHttpResponse clientResponse) throws IOException { InputStream body = clientResponse.getBody(); // put the body input stream in a request attribute so filters can read it. MvcUtils.putAttribute(request.getServerRequest(), MvcUtils.CLIENT_RESPONSE_INPUT_STREAM_ATTR, body); @@ -72,7 +80,8 @@ public class RestClientProxyExchange implements ProxyExchange { InputStream inputStream = MvcUtils.getAttribute(request.getServerRequest(), MvcUtils.CLIENT_RESPONSE_INPUT_STREAM_ATTR); // copy body from request to clientHttpRequest - HttpUtils.copyResponseBody(clientResponse, inputStream, httpServletResponse.getOutputStream()); + RestClientProxyExchange.this.copyResponseBody(clientResponse, inputStream, + httpServletResponse.getOutputStream()); } return null; }); diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/common/HttpUtilsTests.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/common/AbstractProxyExchangeTests.java similarity index 75% rename from spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/common/HttpUtilsTests.java rename to spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/common/AbstractProxyExchangeTests.java index 839232ca..a153c3c8 100644 --- a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/common/HttpUtilsTests.java +++ b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/common/AbstractProxyExchangeTests.java @@ -22,8 +22,10 @@ import java.io.OutputStream; import org.junit.jupiter.api.Test; +import org.springframework.cloud.gateway.server.mvc.config.GatewayMvcProperties; import org.springframework.http.MediaType; import org.springframework.mock.http.client.MockClientHttpResponse; +import org.springframework.web.servlet.function.ServerResponse; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -35,7 +37,7 @@ import static org.mockito.Mockito.when; /** * @author Jens Mallien */ -public class HttpUtilsTests { +public class AbstractProxyExchangeTests { @Test public void copyResponseBodyForJson() throws IOException { @@ -46,7 +48,7 @@ public class HttpUtilsTests { when(inputStream.transferTo(any())).thenReturn(3L); OutputStream outputStream = mock(OutputStream.class); - int result = HttpUtils.copyResponseBody(mockResponse, inputStream, outputStream); + int result = new TestProxyExchange().copyResponseBody(mockResponse, inputStream, outputStream); assertThat(result).isEqualTo(3); verify(outputStream, times(1)).flush(); @@ -58,14 +60,10 @@ public class HttpUtilsTests { mockResponse.getHeaders().setContentType(MediaType.TEXT_EVENT_STREAM); InputStream inputStream = mock(InputStream.class); - when(inputStream.read(any())) - .thenReturn(1) - .thenReturn(1) - .thenReturn(1) - .thenReturn(-1); + when(inputStream.read(any())).thenReturn(1).thenReturn(1).thenReturn(1).thenReturn(-1); OutputStream outputStream = mock(OutputStream.class); - int result = HttpUtils.copyResponseBody(mockResponse, inputStream, outputStream); + int result = new TestProxyExchange().copyResponseBody(mockResponse, inputStream, outputStream); assertThat(result).isEqualTo(3); verify(outputStream, times(4)).flush(); @@ -79,10 +77,23 @@ public class HttpUtilsTests { when(inputStream.transferTo(any())).thenReturn(3L); OutputStream outputStream = mock(OutputStream.class); - int result = HttpUtils.copyResponseBody(mockResponse, inputStream, outputStream); + int result = new TestProxyExchange().copyResponseBody(mockResponse, inputStream, outputStream); assertThat(result).isEqualTo(3); verify(outputStream, times(1)).flush(); } + class TestProxyExchange extends AbstractProxyExchange { + + protected TestProxyExchange() { + super(new GatewayMvcProperties()); + } + + @Override + public ServerResponse exchange(Request request) { + return ServerResponse.ok().build(); + } + + } + } From 679bc2c2323737a36a6b70500ce7b905e6464b35 Mon Sep 17 00:00:00 2001 From: spring-builds Date: Tue, 15 Oct 2024 13:28:14 +0000 Subject: [PATCH 26/90] Bumping versions --- docs/modules/ROOT/partials/_configprops.adoc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/modules/ROOT/partials/_configprops.adoc b/docs/modules/ROOT/partials/_configprops.adoc index b1a778c9..98a11c38 100644 --- a/docs/modules/ROOT/partials/_configprops.adoc +++ b/docs/modules/ROOT/partials/_configprops.adoc @@ -126,6 +126,8 @@ |spring.cloud.gateway.mvc.remove-hop-by-hop-response-headers-filter.enabled | `+++true+++` | Enables the forwarded-request-headers-filter. |spring.cloud.gateway.mvc.routes | | List of Routes. |spring.cloud.gateway.mvc.routes-map | | Map of Routes. +|spring.cloud.gateway.mvc.streaming-buffer-size | `+++16384+++` | Buffer size for streaming media mime-types. +|spring.cloud.gateway.mvc.streaming-media-types | | Mime-types that are streaming. |spring.cloud.gateway.mvc.transfer-encoding-normalization-request-headers-filter.enabled | `+++true+++` | Enables the transfer-encoding-normalization-request-headers-filter. |spring.cloud.gateway.mvc.weight-calculator-filter.enabled | `+++true+++` | Enables the weight-calculator-filter. |spring.cloud.gateway.mvc.x-forwarded-request-headers-filter.enabled | `+++true+++` | If the XForwardedHeadersFilter is enabled. From 6db4ee18015942ebc94b8b0e666969abd3ce6350 Mon Sep 17 00:00:00 2001 From: spencergibb Date: Fri, 18 Oct 2024 10:50:44 -0400 Subject: [PATCH 27/90] Updates to*Case() to use Locale.ROOT --- .../cloud/gateway/mvc/ProxyExchange.java | 5 +++-- .../mvc/config/ProxyExchangeArgumentResolver.java | 3 ++- .../gateway/sample/GatewaySampleApplication.java | 11 ++++++----- .../gateway/server/mvc/common/HttpStatusHolder.java | 4 +++- .../mvc/config/RouterFunctionHolderFactory.java | 5 +++-- .../filter/RemoveHopByHopRequestHeadersFilter.java | 3 ++- .../gateway/server/mvc/ServerMvcIntegrationTests.java | 5 +++-- .../server/mvc/VanillaRouterFunctionTests.java | 3 ++- .../DiscoveryClientRouteDefinitionLocator.java | 3 ++- .../cloud/gateway/filter/WebsocketRoutingFilter.java | 7 ++++--- .../factory/SecureHeadersGatewayFilterFactory.java | 3 ++- .../filter/headers/RemoveHopByHopHeadersFilter.java | 3 ++- .../cloud/gateway/support/NameUtils.java | 3 ++- .../cloud/gateway/support/ServerWebExchangeUtils.java | 3 ++- .../DiscoveryClientRouteDefinitionLocatorTests.java | 4 ++-- .../ModifyRequestBodyGatewayFilterFactoryTests.java | 6 ++++-- .../headers/RemoveHopByHopHeadersFilterTests.java | 7 ++++--- .../cloud/gateway/webflux/ProxyExchange.java | 5 +++-- 18 files changed, 51 insertions(+), 32 deletions(-) diff --git a/spring-cloud-gateway-mvc/src/main/java/org/springframework/cloud/gateway/mvc/ProxyExchange.java b/spring-cloud-gateway-mvc/src/main/java/org/springframework/cloud/gateway/mvc/ProxyExchange.java index 00cbec88..11eb688c 100644 --- a/spring-cloud-gateway-mvc/src/main/java/org/springframework/cloud/gateway/mvc/ProxyExchange.java +++ b/spring-cloud-gateway-mvc/src/main/java/org/springframework/cloud/gateway/mvc/ProxyExchange.java @@ -31,6 +31,7 @@ import java.util.Enumeration; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; +import java.util.Locale; import java.util.Set; import java.util.Vector; import java.util.function.Function; @@ -234,7 +235,7 @@ public class ProxyExchange { this.excluded.clear(); for (String name : names) { - this.excluded.add(name.toLowerCase()); + this.excluded.add(name.toLowerCase(Locale.ROOT)); } return this; } @@ -384,7 +385,7 @@ public class ProxyExchange { private Set filterHeaderKeys(Collection headerNames) { final Set excludedHeaders = this.excluded != null ? this.excluded : Collections.emptySet(); return headerNames.stream() - .filter(header -> !excludedHeaders.contains(header.toLowerCase())) + .filter(header -> !excludedHeaders.contains(header.toLowerCase(Locale.ROOT))) .collect(Collectors.toSet()); } diff --git a/spring-cloud-gateway-mvc/src/main/java/org/springframework/cloud/gateway/mvc/config/ProxyExchangeArgumentResolver.java b/spring-cloud-gateway-mvc/src/main/java/org/springframework/cloud/gateway/mvc/config/ProxyExchangeArgumentResolver.java index 9af97ab2..56335607 100644 --- a/spring-cloud-gateway-mvc/src/main/java/org/springframework/cloud/gateway/mvc/config/ProxyExchangeArgumentResolver.java +++ b/spring-cloud-gateway-mvc/src/main/java/org/springframework/cloud/gateway/mvc/config/ProxyExchangeArgumentResolver.java @@ -20,6 +20,7 @@ import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.Collections; import java.util.Enumeration; +import java.util.Locale; import java.util.Set; import jakarta.servlet.http.HttpServletRequest; @@ -101,7 +102,7 @@ public class ProxyExchangeArgumentResolver implements HandlerMethodArgumentResol HttpHeaders headers = new HttpHeaders(); while (headerNames.hasMoreElements()) { String header = headerNames.nextElement(); - if (this.autoForwardedHeaders.contains(header.toLowerCase())) { + if (this.autoForwardedHeaders.contains(header.toLowerCase(Locale.ROOT))) { headers.addAll(header, Collections.list(nativeRequest.getHeaders(header))); } } diff --git a/spring-cloud-gateway-sample/src/main/java/org/springframework/cloud/gateway/sample/GatewaySampleApplication.java b/spring-cloud-gateway-sample/src/main/java/org/springframework/cloud/gateway/sample/GatewaySampleApplication.java index 4ebe38ec..7a7a07d5 100644 --- a/spring-cloud-gateway-sample/src/main/java/org/springframework/cloud/gateway/sample/GatewaySampleApplication.java +++ b/spring-cloud-gateway-sample/src/main/java/org/springframework/cloud/gateway/sample/GatewaySampleApplication.java @@ -16,6 +16,7 @@ package org.springframework.cloud.gateway.sample; +import java.util.Locale; import java.util.Map; import java.util.concurrent.TimeUnit; @@ -77,7 +78,7 @@ public class GatewaySampleApplication { .addResponseHeader("X-TestHeader", "rewrite_request") .modifyRequestBody(String.class, Hello.class, MediaType.APPLICATION_JSON_VALUE, (exchange, s) -> { - return Mono.just(new Hello(s.toUpperCase())); + return Mono.just(new Hello(s.toUpperCase(Locale.ROOT))); }) ).uri(uri) ) @@ -86,7 +87,7 @@ public class GatewaySampleApplication { .addResponseHeader("X-TestHeader", "rewrite_request_upper") .modifyRequestBody(String.class, String.class, (exchange, s) -> { - return Mono.just(s.toUpperCase() + s.toUpperCase()); + return Mono.just(s.toUpperCase(Locale.ROOT) + s.toUpperCase(Locale.ROOT)); }) ).uri(uri) ) @@ -95,7 +96,7 @@ public class GatewaySampleApplication { .addResponseHeader("X-TestHeader", "rewrite_response_upper") .modifyResponseBody(String.class, String.class, (exchange, s) -> { - return Mono.just(s.toUpperCase()); + return Mono.just(s.toUpperCase(Locale.ROOT)); }) ).uri(uri) ) @@ -107,7 +108,7 @@ public class GatewaySampleApplication { if (s == null) { return Mono.just("emptybody"); } - return Mono.just(s.toUpperCase()); + return Mono.just(s.toUpperCase(Locale.ROOT)); }) ).uri(uri) @@ -120,7 +121,7 @@ public class GatewaySampleApplication { if (s == null) { return Mono.error(new IllegalArgumentException("this should not happen")); } - return Mono.just(s.toUpperCase()); + return Mono.just(s.toUpperCase(Locale.ROOT)); }) ).uri(uri) ) diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/HttpStatusHolder.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/HttpStatusHolder.java index c19f6d47..f5740f4d 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/HttpStatusHolder.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/HttpStatusHolder.java @@ -16,6 +16,8 @@ package org.springframework.cloud.gateway.server.mvc.common; +import java.util.Locale; + import org.springframework.core.style.ToStringCreator; import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatusCode; @@ -36,7 +38,7 @@ public class HttpStatusHolder { public static HttpStatusHolder valueOf(String status) { HttpStatusCode httpStatus; try { - httpStatus = HttpStatus.valueOf(status.toUpperCase()); + httpStatus = HttpStatus.valueOf(status.toUpperCase(Locale.ROOT)); } catch (IllegalArgumentException e) { httpStatus = null; diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/RouterFunctionHolderFactory.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/RouterFunctionHolderFactory.java index e10a06b0..687bdec8 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/RouterFunctionHolderFactory.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/RouterFunctionHolderFactory.java @@ -22,6 +22,7 @@ import java.util.Comparator; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.concurrent.atomic.AtomicReference; @@ -163,13 +164,13 @@ public class RouterFunctionHolderFactory { String scheme = routeProperties.getUri().getScheme(); Map handlerArgs = new HashMap<>(); Optional handlerOperationMethod = findOperation(handlerOperations, - scheme.toLowerCase(), handlerArgs); + scheme.toLowerCase(Locale.ROOT), handlerArgs); if (handlerOperationMethod.isEmpty()) { // single RouteProperties param handlerArgs.clear(); String routePropsKey = StringUtils.uncapitalize(RouteProperties.class.getSimpleName()); handlerArgs.put(routePropsKey, routeProperties); - handlerOperationMethod = findOperation(handlerOperations, scheme.toLowerCase(), handlerArgs); + handlerOperationMethod = findOperation(handlerOperations, scheme.toLowerCase(Locale.ROOT), handlerArgs); if (handlerOperationMethod.isEmpty()) { throw new IllegalStateException("Unable to find HandlerFunction for scheme: " + scheme); } diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/RemoveHopByHopRequestHeadersFilter.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/RemoveHopByHopRequestHeadersFilter.java index 44f21f90..d57c2213 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/RemoveHopByHopRequestHeadersFilter.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/RemoveHopByHopRequestHeadersFilter.java @@ -19,6 +19,7 @@ package org.springframework.cloud.gateway.server.mvc.filter; import java.util.Arrays; import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; @@ -55,7 +56,7 @@ public class RemoveHopByHopRequestHeadersFilter implements RequestHttpHeadersFil HttpHeaders filtered = new HttpHeaders(); for (Map.Entry> entry : input.entrySet()) { - if (!headersToRemove.contains(entry.getKey().toLowerCase())) { + if (!headersToRemove.contains(entry.getKey().toLowerCase(Locale.ROOT))) { filtered.addAll(entry.getKey(), entry.getValue()); } } diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/ServerMvcIntegrationTests.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/ServerMvcIntegrationTests.java index c43fd629..d9a7dc7b 100644 --- a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/ServerMvcIntegrationTests.java +++ b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/ServerMvcIntegrationTests.java @@ -24,6 +24,7 @@ import java.nio.charset.StandardCharsets; import java.time.Duration; import java.util.Collections; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; @@ -1533,12 +1534,12 @@ public class ServerMvcIntegrationTests { return route("testmodifyrequestbodystring") .POST("/post", host("**.modifyrequestbodystring.org"), http()) .before(new HttpbinUriResolver()) - .before(modifyRequestBody(String.class, String.class, null, (request, s) -> s.toUpperCase() + s.toUpperCase())) + .before(modifyRequestBody(String.class, String.class, null, (request, s) -> s.toUpperCase(Locale.ROOT) + s.toUpperCase(Locale.ROOT))) .build().and( route("testmodifyrequestbodyobject") .POST("/post", host("**.modifyrequestbodyobject.org"), http()) .before(new HttpbinUriResolver()) - .before(modifyRequestBody(String.class, Hello.class, MediaType.APPLICATION_JSON_VALUE, (request, s) -> new Hello(s.toUpperCase()))) + .before(modifyRequestBody(String.class, Hello.class, MediaType.APPLICATION_JSON_VALUE, (request, s) -> new Hello(s.toUpperCase(Locale.ROOT)))) .build()); // @formatter:on } diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/VanillaRouterFunctionTests.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/VanillaRouterFunctionTests.java index 4959d897..fd96c938 100644 --- a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/VanillaRouterFunctionTests.java +++ b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/VanillaRouterFunctionTests.java @@ -16,6 +16,7 @@ package org.springframework.cloud.gateway.server.mvc; +import java.util.Locale; import java.util.Map; import org.junit.jupiter.api.Test; @@ -79,7 +80,7 @@ public class VanillaRouterFunctionTests { // @formatter:off return RouterFunctions.route() .POST("/anything/routerfunctionsroute", host("**.routerfunctionsroute.org"), http()) - .before(modifyRequestBody(String.class, String.class, null, (request, s) -> s.toUpperCase())) + .before(modifyRequestBody(String.class, String.class, null, (request, s) -> s.toUpperCase(Locale.ROOT))) .before(new HttpbinUriResolver()) .build(); // @formatter:on diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/discovery/DiscoveryClientRouteDefinitionLocator.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/discovery/DiscoveryClientRouteDefinitionLocator.java index 70050580..4554f248 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/discovery/DiscoveryClientRouteDefinitionLocator.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/discovery/DiscoveryClientRouteDefinitionLocator.java @@ -19,6 +19,7 @@ package org.springframework.cloud.gateway.discovery; import java.net.URI; import java.util.LinkedHashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.function.Predicate; @@ -170,7 +171,7 @@ public class DiscoveryClientRouteDefinitionLocator implements RouteDefinitionLoc @Override public String getServiceId() { if (properties.isLowerCaseServiceId()) { - return delegate.getServiceId().toLowerCase(); + return delegate.getServiceId().toLowerCase(Locale.ROOT); } return delegate.getServiceId(); } diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/WebsocketRoutingFilter.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/WebsocketRoutingFilter.java index 04136bf7..04b02e22 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/WebsocketRoutingFilter.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/WebsocketRoutingFilter.java @@ -21,6 +21,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Locale; import java.util.Map; import org.apache.commons.logging.Log; @@ -79,7 +80,7 @@ public class WebsocketRoutingFilter implements GlobalFilter, Ordered { /* for testing */ static String convertHttpToWs(String scheme) { - scheme = scheme.toLowerCase(); + scheme = scheme.toLowerCase(Locale.ROOT); return "http".equals(scheme) ? "ws" : "https".equals(scheme) ? "wss" : scheme; } @@ -143,7 +144,7 @@ public class WebsocketRoutingFilter implements GlobalFilter, Ordered { headersFilters.add((headers, exchange) -> { HttpHeaders filtered = new HttpHeaders(); for (Map.Entry> entry : headers.entrySet()) { - if (!entry.getKey().toLowerCase().startsWith("sec-websocket")) { + if (!entry.getKey().toLowerCase(Locale.ROOT).startsWith("sec-websocket")) { filtered.addAll(entry.getKey(), entry.getValue()); } } @@ -157,7 +158,7 @@ public class WebsocketRoutingFilter implements GlobalFilter, Ordered { static void changeSchemeIfIsWebSocketUpgrade(ServerWebExchange exchange) { // Check the Upgrade URI requestUrl = exchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR); - String scheme = requestUrl.getScheme().toLowerCase(); + String scheme = requestUrl.getScheme().toLowerCase(Locale.ROOT); String upgrade = exchange.getRequest().getHeaders().getUpgrade(); // change the scheme if the socket client send a "http" or "https" if ("WebSocket".equalsIgnoreCase(upgrade) && ("http".equals(scheme) || "https".equals(scheme))) { diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/SecureHeadersGatewayFilterFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/SecureHeadersGatewayFilterFactory.java index 46d7e780..3a079968 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/SecureHeadersGatewayFilterFactory.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/SecureHeadersGatewayFilterFactory.java @@ -17,6 +17,7 @@ package org.springframework.cloud.gateway.filter.factory; import java.util.List; +import java.util.Locale; import reactor.core.publisher.Mono; @@ -136,7 +137,7 @@ public class SecureHeadersGatewayFilterFactory } private boolean isEnabled(List disabledHeaders, String header) { - return !disabledHeaders.contains(header.toLowerCase()); + return !disabledHeaders.contains(header.toLowerCase(Locale.ROOT)); } public static class Config { diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/RemoveHopByHopHeadersFilter.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/RemoveHopByHopHeadersFilter.java index 091b7aec..2a43414b 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/RemoveHopByHopHeadersFilter.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/RemoveHopByHopHeadersFilter.java @@ -19,6 +19,7 @@ package org.springframework.cloud.gateway.filter.headers; import java.util.Arrays; import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; @@ -74,7 +75,7 @@ public class RemoveHopByHopHeadersFilter implements HttpHeadersFilter, Ordered { headersToRemove.addAll(connectionOptions); for (Map.Entry> entry : originalHeaders.entrySet()) { - if (!headersToRemove.contains(entry.getKey().toLowerCase())) { + if (!headersToRemove.contains(entry.getKey().toLowerCase(Locale.ROOT))) { filtered.addAll(entry.getKey(), entry.getValue()); } } diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/NameUtils.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/NameUtils.java index fb7498cb..69e0baad 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/NameUtils.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/NameUtils.java @@ -16,6 +16,7 @@ package org.springframework.cloud.gateway.support; +import java.util.Locale; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -79,7 +80,7 @@ public final class NameUtils { matcher.appendReplacement(stringBuffer, matcher.group(1)); } } - return stringBuffer.toString().toLowerCase(); + return stringBuffer.toString().toLowerCase(Locale.ROOT); } private static String removeGarbage(String s) { diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ServerWebExchangeUtils.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ServerWebExchangeUtils.java index 36ee17d9..7585a182 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ServerWebExchangeUtils.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ServerWebExchangeUtils.java @@ -20,6 +20,7 @@ import java.net.URI; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashSet; +import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.function.Function; @@ -268,7 +269,7 @@ public final class ServerWebExchangeUtils { } catch (NumberFormatException e) { // try the enum string - httpStatus = HttpStatus.valueOf(statusString.toUpperCase()); + httpStatus = HttpStatus.valueOf(statusString.toUpperCase(Locale.ROOT)); } return httpStatus; } diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/discovery/DiscoveryClientRouteDefinitionLocatorTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/discovery/DiscoveryClientRouteDefinitionLocatorTests.java index 5c1f5a66..fc26a822 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/discovery/DiscoveryClientRouteDefinitionLocatorTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/discovery/DiscoveryClientRouteDefinitionLocatorTests.java @@ -48,9 +48,9 @@ import static org.springframework.cloud.gateway.handler.predicate.RoutePredicate "spring.cloud.gateway.discovery.locator.lower-case-service-id=true" /* * "spring.cloud.gateway.discovery.locator.predicates[0].name=Path", - * "spring.cloud.gateway.discovery.locator.predicates[0].args[pattern]='/'+serviceId.toLowerCase()+'/**'", + * "spring.cloud.gateway.discovery.locator.predicates[0].args[pattern]='/'+serviceId.toLowerCase(Locale.ROOT)+'/**'", * "spring.cloud.gateway.discovery.locator.filters[0].name=RewritePath", - * "spring.cloud.gateway.discovery.locator.filters[0].args[regexp]='/' + serviceId.toLowerCase() + '/(?.*)'" + * "spring.cloud.gateway.discovery.locator.filters[0].args[regexp]='/' + serviceId.toLowerCase(Locale.ROOT) + '/(?.*)'" * , * "spring.cloud.gateway.discovery.locator.filters[0].args[replacement]='/$\\\\{remaining}'", */ diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/rewrite/ModifyRequestBodyGatewayFilterFactoryTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/rewrite/ModifyRequestBodyGatewayFilterFactoryTests.java index d4440c9b..2eb8b7cc 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/rewrite/ModifyRequestBodyGatewayFilterFactoryTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/rewrite/ModifyRequestBodyGatewayFilterFactoryTests.java @@ -16,6 +16,8 @@ package org.springframework.cloud.gateway.filter.factory.rewrite; +import java.util.Locale; + import org.junit.jupiter.api.Test; import reactor.core.publisher.Mono; @@ -134,7 +136,7 @@ public class ModifyRequestBodyGatewayFilterFactoryTests extends BaseWebClientTes if (body == null) { return Mono.just("modifyrequest"); } - return Mono.just(body.toUpperCase()); + return Mono.just(body.toUpperCase(Locale.ROOT)); })) .uri(uri)) .route("test_modify_request_body_to_large", @@ -152,7 +154,7 @@ public class ModifyRequestBodyGatewayFilterFactoryTests extends BaseWebClientTes .filters(f -> f.modifyRequestBody(new ParameterizedTypeReference() { }, new ParameterizedTypeReference() { }, (swe, body) -> { - return Mono.just(body.replaceAll(" ", "_").toUpperCase()); + return Mono.just(body.replaceAll(" ", "_").toUpperCase(Locale.ROOT)); })) .uri(uri)) .build(); diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/RemoveHopByHopHeadersFilterTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/RemoveHopByHopHeadersFilterTests.java index 0a5445fa..86e4c0bd 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/RemoveHopByHopHeadersFilterTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/RemoveHopByHopHeadersFilterTests.java @@ -19,6 +19,7 @@ package org.springframework.cloud.gateway.filter.headers; import java.util.Arrays; import java.util.HashSet; import java.util.LinkedHashSet; +import java.util.Locale; import java.util.Set; import org.junit.jupiter.api.Test; @@ -50,7 +51,7 @@ public class RemoveHopByHopHeadersFilterTests { public void caseInsensitive() { MockServerHttpRequest.BaseBuilder builder = MockServerHttpRequest.get("http://localhost/get"); - HEADERS_REMOVED_ON_REQUEST.forEach(header -> builder.header(header.toLowerCase(), header + "1")); + HEADERS_REMOVED_ON_REQUEST.forEach(header -> builder.header(header.toLowerCase(Locale.ROOT), header + "1")); testFilter(MockServerWebExchange.from(builder)); } @@ -60,7 +61,7 @@ public class RemoveHopByHopHeadersFilterTests { MockServerHttpRequest.BaseBuilder builder = MockServerHttpRequest.get("http://localhost/get"); HEADERS_REMOVED_ON_REQUEST - .forEach(header -> builder.header(StringUtils.capitalize(header.toLowerCase()), header + "1")); + .forEach(header -> builder.header(StringUtils.capitalize(header.toLowerCase(Locale.ROOT)), header + "1")); LinkedHashSet customHeaders = new LinkedHashSet<>(); HEADERS_REMOVED_ON_REQUEST.forEach(header -> { @@ -78,7 +79,7 @@ public class RemoveHopByHopHeadersFilterTests { String arbitraryConnectionOption = "xyz"; assumeThat(HEADERS_REMOVED_ON_REQUEST).doesNotContain(arbitraryConnectionOption); - builder.header(HttpHeaders.CONNECTION, "upgrade", "keep-alive", arbitraryConnectionOption.toUpperCase()); + builder.header(HttpHeaders.CONNECTION, "upgrade", "keep-alive", arbitraryConnectionOption.toUpperCase(Locale.ROOT)); builder.header(HttpHeaders.UPGRADE, "WebSocket"); builder.header("Keep-Alive", "timeout=5"); builder.header(arbitraryConnectionOption, ""); diff --git a/spring-cloud-gateway-webflux/src/main/java/org/springframework/cloud/gateway/webflux/ProxyExchange.java b/spring-cloud-gateway-webflux/src/main/java/org/springframework/cloud/gateway/webflux/ProxyExchange.java index 67cecf50..47dfb962 100644 --- a/spring-cloud-gateway-webflux/src/main/java/org/springframework/cloud/gateway/webflux/ProxyExchange.java +++ b/spring-cloud-gateway-webflux/src/main/java/org/springframework/cloud/gateway/webflux/ProxyExchange.java @@ -22,6 +22,7 @@ import java.net.URISyntaxException; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; +import java.util.Locale; import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; @@ -221,7 +222,7 @@ public class ProxyExchange { this.excluded.clear(); for (String name : names) { - this.excluded.add(name.toLowerCase()); + this.excluded.add(name.toLowerCase(Locale.ROOT)); } return this; } @@ -407,7 +408,7 @@ public class ProxyExchange { final Set excludedHeaders = this.excluded != null ? this.excluded : Collections.emptySet(); return headers.keySet() .stream() - .filter(header -> !excludedHeaders.contains(header.toLowerCase())) + .filter(header -> !excludedHeaders.contains(header.toLowerCase(Locale.ROOT))) .collect(Collectors.toSet()); } From 26926457c27a1138146b747f7a31d30e02b2cc9b Mon Sep 17 00:00:00 2001 From: spring-builds Date: Fri, 25 Oct 2024 13:35:48 +0000 Subject: [PATCH 28/90] Bumping versions --- .../filter/headers/RemoveHopByHopHeadersFilterTests.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/RemoveHopByHopHeadersFilterTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/RemoveHopByHopHeadersFilterTests.java index 86e4c0bd..689821f6 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/RemoveHopByHopHeadersFilterTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/RemoveHopByHopHeadersFilterTests.java @@ -79,7 +79,8 @@ public class RemoveHopByHopHeadersFilterTests { String arbitraryConnectionOption = "xyz"; assumeThat(HEADERS_REMOVED_ON_REQUEST).doesNotContain(arbitraryConnectionOption); - builder.header(HttpHeaders.CONNECTION, "upgrade", "keep-alive", arbitraryConnectionOption.toUpperCase(Locale.ROOT)); + builder.header(HttpHeaders.CONNECTION, "upgrade", "keep-alive", + arbitraryConnectionOption.toUpperCase(Locale.ROOT)); builder.header(HttpHeaders.UPGRADE, "WebSocket"); builder.header("Keep-Alive", "timeout=5"); builder.header(arbitraryConnectionOption, ""); From 57c0258b4e5d081d1b8245cb41d3957d49bcfcff Mon Sep 17 00:00:00 2001 From: spencergibb Date: Mon, 28 Oct 2024 14:28:09 -0400 Subject: [PATCH 29/90] Handle URL Connection file not found 404 Fixes gh-3451 --- .../mvc/handler/RestClientProxyExchange.java | 13 ++- .../server/mvc/ServerMvcIntegrationTests.java | 21 ++++- .../mvc/SimpleHttpClientIntegrationTests.java | 80 +++++++++++++++++++ 3 files changed, 108 insertions(+), 6 deletions(-) create mode 100644 spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/SimpleHttpClientIntegrationTests.java diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/RestClientProxyExchange.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/RestClientProxyExchange.java index 46e9d032..60018148 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/RestClientProxyExchange.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/RestClientProxyExchange.java @@ -16,6 +16,7 @@ package org.springframework.cloud.gateway.server.mvc.handler; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -69,9 +70,15 @@ public class RestClientProxyExchange extends AbstractProxyExchange { } private ServerResponse doExchange(Request request, ClientHttpResponse clientResponse) throws IOException { - InputStream body = clientResponse.getBody(); - // put the body input stream in a request attribute so filters can read it. - MvcUtils.putAttribute(request.getServerRequest(), MvcUtils.CLIENT_RESPONSE_INPUT_STREAM_ATTR, body); + try { + InputStream body = clientResponse.getBody(); + // put the body input stream in a request attribute so filters can read it. + MvcUtils.putAttribute(request.getServerRequest(), MvcUtils.CLIENT_RESPONSE_INPUT_STREAM_ATTR, body); + } + catch (FileNotFoundException e) { + // if using SimpleClientHttpRequestFactory + return ServerResponse.notFound().build(); + } ServerResponse serverResponse = GatewayServerResponse.status(clientResponse.getStatusCode()) .build((req, httpServletResponse) -> { try (clientResponse) { diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/ServerMvcIntegrationTests.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/ServerMvcIntegrationTests.java index d9a7dc7b..6ceffe35 100644 --- a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/ServerMvcIntegrationTests.java +++ b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/ServerMvcIntegrationTests.java @@ -275,12 +275,12 @@ public class ServerMvcIntegrationTests { public void setStatusGatewayRouterFunctionWorks() { restClient.get() .uri("/status/201") + .header("Host", "www.setstatus.org") .exchange() .expectStatus() .isEqualTo(HttpStatus.TOO_MANY_REQUESTS) .expectHeader() - .valueEquals("x-status", "201"); // .expectBody(String.class).isEqualTo("Failed - // with 201"); + .valueEquals("x-status", "201"); } @Test @@ -981,6 +981,11 @@ public class ServerMvcIntegrationTests { }); } + @Test + public void notFoundWorks() { + restClient.get().uri("/status/404").header("Host", "www.notfound.org").exchange().expectStatus().isNotFound(); + } + @SpringBootConfiguration @EnableAutoConfiguration @LoadBalancerClient(name = "httpbin", configuration = TestLoadBalancerConfig.Httpbin.class) @@ -1032,7 +1037,7 @@ public class ServerMvcIntegrationTests { public RouterFunction gatewayRouterFunctionsSetStatusAndAddRespHeader() { // @formatter:off return route("testsetstatus") - .GET("/status/{status}", http()) + .GET("/status/{status}", host("**.setstatus.org"), http()) .before(new HttpbinUriResolver()) .after(setStatus(HttpStatus.TOO_MANY_REQUESTS)) .after(addResponseHeader("X-Status", "{status}")) @@ -1600,6 +1605,16 @@ public class ServerMvcIntegrationTests { // @formatter:on } + @Bean + public RouterFunction gatewayRouterFunctions404() { + // @formatter:off + return route("testnotfound") + .GET("/status/404", host("**.notfound.org"), http()) + .before(new HttpbinUriResolver()) + .build(); + // @formatter:on + } + @Bean public FilterRegistrationBean myFilter() { FilterRegistrationBean reg = new FilterRegistrationBean<>(new MyFilter()); diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/SimpleHttpClientIntegrationTests.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/SimpleHttpClientIntegrationTests.java new file mode 100644 index 00000000..bd42c04e --- /dev/null +++ b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/SimpleHttpClientIntegrationTests.java @@ -0,0 +1,80 @@ +/* + * Copyright 2013-2023 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.gateway.server.mvc; + +import org.junit.jupiter.api.Test; + +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.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.cloud.gateway.server.mvc.test.HttpbinTestcontainers; +import org.springframework.cloud.gateway.server.mvc.test.HttpbinUriResolver; +import org.springframework.cloud.gateway.server.mvc.test.TestLoadBalancerConfig; +import org.springframework.cloud.gateway.server.mvc.test.client.TestRestClient; +import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient; +import org.springframework.context.annotation.Bean; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.web.servlet.function.RouterFunction; +import org.springframework.web.servlet.function.ServerResponse; + +import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route; +import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http; +import static org.springframework.cloud.gateway.server.mvc.predicate.GatewayRequestPredicates.host; + +@SuppressWarnings("unchecked") +@SpringBootTest(properties = { "spring.cloud.gateway.mvc.http-client.type=autodetect" }, + webEnvironment = WebEnvironment.RANDOM_PORT) +@ContextConfiguration(initializers = HttpbinTestcontainers.class) +public class SimpleHttpClientIntegrationTests { + + static { + // if set type to autodetect above + System.setProperty("sun.net.http.allowRestrictedHeaders", "true"); + } + + @LocalServerPort + int port; + + @Autowired + TestRestClient restClient; + + @Test + public void simpleHttpClientNotFoundWorks() { + restClient.get().uri("/status/404").header("Host", "www.notfound.org").exchange().expectStatus().isNotFound(); + } + + @SpringBootConfiguration + @EnableAutoConfiguration + @LoadBalancerClient(name = "httpbin", configuration = TestLoadBalancerConfig.Httpbin.class) + protected static class TestConfiguration { + + @Bean + public RouterFunction gatewayRouterFunctions404() { + // @formatter:off + return route("testnotfound") + .GET("/status/404", host("**.notfound.org"), http()) + .before(new HttpbinUriResolver()) + .build(); + // @formatter:on + } + + } + +} From 7a41f6ad1bf3a3e01e6d86aeca905803326c84f4 Mon Sep 17 00:00:00 2001 From: spencergibb Date: Thu, 31 Oct 2024 13:45:59 -0400 Subject: [PATCH 30/90] Revert "Handle URL Connection file not found 404" This reverts commit 57c0258b4e5d081d1b8245cb41d3957d49bcfcff. --- .../mvc/handler/RestClientProxyExchange.java | 13 +-- .../server/mvc/ServerMvcIntegrationTests.java | 21 +---- .../mvc/SimpleHttpClientIntegrationTests.java | 80 ------------------- 3 files changed, 6 insertions(+), 108 deletions(-) delete mode 100644 spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/SimpleHttpClientIntegrationTests.java diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/RestClientProxyExchange.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/RestClientProxyExchange.java index 60018148..46e9d032 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/RestClientProxyExchange.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/RestClientProxyExchange.java @@ -16,7 +16,6 @@ package org.springframework.cloud.gateway.server.mvc.handler; -import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -70,15 +69,9 @@ public class RestClientProxyExchange extends AbstractProxyExchange { } private ServerResponse doExchange(Request request, ClientHttpResponse clientResponse) throws IOException { - try { - InputStream body = clientResponse.getBody(); - // put the body input stream in a request attribute so filters can read it. - MvcUtils.putAttribute(request.getServerRequest(), MvcUtils.CLIENT_RESPONSE_INPUT_STREAM_ATTR, body); - } - catch (FileNotFoundException e) { - // if using SimpleClientHttpRequestFactory - return ServerResponse.notFound().build(); - } + InputStream body = clientResponse.getBody(); + // put the body input stream in a request attribute so filters can read it. + MvcUtils.putAttribute(request.getServerRequest(), MvcUtils.CLIENT_RESPONSE_INPUT_STREAM_ATTR, body); ServerResponse serverResponse = GatewayServerResponse.status(clientResponse.getStatusCode()) .build((req, httpServletResponse) -> { try (clientResponse) { diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/ServerMvcIntegrationTests.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/ServerMvcIntegrationTests.java index 6ceffe35..d9a7dc7b 100644 --- a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/ServerMvcIntegrationTests.java +++ b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/ServerMvcIntegrationTests.java @@ -275,12 +275,12 @@ public class ServerMvcIntegrationTests { public void setStatusGatewayRouterFunctionWorks() { restClient.get() .uri("/status/201") - .header("Host", "www.setstatus.org") .exchange() .expectStatus() .isEqualTo(HttpStatus.TOO_MANY_REQUESTS) .expectHeader() - .valueEquals("x-status", "201"); + .valueEquals("x-status", "201"); // .expectBody(String.class).isEqualTo("Failed + // with 201"); } @Test @@ -981,11 +981,6 @@ public class ServerMvcIntegrationTests { }); } - @Test - public void notFoundWorks() { - restClient.get().uri("/status/404").header("Host", "www.notfound.org").exchange().expectStatus().isNotFound(); - } - @SpringBootConfiguration @EnableAutoConfiguration @LoadBalancerClient(name = "httpbin", configuration = TestLoadBalancerConfig.Httpbin.class) @@ -1037,7 +1032,7 @@ public class ServerMvcIntegrationTests { public RouterFunction gatewayRouterFunctionsSetStatusAndAddRespHeader() { // @formatter:off return route("testsetstatus") - .GET("/status/{status}", host("**.setstatus.org"), http()) + .GET("/status/{status}", http()) .before(new HttpbinUriResolver()) .after(setStatus(HttpStatus.TOO_MANY_REQUESTS)) .after(addResponseHeader("X-Status", "{status}")) @@ -1605,16 +1600,6 @@ public class ServerMvcIntegrationTests { // @formatter:on } - @Bean - public RouterFunction gatewayRouterFunctions404() { - // @formatter:off - return route("testnotfound") - .GET("/status/404", host("**.notfound.org"), http()) - .before(new HttpbinUriResolver()) - .build(); - // @formatter:on - } - @Bean public FilterRegistrationBean myFilter() { FilterRegistrationBean reg = new FilterRegistrationBean<>(new MyFilter()); diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/SimpleHttpClientIntegrationTests.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/SimpleHttpClientIntegrationTests.java deleted file mode 100644 index bd42c04e..00000000 --- a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/SimpleHttpClientIntegrationTests.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2013-2023 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.gateway.server.mvc; - -import org.junit.jupiter.api.Test; - -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.boot.test.context.SpringBootTest.WebEnvironment; -import org.springframework.boot.test.web.server.LocalServerPort; -import org.springframework.cloud.gateway.server.mvc.test.HttpbinTestcontainers; -import org.springframework.cloud.gateway.server.mvc.test.HttpbinUriResolver; -import org.springframework.cloud.gateway.server.mvc.test.TestLoadBalancerConfig; -import org.springframework.cloud.gateway.server.mvc.test.client.TestRestClient; -import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient; -import org.springframework.context.annotation.Bean; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.web.servlet.function.RouterFunction; -import org.springframework.web.servlet.function.ServerResponse; - -import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route; -import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http; -import static org.springframework.cloud.gateway.server.mvc.predicate.GatewayRequestPredicates.host; - -@SuppressWarnings("unchecked") -@SpringBootTest(properties = { "spring.cloud.gateway.mvc.http-client.type=autodetect" }, - webEnvironment = WebEnvironment.RANDOM_PORT) -@ContextConfiguration(initializers = HttpbinTestcontainers.class) -public class SimpleHttpClientIntegrationTests { - - static { - // if set type to autodetect above - System.setProperty("sun.net.http.allowRestrictedHeaders", "true"); - } - - @LocalServerPort - int port; - - @Autowired - TestRestClient restClient; - - @Test - public void simpleHttpClientNotFoundWorks() { - restClient.get().uri("/status/404").header("Host", "www.notfound.org").exchange().expectStatus().isNotFound(); - } - - @SpringBootConfiguration - @EnableAutoConfiguration - @LoadBalancerClient(name = "httpbin", configuration = TestLoadBalancerConfig.Httpbin.class) - protected static class TestConfiguration { - - @Bean - public RouterFunction gatewayRouterFunctions404() { - // @formatter:off - return route("testnotfound") - .GET("/status/404", host("**.notfound.org"), http()) - .before(new HttpbinUriResolver()) - .build(); - // @formatter:on - } - - } - -} From 4a103dea8885f0da5e1589e598eb3f8e13f76438 Mon Sep 17 00:00:00 2001 From: spencergibb Date: Mon, 11 Nov 2024 13:58:06 -0500 Subject: [PATCH 31/90] Makes test more general --- .../factory/SpringCloudCircuitBreakerFilterFactoryTests.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/SpringCloudCircuitBreakerFilterFactoryTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/SpringCloudCircuitBreakerFilterFactoryTests.java index 9368ed5c..1966fa9e 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/SpringCloudCircuitBreakerFilterFactoryTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/SpringCloudCircuitBreakerFilterFactoryTests.java @@ -22,6 +22,7 @@ import org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable; import org.springframework.cloud.gateway.test.BaseWebClientTests; import org.springframework.http.HttpStatus; +import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.http.MediaType.APPLICATION_JSON; /** @@ -154,11 +155,11 @@ public abstract class SpringCloudCircuitBreakerFilterFactoryTests extends BaseWe .is5xxServerError() .expectBody() .jsonPath("$.status") - .isEqualTo(504) + .value(status -> assertThat(HttpStatus.valueOf((Integer) status).is5xxServerError()).isTrue()) .jsonPath("$.message") .isNotEmpty() .jsonPath("$.error") - .isEqualTo("Gateway Timeout"); + .isNotEmpty(); } @Test From 068c4ea79db1f6e0727deee72705647adc53f15b Mon Sep 17 00:00:00 2001 From: spencergibb Date: Mon, 11 Nov 2024 14:40:52 -0500 Subject: [PATCH 32/90] Disable test for now --- .../handler/predicate/PathRoutePredicateFactoryTests.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/PathRoutePredicateFactoryTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/PathRoutePredicateFactoryTests.java index d986e0e9..4c5bf50e 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/PathRoutePredicateFactoryTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/PathRoutePredicateFactoryTests.java @@ -19,6 +19,7 @@ package org.springframework.cloud.gateway.handler.predicate; import java.util.Arrays; import java.util.function.Predicate; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Value; @@ -92,6 +93,7 @@ public class PathRoutePredicateFactoryTests extends BaseWebClientTests { } @Test + @Disabled public void pathRouteWorksWithPercent() { testClient.get() .uri("/abc/123%/function") From 6f5d1e720bfd8b92c429ce6b6a3a434e45fa6ae5 Mon Sep 17 00:00:00 2001 From: spencergibb Date: Mon, 11 Nov 2024 15:04:02 -0500 Subject: [PATCH 33/90] Reenable test with firewall disabled. --- .../PathRoutePredicateFactoryTests.java | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/PathRoutePredicateFactoryTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/PathRoutePredicateFactoryTests.java index 4c5bf50e..cb92f046 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/PathRoutePredicateFactoryTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/PathRoutePredicateFactoryTests.java @@ -22,7 +22,9 @@ import java.util.function.Predicate; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import org.springframework.beans.BeansException; import org.springframework.beans.factory.annotation.Value; +import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; @@ -34,6 +36,8 @@ import org.springframework.cloud.gateway.test.BaseWebClientTests; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; import org.springframework.http.HttpHeaders; +import org.springframework.security.web.server.WebFilterChainProxy; +import org.springframework.security.web.server.firewall.StrictServerWebExchangeFirewall; import org.springframework.test.annotation.DirtiesContext; import org.springframework.web.server.ServerWebExchange; @@ -93,7 +97,7 @@ public class PathRoutePredicateFactoryTests extends BaseWebClientTests { } @Test - @Disabled + //@Disabled public void pathRouteWorksWithPercent() { testClient.get() .uri("/abc/123%/function") @@ -149,6 +153,22 @@ public class PathRoutePredicateFactoryTests extends BaseWebClientTests { @Value("${test.uri}") String uri; + // TODO: move to bean of StrictServerWebExchangeFirewall + @Bean + public BeanPostProcessor firewallPostProcessor() { + return new BeanPostProcessor() { + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + if (bean instanceof WebFilterChainProxy webFilterChainProxy) { + StrictServerWebExchangeFirewall firewall = new StrictServerWebExchangeFirewall(); + firewall.setAllowUrlEncodedPercent(true); + webFilterChainProxy.setFirewall(firewall); + } + return bean; + } + }; + } + @Bean public RouteLocator testRouteLocator(RouteLocatorBuilder builder) { return builder.routes() From eea507f98f3b634d23b22faa80fdb01a7c094c6d Mon Sep 17 00:00:00 2001 From: spencergibb Date: Mon, 11 Nov 2024 16:26:57 -0500 Subject: [PATCH 34/90] removed unused import --- .../handler/predicate/PathRoutePredicateFactoryTests.java | 1 - 1 file changed, 1 deletion(-) diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/PathRoutePredicateFactoryTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/PathRoutePredicateFactoryTests.java index cb92f046..73493367 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/PathRoutePredicateFactoryTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/PathRoutePredicateFactoryTests.java @@ -19,7 +19,6 @@ package org.springframework.cloud.gateway.handler.predicate; import java.util.Arrays; import java.util.function.Predicate; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.beans.BeansException; From 353a503dc12716659bc16524b9e961af9b4ad2cc Mon Sep 17 00:00:00 2001 From: spencergibb Date: Thu, 14 Nov 2024 12:04:35 -0500 Subject: [PATCH 35/90] Formatting --- .../handler/predicate/PathRoutePredicateFactoryTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/PathRoutePredicateFactoryTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/PathRoutePredicateFactoryTests.java index 73493367..dde2af02 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/PathRoutePredicateFactoryTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/PathRoutePredicateFactoryTests.java @@ -96,7 +96,7 @@ public class PathRoutePredicateFactoryTests extends BaseWebClientTests { } @Test - //@Disabled + // @Disabled public void pathRouteWorksWithPercent() { testClient.get() .uri("/abc/123%/function") From f2ee0c068d4c7797a304a56fc843edd656aba157 Mon Sep 17 00:00:00 2001 From: spencergibb Date: Thu, 14 Nov 2024 12:05:00 -0500 Subject: [PATCH 36/90] Use HttpHeaders#headerSet where appropriate Fixes gh-3596 --- .../server/mvc/filter/ForwardedRequestHeadersFilter.java | 2 +- .../server/mvc/filter/RemoveHopByHopRequestHeadersFilter.java | 2 +- .../server/mvc/filter/XForwardedRequestHeadersFilter.java | 2 +- .../cloud/gateway/server/mvc/test/client/ExchangeResult.java | 2 +- .../cloud/gateway/filter/WebsocketRoutingFilter.java | 2 +- .../filter/factory/RequestHeaderSizeGatewayFilterFactory.java | 2 +- .../cloud/gateway/filter/headers/ForwardedHeadersFilter.java | 2 +- .../cloud/gateway/filter/headers/GRPCRequestHeadersFilter.java | 2 +- .../gateway/filter/headers/RemoveHopByHopHeadersFilter.java | 2 +- .../cloud/gateway/filter/headers/XForwardedHeadersFilter.java | 2 +- .../gateway/filter/headers/HttpHeadersFilterMixedTypeTests.java | 2 +- .../cloud/gateway/filter/headers/HttpHeadersFilterTests.java | 2 +- .../cloud/gateway/test/HttpBinCompatibleController.java | 2 +- 13 files changed, 13 insertions(+), 13 deletions(-) diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/ForwardedRequestHeadersFilter.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/ForwardedRequestHeadersFilter.java index 7924c5ef..e4fedaf9 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/ForwardedRequestHeadersFilter.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/ForwardedRequestHeadersFilter.java @@ -93,7 +93,7 @@ public class ForwardedRequestHeadersFilter implements HttpHeadersFilter.RequestH HttpHeaders updated = new HttpHeaders(); // copy all headers except Forwarded - for (Map.Entry> entry : original.entrySet()) { + for (Map.Entry> entry : original.headerSet()) { if (!entry.getKey().equalsIgnoreCase(FORWARDED_HEADER)) { updated.addAll(entry.getKey(), entry.getValue()); } diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/RemoveHopByHopRequestHeadersFilter.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/RemoveHopByHopRequestHeadersFilter.java index d57c2213..0206a38d 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/RemoveHopByHopRequestHeadersFilter.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/RemoveHopByHopRequestHeadersFilter.java @@ -55,7 +55,7 @@ public class RemoveHopByHopRequestHeadersFilter implements RequestHttpHeadersFil static HttpHeaders filter(HttpHeaders input, Set headersToRemove) { HttpHeaders filtered = new HttpHeaders(); - for (Map.Entry> entry : input.entrySet()) { + for (Map.Entry> entry : input.headerSet()) { if (!headersToRemove.contains(entry.getKey().toLowerCase(Locale.ROOT))) { filtered.addAll(entry.getKey(), entry.getValue()); } diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/XForwardedRequestHeadersFilter.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/XForwardedRequestHeadersFilter.java index 428370b6..db5e0539 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/XForwardedRequestHeadersFilter.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/XForwardedRequestHeadersFilter.java @@ -374,7 +374,7 @@ public class XForwardedRequestHeadersFilter implements HttpHeadersFilter.Request HttpHeaders original = input; HttpHeaders updated = new HttpHeaders(); - for (Map.Entry> entry : original.entrySet()) { + for (Map.Entry> entry : original.headerSet()) { updated.addAll(entry.getKey(), entry.getValue()); } diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/client/ExchangeResult.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/client/ExchangeResult.java index 427aed77..e87ea133 100644 --- a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/client/ExchangeResult.java +++ b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/client/ExchangeResult.java @@ -264,7 +264,7 @@ public class ExchangeResult { } private String formatHeaders(HttpHeaders headers, String delimiter) { - return headers.entrySet() + return headers.headerSet() .stream() .map(entry -> entry.getKey() + ": " + entry.getValue()) .collect(Collectors.joining(delimiter)); diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/WebsocketRoutingFilter.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/WebsocketRoutingFilter.java index 04b02e22..7a0bddb0 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/WebsocketRoutingFilter.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/WebsocketRoutingFilter.java @@ -143,7 +143,7 @@ public class WebsocketRoutingFilter implements GlobalFilter, Ordered { headersFilters.add((headers, exchange) -> { HttpHeaders filtered = new HttpHeaders(); - for (Map.Entry> entry : headers.entrySet()) { + for (Map.Entry> entry : headers.headerSet()) { if (!entry.getKey().toLowerCase(Locale.ROOT).startsWith("sec-websocket")) { filtered.addAll(entry.getKey(), entry.getValue()); } diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RequestHeaderSizeGatewayFilterFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RequestHeaderSizeGatewayFilterFactory.java index 8dcd9413..b6e0d6b6 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RequestHeaderSizeGatewayFilterFactory.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RequestHeaderSizeGatewayFilterFactory.java @@ -69,7 +69,7 @@ public class RequestHeaderSizeGatewayFilterFactory HttpHeaders headers = request.getHeaders(); HashMap longHeaders = new HashMap<>(); - for (Map.Entry> headerEntry : headers.entrySet()) { + for (Map.Entry> headerEntry : headers.headerSet()) { long headerSizeInBytes = 0L; headerSizeInBytes += headerEntry.getKey().getBytes().length; List values = headerEntry.getValue(); diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/ForwardedHeadersFilter.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/ForwardedHeadersFilter.java index 788d305c..b95cb191 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/ForwardedHeadersFilter.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/ForwardedHeadersFilter.java @@ -96,7 +96,7 @@ public class ForwardedHeadersFilter implements HttpHeadersFilter, Ordered { HttpHeaders updated = new HttpHeaders(); // copy all headers except Forwarded - for (Map.Entry> entry : original.entrySet()) { + for (Map.Entry> entry : original.headerSet()) { if (!entry.getKey().equalsIgnoreCase(FORWARDED_HEADER)) { updated.addAll(entry.getKey(), entry.getValue()); } diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/GRPCRequestHeadersFilter.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/GRPCRequestHeadersFilter.java index 71489ae9..1c7e9318 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/GRPCRequestHeadersFilter.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/GRPCRequestHeadersFilter.java @@ -33,7 +33,7 @@ public class GRPCRequestHeadersFilter implements HttpHeadersFilter, Ordered { public HttpHeaders filter(HttpHeaders headers, ServerWebExchange exchange) { HttpHeaders updated = new HttpHeaders(); - for (Map.Entry> entry : headers.entrySet()) { + for (Map.Entry> entry : headers.headerSet()) { updated.addAll(entry.getKey(), entry.getValue()); } diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/RemoveHopByHopHeadersFilter.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/RemoveHopByHopHeadersFilter.java index 2a43414b..556f1c97 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/RemoveHopByHopHeadersFilter.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/RemoveHopByHopHeadersFilter.java @@ -74,7 +74,7 @@ public class RemoveHopByHopHeadersFilter implements HttpHeadersFilter, Ordered { Set headersToRemove = new HashSet<>(headers); headersToRemove.addAll(connectionOptions); - for (Map.Entry> entry : originalHeaders.entrySet()) { + for (Map.Entry> entry : originalHeaders.headerSet()) { if (!headersToRemove.contains(entry.getKey().toLowerCase(Locale.ROOT))) { filtered.addAll(entry.getKey(), entry.getValue()); } diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/XForwardedHeadersFilter.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/XForwardedHeadersFilter.java index 1623349e..2c1e8363 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/XForwardedHeadersFilter.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/XForwardedHeadersFilter.java @@ -202,7 +202,7 @@ public class XForwardedHeadersFilter implements HttpHeadersFilter, Ordered { HttpHeaders original = input; HttpHeaders updated = new HttpHeaders(); - for (Map.Entry> entry : original.entrySet()) { + for (Map.Entry> entry : original.headerSet()) { updated.addAll(entry.getKey(), entry.getValue()); } diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/HttpHeadersFilterMixedTypeTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/HttpHeadersFilterMixedTypeTests.java index 1ee3cb38..cbb9a4d2 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/HttpHeadersFilterMixedTypeTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/HttpHeadersFilterMixedTypeTests.java @@ -59,7 +59,7 @@ public class HttpHeadersFilterMixedTypeTests { @Override public HttpHeaders filter(HttpHeaders headers, ServerWebExchange exchange) { HttpHeaders result = new HttpHeaders(); - headers.entrySet().forEach(entry -> { + headers.headerSet().forEach(entry -> { if (!headerNamesSet.contains(entry.getKey())) { result.put(entry.getKey(), entry.getValue()); } diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/HttpHeadersFilterTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/HttpHeadersFilterTests.java index 9940d035..9619b71f 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/HttpHeadersFilterTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/HttpHeadersFilterTests.java @@ -52,7 +52,7 @@ public class HttpHeadersFilterTests { private HttpHeaders filter(HttpHeaders input, String keyToFilter) { HttpHeaders filtered = new HttpHeaders(); - input.entrySet() + input.headerSet() .stream() .filter(entry -> !entry.getKey().equals(keyToFilter)) .forEach(entry -> filtered.addAll(entry.getKey(), entry.getValue())); diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/HttpBinCompatibleController.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/HttpBinCompatibleController.java index dbf7dd24..5872965f 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/HttpBinCompatibleController.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/HttpBinCompatibleController.java @@ -177,7 +177,7 @@ public class HttpBinCompatibleController { public ResponseEntity> responseHeaders(@PathVariable int status, ServerWebExchange exchange) { HttpHeaders httpHeaders = exchange.getRequest() .getHeaders() - .entrySet() + .headerSet() .stream() .filter(entry -> entry.getKey().startsWith("X-Test-")) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, From 8f3ce7666831575f8b0d537a43565614a7a4395d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Khan=20/=20=EC=9D=B4=EC=B0=BD=EB=AF=BC?= Date: Sat, 9 Nov 2024 00:24:42 +0900 Subject: [PATCH 37/90] Fix: incorrect link in configuration-properties --- .../pages/spring-cloud-gateway/configuration-properties.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/modules/ROOT/pages/spring-cloud-gateway/configuration-properties.adoc b/docs/modules/ROOT/pages/spring-cloud-gateway/configuration-properties.adoc index 1aed5173..7d7642d2 100644 --- a/docs/modules/ROOT/pages/spring-cloud-gateway/configuration-properties.adoc +++ b/docs/modules/ROOT/pages/spring-cloud-gateway/configuration-properties.adoc @@ -2,4 +2,4 @@ = Configuration properties :page-section-summary-toc: 1 -To see the list of all Spring Cloud Gateway related configuration properties, see link:appendix.html[the appendix]. +To see the list of all Spring Cloud Gateway related configuration properties, see link:../appendix.html[the appendix]. From 4067254809fd5162df768d3c5c2107f11c0495d8 Mon Sep 17 00:00:00 2001 From: spring-builds Date: Wed, 20 Nov 2024 13:30:57 +0000 Subject: [PATCH 38/90] Bumping versions --- README.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.adoc b/README.adoc index 32d1ae73..f980ffa7 100644 --- a/README.adoc +++ b/README.adoc @@ -52,9 +52,9 @@ given the ability to merge pull requests. [[code-of-conduct]] == Code of Conduct -This project adheres to the Contributor Covenant https://github.com/spring-cloud/spring-cloud-build/blob/main/docs/src/main/asciidoc/code-of-conduct.adoc[code of +This project adheres to the Contributor Covenant https://github.com/spring-cloud/spring-cloud-build/blob/main/docs/modules/ROOT/partials/code-of-conduct.adoc[code of conduct]. By participating, you are expected to uphold this code. Please report -unacceptable behavior to spring-code-of-conduct@pivotal.io. +unacceptable behavior to code-of-conduct@spring.io. [[code-conventions-and-housekeeping]] == Code Conventions and Housekeeping From 571c35bca5d41649a2411a23dfe40fa1775feece Mon Sep 17 00:00:00 2001 From: spring-builds Date: Tue, 26 Nov 2024 17:18:37 +0000 Subject: [PATCH 39/90] Update SNAPSHOT to 4.1.6 --- docs/pom.xml | 2 +- pom.xml | 8 ++++---- spring-cloud-gateway-dependencies/pom.xml | 4 ++-- spring-cloud-gateway-integration-tests/grpc/pom.xml | 2 +- spring-cloud-gateway-integration-tests/http2/pom.xml | 2 +- .../mvc-failure-analyzer/pom.xml | 2 +- spring-cloud-gateway-integration-tests/pom.xml | 2 +- spring-cloud-gateway-mvc/pom.xml | 2 +- spring-cloud-gateway-sample/pom.xml | 2 +- spring-cloud-gateway-server-mvc/pom.xml | 2 +- spring-cloud-gateway-server/pom.xml | 2 +- spring-cloud-gateway-webflux/pom.xml | 2 +- spring-cloud-starter-gateway-mvc/pom.xml | 2 +- spring-cloud-starter-gateway/pom.xml | 2 +- 14 files changed, 18 insertions(+), 18 deletions(-) diff --git a/docs/pom.xml b/docs/pom.xml index 085bbd1d..687e2076 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -6,7 +6,7 @@ org.springframework.cloud spring-cloud-gateway - 4.1.6-SNAPSHOT + 4.1.6 spring-cloud-gateway-docs jar diff --git a/pom.xml b/pom.xml index 008753e5..55b1f66b 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.springframework.cloud spring-cloud-gateway - 4.1.6-SNAPSHOT + 4.1.6 pom Spring Cloud Gateway @@ -15,7 +15,7 @@ org.springframework.cloud spring-cloud-build - 4.1.4-SNAPSHOT + 4.1.4 @@ -55,8 +55,8 @@ 1.0.8.RELEASE 17 1.9.1 - 3.1.3-SNAPSHOT - 4.1.5-SNAPSHOT + 3.1.3 + 4.1.5 diff --git a/spring-cloud-gateway-dependencies/pom.xml b/spring-cloud-gateway-dependencies/pom.xml index f82500bf..a4b233f8 100644 --- a/spring-cloud-gateway-dependencies/pom.xml +++ b/spring-cloud-gateway-dependencies/pom.xml @@ -6,12 +6,12 @@ spring-cloud-dependencies-parent org.springframework.cloud - 4.1.4-SNAPSHOT + 4.1.4 spring-cloud-gateway-dependencies - 4.1.6-SNAPSHOT + 4.1.6 pom spring-cloud-gateway-dependencies diff --git a/spring-cloud-gateway-integration-tests/grpc/pom.xml b/spring-cloud-gateway-integration-tests/grpc/pom.xml index d7be74ae..cd7eb302 100644 --- a/spring-cloud-gateway-integration-tests/grpc/pom.xml +++ b/spring-cloud-gateway-integration-tests/grpc/pom.xml @@ -18,7 +18,7 @@ org.springframework.cloud spring-cloud-gateway-integration-tests - 4.1.6-SNAPSHOT + 4.1.6 .. diff --git a/spring-cloud-gateway-integration-tests/http2/pom.xml b/spring-cloud-gateway-integration-tests/http2/pom.xml index 0b57feb5..ee5ab3dc 100644 --- a/spring-cloud-gateway-integration-tests/http2/pom.xml +++ b/spring-cloud-gateway-integration-tests/http2/pom.xml @@ -16,7 +16,7 @@ org.springframework.cloud spring-cloud-gateway-integration-tests - 4.1.6-SNAPSHOT + 4.1.6 .. diff --git a/spring-cloud-gateway-integration-tests/mvc-failure-analyzer/pom.xml b/spring-cloud-gateway-integration-tests/mvc-failure-analyzer/pom.xml index 4b6f30c2..2d988354 100644 --- a/spring-cloud-gateway-integration-tests/mvc-failure-analyzer/pom.xml +++ b/spring-cloud-gateway-integration-tests/mvc-failure-analyzer/pom.xml @@ -16,7 +16,7 @@ org.springframework.cloud spring-cloud-gateway-integration-tests - 4.1.6-SNAPSHOT + 4.1.6 .. diff --git a/spring-cloud-gateway-integration-tests/pom.xml b/spring-cloud-gateway-integration-tests/pom.xml index f89b909d..1e01e12f 100644 --- a/spring-cloud-gateway-integration-tests/pom.xml +++ b/spring-cloud-gateway-integration-tests/pom.xml @@ -16,7 +16,7 @@ org.springframework.cloud spring-cloud-gateway - 4.1.6-SNAPSHOT + 4.1.6 .. diff --git a/spring-cloud-gateway-mvc/pom.xml b/spring-cloud-gateway-mvc/pom.xml index ad39869a..67aefea0 100644 --- a/spring-cloud-gateway-mvc/pom.xml +++ b/spring-cloud-gateway-mvc/pom.xml @@ -11,7 +11,7 @@ org.springframework.cloud spring-cloud-gateway - 4.1.6-SNAPSHOT + 4.1.6 .. diff --git a/spring-cloud-gateway-sample/pom.xml b/spring-cloud-gateway-sample/pom.xml index 8cd7a0c9..7ab9ce7d 100644 --- a/spring-cloud-gateway-sample/pom.xml +++ b/spring-cloud-gateway-sample/pom.xml @@ -16,7 +16,7 @@ org.springframework.cloud spring-cloud-gateway - 4.1.6-SNAPSHOT + 4.1.6 .. diff --git a/spring-cloud-gateway-server-mvc/pom.xml b/spring-cloud-gateway-server-mvc/pom.xml index b04f8739..58a8a192 100644 --- a/spring-cloud-gateway-server-mvc/pom.xml +++ b/spring-cloud-gateway-server-mvc/pom.xml @@ -22,7 +22,7 @@ org.springframework.cloud spring-cloud-gateway - 4.1.6-SNAPSHOT + 4.1.6 .. spring-cloud-gateway-server-mvc diff --git a/spring-cloud-gateway-server/pom.xml b/spring-cloud-gateway-server/pom.xml index a026b0f6..7942051b 100644 --- a/spring-cloud-gateway-server/pom.xml +++ b/spring-cloud-gateway-server/pom.xml @@ -7,7 +7,7 @@ org.springframework.cloud spring-cloud-gateway - 4.1.6-SNAPSHOT + 4.1.6 .. spring-cloud-gateway-server diff --git a/spring-cloud-gateway-webflux/pom.xml b/spring-cloud-gateway-webflux/pom.xml index f3f384aa..6a596348 100644 --- a/spring-cloud-gateway-webflux/pom.xml +++ b/spring-cloud-gateway-webflux/pom.xml @@ -11,7 +11,7 @@ org.springframework.cloud spring-cloud-gateway - 4.1.6-SNAPSHOT + 4.1.6 .. diff --git a/spring-cloud-starter-gateway-mvc/pom.xml b/spring-cloud-starter-gateway-mvc/pom.xml index c682053b..4c589ad4 100644 --- a/spring-cloud-starter-gateway-mvc/pom.xml +++ b/spring-cloud-starter-gateway-mvc/pom.xml @@ -6,7 +6,7 @@ org.springframework.cloud spring-cloud-gateway - 4.1.6-SNAPSHOT + 4.1.6 .. spring-cloud-starter-gateway-mvc diff --git a/spring-cloud-starter-gateway/pom.xml b/spring-cloud-starter-gateway/pom.xml index a60b865b..1cb24e46 100644 --- a/spring-cloud-starter-gateway/pom.xml +++ b/spring-cloud-starter-gateway/pom.xml @@ -6,7 +6,7 @@ org.springframework.cloud spring-cloud-gateway - 4.1.6-SNAPSHOT + 4.1.6 .. spring-cloud-starter-gateway From 21ad74985785ca32e72cd7c1398ce7a731da4c2d Mon Sep 17 00:00:00 2001 From: spring-builds Date: Tue, 26 Nov 2024 17:20:53 +0000 Subject: [PATCH 40/90] Going back to snapshots --- docs/pom.xml | 2 +- pom.xml | 8 ++++---- spring-cloud-gateway-dependencies/pom.xml | 4 ++-- spring-cloud-gateway-integration-tests/grpc/pom.xml | 2 +- spring-cloud-gateway-integration-tests/http2/pom.xml | 2 +- .../mvc-failure-analyzer/pom.xml | 2 +- spring-cloud-gateway-integration-tests/pom.xml | 2 +- spring-cloud-gateway-mvc/pom.xml | 2 +- spring-cloud-gateway-sample/pom.xml | 2 +- spring-cloud-gateway-server-mvc/pom.xml | 2 +- spring-cloud-gateway-server/pom.xml | 2 +- spring-cloud-gateway-webflux/pom.xml | 2 +- spring-cloud-starter-gateway-mvc/pom.xml | 2 +- spring-cloud-starter-gateway/pom.xml | 2 +- 14 files changed, 18 insertions(+), 18 deletions(-) diff --git a/docs/pom.xml b/docs/pom.xml index 687e2076..085bbd1d 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -6,7 +6,7 @@ org.springframework.cloud spring-cloud-gateway - 4.1.6 + 4.1.6-SNAPSHOT spring-cloud-gateway-docs jar diff --git a/pom.xml b/pom.xml index 55b1f66b..008753e5 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.springframework.cloud spring-cloud-gateway - 4.1.6 + 4.1.6-SNAPSHOT pom Spring Cloud Gateway @@ -15,7 +15,7 @@ org.springframework.cloud spring-cloud-build - 4.1.4 + 4.1.4-SNAPSHOT @@ -55,8 +55,8 @@ 1.0.8.RELEASE 17 1.9.1 - 3.1.3 - 4.1.5 + 3.1.3-SNAPSHOT + 4.1.5-SNAPSHOT diff --git a/spring-cloud-gateway-dependencies/pom.xml b/spring-cloud-gateway-dependencies/pom.xml index a4b233f8..f82500bf 100644 --- a/spring-cloud-gateway-dependencies/pom.xml +++ b/spring-cloud-gateway-dependencies/pom.xml @@ -6,12 +6,12 @@ spring-cloud-dependencies-parent org.springframework.cloud - 4.1.4 + 4.1.4-SNAPSHOT spring-cloud-gateway-dependencies - 4.1.6 + 4.1.6-SNAPSHOT pom spring-cloud-gateway-dependencies diff --git a/spring-cloud-gateway-integration-tests/grpc/pom.xml b/spring-cloud-gateway-integration-tests/grpc/pom.xml index cd7eb302..d7be74ae 100644 --- a/spring-cloud-gateway-integration-tests/grpc/pom.xml +++ b/spring-cloud-gateway-integration-tests/grpc/pom.xml @@ -18,7 +18,7 @@ org.springframework.cloud spring-cloud-gateway-integration-tests - 4.1.6 + 4.1.6-SNAPSHOT .. diff --git a/spring-cloud-gateway-integration-tests/http2/pom.xml b/spring-cloud-gateway-integration-tests/http2/pom.xml index ee5ab3dc..0b57feb5 100644 --- a/spring-cloud-gateway-integration-tests/http2/pom.xml +++ b/spring-cloud-gateway-integration-tests/http2/pom.xml @@ -16,7 +16,7 @@ org.springframework.cloud spring-cloud-gateway-integration-tests - 4.1.6 + 4.1.6-SNAPSHOT .. diff --git a/spring-cloud-gateway-integration-tests/mvc-failure-analyzer/pom.xml b/spring-cloud-gateway-integration-tests/mvc-failure-analyzer/pom.xml index 2d988354..4b6f30c2 100644 --- a/spring-cloud-gateway-integration-tests/mvc-failure-analyzer/pom.xml +++ b/spring-cloud-gateway-integration-tests/mvc-failure-analyzer/pom.xml @@ -16,7 +16,7 @@ org.springframework.cloud spring-cloud-gateway-integration-tests - 4.1.6 + 4.1.6-SNAPSHOT .. diff --git a/spring-cloud-gateway-integration-tests/pom.xml b/spring-cloud-gateway-integration-tests/pom.xml index 1e01e12f..f89b909d 100644 --- a/spring-cloud-gateway-integration-tests/pom.xml +++ b/spring-cloud-gateway-integration-tests/pom.xml @@ -16,7 +16,7 @@ org.springframework.cloud spring-cloud-gateway - 4.1.6 + 4.1.6-SNAPSHOT .. diff --git a/spring-cloud-gateway-mvc/pom.xml b/spring-cloud-gateway-mvc/pom.xml index 67aefea0..ad39869a 100644 --- a/spring-cloud-gateway-mvc/pom.xml +++ b/spring-cloud-gateway-mvc/pom.xml @@ -11,7 +11,7 @@ org.springframework.cloud spring-cloud-gateway - 4.1.6 + 4.1.6-SNAPSHOT .. diff --git a/spring-cloud-gateway-sample/pom.xml b/spring-cloud-gateway-sample/pom.xml index 7ab9ce7d..8cd7a0c9 100644 --- a/spring-cloud-gateway-sample/pom.xml +++ b/spring-cloud-gateway-sample/pom.xml @@ -16,7 +16,7 @@ org.springframework.cloud spring-cloud-gateway - 4.1.6 + 4.1.6-SNAPSHOT .. diff --git a/spring-cloud-gateway-server-mvc/pom.xml b/spring-cloud-gateway-server-mvc/pom.xml index 58a8a192..b04f8739 100644 --- a/spring-cloud-gateway-server-mvc/pom.xml +++ b/spring-cloud-gateway-server-mvc/pom.xml @@ -22,7 +22,7 @@ org.springframework.cloud spring-cloud-gateway - 4.1.6 + 4.1.6-SNAPSHOT .. spring-cloud-gateway-server-mvc diff --git a/spring-cloud-gateway-server/pom.xml b/spring-cloud-gateway-server/pom.xml index 7942051b..a026b0f6 100644 --- a/spring-cloud-gateway-server/pom.xml +++ b/spring-cloud-gateway-server/pom.xml @@ -7,7 +7,7 @@ org.springframework.cloud spring-cloud-gateway - 4.1.6 + 4.1.6-SNAPSHOT .. spring-cloud-gateway-server diff --git a/spring-cloud-gateway-webflux/pom.xml b/spring-cloud-gateway-webflux/pom.xml index 6a596348..f3f384aa 100644 --- a/spring-cloud-gateway-webflux/pom.xml +++ b/spring-cloud-gateway-webflux/pom.xml @@ -11,7 +11,7 @@ org.springframework.cloud spring-cloud-gateway - 4.1.6 + 4.1.6-SNAPSHOT .. diff --git a/spring-cloud-starter-gateway-mvc/pom.xml b/spring-cloud-starter-gateway-mvc/pom.xml index 4c589ad4..c682053b 100644 --- a/spring-cloud-starter-gateway-mvc/pom.xml +++ b/spring-cloud-starter-gateway-mvc/pom.xml @@ -6,7 +6,7 @@ org.springframework.cloud spring-cloud-gateway - 4.1.6 + 4.1.6-SNAPSHOT .. spring-cloud-starter-gateway-mvc diff --git a/spring-cloud-starter-gateway/pom.xml b/spring-cloud-starter-gateway/pom.xml index 1cb24e46..a60b865b 100644 --- a/spring-cloud-starter-gateway/pom.xml +++ b/spring-cloud-starter-gateway/pom.xml @@ -6,7 +6,7 @@ org.springframework.cloud spring-cloud-gateway - 4.1.6 + 4.1.6-SNAPSHOT .. spring-cloud-starter-gateway From 92c59468a5affb7f8d4f63bfbcd744d4c07ea3d5 Mon Sep 17 00:00:00 2001 From: spring-builds Date: Tue, 26 Nov 2024 17:20:54 +0000 Subject: [PATCH 41/90] Bumping versions to 4.1.7-SNAPSHOT after release --- docs/pom.xml | 2 +- pom.xml | 2 +- spring-cloud-gateway-dependencies/pom.xml | 2 +- spring-cloud-gateway-integration-tests/grpc/pom.xml | 2 +- spring-cloud-gateway-integration-tests/http2/pom.xml | 2 +- .../mvc-failure-analyzer/pom.xml | 2 +- spring-cloud-gateway-integration-tests/pom.xml | 2 +- spring-cloud-gateway-mvc/pom.xml | 2 +- spring-cloud-gateway-sample/pom.xml | 2 +- spring-cloud-gateway-server-mvc/pom.xml | 2 +- spring-cloud-gateway-server/pom.xml | 2 +- spring-cloud-gateway-webflux/pom.xml | 2 +- spring-cloud-starter-gateway-mvc/pom.xml | 2 +- spring-cloud-starter-gateway/pom.xml | 2 +- 14 files changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/pom.xml b/docs/pom.xml index 085bbd1d..4c71f5db 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -6,7 +6,7 @@ org.springframework.cloud spring-cloud-gateway - 4.1.6-SNAPSHOT + 4.1.7-SNAPSHOT spring-cloud-gateway-docs jar diff --git a/pom.xml b/pom.xml index 008753e5..5a195f32 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.springframework.cloud spring-cloud-gateway - 4.1.6-SNAPSHOT + 4.1.7-SNAPSHOT pom Spring Cloud Gateway diff --git a/spring-cloud-gateway-dependencies/pom.xml b/spring-cloud-gateway-dependencies/pom.xml index f82500bf..2fa66a0f 100644 --- a/spring-cloud-gateway-dependencies/pom.xml +++ b/spring-cloud-gateway-dependencies/pom.xml @@ -11,7 +11,7 @@ spring-cloud-gateway-dependencies - 4.1.6-SNAPSHOT + 4.1.7-SNAPSHOT pom spring-cloud-gateway-dependencies diff --git a/spring-cloud-gateway-integration-tests/grpc/pom.xml b/spring-cloud-gateway-integration-tests/grpc/pom.xml index d7be74ae..5df23c4d 100644 --- a/spring-cloud-gateway-integration-tests/grpc/pom.xml +++ b/spring-cloud-gateway-integration-tests/grpc/pom.xml @@ -18,7 +18,7 @@ org.springframework.cloud spring-cloud-gateway-integration-tests - 4.1.6-SNAPSHOT + 4.1.7-SNAPSHOT .. diff --git a/spring-cloud-gateway-integration-tests/http2/pom.xml b/spring-cloud-gateway-integration-tests/http2/pom.xml index 0b57feb5..f74bfb93 100644 --- a/spring-cloud-gateway-integration-tests/http2/pom.xml +++ b/spring-cloud-gateway-integration-tests/http2/pom.xml @@ -16,7 +16,7 @@ org.springframework.cloud spring-cloud-gateway-integration-tests - 4.1.6-SNAPSHOT + 4.1.7-SNAPSHOT .. diff --git a/spring-cloud-gateway-integration-tests/mvc-failure-analyzer/pom.xml b/spring-cloud-gateway-integration-tests/mvc-failure-analyzer/pom.xml index 4b6f30c2..db507feb 100644 --- a/spring-cloud-gateway-integration-tests/mvc-failure-analyzer/pom.xml +++ b/spring-cloud-gateway-integration-tests/mvc-failure-analyzer/pom.xml @@ -16,7 +16,7 @@ org.springframework.cloud spring-cloud-gateway-integration-tests - 4.1.6-SNAPSHOT + 4.1.7-SNAPSHOT .. diff --git a/spring-cloud-gateway-integration-tests/pom.xml b/spring-cloud-gateway-integration-tests/pom.xml index f89b909d..121e9019 100644 --- a/spring-cloud-gateway-integration-tests/pom.xml +++ b/spring-cloud-gateway-integration-tests/pom.xml @@ -16,7 +16,7 @@ org.springframework.cloud spring-cloud-gateway - 4.1.6-SNAPSHOT + 4.1.7-SNAPSHOT .. diff --git a/spring-cloud-gateway-mvc/pom.xml b/spring-cloud-gateway-mvc/pom.xml index ad39869a..6141755d 100644 --- a/spring-cloud-gateway-mvc/pom.xml +++ b/spring-cloud-gateway-mvc/pom.xml @@ -11,7 +11,7 @@ org.springframework.cloud spring-cloud-gateway - 4.1.6-SNAPSHOT + 4.1.7-SNAPSHOT .. diff --git a/spring-cloud-gateway-sample/pom.xml b/spring-cloud-gateway-sample/pom.xml index 8cd7a0c9..2f7b6297 100644 --- a/spring-cloud-gateway-sample/pom.xml +++ b/spring-cloud-gateway-sample/pom.xml @@ -16,7 +16,7 @@ org.springframework.cloud spring-cloud-gateway - 4.1.6-SNAPSHOT + 4.1.7-SNAPSHOT .. diff --git a/spring-cloud-gateway-server-mvc/pom.xml b/spring-cloud-gateway-server-mvc/pom.xml index b04f8739..9ecf0a73 100644 --- a/spring-cloud-gateway-server-mvc/pom.xml +++ b/spring-cloud-gateway-server-mvc/pom.xml @@ -22,7 +22,7 @@ org.springframework.cloud spring-cloud-gateway - 4.1.6-SNAPSHOT + 4.1.7-SNAPSHOT .. spring-cloud-gateway-server-mvc diff --git a/spring-cloud-gateway-server/pom.xml b/spring-cloud-gateway-server/pom.xml index a026b0f6..f7bc1beb 100644 --- a/spring-cloud-gateway-server/pom.xml +++ b/spring-cloud-gateway-server/pom.xml @@ -7,7 +7,7 @@ org.springframework.cloud spring-cloud-gateway - 4.1.6-SNAPSHOT + 4.1.7-SNAPSHOT .. spring-cloud-gateway-server diff --git a/spring-cloud-gateway-webflux/pom.xml b/spring-cloud-gateway-webflux/pom.xml index f3f384aa..2c529a28 100644 --- a/spring-cloud-gateway-webflux/pom.xml +++ b/spring-cloud-gateway-webflux/pom.xml @@ -11,7 +11,7 @@ org.springframework.cloud spring-cloud-gateway - 4.1.6-SNAPSHOT + 4.1.7-SNAPSHOT .. diff --git a/spring-cloud-starter-gateway-mvc/pom.xml b/spring-cloud-starter-gateway-mvc/pom.xml index c682053b..ccfa5c5f 100644 --- a/spring-cloud-starter-gateway-mvc/pom.xml +++ b/spring-cloud-starter-gateway-mvc/pom.xml @@ -6,7 +6,7 @@ org.springframework.cloud spring-cloud-gateway - 4.1.6-SNAPSHOT + 4.1.7-SNAPSHOT .. spring-cloud-starter-gateway-mvc diff --git a/spring-cloud-starter-gateway/pom.xml b/spring-cloud-starter-gateway/pom.xml index a60b865b..614e9dd5 100644 --- a/spring-cloud-starter-gateway/pom.xml +++ b/spring-cloud-starter-gateway/pom.xml @@ -6,7 +6,7 @@ org.springframework.cloud spring-cloud-gateway - 4.1.6-SNAPSHOT + 4.1.7-SNAPSHOT .. spring-cloud-starter-gateway From 3487f61dc5177304b524359b7597e3fbcbb92963 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Tue, 26 Nov 2024 18:23:45 +0100 Subject: [PATCH 42/90] Update pom.xml --- docs/pom.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/pom.xml b/docs/pom.xml index 4c71f5db..58b6f5f2 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -25,6 +25,7 @@ .* ${maven.multiModuleProjectDirectory}/docs/modules/ROOT/partials/ + src/main/asciidoc From 908471d897c9526128a0c8878700b9504e7c9c7c Mon Sep 17 00:00:00 2001 From: spring-builds Date: Tue, 26 Nov 2024 17:43:43 +0000 Subject: [PATCH 43/90] Update SNAPSHOT to 4.1.6 --- docs/pom.xml | 2 +- pom.xml | 8 ++++---- spring-cloud-gateway-dependencies/pom.xml | 4 ++-- spring-cloud-gateway-integration-tests/grpc/pom.xml | 2 +- spring-cloud-gateway-integration-tests/http2/pom.xml | 2 +- .../mvc-failure-analyzer/pom.xml | 2 +- spring-cloud-gateway-integration-tests/pom.xml | 2 +- spring-cloud-gateway-mvc/pom.xml | 2 +- spring-cloud-gateway-sample/pom.xml | 2 +- spring-cloud-gateway-server-mvc/pom.xml | 2 +- spring-cloud-gateway-server/pom.xml | 2 +- spring-cloud-gateway-webflux/pom.xml | 2 +- spring-cloud-starter-gateway-mvc/pom.xml | 2 +- spring-cloud-starter-gateway/pom.xml | 2 +- 14 files changed, 18 insertions(+), 18 deletions(-) diff --git a/docs/pom.xml b/docs/pom.xml index 58b6f5f2..2ed38261 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -6,7 +6,7 @@ org.springframework.cloud spring-cloud-gateway - 4.1.7-SNAPSHOT + 4.1.6 spring-cloud-gateway-docs jar diff --git a/pom.xml b/pom.xml index 5a195f32..55b1f66b 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.springframework.cloud spring-cloud-gateway - 4.1.7-SNAPSHOT + 4.1.6 pom Spring Cloud Gateway @@ -15,7 +15,7 @@ org.springframework.cloud spring-cloud-build - 4.1.4-SNAPSHOT + 4.1.4 @@ -55,8 +55,8 @@ 1.0.8.RELEASE 17 1.9.1 - 3.1.3-SNAPSHOT - 4.1.5-SNAPSHOT + 3.1.3 + 4.1.5 diff --git a/spring-cloud-gateway-dependencies/pom.xml b/spring-cloud-gateway-dependencies/pom.xml index 2fa66a0f..a4b233f8 100644 --- a/spring-cloud-gateway-dependencies/pom.xml +++ b/spring-cloud-gateway-dependencies/pom.xml @@ -6,12 +6,12 @@ spring-cloud-dependencies-parent org.springframework.cloud - 4.1.4-SNAPSHOT + 4.1.4 spring-cloud-gateway-dependencies - 4.1.7-SNAPSHOT + 4.1.6 pom spring-cloud-gateway-dependencies diff --git a/spring-cloud-gateway-integration-tests/grpc/pom.xml b/spring-cloud-gateway-integration-tests/grpc/pom.xml index 5df23c4d..cd7eb302 100644 --- a/spring-cloud-gateway-integration-tests/grpc/pom.xml +++ b/spring-cloud-gateway-integration-tests/grpc/pom.xml @@ -18,7 +18,7 @@ org.springframework.cloud spring-cloud-gateway-integration-tests - 4.1.7-SNAPSHOT + 4.1.6 .. diff --git a/spring-cloud-gateway-integration-tests/http2/pom.xml b/spring-cloud-gateway-integration-tests/http2/pom.xml index f74bfb93..ee5ab3dc 100644 --- a/spring-cloud-gateway-integration-tests/http2/pom.xml +++ b/spring-cloud-gateway-integration-tests/http2/pom.xml @@ -16,7 +16,7 @@ org.springframework.cloud spring-cloud-gateway-integration-tests - 4.1.7-SNAPSHOT + 4.1.6 .. diff --git a/spring-cloud-gateway-integration-tests/mvc-failure-analyzer/pom.xml b/spring-cloud-gateway-integration-tests/mvc-failure-analyzer/pom.xml index db507feb..2d988354 100644 --- a/spring-cloud-gateway-integration-tests/mvc-failure-analyzer/pom.xml +++ b/spring-cloud-gateway-integration-tests/mvc-failure-analyzer/pom.xml @@ -16,7 +16,7 @@ org.springframework.cloud spring-cloud-gateway-integration-tests - 4.1.7-SNAPSHOT + 4.1.6 .. diff --git a/spring-cloud-gateway-integration-tests/pom.xml b/spring-cloud-gateway-integration-tests/pom.xml index 121e9019..1e01e12f 100644 --- a/spring-cloud-gateway-integration-tests/pom.xml +++ b/spring-cloud-gateway-integration-tests/pom.xml @@ -16,7 +16,7 @@ org.springframework.cloud spring-cloud-gateway - 4.1.7-SNAPSHOT + 4.1.6 .. diff --git a/spring-cloud-gateway-mvc/pom.xml b/spring-cloud-gateway-mvc/pom.xml index 6141755d..67aefea0 100644 --- a/spring-cloud-gateway-mvc/pom.xml +++ b/spring-cloud-gateway-mvc/pom.xml @@ -11,7 +11,7 @@ org.springframework.cloud spring-cloud-gateway - 4.1.7-SNAPSHOT + 4.1.6 .. diff --git a/spring-cloud-gateway-sample/pom.xml b/spring-cloud-gateway-sample/pom.xml index 2f7b6297..7ab9ce7d 100644 --- a/spring-cloud-gateway-sample/pom.xml +++ b/spring-cloud-gateway-sample/pom.xml @@ -16,7 +16,7 @@ org.springframework.cloud spring-cloud-gateway - 4.1.7-SNAPSHOT + 4.1.6 .. diff --git a/spring-cloud-gateway-server-mvc/pom.xml b/spring-cloud-gateway-server-mvc/pom.xml index 9ecf0a73..58a8a192 100644 --- a/spring-cloud-gateway-server-mvc/pom.xml +++ b/spring-cloud-gateway-server-mvc/pom.xml @@ -22,7 +22,7 @@ org.springframework.cloud spring-cloud-gateway - 4.1.7-SNAPSHOT + 4.1.6 .. spring-cloud-gateway-server-mvc diff --git a/spring-cloud-gateway-server/pom.xml b/spring-cloud-gateway-server/pom.xml index f7bc1beb..7942051b 100644 --- a/spring-cloud-gateway-server/pom.xml +++ b/spring-cloud-gateway-server/pom.xml @@ -7,7 +7,7 @@ org.springframework.cloud spring-cloud-gateway - 4.1.7-SNAPSHOT + 4.1.6 .. spring-cloud-gateway-server diff --git a/spring-cloud-gateway-webflux/pom.xml b/spring-cloud-gateway-webflux/pom.xml index 2c529a28..6a596348 100644 --- a/spring-cloud-gateway-webflux/pom.xml +++ b/spring-cloud-gateway-webflux/pom.xml @@ -11,7 +11,7 @@ org.springframework.cloud spring-cloud-gateway - 4.1.7-SNAPSHOT + 4.1.6 .. diff --git a/spring-cloud-starter-gateway-mvc/pom.xml b/spring-cloud-starter-gateway-mvc/pom.xml index ccfa5c5f..4c589ad4 100644 --- a/spring-cloud-starter-gateway-mvc/pom.xml +++ b/spring-cloud-starter-gateway-mvc/pom.xml @@ -6,7 +6,7 @@ org.springframework.cloud spring-cloud-gateway - 4.1.7-SNAPSHOT + 4.1.6 .. spring-cloud-starter-gateway-mvc diff --git a/spring-cloud-starter-gateway/pom.xml b/spring-cloud-starter-gateway/pom.xml index 614e9dd5..1cb24e46 100644 --- a/spring-cloud-starter-gateway/pom.xml +++ b/spring-cloud-starter-gateway/pom.xml @@ -6,7 +6,7 @@ org.springframework.cloud spring-cloud-gateway - 4.1.7-SNAPSHOT + 4.1.6 .. spring-cloud-starter-gateway From 5c5ce6e8c80beaa82a9463e58d70efaa6f135817 Mon Sep 17 00:00:00 2001 From: spring-builds Date: Tue, 26 Nov 2024 17:46:48 +0000 Subject: [PATCH 44/90] Going back to snapshots --- docs/pom.xml | 2 +- pom.xml | 8 ++++---- spring-cloud-gateway-dependencies/pom.xml | 4 ++-- spring-cloud-gateway-integration-tests/grpc/pom.xml | 2 +- spring-cloud-gateway-integration-tests/http2/pom.xml | 2 +- .../mvc-failure-analyzer/pom.xml | 2 +- spring-cloud-gateway-integration-tests/pom.xml | 2 +- spring-cloud-gateway-mvc/pom.xml | 2 +- spring-cloud-gateway-sample/pom.xml | 2 +- spring-cloud-gateway-server-mvc/pom.xml | 2 +- spring-cloud-gateway-server/pom.xml | 2 +- spring-cloud-gateway-webflux/pom.xml | 2 +- spring-cloud-starter-gateway-mvc/pom.xml | 2 +- spring-cloud-starter-gateway/pom.xml | 2 +- 14 files changed, 18 insertions(+), 18 deletions(-) diff --git a/docs/pom.xml b/docs/pom.xml index 2ed38261..58b6f5f2 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -6,7 +6,7 @@ org.springframework.cloud spring-cloud-gateway - 4.1.6 + 4.1.7-SNAPSHOT spring-cloud-gateway-docs jar diff --git a/pom.xml b/pom.xml index 55b1f66b..5a195f32 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.springframework.cloud spring-cloud-gateway - 4.1.6 + 4.1.7-SNAPSHOT pom Spring Cloud Gateway @@ -15,7 +15,7 @@ org.springframework.cloud spring-cloud-build - 4.1.4 + 4.1.4-SNAPSHOT @@ -55,8 +55,8 @@ 1.0.8.RELEASE 17 1.9.1 - 3.1.3 - 4.1.5 + 3.1.3-SNAPSHOT + 4.1.5-SNAPSHOT diff --git a/spring-cloud-gateway-dependencies/pom.xml b/spring-cloud-gateway-dependencies/pom.xml index a4b233f8..2fa66a0f 100644 --- a/spring-cloud-gateway-dependencies/pom.xml +++ b/spring-cloud-gateway-dependencies/pom.xml @@ -6,12 +6,12 @@ spring-cloud-dependencies-parent org.springframework.cloud - 4.1.4 + 4.1.4-SNAPSHOT spring-cloud-gateway-dependencies - 4.1.6 + 4.1.7-SNAPSHOT pom spring-cloud-gateway-dependencies diff --git a/spring-cloud-gateway-integration-tests/grpc/pom.xml b/spring-cloud-gateway-integration-tests/grpc/pom.xml index cd7eb302..5df23c4d 100644 --- a/spring-cloud-gateway-integration-tests/grpc/pom.xml +++ b/spring-cloud-gateway-integration-tests/grpc/pom.xml @@ -18,7 +18,7 @@ org.springframework.cloud spring-cloud-gateway-integration-tests - 4.1.6 + 4.1.7-SNAPSHOT .. diff --git a/spring-cloud-gateway-integration-tests/http2/pom.xml b/spring-cloud-gateway-integration-tests/http2/pom.xml index ee5ab3dc..f74bfb93 100644 --- a/spring-cloud-gateway-integration-tests/http2/pom.xml +++ b/spring-cloud-gateway-integration-tests/http2/pom.xml @@ -16,7 +16,7 @@ org.springframework.cloud spring-cloud-gateway-integration-tests - 4.1.6 + 4.1.7-SNAPSHOT .. diff --git a/spring-cloud-gateway-integration-tests/mvc-failure-analyzer/pom.xml b/spring-cloud-gateway-integration-tests/mvc-failure-analyzer/pom.xml index 2d988354..db507feb 100644 --- a/spring-cloud-gateway-integration-tests/mvc-failure-analyzer/pom.xml +++ b/spring-cloud-gateway-integration-tests/mvc-failure-analyzer/pom.xml @@ -16,7 +16,7 @@ org.springframework.cloud spring-cloud-gateway-integration-tests - 4.1.6 + 4.1.7-SNAPSHOT .. diff --git a/spring-cloud-gateway-integration-tests/pom.xml b/spring-cloud-gateway-integration-tests/pom.xml index 1e01e12f..121e9019 100644 --- a/spring-cloud-gateway-integration-tests/pom.xml +++ b/spring-cloud-gateway-integration-tests/pom.xml @@ -16,7 +16,7 @@ org.springframework.cloud spring-cloud-gateway - 4.1.6 + 4.1.7-SNAPSHOT .. diff --git a/spring-cloud-gateway-mvc/pom.xml b/spring-cloud-gateway-mvc/pom.xml index 67aefea0..6141755d 100644 --- a/spring-cloud-gateway-mvc/pom.xml +++ b/spring-cloud-gateway-mvc/pom.xml @@ -11,7 +11,7 @@ org.springframework.cloud spring-cloud-gateway - 4.1.6 + 4.1.7-SNAPSHOT .. diff --git a/spring-cloud-gateway-sample/pom.xml b/spring-cloud-gateway-sample/pom.xml index 7ab9ce7d..2f7b6297 100644 --- a/spring-cloud-gateway-sample/pom.xml +++ b/spring-cloud-gateway-sample/pom.xml @@ -16,7 +16,7 @@ org.springframework.cloud spring-cloud-gateway - 4.1.6 + 4.1.7-SNAPSHOT .. diff --git a/spring-cloud-gateway-server-mvc/pom.xml b/spring-cloud-gateway-server-mvc/pom.xml index 58a8a192..9ecf0a73 100644 --- a/spring-cloud-gateway-server-mvc/pom.xml +++ b/spring-cloud-gateway-server-mvc/pom.xml @@ -22,7 +22,7 @@ org.springframework.cloud spring-cloud-gateway - 4.1.6 + 4.1.7-SNAPSHOT .. spring-cloud-gateway-server-mvc diff --git a/spring-cloud-gateway-server/pom.xml b/spring-cloud-gateway-server/pom.xml index 7942051b..f7bc1beb 100644 --- a/spring-cloud-gateway-server/pom.xml +++ b/spring-cloud-gateway-server/pom.xml @@ -7,7 +7,7 @@ org.springframework.cloud spring-cloud-gateway - 4.1.6 + 4.1.7-SNAPSHOT .. spring-cloud-gateway-server diff --git a/spring-cloud-gateway-webflux/pom.xml b/spring-cloud-gateway-webflux/pom.xml index 6a596348..2c529a28 100644 --- a/spring-cloud-gateway-webflux/pom.xml +++ b/spring-cloud-gateway-webflux/pom.xml @@ -11,7 +11,7 @@ org.springframework.cloud spring-cloud-gateway - 4.1.6 + 4.1.7-SNAPSHOT .. diff --git a/spring-cloud-starter-gateway-mvc/pom.xml b/spring-cloud-starter-gateway-mvc/pom.xml index 4c589ad4..ccfa5c5f 100644 --- a/spring-cloud-starter-gateway-mvc/pom.xml +++ b/spring-cloud-starter-gateway-mvc/pom.xml @@ -6,7 +6,7 @@ org.springframework.cloud spring-cloud-gateway - 4.1.6 + 4.1.7-SNAPSHOT .. spring-cloud-starter-gateway-mvc diff --git a/spring-cloud-starter-gateway/pom.xml b/spring-cloud-starter-gateway/pom.xml index 1cb24e46..614e9dd5 100644 --- a/spring-cloud-starter-gateway/pom.xml +++ b/spring-cloud-starter-gateway/pom.xml @@ -6,7 +6,7 @@ org.springframework.cloud spring-cloud-gateway - 4.1.6 + 4.1.7-SNAPSHOT .. spring-cloud-starter-gateway From 4cae649e57b52fc1df1be95e422f13be2d467943 Mon Sep 17 00:00:00 2001 From: spring-builds Date: Tue, 26 Nov 2024 17:47:51 +0000 Subject: [PATCH 45/90] Bumping dependency versions after release --- pom.xml | 6 +++--- spring-cloud-gateway-dependencies/pom.xml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index 5a195f32..17a0178f 100644 --- a/pom.xml +++ b/pom.xml @@ -15,7 +15,7 @@ org.springframework.cloud spring-cloud-build - 4.1.4-SNAPSHOT + 4.1.5-SNAPSHOT @@ -55,8 +55,8 @@ 1.0.8.RELEASE 17 1.9.1 - 3.1.3-SNAPSHOT - 4.1.5-SNAPSHOT + 3.1.4-SNAPSHOT + 4.1.6-SNAPSHOT diff --git a/spring-cloud-gateway-dependencies/pom.xml b/spring-cloud-gateway-dependencies/pom.xml index 2fa66a0f..5a721b66 100644 --- a/spring-cloud-gateway-dependencies/pom.xml +++ b/spring-cloud-gateway-dependencies/pom.xml @@ -6,7 +6,7 @@ spring-cloud-dependencies-parent org.springframework.cloud - 4.1.4-SNAPSHOT + 4.1.5-SNAPSHOT From 20a7c9079146ab992867b66ea513f92fdae9a9f4 Mon Sep 17 00:00:00 2001 From: Olga Maciaszek-Sharma Date: Wed, 27 Nov 2024 18:09:13 +0100 Subject: [PATCH 46/90] Revert "Update pom.xml" This reverts commit 3487f61dc5177304b524359b7597e3fbcbb92963. --- docs/pom.xml | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/pom.xml b/docs/pom.xml index 58b6f5f2..4c71f5db 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -25,7 +25,6 @@ .* ${maven.multiModuleProjectDirectory}/docs/modules/ROOT/partials/ - src/main/asciidoc From 3419b41caa73134077f21bc4cad538521f783aa4 Mon Sep 17 00:00:00 2001 From: spencergibb Date: Fri, 13 Dec 2024 12:39:12 -0500 Subject: [PATCH 47/90] Re-enables CustomBlockHoundIntegrationTest --- .../cloud/gateway/test/CustomBlockHoundIntegrationTest.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/CustomBlockHoundIntegrationTest.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/CustomBlockHoundIntegrationTest.java index 69558c90..67ba0f7c 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/CustomBlockHoundIntegrationTest.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/CustomBlockHoundIntegrationTest.java @@ -18,8 +18,6 @@ package org.springframework.cloud.gateway.test; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.DisabledForJreRange; -import org.junit.jupiter.api.condition.JRE; import reactor.core.publisher.Mono; import reactor.core.scheduler.Schedulers; @@ -29,7 +27,6 @@ import reactor.core.scheduler.Schedulers; public class CustomBlockHoundIntegrationTest { @Test - @DisabledForJreRange(min = JRE.JAVA_16) public void shouldThrowErrorForBlockingCallWithCustomBlockHoundIntegration() { Assertions.assertThrows(RuntimeException.class, () -> Mono.fromCallable(() -> { Thread.sleep(1); From dec24e02b9183c64d88a4d5efb511ef6ac8c0352 Mon Sep 17 00:00:00 2001 From: spencergibb Date: Fri, 13 Dec 2024 14:35:39 -0500 Subject: [PATCH 48/90] Limits CustomBlockHoundIntegrationTest to java 17 --- .../cloud/gateway/test/CustomBlockHoundIntegrationTest.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/CustomBlockHoundIntegrationTest.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/CustomBlockHoundIntegrationTest.java index 67ba0f7c..228679ac 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/CustomBlockHoundIntegrationTest.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/CustomBlockHoundIntegrationTest.java @@ -18,6 +18,8 @@ package org.springframework.cloud.gateway.test; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledForJreRange; +import org.junit.jupiter.api.condition.JRE; import reactor.core.publisher.Mono; import reactor.core.scheduler.Schedulers; @@ -27,6 +29,7 @@ import reactor.core.scheduler.Schedulers; public class CustomBlockHoundIntegrationTest { @Test + @DisabledForJreRange(min = JRE.JAVA_18) public void shouldThrowErrorForBlockingCallWithCustomBlockHoundIntegration() { Assertions.assertThrows(RuntimeException.class, () -> Mono.fromCallable(() -> { Thread.sleep(1); From 3ec77a2d8dedee23b50111797011f95960a40a08 Mon Sep 17 00:00:00 2001 From: Ryan Baxter Date: Tue, 17 Dec 2024 10:50:14 -0500 Subject: [PATCH 49/90] Add the ability to disable configuration properties in docs pom --- docs/antora.yml | 2 +- docs/pom.xml | 28 ++++++++++++++++++---------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/docs/antora.yml b/docs/antora.yml index fad926be..4bfa002b 100644 --- a/docs/antora.yml +++ b/docs/antora.yml @@ -6,7 +6,7 @@ nav: ext: collector: run: - command: ./mvnw --no-transfer-progress -B process-resources -Pdocs -pl docs -Dantora-maven-plugin.phase=none -Dgenerate-docs.phase=none -Dgenerate-readme.phase=none -Dgenerate-cloud-resources.phase=none -Dmaven-dependency-plugin-for-docs.phase=none -Dmaven-dependency-plugin-for-docs-classes.phase=none -DskipTests + command: ./mvnw --no-transfer-progress -B process-resources -Pdocs -pl docs -Dantora-maven-plugin.phase=none -Dgenerate-docs.phase=none -Dgenerate-readme.phase=none -Dgenerate-cloud-resources.phase=none -Dmaven-dependency-plugin-for-docs.phase=none -Dmaven-dependency-plugin-for-docs-classes.phase=none -DskipTests -DdisableConfigurationProperties local: true scan: dir: ./target/classes/antora-resources/ diff --git a/docs/pom.xml b/docs/pom.xml index 4c71f5db..0fed8431 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -25,20 +25,28 @@ .* ${maven.multiModuleProjectDirectory}/docs/modules/ROOT/partials/ - - - ${project.groupId} - spring-cloud-starter-gateway - - - ${project.groupId} - spring-cloud-starter-gateway-mvc - - src/main/asciidoc + + enable-configuration-properties + + + !disableConfigurationProperties + + + + + ${project.groupId} + spring-cloud-starter-gateway + + + ${project.groupId} + spring-cloud-starter-gateway-mvc + + + docs From 9da4abd50c99cabb313621a461a8f7a345caf07c Mon Sep 17 00:00:00 2001 From: spring-builds Date: Mon, 13 Jan 2025 22:11:00 +0000 Subject: [PATCH 50/90] Bumping versions --- pom.xml | 4 ++-- spring-cloud-gateway-dependencies/pom.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 17a0178f..353ccff7 100644 --- a/pom.xml +++ b/pom.xml @@ -15,7 +15,7 @@ org.springframework.cloud spring-cloud-build - 4.1.5-SNAPSHOT + 4.1.6-SNAPSHOT @@ -55,7 +55,7 @@ 1.0.8.RELEASE 17 1.9.1 - 3.1.4-SNAPSHOT + 3.1.5-SNAPSHOT 4.1.6-SNAPSHOT diff --git a/spring-cloud-gateway-dependencies/pom.xml b/spring-cloud-gateway-dependencies/pom.xml index 5a721b66..198369ec 100644 --- a/spring-cloud-gateway-dependencies/pom.xml +++ b/spring-cloud-gateway-dependencies/pom.xml @@ -6,7 +6,7 @@ spring-cloud-dependencies-parent org.springframework.cloud - 4.1.5-SNAPSHOT + 4.1.6-SNAPSHOT From 21caf96774b2db94aca3ed7da8fa0e2f4bf60ed9 Mon Sep 17 00:00:00 2001 From: spencergibb Date: Thu, 16 Jan 2025 15:06:34 -0500 Subject: [PATCH 51/90] Adds dco.yml --- .github/dco.yml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .github/dco.yml diff --git a/.github/dco.yml b/.github/dco.yml new file mode 100644 index 00000000..0c4b142e --- /dev/null +++ b/.github/dco.yml @@ -0,0 +1,2 @@ +require: + members: false From 3802658b452edd67695a28af68fff65b7a49b37d Mon Sep 17 00:00:00 2001 From: spring-builds Date: Fri, 17 Jan 2025 13:30:00 +0000 Subject: [PATCH 52/90] Bumping versions --- README.adoc | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/README.adoc b/README.adoc index f980ffa7..9d84e36e 100644 --- a/README.adoc +++ b/README.adoc @@ -40,15 +40,11 @@ tracker for issues and merging pull requests into main. If you want to contribute even something trivial please do not hesitate, but follow the guidelines below. -[[sign-the-contributor-license-agreement]] -== Sign the Contributor License Agreement +[[developer-certificate-of-origin]] +== Developer Certificate of Origin (DCO) -Before we accept a non-trivial patch or pull request we will need you to sign the -https://cla.pivotal.io/sign/spring[Contributor License Agreement]. -Signing the contributor's agreement does not grant anyone commit rights to the main -repository, but it does mean that we can accept your contributions, and you will get an -author credit if we do. Active contributors might be asked to join the core team, and -given the ability to merge pull requests. +All commits must include a __Signed-off-by__ trailer at the end of each commit message to indicate that the contributor agrees to the Developer Certificate of Origin. +For additional details, please refer to the blog post https://spring.io/blog/2025/01/06/hello-dco-goodbye-cla-simplifying-contributions-to-spring[Hello DCO, Goodbye CLA: Simplifying Contributions to Spring]. [[code-of-conduct]] == Code of Conduct From 5abf0238e2aae2dc87f6ef790b84387e96f05a7c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Jan 2025 12:12:07 -0500 Subject: [PATCH 53/90] Bump antora from 3.2.0-alpha.4 to 3.2.0-alpha.8 in /docs (#3628) Bumps [antora](https://gitlab.com/antora/antora) from 3.2.0-alpha.4 to 3.2.0-alpha.8. - [Changelog](https://gitlab.com/antora/antora/blob/main/CHANGELOG.adoc) - [Commits](https://gitlab.com/antora/antora/compare/v3.2.0-alpha.4...v3.2.0-alpha.8) --- updated-dependencies: - dependency-name: antora dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- docs/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/package.json b/docs/package.json index c3570e2f..4c4e3383 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,6 +1,6 @@ { "dependencies": { - "antora": "3.2.0-alpha.4", + "antora": "3.2.0-alpha.8", "@antora/atlas-extension": "1.0.0-alpha.2", "@antora/collector-extension": "1.0.0-alpha.3", "@asciidoctor/tabs": "1.0.0-beta.6", From 43f3408228d6ef99df884c66faabf3aabc4f2d57 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Jan 2025 12:13:59 -0500 Subject: [PATCH 54/90] Bump org.jetbrains.kotlin:kotlin-maven-plugin from 1.9.23 to 1.9.25 (#3612) Bumps org.jetbrains.kotlin:kotlin-maven-plugin from 1.9.23 to 1.9.25. --- updated-dependencies: - dependency-name: org.jetbrains.kotlin:kotlin-maven-plugin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- spring-cloud-gateway-server/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-cloud-gateway-server/pom.xml b/spring-cloud-gateway-server/pom.xml index f7bc1beb..52a624d7 100644 --- a/spring-cloud-gateway-server/pom.xml +++ b/spring-cloud-gateway-server/pom.xml @@ -235,7 +235,7 @@ kotlin-maven-plugin org.jetbrains.kotlin - 1.9.23 + 1.9.25 17 From f0c204bab7bbe43ba28c4adeea12a38f6e10efb1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Jan 2025 12:16:10 -0500 Subject: [PATCH 55/90] Bump @antora/collector-extension from 1.0.0-alpha.3 to 1.0.1 in /docs (#3619) --- updated-dependencies: - dependency-name: "@antora/collector-extension" dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- docs/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/package.json b/docs/package.json index 4c4e3383..b6c96a18 100644 --- a/docs/package.json +++ b/docs/package.json @@ -2,7 +2,7 @@ "dependencies": { "antora": "3.2.0-alpha.8", "@antora/atlas-extension": "1.0.0-alpha.2", - "@antora/collector-extension": "1.0.0-alpha.3", + "@antora/collector-extension": "1.0.1", "@asciidoctor/tabs": "1.0.0-beta.6", "@springio/antora-extensions": "1.11.1", "@springio/asciidoctor-extensions": "1.0.0-alpha.10" From cdf3e5b2bab948d6b9aa726ad7a59eb3a31da45b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Jan 2025 12:18:36 -0500 Subject: [PATCH 56/90] Bump @springio/asciidoctor-extensions in /docs (#3614) Bumps [@springio/asciidoctor-extensions](https://github.com/spring-io/asciidoctor-extensions) from 1.0.0-alpha.10 to 1.0.0-alpha.14. - [Changelog](https://github.com/spring-io/asciidoctor-extensions/blob/main/CHANGELOG.adoc) - [Commits](https://github.com/spring-io/asciidoctor-extensions/compare/v1.0.0-alpha.10...v1.0.0-alpha.14) --- updated-dependencies: - dependency-name: "@springio/asciidoctor-extensions" dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- docs/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/package.json b/docs/package.json index b6c96a18..35722055 100644 --- a/docs/package.json +++ b/docs/package.json @@ -5,6 +5,6 @@ "@antora/collector-extension": "1.0.1", "@asciidoctor/tabs": "1.0.0-beta.6", "@springio/antora-extensions": "1.11.1", - "@springio/asciidoctor-extensions": "1.0.0-alpha.10" + "@springio/asciidoctor-extensions": "1.0.0-alpha.14" } } From c12287390b32b69bdeb5f05260eb4ab7be98f695 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Jan 2025 12:20:27 -0500 Subject: [PATCH 57/90] Bump @springio/antora-extensions from 1.11.1 to 1.14.2 in /docs (#3623) Bumps [@springio/antora-extensions](https://github.com/spring-io/antora-extensions) from 1.11.1 to 1.14.2. - [Changelog](https://github.com/spring-io/antora-extensions/blob/main/CHANGELOG.adoc) - [Commits](https://github.com/spring-io/antora-extensions/compare/v1.11.1...v1.14.2) --- updated-dependencies: - dependency-name: "@springio/antora-extensions" dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: Spencer Gibb Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Spencer Gibb --- docs/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/package.json b/docs/package.json index 35722055..567c1f3a 100644 --- a/docs/package.json +++ b/docs/package.json @@ -4,7 +4,7 @@ "@antora/atlas-extension": "1.0.0-alpha.2", "@antora/collector-extension": "1.0.1", "@asciidoctor/tabs": "1.0.0-beta.6", - "@springio/antora-extensions": "1.11.1", + "@springio/antora-extensions": "1.14.2", "@springio/asciidoctor-extensions": "1.0.0-alpha.14" } } From 805588953ed32f879afcf62febabd2237e8460b4 Mon Sep 17 00:00:00 2001 From: spencergibb Date: Wed, 22 Jan 2025 16:20:50 -0500 Subject: [PATCH 58/90] Sets spring.servlet.multipart.enabled to false by default. Fixes gh-3527 --- .../MultipartEnvironmentPostProcessor.java | 44 +++++++++++++++ .../main/resources/META-INF/spring.factories | 20 +++++++ ...ultipartEnvironmentPostProcessorTests.java | 55 +++++++++++++++++++ 3 files changed, 119 insertions(+) create mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/MultipartEnvironmentPostProcessor.java create mode 100644 spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/common/MultipartEnvironmentPostProcessorTests.java diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/MultipartEnvironmentPostProcessor.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/MultipartEnvironmentPostProcessor.java new file mode 100644 index 00000000..7558cf26 --- /dev/null +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/MultipartEnvironmentPostProcessor.java @@ -0,0 +1,44 @@ +/* + * Copyright 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.springframework.cloud.gateway.server.mvc.common; + +import java.util.Map; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.env.EnvironmentPostProcessor; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.MapPropertySource; +import org.springframework.util.StringUtils; + +public class MultipartEnvironmentPostProcessor implements EnvironmentPostProcessor { + + /* for testing */ static final String MULTIPART_ENABLED_PROPERTY = "spring.servlet.multipart.enabled"; + /* for testing */ static final String MULTIPART_PROPERTY_SOURCE_NAME = "gatewayServerWebmvcMultipartPropertySource"; + + @Override + public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { + String multipartEnabled = environment.getProperty(MULTIPART_ENABLED_PROPERTY); + if (!StringUtils.hasText(multipartEnabled)) { + // no user set property, set it to false. + MapPropertySource propertySource = new MapPropertySource(MULTIPART_PROPERTY_SOURCE_NAME, + Map.of(MULTIPART_ENABLED_PROPERTY, Boolean.FALSE)); + environment.getPropertySources().addFirst(propertySource); + } + } + +} diff --git a/spring-cloud-gateway-server-mvc/src/main/resources/META-INF/spring.factories b/spring-cloud-gateway-server-mvc/src/main/resources/META-INF/spring.factories index 972cdb6d..4b1b5077 100644 --- a/spring-cloud-gateway-server-mvc/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-gateway-server-mvc/src/main/resources/META-INF/spring.factories @@ -1,3 +1,20 @@ +# +# Copyright 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. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# + org.springframework.cloud.gateway.server.mvc.filter.FilterSupplier=\ org.springframework.cloud.gateway.server.mvc.filter.Bucket4jFilterFunctions.FilterSupplier,\ org.springframework.cloud.gateway.server.mvc.filter.CircuitBreakerFilterFunctions.FilterSupplier,\ @@ -12,3 +29,6 @@ org.springframework.cloud.gateway.server.mvc.handler.HandlerSupplier=\ org.springframework.cloud.gateway.server.mvc.predicate.PredicateSupplier=\ org.springframework.cloud.gateway.server.mvc.predicate.MvcPredicateSupplier,\ org.springframework.cloud.gateway.server.mvc.predicate.GatewayRequestPredicates.PredicateSupplier + +org.springframework.boot.env.EnvironmentPostProcessor=\ + org.springframework.cloud.gateway.server.mvc.common.MultipartEnvironmentPostProcessor \ No newline at end of file diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/common/MultipartEnvironmentPostProcessorTests.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/common/MultipartEnvironmentPostProcessorTests.java new file mode 100644 index 00000000..1815d9ca --- /dev/null +++ b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/common/MultipartEnvironmentPostProcessorTests.java @@ -0,0 +1,55 @@ +/* + * 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + + +package org.springframework.cloud.gateway.server.mvc.common; + +import org.junit.jupiter.api.Test; + +import org.springframework.mock.env.MockEnvironment; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.cloud.gateway.server.mvc.common.MultipartEnvironmentPostProcessor.MULTIPART_ENABLED_PROPERTY; +import static org.springframework.cloud.gateway.server.mvc.common.MultipartEnvironmentPostProcessor.MULTIPART_PROPERTY_SOURCE_NAME; + +public class MultipartEnvironmentPostProcessorTests { + + @Test + void multipartDisabledByDefault() { + MockEnvironment environment = new MockEnvironment(); + MultipartEnvironmentPostProcessor processor = new MultipartEnvironmentPostProcessor(); + processor.postProcessEnvironment(environment, null); + + assertThat(environment.getPropertySources().contains(MULTIPART_PROPERTY_SOURCE_NAME)).isTrue(); + + Boolean multipartEnabled = environment.getProperty(MULTIPART_ENABLED_PROPERTY, Boolean.class); + assertThat(multipartEnabled).isFalse(); + } + + @Test + void multipartEnabledByUser() { + MockEnvironment environment = new MockEnvironment(); + environment.setProperty(MULTIPART_ENABLED_PROPERTY, "true"); + MultipartEnvironmentPostProcessor processor = new MultipartEnvironmentPostProcessor(); + processor.postProcessEnvironment(environment, null); + + assertThat(environment.getPropertySources().contains(MULTIPART_PROPERTY_SOURCE_NAME)).isFalse(); + + Boolean multipartEnabled = environment.getProperty(MULTIPART_ENABLED_PROPERTY, Boolean.class); + assertThat(multipartEnabled).isTrue(); + } +} From 64c36c46b917cc6d0db8c7bdf015da88de89d185 Mon Sep 17 00:00:00 2001 From: spencergibb Date: Mon, 27 Jan 2025 12:55:00 -0500 Subject: [PATCH 59/90] Formatting --- .../server/mvc/common/MultipartEnvironmentPostProcessor.java | 4 ++-- .../mvc/common/MultipartEnvironmentPostProcessorTests.java | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/MultipartEnvironmentPostProcessor.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/MultipartEnvironmentPostProcessor.java index 7558cf26..e06226f9 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/MultipartEnvironmentPostProcessor.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/MultipartEnvironmentPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2025 the original author or authors. + * Copyright 2012-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. @@ -12,7 +12,6 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - * */ package org.springframework.cloud.gateway.server.mvc.common; @@ -28,6 +27,7 @@ import org.springframework.util.StringUtils; public class MultipartEnvironmentPostProcessor implements EnvironmentPostProcessor { /* for testing */ static final String MULTIPART_ENABLED_PROPERTY = "spring.servlet.multipart.enabled"; + /* for testing */ static final String MULTIPART_PROPERTY_SOURCE_NAME = "gatewayServerWebmvcMultipartPropertySource"; @Override diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/common/MultipartEnvironmentPostProcessorTests.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/common/MultipartEnvironmentPostProcessorTests.java index 1815d9ca..72dd1b3f 100644 --- a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/common/MultipartEnvironmentPostProcessorTests.java +++ b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/common/MultipartEnvironmentPostProcessorTests.java @@ -12,10 +12,8 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - * */ - package org.springframework.cloud.gateway.server.mvc.common; import org.junit.jupiter.api.Test; @@ -52,4 +50,5 @@ public class MultipartEnvironmentPostProcessorTests { Boolean multipartEnabled = environment.getProperty(MULTIPART_ENABLED_PROPERTY, Boolean.class); assertThat(multipartEnabled).isTrue(); } + } From cb404cf836e0590e78575a9b3e2d3245f85beca4 Mon Sep 17 00:00:00 2001 From: spencergibb Date: Mon, 27 Jan 2025 16:16:17 -0500 Subject: [PATCH 60/90] Effectively disables MultipartEnvironmentPostProcessor --- .../server/mvc/common/MultipartEnvironmentPostProcessor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/MultipartEnvironmentPostProcessor.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/MultipartEnvironmentPostProcessor.java index e06226f9..06b77e58 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/MultipartEnvironmentPostProcessor.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/MultipartEnvironmentPostProcessor.java @@ -37,7 +37,7 @@ public class MultipartEnvironmentPostProcessor implements EnvironmentPostProcess // no user set property, set it to false. MapPropertySource propertySource = new MapPropertySource(MULTIPART_PROPERTY_SOURCE_NAME, Map.of(MULTIPART_ENABLED_PROPERTY, Boolean.FALSE)); - environment.getPropertySources().addFirst(propertySource); + //environment.getPropertySources().addFirst(propertySource); } } From 92e8cfc0692842c33b12dd363427cd9005aa499f Mon Sep 17 00:00:00 2001 From: spencergibb Date: Tue, 28 Jan 2025 13:26:40 -0500 Subject: [PATCH 61/90] Effectively disables MultipartEnvironmentPostProcessor --- .../mvc/common/MultipartEnvironmentPostProcessorTests.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/common/MultipartEnvironmentPostProcessorTests.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/common/MultipartEnvironmentPostProcessorTests.java index 72dd1b3f..f62373ec 100644 --- a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/common/MultipartEnvironmentPostProcessorTests.java +++ b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/common/MultipartEnvironmentPostProcessorTests.java @@ -15,7 +15,7 @@ */ package org.springframework.cloud.gateway.server.mvc.common; - +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.mock.env.MockEnvironment; @@ -27,6 +27,7 @@ import static org.springframework.cloud.gateway.server.mvc.common.MultipartEnvir public class MultipartEnvironmentPostProcessorTests { @Test + @Disabled void multipartDisabledByDefault() { MockEnvironment environment = new MockEnvironment(); MultipartEnvironmentPostProcessor processor = new MultipartEnvironmentPostProcessor(); From ff2c7e7ae83c5dc7b49025a9de88a304aa05f609 Mon Sep 17 00:00:00 2001 From: spencergibb Date: Tue, 28 Jan 2025 13:29:58 -0500 Subject: [PATCH 62/90] Effectively disables MultipartEnvironmentPostProcessor --- .../server/mvc/common/MultipartEnvironmentPostProcessor.java | 2 +- .../mvc/common/MultipartEnvironmentPostProcessorTests.java | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/MultipartEnvironmentPostProcessor.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/MultipartEnvironmentPostProcessor.java index 06b77e58..1ac9abdc 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/MultipartEnvironmentPostProcessor.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/MultipartEnvironmentPostProcessor.java @@ -37,7 +37,7 @@ public class MultipartEnvironmentPostProcessor implements EnvironmentPostProcess // no user set property, set it to false. MapPropertySource propertySource = new MapPropertySource(MULTIPART_PROPERTY_SOURCE_NAME, Map.of(MULTIPART_ENABLED_PROPERTY, Boolean.FALSE)); - //environment.getPropertySources().addFirst(propertySource); + // environment.getPropertySources().addFirst(propertySource); } } diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/common/MultipartEnvironmentPostProcessorTests.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/common/MultipartEnvironmentPostProcessorTests.java index f62373ec..638bb86e 100644 --- a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/common/MultipartEnvironmentPostProcessorTests.java +++ b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/common/MultipartEnvironmentPostProcessorTests.java @@ -15,6 +15,7 @@ */ package org.springframework.cloud.gateway.server.mvc.common; + import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; From d83e890cd1b9ff7cd3f6d07ccef11e0c56d3ec70 Mon Sep 17 00:00:00 2001 From: spencergibb Date: Tue, 4 Feb 2025 16:27:42 -0500 Subject: [PATCH 63/90] Removed var --- .../server/mvc/handler/ProxyExchangeHandlerFunction.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/ProxyExchangeHandlerFunction.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/ProxyExchangeHandlerFunction.java index 84ce2018..0ea445fc 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/ProxyExchangeHandlerFunction.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/ProxyExchangeHandlerFunction.java @@ -123,9 +123,9 @@ public class ProxyExchangeHandlerFunction private HttpHeaders filterHeaders(List filters, HttpHeaders original, REQUEST_OR_RESPONSE requestOrResponse) { HttpHeaders filtered = original; - for (var filter : filters) { + for (Object filter : filters) { @SuppressWarnings("unchecked") - var typed = ((HttpHeadersFilter) filter); + HttpHeadersFilter typed = ((HttpHeadersFilter) filter); filtered = typed.apply(filtered, requestOrResponse); } return filtered; From eef10abba3967ce93463bdcc0b6e0af21b791e32 Mon Sep 17 00:00:00 2001 From: spencergibb Date: Tue, 4 Feb 2025 16:29:12 -0500 Subject: [PATCH 64/90] Re-enables MultipartEnvironmentPostProcessor Closes gh-3527 --- .../MultipartEnvironmentPostProcessor.java | 2 +- .../server/mvc/ServerMvcIntegrationTests.java | 23 +++++++++--- ...ultipartEnvironmentPostProcessorTests.java | 2 -- .../server/mvc/test/TestController.java | 36 +------------------ 4 files changed, 20 insertions(+), 43 deletions(-) diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/MultipartEnvironmentPostProcessor.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/MultipartEnvironmentPostProcessor.java index 1ac9abdc..e06226f9 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/MultipartEnvironmentPostProcessor.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/MultipartEnvironmentPostProcessor.java @@ -37,7 +37,7 @@ public class MultipartEnvironmentPostProcessor implements EnvironmentPostProcess // no user set property, set it to false. MapPropertySource propertySource = new MapPropertySource(MULTIPART_PROPERTY_SOURCE_NAME, Map.of(MULTIPART_ENABLED_PROPERTY, Boolean.FALSE)); - // environment.getPropertySources().addFirst(propertySource); + environment.getPropertySources().addFirst(propertySource); } } diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/ServerMvcIntegrationTests.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/ServerMvcIntegrationTests.java index d9a7dc7b..4af00c8b 100644 --- a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/ServerMvcIntegrationTests.java +++ b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/ServerMvcIntegrationTests.java @@ -22,6 +22,7 @@ import java.io.InputStream; import java.net.URI; import java.nio.charset.StandardCharsets; import java.time.Duration; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Locale; @@ -484,7 +485,7 @@ public class ServerMvcIntegrationTests { @Test public void rewritePathPostLocalWorks() { restClient.post() - .uri("/baz/post") + .uri("/baz/localpost") .bodyValue("hello") .header("Host", "www.rewritepathpostlocal.org") .exchange() @@ -636,8 +637,21 @@ public class ServerMvcIntegrationTests { private void assertMultipartData(Map responseBody) { Map files = (Map) responseBody.get("files"); assertThat(files).containsKey("imgpart"); - String file = (String) files.get("imgpart"); - assertThat(file).startsWith("data:").contains(";base64,"); + Object imgpart = files.get("imgpart"); + if (imgpart instanceof List l) { + String file = (String) l.get(0); + assertThat(isPNG(file.getBytes())); + } + else { + String file = (String) imgpart; + assertThat(file).startsWith("data:").contains(";base64,"); + } + } + + private static boolean isPNG(byte[] bytes) { + byte[] pngSignature = { (byte) 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A }; + byte[] header = Arrays.copyOf(bytes, pngSignature.length); + return Arrays.equals(pngSignature, header); } @Test @@ -1279,8 +1293,7 @@ public class ServerMvcIntegrationTests { // @formatter:off return route("testform") .POST("/post", host("**.testform.org"), http()) - .before(new LocalServerPortUriResolver()) - .filter(prefixPath("/test")) + .filter(new HttpbinUriResolver()) .filter(addRequestHeader("X-Test", "form")) .build(); // @formatter:on diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/common/MultipartEnvironmentPostProcessorTests.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/common/MultipartEnvironmentPostProcessorTests.java index 638bb86e..72dd1b3f 100644 --- a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/common/MultipartEnvironmentPostProcessorTests.java +++ b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/common/MultipartEnvironmentPostProcessorTests.java @@ -16,7 +16,6 @@ package org.springframework.cloud.gateway.server.mvc.common; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.mock.env.MockEnvironment; @@ -28,7 +27,6 @@ import static org.springframework.cloud.gateway.server.mvc.common.MultipartEnvir public class MultipartEnvironmentPostProcessorTests { @Test - @Disabled void multipartDisabledByDefault() { MockEnvironment environment = new MockEnvironment(); MultipartEnvironmentPostProcessor processor = new MultipartEnvironmentPostProcessor(); diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/TestController.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/TestController.java index d65d50f9..5048c042 100644 --- a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/TestController.java +++ b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/TestController.java @@ -16,27 +16,21 @@ package org.springframework.cloud.gateway.server.mvc.test; -import java.io.IOException; import java.util.Enumeration; import java.util.HashMap; -import java.util.List; import java.util.Map; -import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; -import org.springframework.util.MultiValueMap; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.multipart.MultipartFile; import org.springframework.web.server.ServerWebExchange; @RestController @@ -58,35 +52,7 @@ public class TestController { return result; } - @PostMapping(value = "/post", consumes = MediaType.MULTIPART_FORM_DATA_VALUE, - produces = MediaType.APPLICATION_JSON_VALUE) - public Map postFormData(HttpServletRequest request, - @RequestParam MultiValueMap parts) throws ServletException, IOException { - HashMap ret = new HashMap<>(); - ret.put("headers", getHeaders(request)); - HashMap files = new HashMap<>(); - ret.put("files", files); - - parts.values().stream().flatMap(List::stream).forEach(part -> { - String contentType = part.getContentType(); - long contentLength = part.getSize(); - // TODO: get part data - files.put(part.getName(), "data:" + contentType + ";base64," + contentLength); - }); - return ret; - } - - @PostMapping(path = "/post", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, - produces = MediaType.APPLICATION_JSON_VALUE) - public Map postUrlEncoded(HttpServletRequest request, - @RequestBody(required = false) MultiValueMap form) throws IOException { - HashMap ret = new HashMap<>(); - ret.put("headers", getHeaders(request)); - ret.put("form", form); - return ret; - } - - @PostMapping(path = "/post", produces = MediaType.APPLICATION_JSON_VALUE) + @PostMapping(path = "/localpost", produces = MediaType.APPLICATION_JSON_VALUE) public Map post(HttpServletRequest request, @RequestBody(required = false) String body) { HashMap ret = new HashMap<>(); ret.put("headers", getHeaders(request)); From 159ce34abe4887efd1eee974d180ba291ac6bc64 Mon Sep 17 00:00:00 2001 From: spencergibb Date: Tue, 11 Feb 2025 19:39:39 -0500 Subject: [PATCH 65/90] Adds support for retry with request body. Adds MvcUtils.getOrCacheBody() used by RetryFilterFunctions.retry(). Moves retry tests into RetryFilterFunctionTests.java Fixes gh-3336 --- .../filters/retry.adoc | 10 +- .../gateway/server/mvc/common/MvcUtils.java | 8 + .../mvc/filter/RetryFilterFunctions.java | 15 ++ .../server/mvc/ServerMvcIntegrationTests.java | 71 ------- .../mvc/filter/RetryFilterFunctionTests.java | 180 ++++++++++++++++++ 5 files changed, 212 insertions(+), 72 deletions(-) create mode 100644 spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/filter/RetryFilterFunctionTests.java diff --git a/docs/modules/ROOT/pages/spring-cloud-gateway-server-mvc/filters/retry.adoc b/docs/modules/ROOT/pages/spring-cloud-gateway-server-mvc/filters/retry.adoc index fa6529fa..96165f0d 100644 --- a/docs/modules/ROOT/pages/spring-cloud-gateway-server-mvc/filters/retry.adoc +++ b/docs/modules/ROOT/pages/spring-cloud-gateway-server-mvc/filters/retry.adoc @@ -9,6 +9,7 @@ The `Retry` filter supports the following parameters: * `methods`: The HTTP methods that should be retried, represented by using `org.springframework.http.HttpMethod`. * `series`: The series of status codes to be retried, represented by using `org.springframework.http.HttpStatus.Series`. * `exceptions`: A list of thrown exceptions that should be retried. +* `cacheBody`: A flag to signal if the request body should be cached. If set to `true`, the `adaptCacheBody` filter must be used to send the cached body downstream. //* `backoff`: The configured exponential backoff for the retries. //Retries are performed after a backoff interval of `firstBackoff * (factor ^ n)`, where `n` is the iteration. //If `maxBackoff` is configured, the maximum backoff applied is limited to `maxBackoff`. @@ -20,8 +21,11 @@ The following defaults are configured for `Retry` filter, if enabled: * `series`: 5XX series * `methods`: GET method * `exceptions`: `IOException`, `TimeoutException` and `RetryException` +* `cacheBody`: `false` //* `backoff`: disabled +WARNING: Setting `cacheBody` to `true` causes the gateway to read the whole body into memory. This should be used with caution. + The following listing configures a Retry filter: .application.yml @@ -42,11 +46,14 @@ spring: retries: 3 series: SERVER_ERROR methods: GET,POST + cacheBody: true + - name: AdaptCachedBody ---- .GatewaySampleApplication.java [source,java] ---- +import static org.springframework.cloud.gateway.server.mvc.filter.FilterFunctions.adaptCachedBody; import static org.springframework.cloud.gateway.server.mvc.filter.RetryFilterFunctions.retry; import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route; import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http; @@ -59,7 +66,8 @@ class RouteConfiguration { public RouterFunction gatewayRouterFunctionsAddReqHeader() { return route("add_request_parameter_route") .route(host("*.retry.com"), http("https://example.org")) - .filter(retry(config -> config.setRetries(3).setSeries(Set.of(HttpStatus.Series.SERVER_ERROR)).setMethods(Set.of(HttpMethod.GET, HttpMethod.POST)))) + .filter(retry(config -> config.setRetries(3).setSeries(Set.of(HttpStatus.Series.SERVER_ERROR)).setMethods(Set.of(HttpMethod.GET, HttpMethod.POST)).setCacheBody(true))) + .filter(adaptCachedBody()) .build(); } } diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/MvcUtils.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/MvcUtils.java index 5e635b71..72f30289 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/MvcUtils.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/MvcUtils.java @@ -116,6 +116,14 @@ public abstract class MvcUtils { } } + public static ByteArrayInputStream getOrCacheBody(ServerRequest request) { + ByteArrayInputStream body = getAttribute(request, MvcUtils.CACHED_REQUEST_BODY_ATTR); + if (body != null) { + return body; + } + return cacheBody(request); + } + public static String expand(ServerRequest request, String template) { Assert.notNull(request, "request may not be null"); Assert.notNull(template, "template may not be null"); diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/RetryFilterFunctions.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/RetryFilterFunctions.java index 8a550ce2..272b1e6c 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/RetryFilterFunctions.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/RetryFilterFunctions.java @@ -27,6 +27,7 @@ import java.util.concurrent.TimeoutException; import java.util.function.Consumer; import org.springframework.cloud.gateway.server.mvc.common.Configurable; +import org.springframework.cloud.gateway.server.mvc.common.MvcUtils; import org.springframework.cloud.gateway.server.mvc.common.Shortcut; import org.springframework.core.NestedRuntimeException; import org.springframework.http.HttpMethod; @@ -71,6 +72,9 @@ public abstract class RetryFilterFunctions { .setPolicies(Arrays.asList(simpleRetryPolicy, new HttpRetryPolicy(config)).toArray(new RetryPolicy[0])); RetryTemplate retryTemplate = retryTemplateBuilder.customPolicy(compositeRetryPolicy).build(); return (request, next) -> retryTemplate.execute(context -> { + if (config.isCacheBody()) { + MvcUtils.getOrCacheBody(request); + } ServerResponse serverResponse = next.handle(request); if (isRetryableStatusCode(serverResponse.statusCode(), config) @@ -121,6 +125,8 @@ public abstract class RetryFilterFunctions { private Set methods = new HashSet<>(List.of(HttpMethod.GET)); + private boolean cacheBody = false; + // TODO: individual statuses // TODO: backoff // TODO: support more Spring Retry policies @@ -176,6 +182,15 @@ public abstract class RetryFilterFunctions { return this; } + public boolean isCacheBody() { + return cacheBody; + } + + public RetryConfig setCacheBody(boolean cacheBody) { + this.cacheBody = cacheBody; + return this; + } + } private static class RetryException extends NestedRuntimeException { diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/ServerMvcIntegrationTests.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/ServerMvcIntegrationTests.java index 4af00c8b..9deff0ed 100644 --- a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/ServerMvcIntegrationTests.java +++ b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/ServerMvcIntegrationTests.java @@ -27,8 +27,6 @@ import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Predicate; import com.github.benmanes.caffeine.cache.Caffeine; @@ -41,8 +39,6 @@ import jakarta.servlet.ServletException; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; @@ -80,10 +76,8 @@ import org.springframework.test.context.ContextConfiguration; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.util.StreamUtils; -import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.function.HandlerFunction; import org.springframework.web.servlet.function.RouterFunction; @@ -118,7 +112,6 @@ import static org.springframework.cloud.gateway.server.mvc.filter.CircuitBreaker import static org.springframework.cloud.gateway.server.mvc.filter.FilterFunctions.addRequestHeader; import static org.springframework.cloud.gateway.server.mvc.filter.FilterFunctions.addRequestHeadersIfNotPresent; import static org.springframework.cloud.gateway.server.mvc.filter.FilterFunctions.addRequestParameter; -import static org.springframework.cloud.gateway.server.mvc.filter.FilterFunctions.prefixPath; import static org.springframework.cloud.gateway.server.mvc.filter.FilterFunctions.redirectTo; import static org.springframework.cloud.gateway.server.mvc.filter.FilterFunctions.removeRequestHeader; import static org.springframework.cloud.gateway.server.mvc.filter.FilterFunctions.rewritePath; @@ -127,7 +120,6 @@ import static org.springframework.cloud.gateway.server.mvc.filter.FilterFunction import static org.springframework.cloud.gateway.server.mvc.filter.FilterFunctions.setRequestHostHeader; import static org.springframework.cloud.gateway.server.mvc.filter.FilterFunctions.stripPrefix; import static org.springframework.cloud.gateway.server.mvc.filter.LoadBalancerFilterFunctions.lb; -import static org.springframework.cloud.gateway.server.mvc.filter.RetryFilterFunctions.retry; import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route; import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.forward; import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http; @@ -394,20 +386,6 @@ public class ServerMvcIntegrationTests { // @formatter:on } - @Test - public void retryWorks() { - restClient.get().uri("/retry?key=get").exchange().expectStatus().isOk().expectBody(String.class).isEqualTo("3"); - // test for: java.lang.IllegalArgumentException: You have already selected another - // retry policy - restClient.get() - .uri("/retry?key=get2") - .exchange() - .expectStatus() - .isOk() - .expectBody(String.class) - .isEqualTo("3"); - } - @Test public void rateLimitWorks() { restClient.get().uri("/anything/ratelimit").exchange().expectStatus().isOk(); @@ -1005,11 +983,6 @@ public class ServerMvcIntegrationTests { return new TestHandler(); } - @Bean - RetryController retryController() { - return new RetryController(); - } - @Bean EventController eventController() { return new EventController(); @@ -1194,19 +1167,6 @@ public class ServerMvcIntegrationTests { // @formatter:on } - @Bean - public RouterFunction gatewayRouterFunctionsRetry() { - // @formatter:off - return route("testretry") - .route(path("/retry"), http()) - .before(new LocalServerPortUriResolver()) - .filter(retry(3)) - //.filter(retry(config -> config.setRetries(3).setSeries(Set.of(HttpStatus.Series.SERVER_ERROR)).setMethods(Set.of(HttpMethod.GET, HttpMethod.POST)))) - .filter(prefixPath("/do")) - .build(); - // @formatter:on - } - @Bean public RouterFunction gatewayRouterFunctionsRateLimit() { // @formatter:off @@ -1690,37 +1650,6 @@ public class ServerMvcIntegrationTests { } - @RestController - protected static class RetryController { - - Log log = LogFactory.getLog(getClass()); - - ConcurrentHashMap map = new ConcurrentHashMap<>(); - - @GetMapping("/do/retry") - public ResponseEntity retry(@RequestParam("key") String key, - @RequestParam(name = "count", defaultValue = "3") int count, - @RequestParam(name = "failStatus", required = false) Integer failStatus) { - AtomicInteger num = getCount(key); - int i = num.incrementAndGet(); - log.warn("Retry count: " + i); - String body = String.valueOf(i); - if (i < count) { - HttpStatus httpStatus = HttpStatus.INTERNAL_SERVER_ERROR; - if (failStatus != null) { - httpStatus = HttpStatus.resolve(failStatus); - } - return ResponseEntity.status(httpStatus).header("X-Retry-Count", body).body("temporarily broken"); - } - return ResponseEntity.status(HttpStatus.OK).header("X-Retry-Count", body).body(body); - } - - AtomicInteger getCount(String key) { - return map.computeIfAbsent(key, s -> new AtomicInteger()); - } - - } - protected static class TestHandler implements HandlerFunction { @Override diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/filter/RetryFilterFunctionTests.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/filter/RetryFilterFunctionTests.java new file mode 100644 index 00000000..edae11e4 --- /dev/null +++ b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/filter/RetryFilterFunctionTests.java @@ -0,0 +1,180 @@ +/* + * 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.gateway.server.mvc.filter; + +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.junit.jupiter.api.Test; + +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.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.cloud.gateway.server.mvc.test.HttpbinTestcontainers; +import org.springframework.cloud.gateway.server.mvc.test.LocalServerPortUriResolver; +import org.springframework.cloud.gateway.server.mvc.test.TestLoadBalancerConfig; +import org.springframework.cloud.gateway.server.mvc.test.client.TestRestClient; +import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient; +import org.springframework.context.annotation.Bean; +import org.springframework.core.log.LogMessage; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.function.RouterFunction; +import org.springframework.web.servlet.function.ServerResponse; + +import static org.springframework.cloud.gateway.server.mvc.filter.FilterFunctions.adaptCachedBody; +import static org.springframework.cloud.gateway.server.mvc.filter.FilterFunctions.prefixPath; +import static org.springframework.cloud.gateway.server.mvc.filter.RetryFilterFunctions.retry; +import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route; +import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http; + +@SuppressWarnings("unchecked") +@SpringBootTest(properties = {}, webEnvironment = WebEnvironment.RANDOM_PORT) +@ContextConfiguration(initializers = HttpbinTestcontainers.class) +public class RetryFilterFunctionTests { + + @LocalServerPort + int port; + + @Autowired + TestRestClient restClient; + + @Test + public void retryWorks() { + restClient.get().uri("/retry?key=get").exchange().expectStatus().isOk().expectBody(String.class).isEqualTo("3"); + // test for: java.lang.IllegalArgumentException: You have already selected another + // retry policy + restClient.get() + .uri("/retry?key=get2") + .exchange() + .expectStatus() + .isOk() + .expectBody(String.class) + .isEqualTo("3"); + } + + @Test + public void retryBodyWorks() { + restClient.post() + .uri("/retrybody?key=post") + .bodyValue("thebody") + .exchange() + .expectStatus() + .isOk() + .expectBody(String.class) + .isEqualTo("3"); + } + + @SpringBootConfiguration + @EnableAutoConfiguration + @LoadBalancerClient(name = "httpbin", configuration = TestLoadBalancerConfig.Httpbin.class) + protected static class TestConfiguration { + + @Bean + public RouterFunction gatewayRouterFunctionsRetry() { + // @formatter:off + return route("testretry") + .GET("/retry", http()) + .before(new LocalServerPortUriResolver()) + .filter(retry(3)) + .filter(prefixPath("/do")) + .build(); + // @formatter:on + } + + @Bean + public RouterFunction gatewayRouterFunctionsRetryBody() { + // @formatter:off + return route("testretrybody") + .POST("/retrybody", http()) + .before(new LocalServerPortUriResolver()) + .filter(retry(config -> config.setRetries(3).setSeries(Set.of(HttpStatus.Series.SERVER_ERROR)) + .setMethods(Set.of(HttpMethod.GET, HttpMethod.POST)).setCacheBody(true))) + .filter(adaptCachedBody()) + .filter(prefixPath("/do")) + .build(); + // @formatter:on + } + + @RestController + protected static class RetryController { + + Log log = LogFactory.getLog(getClass()); + + ConcurrentHashMap map = new ConcurrentHashMap<>(); + + @GetMapping("/do/retry") + public ResponseEntity retry(@RequestParam("key") String key, + @RequestParam(name = "count", defaultValue = "3") int count, + @RequestParam(name = "failStatus", required = false) Integer failStatus) { + AtomicInteger num = getCount(key); + int i = num.incrementAndGet(); + log.warn("Retry count: " + i); + String body = String.valueOf(i); + if (i < count) { + HttpStatus httpStatus = HttpStatus.INTERNAL_SERVER_ERROR; + if (failStatus != null) { + httpStatus = HttpStatus.resolve(failStatus); + } + return ResponseEntity.status(httpStatus).header("X-Retry-Count", body).body("temporarily broken"); + } + return ResponseEntity.status(HttpStatus.OK).header("X-Retry-Count", body).body(body); + } + + @PostMapping("/do/retrybody") + public ResponseEntity retryBody(@RequestParam("key") String key, + @RequestParam(name = "count", defaultValue = "3") int count, @RequestBody String requestBody) { + AtomicInteger num = getCount(key); + int i = num.incrementAndGet(); + log.warn(LogMessage.format("Retry count: %s, body: %s", i, requestBody)); + String body = String.valueOf(i); + if (!StringUtils.hasText(requestBody)) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .header("X-Retry-Count", body) + .body("missing body"); + } + if (i < count) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .header("X-Retry-Count", body) + .body("temporarily broken"); + } + return ResponseEntity.status(HttpStatus.OK).header("X-Retry-Count", body).body(body); + } + + AtomicInteger getCount(String key) { + return map.computeIfAbsent(key, s -> new AtomicInteger()); + } + + } + + } + +} From 4564a139332c7d138c7e9879460575e8e9ec172f Mon Sep 17 00:00:00 2001 From: spencergibb Date: Fri, 21 Feb 2025 10:42:03 -0500 Subject: [PATCH 66/90] Guard for null pointer if no host header. Fixes gh-3699 --- .../predicate/GatewayRequestPredicates.java | 3 ++ .../GatewayRequestPredicatesTests.java | 37 +++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/predicate/GatewayRequestPredicatesTests.java diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/predicate/GatewayRequestPredicates.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/predicate/GatewayRequestPredicates.java index 5dbb3c7d..68f54b73 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/predicate/GatewayRequestPredicates.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/predicate/GatewayRequestPredicates.java @@ -374,6 +374,9 @@ public abstract class GatewayRequestPredicates { @Override public boolean test(ServerRequest request) { String host = request.headers().firstHeader(HttpHeaders.HOST); + if (host == null) { + host = ""; + } PathContainer pathContainer = PathContainer.parsePath(host, PathContainer.Options.MESSAGE_ROUTE); PathPattern.PathMatchInfo info = this.pattern.matchAndExtract(pathContainer); traceMatch("Pattern", this.pattern.getPatternString(), host, info != null); diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/predicate/GatewayRequestPredicatesTests.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/predicate/GatewayRequestPredicatesTests.java new file mode 100644 index 00000000..15876d87 --- /dev/null +++ b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/predicate/GatewayRequestPredicatesTests.java @@ -0,0 +1,37 @@ +/* + * 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.gateway.server.mvc.predicate; + +import java.util.Collections; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.web.servlet.function.ServerRequest; + +public class GatewayRequestPredicatesTests { + + @Test + void nullHostPassedToHostPredicate() { + MockHttpServletRequest servletRequest = new MockHttpServletRequest(); + ServerRequest serverRequest = ServerRequest.create(servletRequest, Collections.emptyList()); + boolean result = GatewayRequestPredicates.host("*.myhost.org").test(serverRequest); + Assertions.assertThat(result).isFalse(); + } + +} From 537c21ca3bcc5ff1d1cecde81bf2eb8f8781302b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Feb 2025 18:00:21 +0000 Subject: [PATCH 67/90] Bump @springio/antora-extensions from 1.14.2 to 1.14.4 in /docs Bumps [@springio/antora-extensions](https://github.com/spring-io/antora-extensions) from 1.14.2 to 1.14.4. - [Changelog](https://github.com/spring-io/antora-extensions/blob/main/CHANGELOG.adoc) - [Commits](https://github.com/spring-io/antora-extensions/compare/v1.14.2...v1.14.4) --- updated-dependencies: - dependency-name: "@springio/antora-extensions" dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- docs/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/package.json b/docs/package.json index 567c1f3a..c2942be0 100644 --- a/docs/package.json +++ b/docs/package.json @@ -4,7 +4,7 @@ "@antora/atlas-extension": "1.0.0-alpha.2", "@antora/collector-extension": "1.0.1", "@asciidoctor/tabs": "1.0.0-beta.6", - "@springio/antora-extensions": "1.14.2", + "@springio/antora-extensions": "1.14.4", "@springio/asciidoctor-extensions": "1.0.0-alpha.14" } } From 43f6fde6dbcaf1a3ecab885fbd4abfe96ef4ab43 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 11 Mar 2025 19:00:38 +0000 Subject: [PATCH 68/90] Bump @springio/asciidoctor-extensions in /docs Bumps [@springio/asciidoctor-extensions](https://github.com/spring-io/asciidoctor-extensions) from 1.0.0-alpha.14 to 1.0.0-alpha.16. - [Changelog](https://github.com/spring-io/asciidoctor-extensions/blob/main/CHANGELOG.adoc) - [Commits](https://github.com/spring-io/asciidoctor-extensions/compare/v1.0.0-alpha.14...v1.0.0-alpha.16) --- updated-dependencies: - dependency-name: "@springio/asciidoctor-extensions" dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- docs/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/package.json b/docs/package.json index c2942be0..6c97a4cc 100644 --- a/docs/package.json +++ b/docs/package.json @@ -5,6 +5,6 @@ "@antora/collector-extension": "1.0.1", "@asciidoctor/tabs": "1.0.0-beta.6", "@springio/antora-extensions": "1.14.4", - "@springio/asciidoctor-extensions": "1.0.0-alpha.14" + "@springio/asciidoctor-extensions": "1.0.0-alpha.16" } } From 131123ca17512ae7224d9a4a3f7b367f83e944c2 Mon Sep 17 00:00:00 2001 From: Ryan Baxter Date: Wed, 12 Mar 2025 20:01:22 -0400 Subject: [PATCH 69/90] Disable test that fails on GitHub actions --- .../cloud/gateway/test/CustomBlockHoundIntegrationTest.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/CustomBlockHoundIntegrationTest.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/CustomBlockHoundIntegrationTest.java index 228679ac..308c0b93 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/CustomBlockHoundIntegrationTest.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/CustomBlockHoundIntegrationTest.java @@ -17,6 +17,7 @@ package org.springframework.cloud.gateway.test; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.DisabledForJreRange; import org.junit.jupiter.api.condition.JRE; @@ -30,6 +31,8 @@ public class CustomBlockHoundIntegrationTest { @Test @DisabledForJreRange(min = JRE.JAVA_18) + // Disable this test for now flaky on GitHub Actions + @Disabled public void shouldThrowErrorForBlockingCallWithCustomBlockHoundIntegration() { Assertions.assertThrows(RuntimeException.class, () -> Mono.fromCallable(() -> { Thread.sleep(1); From 74fd106b21402d4a1e525ec2ceed863305db3583 Mon Sep 17 00:00:00 2001 From: spring-builds Date: Thu, 13 Mar 2025 13:28:24 +0000 Subject: [PATCH 70/90] Bumping versions --- README.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.adoc b/README.adoc index 9d84e36e..d4195c40 100644 --- a/README.adoc +++ b/README.adoc @@ -224,7 +224,7 @@ Spring Cloud Build brings along the `basepom:duplicate-finder-maven-plugin`, th [[duplicate-finder-configuration]] === Duplicate Finder configuration -Duplicate finder is *enabled by default* and will run in the `verify` phase of your Maven build, but it will only take effect in your project if you add the `duplicate-finder-maven-plugin` to the `build` section of the projecst's `pom.xml`. +Duplicate finder is *enabled by default* and will run in the `verify` phase of your Maven build, but it will only take effect in your project if you add the `duplicate-finder-maven-plugin` to the `build` section of the project's `pom.xml`. .pom.xml [source,xml] From 6415f1204fa7bf7052971002ce5739b0d931d232 Mon Sep 17 00:00:00 2001 From: raccoonback Date: Mon, 18 Nov 2024 08:45:13 +0900 Subject: [PATCH 71/90] Prevent duplicate encoding issue in HTTP request parameter rewrite filter Signed-off-by: raccoonback --- ...eRequestParameterGatewayFilterFactory.java | 25 +++++++++++++++---- ...estParameterGatewayFilterFactoryTests.java | 14 ++++++++++- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RewriteRequestParameterGatewayFilterFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RewriteRequestParameterGatewayFilterFactory.java index ecffdf15..02bccdaf 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RewriteRequestParameterGatewayFilterFactory.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RewriteRequestParameterGatewayFilterFactory.java @@ -26,10 +26,14 @@ import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.util.Assert; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.util.UriComponentsBuilder; +import org.springframework.web.util.UriUtils; import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator; +import static org.springframework.util.CollectionUtils.unmodifiableMultiValueMap; /** * @author Fredrich Ombico @@ -59,14 +63,25 @@ public class RewriteRequestParameterGatewayFilterFactory ServerHttpRequest req = exchange.getRequest(); UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromUri(req.getURI()); - if (req.getQueryParams().containsKey(config.getName())) { - uriComponentsBuilder.replaceQueryParam(config.getName(), config.getReplacement()); + + MultiValueMap queryParams = new LinkedMultiValueMap<>(req.getQueryParams()); + if (queryParams.containsKey(config.getName())) { + queryParams.remove(config.getName()); + queryParams.add(config.getName(), config.getReplacement()); } - URI uri = uriComponentsBuilder.build().toUri(); - ServerHttpRequest request = req.mutate().uri(uri).build(); + try { + MultiValueMap encodedQueryParams = UriUtils.encodeQueryParams(queryParams); + URI uri = uriComponentsBuilder.replaceQueryParams(unmodifiableMultiValueMap(encodedQueryParams)) + .build(true) + .toUri(); - return chain.filter(exchange.mutate().request(request).build()); + ServerHttpRequest request = req.mutate().uri(uri).build(); + return chain.filter(exchange.mutate().request(request).build()); + } + catch (IllegalArgumentException ex) { + throw new IllegalStateException("Invalid URI query: \"" + queryParams + "\""); + } } @Override diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RewriteRequestParameterGatewayFilterFactoryTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RewriteRequestParameterGatewayFilterFactoryTests.java index 9df80c61..f7803861 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RewriteRequestParameterGatewayFilterFactoryTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RewriteRequestParameterGatewayFilterFactoryTests.java @@ -71,11 +71,23 @@ class RewriteRequestParameterGatewayFilterFactoryTests { } @Test - void rewriteRequestParameterFilterWorksWithSpecialCharacters() { + void rewriteRequestParameterFilterWithSpecialCharactersInParameterValue() { testRewriteRequestParameterFilter("campaign", "black friday~(1.A-B_C!)", "campaign=old&color=green", Map.of("campaign", List.of("black friday~(1.A-B_C!)"), "color", List.of("green"))); } + @Test + void rewriteRequestParameterFilterWithSpecialCharactersInParameterName() { + testRewriteRequestParameterFilter("campaign[]", "red", "campaign%5B%5D=blue&color=green", + Map.of("campaign[]", List.of("red"), "color", List.of("green"))); + } + + @Test + void rewriteRequestParameterFilterKeepsOtherParamsEncoded() { + testRewriteRequestParameterFilter("color", "white", "campaign%5B%5D=blue&color=green", + Map.of("campaign[]", List.of("blue"), "color", List.of("white"))); + } + private void testRewriteRequestParameterFilter(String name, String replacement, String query, Map> expectedQueryParams) { GatewayFilter filter = new RewriteRequestParameterGatewayFilterFactory() From 49b6d0a0a3af744e8ca364becfa8c8ac4b18e58e Mon Sep 17 00:00:00 2001 From: raccoonback Date: Mon, 18 Nov 2024 08:45:35 +0900 Subject: [PATCH 72/90] Prevent duplicate encoding issue in HTTP request parameter remove filter Signed-off-by: raccoonback --- ...eRequestParameterGatewayFilterFactory.java | 20 ++++++---- ...estParameterGatewayFilterFactoryTests.java | 37 +++++++++++++++++++ 2 files changed, 50 insertions(+), 7 deletions(-) diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RemoveRequestParameterGatewayFilterFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RemoveRequestParameterGatewayFilterFactory.java index a2e2c92a..9d17f6ef 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RemoveRequestParameterGatewayFilterFactory.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RemoveRequestParameterGatewayFilterFactory.java @@ -29,6 +29,7 @@ import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.util.UriComponentsBuilder; +import org.springframework.web.util.UriUtils; import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator; import static org.springframework.util.CollectionUtils.unmodifiableMultiValueMap; @@ -57,14 +58,19 @@ public class RemoveRequestParameterGatewayFilterFactory MultiValueMap queryParams = new LinkedMultiValueMap<>(request.getQueryParams()); queryParams.remove(config.getName()); - URI newUri = UriComponentsBuilder.fromUri(request.getURI()) - .replaceQueryParams(unmodifiableMultiValueMap(queryParams)) - .build() - .toUri(); + try { + MultiValueMap encodedQueryParams = UriUtils.encodeQueryParams(queryParams); + URI newUri = UriComponentsBuilder.fromUri(request.getURI()) + .replaceQueryParams(unmodifiableMultiValueMap(encodedQueryParams)) + .build(true) + .toUri(); - ServerHttpRequest updatedRequest = exchange.getRequest().mutate().uri(newUri).build(); - - return chain.filter(exchange.mutate().request(updatedRequest).build()); + ServerHttpRequest updatedRequest = exchange.getRequest().mutate().uri(newUri).build(); + return chain.filter(exchange.mutate().request(updatedRequest).build()); + } + catch (IllegalArgumentException ex) { + throw new IllegalStateException("Invalid URI query: \"" + queryParams + "\""); + } } @Override diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RemoveRequestParameterGatewayFilterFactoryTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RemoveRequestParameterGatewayFilterFactoryTests.java index a1fdf471..c5accf12 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RemoveRequestParameterGatewayFilterFactoryTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RemoveRequestParameterGatewayFilterFactoryTests.java @@ -123,4 +123,41 @@ public class RemoveRequestParameterGatewayFilterFactoryTests { assertThat(actualRequest.getQueryParams()).containsEntry("ccc", singletonList(",xyz")); } + + @Test + public void removeRequestParameterFilterShouldHandleEncodedParameterName() { + MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost") + .queryParam("foo", "bar") + .queryParam("baz[]", "qux") + .build(); + exchange = MockServerWebExchange.from(request); + NameConfig config = new NameConfig(); + config.setName("baz[]"); + GatewayFilter filter = new RemoveRequestParameterGatewayFilterFactory().apply(config); + + filter.filter(exchange, filterChain); + + ServerHttpRequest actualRequest = captor.getValue().getRequest(); + assertThat(actualRequest.getQueryParams()).doesNotContainKey("baz[]"); + assertThat(actualRequest.getQueryParams()).containsEntry("foo", singletonList("bar")); + } + + @Test + public void removeRequestParameterFilterShouldMaintainEncodedParameters() { + MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost") + .queryParam("foo", "bar") + .queryParam("baz[]", "qux") + .build(); + exchange = MockServerWebExchange.from(request); + NameConfig config = new NameConfig(); + config.setName("foo"); + GatewayFilter filter = new RemoveRequestParameterGatewayFilterFactory().apply(config); + + filter.filter(exchange, filterChain); + + ServerHttpRequest actualRequest = captor.getValue().getRequest(); + assertThat(actualRequest.getQueryParams()).doesNotContainKey("foo"); + assertThat(actualRequest.getQueryParams()).containsEntry("baz[]", singletonList("qux")); + } + } From c62f2b44599d112111803a4480d9cba1934e08e5 Mon Sep 17 00:00:00 2001 From: raccoonback Date: Mon, 18 Nov 2024 08:50:49 +0900 Subject: [PATCH 73/90] Remove unnecessary public modifiers from test methods Signed-off-by: raccoonback --- ...questParameterGatewayFilterFactoryTests.java | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RemoveRequestParameterGatewayFilterFactoryTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RemoveRequestParameterGatewayFilterFactoryTests.java index c5accf12..a96aef38 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RemoveRequestParameterGatewayFilterFactoryTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RemoveRequestParameterGatewayFilterFactoryTests.java @@ -37,7 +37,7 @@ import static org.mockito.Mockito.when; /** * @author Thirunavukkarasu Ravichandran */ -public class RemoveRequestParameterGatewayFilterFactoryTests { +class RemoveRequestParameterGatewayFilterFactoryTests { private ServerWebExchange exchange; @@ -46,7 +46,7 @@ public class RemoveRequestParameterGatewayFilterFactoryTests { private ArgumentCaptor captor; @BeforeEach - public void setUp() { + void setUp() { filterChain = mock(GatewayFilterChain.class); captor = ArgumentCaptor.forClass(ServerWebExchange.class); when(filterChain.filter(captor.capture())).thenReturn(Mono.empty()); @@ -54,7 +54,7 @@ public class RemoveRequestParameterGatewayFilterFactoryTests { } @Test - public void removeRequestParameterFilterWorks() { + void removeRequestParameterFilterWorks() { MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost") .queryParam("foo", singletonList("bar")) .build(); @@ -70,7 +70,7 @@ public class RemoveRequestParameterGatewayFilterFactoryTests { } @Test - public void removeRequestParameterFilterWorksWhenParamIsNotPresentInRequest() { + void removeRequestParameterFilterWorksWhenParamIsNotPresentInRequest() { MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost").build(); exchange = MockServerWebExchange.from(request); NameConfig config = new NameConfig(); @@ -84,7 +84,7 @@ public class RemoveRequestParameterGatewayFilterFactoryTests { } @Test - public void removeRequestParameterFilterShouldOnlyRemoveSpecifiedParam() { + void removeRequestParameterFilterShouldOnlyRemoveSpecifiedParam() { MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost") .queryParam("foo", "bar") .queryParam("abc", "xyz") @@ -102,7 +102,7 @@ public class RemoveRequestParameterGatewayFilterFactoryTests { } @Test - public void removeRequestParameterFilterShouldHandleRemainingParamsWhichRequiringEncoding() { + void removeRequestParameterFilterShouldHandleRemainingParamsWhichRequiringEncoding() { MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost") .queryParam("foo", "bar") .queryParam("aaa", "abc xyz") @@ -123,9 +123,8 @@ public class RemoveRequestParameterGatewayFilterFactoryTests { assertThat(actualRequest.getQueryParams()).containsEntry("ccc", singletonList(",xyz")); } - @Test - public void removeRequestParameterFilterShouldHandleEncodedParameterName() { + void removeRequestParameterFilterShouldHandleEncodedParameterName() { MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost") .queryParam("foo", "bar") .queryParam("baz[]", "qux") @@ -143,7 +142,7 @@ public class RemoveRequestParameterGatewayFilterFactoryTests { } @Test - public void removeRequestParameterFilterShouldMaintainEncodedParameters() { + void removeRequestParameterFilterShouldMaintainEncodedParameters() { MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost") .queryParam("foo", "bar") .queryParam("baz[]", "qux") From 1bc974e82430898ee4f950ae1a5ea97a96575494 Mon Sep 17 00:00:00 2001 From: spring-builds Date: Thu, 13 Mar 2025 13:28:24 +0000 Subject: [PATCH 74/90] Bumping versions Signed-off-by: raccoonback --- README.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.adoc b/README.adoc index 9d84e36e..d4195c40 100644 --- a/README.adoc +++ b/README.adoc @@ -224,7 +224,7 @@ Spring Cloud Build brings along the `basepom:duplicate-finder-maven-plugin`, th [[duplicate-finder-configuration]] === Duplicate Finder configuration -Duplicate finder is *enabled by default* and will run in the `verify` phase of your Maven build, but it will only take effect in your project if you add the `duplicate-finder-maven-plugin` to the `build` section of the projecst's `pom.xml`. +Duplicate finder is *enabled by default* and will run in the `verify` phase of your Maven build, but it will only take effect in your project if you add the `duplicate-finder-maven-plugin` to the `build` section of the project's `pom.xml`. .pom.xml [source,xml] From 2f73ed234a6bd9654a17fadce3ac340a5fd7dca9 Mon Sep 17 00:00:00 2001 From: raccoonback Date: Mon, 2 Dec 2024 23:39:39 +0900 Subject: [PATCH 75/90] fix typo Signed-off-by: raccoonback --- .../cloud/gateway/filter/NettyWriteResponseFilter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/NettyWriteResponseFilter.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/NettyWriteResponseFilter.java index c06c175b..3dae9d95 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/NettyWriteResponseFilter.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/NettyWriteResponseFilter.java @@ -116,7 +116,7 @@ public class NettyWriteResponseFilter implements GlobalFilter, Ordered { byteBuf.release(); return buffer; } - throw new IllegalArgumentException("Unkown DataBufferFactory type " + bufferFactory.getClass()); + throw new IllegalArgumentException("Unknown DataBufferFactory type " + bufferFactory.getClass()); } private void cleanup(ServerWebExchange exchange) { From 3ecb4b62dbd89f67bca1151a95c561cf7ee15fa9 Mon Sep 17 00:00:00 2001 From: raccoonback Date: Mon, 2 Dec 2024 23:40:54 +0900 Subject: [PATCH 76/90] dispose the connection regardless of the channel state Signed-off-by: raccoonback --- .../gateway/filter/NettyWriteResponseFilter.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/NettyWriteResponseFilter.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/NettyWriteResponseFilter.java index 3dae9d95..278c428b 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/NettyWriteResponseFilter.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/NettyWriteResponseFilter.java @@ -23,6 +23,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import reactor.core.publisher.SignalType; import reactor.netty.Connection; import org.springframework.core.Ordered; @@ -98,8 +99,12 @@ public class NettyWriteResponseFilter implements GlobalFilter, Ordered { return (isStreamingMediaType(contentType) ? response.writeAndFlushWith(body.map(Flux::just)) : response.writeWith(body)); - })).doOnCancel(() -> cleanup(exchange)) - .doOnError(throwable -> cleanup(exchange)); + })) + .doFinally(signalType -> { + if (signalType == SignalType.CANCEL || signalType == SignalType.ON_ERROR) { + cleanup(exchange); + } + }); // @formatter:on } @@ -121,7 +126,7 @@ public class NettyWriteResponseFilter implements GlobalFilter, Ordered { private void cleanup(ServerWebExchange exchange) { Connection connection = exchange.getAttribute(CLIENT_RESPONSE_CONN_ATTR); - if (connection != null && connection.channel().isActive()) { + if (connection != null) { connection.dispose(); } } From 1f10961bd729559ed043cb2fdf6f8e3d47fa484f Mon Sep 17 00:00:00 2001 From: spring-builds Date: Thu, 13 Mar 2025 13:28:24 +0000 Subject: [PATCH 77/90] Bumping versions Signed-off-by: raccoonback --- README.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.adoc b/README.adoc index 9d84e36e..d4195c40 100644 --- a/README.adoc +++ b/README.adoc @@ -224,7 +224,7 @@ Spring Cloud Build brings along the `basepom:duplicate-finder-maven-plugin`, th [[duplicate-finder-configuration]] === Duplicate Finder configuration -Duplicate finder is *enabled by default* and will run in the `verify` phase of your Maven build, but it will only take effect in your project if you add the `duplicate-finder-maven-plugin` to the `build` section of the projecst's `pom.xml`. +Duplicate finder is *enabled by default* and will run in the `verify` phase of your Maven build, but it will only take effect in your project if you add the `duplicate-finder-maven-plugin` to the `build` section of the project's `pom.xml`. .pom.xml [source,xml] From 07aca3e63e7279e9265779083a416848e5af238d Mon Sep 17 00:00:00 2001 From: raccoonback Date: Sun, 5 Jan 2025 15:54:56 +0900 Subject: [PATCH 78/90] Prevent double encoding in setPath method The setPath method was causing URLs to be encoded twice under certain conditions. This fix ensures proper handling of parameters to avoid redundant encoding. Signed-off-by: raccoonback --- .../mvc/filter/BeforeFilterFunctions.java | 6 +- .../filter/BeforeFilterFunctionsTests.java | 160 ++++++++++++++++++ 2 files changed, 164 insertions(+), 2 deletions(-) create mode 100644 spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/filter/BeforeFilterFunctionsTests.java diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/BeforeFilterFunctions.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/BeforeFilterFunctions.java index 93a3eb7c..4f273ea2 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/BeforeFilterFunctions.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/BeforeFilterFunctions.java @@ -350,9 +350,11 @@ public abstract class BeforeFilterFunctions { return request -> { Map uriVariables = MvcUtils.getUriTemplateVariables(request); URI uri = uriTemplate.expand(uriVariables); - String newPath = uri.getRawPath(); - URI prefixedUri = UriComponentsBuilder.fromUri(request.uri()).replacePath(newPath).build().toUri(); + URI prefixedUri = UriComponentsBuilder.fromUri(request.uri()) + .replacePath(uri.getRawPath()) + .build(true) + .toUri(); return ServerRequest.from(request).uri(prefixedUri).build(); }; } diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/filter/BeforeFilterFunctionsTests.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/filter/BeforeFilterFunctionsTests.java new file mode 100644 index 00000000..d72fe496 --- /dev/null +++ b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/filter/BeforeFilterFunctionsTests.java @@ -0,0 +1,160 @@ +/* + * 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.gateway.server.mvc.filter; + +import java.util.Collections; + +import org.junit.jupiter.api.Test; + +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.web.servlet.function.ServerRequest; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author raccoonback + */ +class BeforeFilterFunctionsTests { + + @Test + void rewriteRequestParameter() { + MockHttpServletRequest servletRequest = MockMvcRequestBuilders.get("http://localhost/path") + .param("foo", "bar") + .param("baz", "qux") + .buildRequest(null); + + ServerRequest request = ServerRequest.create(servletRequest, Collections.emptyList()); + + ServerRequest result = BeforeFilterFunctions.rewriteRequestParameter("foo", "replacement").apply(request); + + assertThat(result.param("foo")).isPresent().hasValue("replacement"); + assertThat(result.uri().toString()).hasToString("http://localhost/path?baz=qux&foo=replacement"); + } + + @Test + void rewriteOnlyFirstRequestParameter() { + MockHttpServletRequest servletRequest = MockMvcRequestBuilders.get("http://localhost/path") + .param("foo", "bar_1") + .param("foo", "bar_2") + .param("foo", "bar_3") + .param("baz", "qux") + .buildRequest(null); + + ServerRequest request = ServerRequest.create(servletRequest, Collections.emptyList()); + + ServerRequest result = BeforeFilterFunctions.rewriteRequestParameter("foo", "replacement").apply(request); + + assertThat(result.param("foo")).isPresent().hasValue("replacement"); + assertThat(result.uri().toString()).hasToString("http://localhost/path?baz=qux&foo=replacement"); + } + + @Test + void rewriteEncodedRequestParameter() { + MockHttpServletRequest servletRequest = MockMvcRequestBuilders.get("http://localhost/path") + .param("foo[]", "bar") + .param("baz", "qux") + .buildRequest(null); + + ServerRequest request = ServerRequest.create(servletRequest, Collections.emptyList()); + + ServerRequest result = BeforeFilterFunctions.rewriteRequestParameter("foo[]", "replacement[]").apply(request); + + assertThat(result.param("foo[]")).isPresent().hasValue("replacement[]"); + assertThat(result.uri().toString()).hasToString("http://localhost/path?baz=qux&foo%5B%5D=replacement%5B%5D"); + } + + @Test + void rewriteRequestParameterWithEncodedRemainParameters() { + MockHttpServletRequest servletRequest = MockMvcRequestBuilders.get("http://localhost/path") + .param("foo", "bar") + .param("baz[]", "qux[]") + .buildRequest(null); + + ServerRequest request = ServerRequest.create(servletRequest, Collections.emptyList()); + + ServerRequest result = BeforeFilterFunctions.rewriteRequestParameter("foo", "replacement").apply(request); + + assertThat(result.param("foo")).isPresent().hasValue("replacement"); + assertThat(result.uri().toString()).hasToString("http://localhost/path?baz%5B%5D=qux%5B%5D&foo=replacement"); + } + + @Test + void rewriteRequestParameterWithEncodedPath() { + MockHttpServletRequest servletRequest = MockMvcRequestBuilders.get("http://localhost/path/é/last") + .param("foo", "bar") + .buildRequest(null); + + ServerRequest request = ServerRequest.create(servletRequest, Collections.emptyList()); + + ServerRequest result = BeforeFilterFunctions.rewriteRequestParameter("foo", "replacement").apply(request); + + assertThat(result.param("foo")).isPresent().hasValue("replacement"); + assertThat(result.uri().toString()).hasToString("http://localhost/path/%C3%A9/last?foo=replacement"); + } + + @Test + void setPath() { + MockHttpServletRequest servletRequest = MockMvcRequestBuilders.get("http://localhost/legacy/path") + .buildRequest(null); + + ServerRequest request = ServerRequest.create(servletRequest, Collections.emptyList()); + + ServerRequest result = BeforeFilterFunctions.setPath("/new/path").apply(request); + + assertThat(result.uri().toString()).isEqualTo("http://localhost/new/path"); + } + + @Test + void setEncodedPath() { + MockHttpServletRequest servletRequest = MockMvcRequestBuilders.get("http://localhost/legacy/path") + .buildRequest(null); + + ServerRequest request = ServerRequest.create(servletRequest, Collections.emptyList()); + + ServerRequest result = BeforeFilterFunctions.setPath("/new/é").apply(request); + + assertThat(result.uri().toString()).isEqualTo("http://localhost/new/%C3%A9"); + } + + @Test + void setPathWithParameters() { + MockHttpServletRequest servletRequest = MockMvcRequestBuilders.get("http://localhost/legacy/path") + .queryParam("foo", "bar") + .buildRequest(null); + + ServerRequest request = ServerRequest.create(servletRequest, Collections.emptyList()); + + ServerRequest result = BeforeFilterFunctions.setPath("/new/path").apply(request); + + assertThat(result.uri().toString()).isEqualTo("http://localhost/new/path?foo=bar"); + } + + @Test + void setPathWithEncodedParameters() { + MockHttpServletRequest servletRequest = MockMvcRequestBuilders.get("http://localhost/legacy/path") + .queryParam("foo[]", "bar[]") + .buildRequest(null); + + ServerRequest request = ServerRequest.create(servletRequest, Collections.emptyList()); + + ServerRequest result = BeforeFilterFunctions.setPath("/new/path").apply(request); + + assertThat(result.uri().toString()).isEqualTo("http://localhost/new/path?foo%5B%5D=bar%5B%5D"); + } + +} From 962c8c34e5b631a9839ec5dfa74ef50b051538b0 Mon Sep 17 00:00:00 2001 From: raccoonback Date: Sun, 5 Jan 2025 15:57:37 +0900 Subject: [PATCH 79/90] Prevent double encoding in stripPrefix method The stripPrefix method was causing URLs to be encoded twice under certain conditions. This fix ensures proper handling of parameters to avoid redundant encoding. Signed-off-by: raccoonback --- .../mvc/filter/BeforeFilterFunctions.java | 2 +- .../filter/BeforeFilterFunctionsTests.java | 38 +++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/BeforeFilterFunctions.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/BeforeFilterFunctions.java index 4f273ea2..bf4a5180 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/BeforeFilterFunctions.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/BeforeFilterFunctions.java @@ -409,7 +409,7 @@ public abstract class BeforeFilterFunctions { URI prefixedUri = UriComponentsBuilder.fromUri(request.uri()) .replacePath(newPath.toString()) - .build() + .build(true) .toUri(); return ServerRequest.from(request).uri(prefixedUri).build(); }; diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/filter/BeforeFilterFunctionsTests.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/filter/BeforeFilterFunctionsTests.java index d72fe496..7944baac 100644 --- a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/filter/BeforeFilterFunctionsTests.java +++ b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/filter/BeforeFilterFunctionsTests.java @@ -157,4 +157,42 @@ class BeforeFilterFunctionsTests { assertThat(result.uri().toString()).isEqualTo("http://localhost/new/path?foo%5B%5D=bar%5B%5D"); } + @Test + void stripPrefix() { + MockHttpServletRequest servletRequest = MockMvcRequestBuilders.get("http://localhost/depth1/depth2/depth3") + .buildRequest(null); + + ServerRequest request = ServerRequest.create(servletRequest, Collections.emptyList()); + + ServerRequest result = BeforeFilterFunctions.stripPrefix(2).apply(request); + + assertThat(result.uri().toString()).isEqualTo("http://localhost/depth3"); + } + + @Test + void stripPrefixWithEncodedPath() { + MockHttpServletRequest servletRequest = MockMvcRequestBuilders.get("http://localhost/depth1/depth2/depth3/é") + .buildRequest(null); + + ServerRequest request = ServerRequest.create(servletRequest, Collections.emptyList()); + + ServerRequest result = BeforeFilterFunctions.stripPrefix(2).apply(request); + + assertThat(result.uri().toString()).isEqualTo("http://localhost/depth3/%C3%A9"); + } + + @Test + void stripPrefixWithEncodedParameters() { + MockHttpServletRequest servletRequest = MockMvcRequestBuilders.get("http://localhost/depth1/depth2/depth3") + .queryParam("baz[]", "qux[]") + .buildRequest(null); + + ServerRequest request = ServerRequest.create(servletRequest, Collections.emptyList()); + + ServerRequest result = BeforeFilterFunctions.stripPrefix(2).apply(request); + + assertThat(result.param("baz[]")).isPresent().hasValue("qux[]"); + assertThat(result.uri().toString()).isEqualTo("http://localhost/depth3?baz%5B%5D=qux%5B%5D"); + } + } From 478fab319f43efd0d6fe7e1df893493158164429 Mon Sep 17 00:00:00 2001 From: raccoonback Date: Fri, 14 Mar 2025 08:10:03 +0900 Subject: [PATCH 80/90] Prevent double encoding in removeRequestParameter method The removeRequestParameter method was causing URLs to be encoded twice under certain conditions. This fix ensures proper handling of parameters to avoid redundant encoding. Signed-off-by: raccoonback --- .../mvc/filter/BeforeFilterFunctions.java | 7 +- .../filter/BeforeFilterFunctionsTests.java | 172 ++++++++---------- 2 files changed, 84 insertions(+), 95 deletions(-) diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/BeforeFilterFunctions.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/BeforeFilterFunctions.java index bf4a5180..650f9ca6 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/BeforeFilterFunctions.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/BeforeFilterFunctions.java @@ -45,6 +45,7 @@ import org.springframework.web.server.ResponseStatusException; import org.springframework.web.servlet.function.ServerRequest; import org.springframework.web.util.UriComponentsBuilder; import org.springframework.web.util.UriTemplate; +import org.springframework.web.util.UriUtils; import static org.springframework.cloud.gateway.server.mvc.common.MvcUtils.CIRCUITBREAKER_EXECUTION_EXCEPTION_ATTR; import static org.springframework.util.CollectionUtils.unmodifiableMultiValueMap; @@ -214,10 +215,12 @@ public abstract class BeforeFilterFunctions { MultiValueMap queryParams = new LinkedMultiValueMap<>(request.params()); queryParams.remove(name); + MultiValueMap encodedQueryParams = UriUtils.encodeQueryParams(queryParams); + // remove from uri URI newUri = UriComponentsBuilder.fromUri(request.uri()) - .replaceQueryParams(unmodifiableMultiValueMap(queryParams)) - .build() + .replaceQueryParams(unmodifiableMultiValueMap(encodedQueryParams)) + .build(true) .toUri(); // remove resolved params from request diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/filter/BeforeFilterFunctionsTests.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/filter/BeforeFilterFunctionsTests.java index 7944baac..095ec4f9 100644 --- a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/filter/BeforeFilterFunctionsTests.java +++ b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/filter/BeforeFilterFunctionsTests.java @@ -31,168 +31,154 @@ import static org.assertj.core.api.Assertions.assertThat; */ class BeforeFilterFunctionsTests { - @Test - void rewriteRequestParameter() { - MockHttpServletRequest servletRequest = MockMvcRequestBuilders.get("http://localhost/path") - .param("foo", "bar") - .param("baz", "qux") - .buildRequest(null); - - ServerRequest request = ServerRequest.create(servletRequest, Collections.emptyList()); - - ServerRequest result = BeforeFilterFunctions.rewriteRequestParameter("foo", "replacement").apply(request); - - assertThat(result.param("foo")).isPresent().hasValue("replacement"); - assertThat(result.uri().toString()).hasToString("http://localhost/path?baz=qux&foo=replacement"); - } - - @Test - void rewriteOnlyFirstRequestParameter() { - MockHttpServletRequest servletRequest = MockMvcRequestBuilders.get("http://localhost/path") - .param("foo", "bar_1") - .param("foo", "bar_2") - .param("foo", "bar_3") - .param("baz", "qux") - .buildRequest(null); - - ServerRequest request = ServerRequest.create(servletRequest, Collections.emptyList()); - - ServerRequest result = BeforeFilterFunctions.rewriteRequestParameter("foo", "replacement").apply(request); - - assertThat(result.param("foo")).isPresent().hasValue("replacement"); - assertThat(result.uri().toString()).hasToString("http://localhost/path?baz=qux&foo=replacement"); - } - - @Test - void rewriteEncodedRequestParameter() { - MockHttpServletRequest servletRequest = MockMvcRequestBuilders.get("http://localhost/path") - .param("foo[]", "bar") - .param("baz", "qux") - .buildRequest(null); - - ServerRequest request = ServerRequest.create(servletRequest, Collections.emptyList()); - - ServerRequest result = BeforeFilterFunctions.rewriteRequestParameter("foo[]", "replacement[]").apply(request); - - assertThat(result.param("foo[]")).isPresent().hasValue("replacement[]"); - assertThat(result.uri().toString()).hasToString("http://localhost/path?baz=qux&foo%5B%5D=replacement%5B%5D"); - } - - @Test - void rewriteRequestParameterWithEncodedRemainParameters() { - MockHttpServletRequest servletRequest = MockMvcRequestBuilders.get("http://localhost/path") - .param("foo", "bar") - .param("baz[]", "qux[]") - .buildRequest(null); - - ServerRequest request = ServerRequest.create(servletRequest, Collections.emptyList()); - - ServerRequest result = BeforeFilterFunctions.rewriteRequestParameter("foo", "replacement").apply(request); - - assertThat(result.param("foo")).isPresent().hasValue("replacement"); - assertThat(result.uri().toString()).hasToString("http://localhost/path?baz%5B%5D=qux%5B%5D&foo=replacement"); - } - - @Test - void rewriteRequestParameterWithEncodedPath() { - MockHttpServletRequest servletRequest = MockMvcRequestBuilders.get("http://localhost/path/é/last") - .param("foo", "bar") - .buildRequest(null); - - ServerRequest request = ServerRequest.create(servletRequest, Collections.emptyList()); - - ServerRequest result = BeforeFilterFunctions.rewriteRequestParameter("foo", "replacement").apply(request); - - assertThat(result.param("foo")).isPresent().hasValue("replacement"); - assertThat(result.uri().toString()).hasToString("http://localhost/path/%C3%A9/last?foo=replacement"); - } - @Test void setPath() { MockHttpServletRequest servletRequest = MockMvcRequestBuilders.get("http://localhost/legacy/path") - .buildRequest(null); + .buildRequest(null); ServerRequest request = ServerRequest.create(servletRequest, Collections.emptyList()); ServerRequest result = BeforeFilterFunctions.setPath("/new/path").apply(request); - assertThat(result.uri().toString()).isEqualTo("http://localhost/new/path"); + assertThat(result.uri().toString()).hasToString("http://localhost/new/path"); } @Test void setEncodedPath() { MockHttpServletRequest servletRequest = MockMvcRequestBuilders.get("http://localhost/legacy/path") - .buildRequest(null); + .buildRequest(null); ServerRequest request = ServerRequest.create(servletRequest, Collections.emptyList()); ServerRequest result = BeforeFilterFunctions.setPath("/new/é").apply(request); - assertThat(result.uri().toString()).isEqualTo("http://localhost/new/%C3%A9"); + assertThat(result.uri().toString()).hasToString("http://localhost/new/%C3%A9"); } @Test void setPathWithParameters() { MockHttpServletRequest servletRequest = MockMvcRequestBuilders.get("http://localhost/legacy/path") - .queryParam("foo", "bar") - .buildRequest(null); + .queryParam("foo", "bar") + .buildRequest(null); ServerRequest request = ServerRequest.create(servletRequest, Collections.emptyList()); ServerRequest result = BeforeFilterFunctions.setPath("/new/path").apply(request); - assertThat(result.uri().toString()).isEqualTo("http://localhost/new/path?foo=bar"); + assertThat(result.uri().toString()).hasToString("http://localhost/new/path?foo=bar"); } @Test void setPathWithEncodedParameters() { MockHttpServletRequest servletRequest = MockMvcRequestBuilders.get("http://localhost/legacy/path") - .queryParam("foo[]", "bar[]") - .buildRequest(null); + .queryParam("foo[]", "bar[]") + .buildRequest(null); ServerRequest request = ServerRequest.create(servletRequest, Collections.emptyList()); ServerRequest result = BeforeFilterFunctions.setPath("/new/path").apply(request); - assertThat(result.uri().toString()).isEqualTo("http://localhost/new/path?foo%5B%5D=bar%5B%5D"); + assertThat(result.uri().toString()).hasToString("http://localhost/new/path?foo%5B%5D=bar%5B%5D"); + } + + @Test + void removeRequestParameter() { + MockHttpServletRequest servletRequest = MockMvcRequestBuilders.get("http://localhost/path") + .queryParam("foo", "bar") + .queryParam("baz", "qux") + .buildRequest(null); + + ServerRequest request = ServerRequest.create(servletRequest, Collections.emptyList()); + + ServerRequest result = BeforeFilterFunctions.removeRequestParameter("foo").apply(request); + + assertThat(result.param("foo")).isEmpty(); + assertThat(result.param("baz")).isPresent().hasValue("qux"); + assertThat(result.uri().toString()).hasToString("http://localhost/path?baz=qux"); + } + + @Test + void removeEncodedRequestParameter() { + MockHttpServletRequest servletRequest = MockMvcRequestBuilders.get("http://localhost/path") + .queryParam("foo[]", "bar") + .queryParam("baz", "qux") + .buildRequest(null); + + ServerRequest request = ServerRequest.create(servletRequest, Collections.emptyList()); + + ServerRequest result = BeforeFilterFunctions.removeRequestParameter("foo[]").apply(request); + + assertThat(result.param("foo[]")).isEmpty(); + assertThat(result.param("baz")).isPresent().hasValue("qux"); + assertThat(result.uri().toString()).hasToString("http://localhost/path?baz=qux"); + } + + @Test + void removeRequestParameterWithEncodedRemainParameters() { + MockHttpServletRequest servletRequest = MockMvcRequestBuilders.get("http://localhost/path") + .queryParam("foo", "bar") + .queryParam("baz[]", "qux[]") + .buildRequest(null); + + ServerRequest request = ServerRequest.create(servletRequest, Collections.emptyList()); + + ServerRequest result = BeforeFilterFunctions.removeRequestParameter("foo").apply(request); + + assertThat(result.param("foo")).isEmpty(); + assertThat(result.param("baz[]")).isPresent().hasValue("qux[]"); + assertThat(result.uri().toString()).hasToString("http://localhost/path?baz%5B%5D=qux%5B%5D"); + } + + @Test + void removeRequestParameterWithEncodedPath() { + MockHttpServletRequest servletRequest = MockMvcRequestBuilders.get("http://localhost/é") + .queryParam("foo", "bar") + .buildRequest(null); + + ServerRequest request = ServerRequest.create(servletRequest, Collections.emptyList()); + + ServerRequest result = BeforeFilterFunctions.removeRequestParameter("foo").apply(request); + + assertThat(result.param("foo")).isEmpty(); + assertThat(result.uri().toString()).hasToString("http://localhost/%C3%A9"); } @Test void stripPrefix() { MockHttpServletRequest servletRequest = MockMvcRequestBuilders.get("http://localhost/depth1/depth2/depth3") - .buildRequest(null); + .buildRequest(null); ServerRequest request = ServerRequest.create(servletRequest, Collections.emptyList()); ServerRequest result = BeforeFilterFunctions.stripPrefix(2).apply(request); - assertThat(result.uri().toString()).isEqualTo("http://localhost/depth3"); + assertThat(result.uri().toString()).hasToString("http://localhost/depth3"); } @Test void stripPrefixWithEncodedPath() { MockHttpServletRequest servletRequest = MockMvcRequestBuilders.get("http://localhost/depth1/depth2/depth3/é") - .buildRequest(null); + .buildRequest(null); ServerRequest request = ServerRequest.create(servletRequest, Collections.emptyList()); ServerRequest result = BeforeFilterFunctions.stripPrefix(2).apply(request); - assertThat(result.uri().toString()).isEqualTo("http://localhost/depth3/%C3%A9"); + assertThat(result.uri().toString()).hasToString("http://localhost/depth3/%C3%A9"); } @Test void stripPrefixWithEncodedParameters() { MockHttpServletRequest servletRequest = MockMvcRequestBuilders.get("http://localhost/depth1/depth2/depth3") - .queryParam("baz[]", "qux[]") - .buildRequest(null); + .queryParam("baz[]", "qux[]") + .buildRequest(null); ServerRequest request = ServerRequest.create(servletRequest, Collections.emptyList()); ServerRequest result = BeforeFilterFunctions.stripPrefix(2).apply(request); assertThat(result.param("baz[]")).isPresent().hasValue("qux[]"); - assertThat(result.uri().toString()).isEqualTo("http://localhost/depth3?baz%5B%5D=qux%5B%5D"); + assertThat(result.uri().toString()).hasToString("http://localhost/depth3?baz%5B%5D=qux%5B%5D"); } } From 9816b1adfecb30bf35205c6f2b11ec3c952c1b24 Mon Sep 17 00:00:00 2001 From: spring-builds Date: Thu, 13 Mar 2025 13:28:24 +0000 Subject: [PATCH 81/90] Bumping versions Signed-off-by: qnnn <65326092+qnnn@users.noreply.github.com> --- README.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.adoc b/README.adoc index 9d84e36e..d4195c40 100644 --- a/README.adoc +++ b/README.adoc @@ -224,7 +224,7 @@ Spring Cloud Build brings along the `basepom:duplicate-finder-maven-plugin`, th [[duplicate-finder-configuration]] === Duplicate Finder configuration -Duplicate finder is *enabled by default* and will run in the `verify` phase of your Maven build, but it will only take effect in your project if you add the `duplicate-finder-maven-plugin` to the `build` section of the projecst's `pom.xml`. +Duplicate finder is *enabled by default* and will run in the `verify` phase of your Maven build, but it will only take effect in your project if you add the `duplicate-finder-maven-plugin` to the `build` section of the project's `pom.xml`. .pom.xml [source,xml] From fcba3da5d3c4564c674e688fbc9e07ddb47b5682 Mon Sep 17 00:00:00 2001 From: qnnn <65326092+qnnn@users.noreply.github.com> Date: Fri, 14 Mar 2025 13:23:37 +0800 Subject: [PATCH 82/90] Enable body caching in CircuitBreakerFilterFactory. Signed-off-by: qnnn <65326092+qnnn@users.noreply.github.com> --- .../factory/AbstractGatewayFilterFactory.java | 8 ++++++++ .../filter/factory/RetryGatewayFilterFactory.java | 6 +----- .../SpringCloudCircuitBreakerFilterFactory.java | 1 + ...SpringCloudCircuitBreakerFilterFactoryTests.java | 8 ++++++++ .../SpringCloudCircuitBreakerTestConfig.java | 7 +++++++ .../src/test/resources/application.yml | 13 +++++++++++++ 6 files changed, 38 insertions(+), 5 deletions(-) diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/AbstractGatewayFilterFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/AbstractGatewayFilterFactory.java index e94a2ede..2413fbda 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/AbstractGatewayFilterFactory.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/AbstractGatewayFilterFactory.java @@ -16,6 +16,7 @@ package org.springframework.cloud.gateway.filter.factory; +import org.springframework.cloud.gateway.event.EnableBodyCachingEvent; import org.springframework.cloud.gateway.support.AbstractConfigurable; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; @@ -43,6 +44,13 @@ public abstract class AbstractGatewayFilterFactory extends AbstractConfigurab return this.publisher; } + protected void enableBodyCaching(String routeId) { + if (routeId != null && getPublisher() != null) { + // send an event to enable caching + getPublisher().publishEvent(new EnableBodyCachingEvent(this, routeId)); + } + } + @Override public void setApplicationEventPublisher(ApplicationEventPublisher publisher) { this.publisher = publisher; diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RetryGatewayFilterFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RetryGatewayFilterFactory.java index d8046e27..814a6ecf 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RetryGatewayFilterFactory.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RetryGatewayFilterFactory.java @@ -35,7 +35,6 @@ import reactor.retry.RepeatContext; import reactor.retry.Retry; import reactor.retry.RetryContext; -import org.springframework.cloud.gateway.event.EnableBodyCachingEvent; import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.support.HasRouteId; @@ -229,10 +228,7 @@ public class RetryGatewayFilterFactory extends AbstractGatewayFilterFactory repeat, Retry retry) { - if (routeId != null && getPublisher() != null) { - // send an event to enable caching - getPublisher().publishEvent(new EnableBodyCachingEvent(this, routeId)); - } + enableBodyCaching(routeId); return (exchange, chain) -> { trace("Entering retry-filter"); diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/SpringCloudCircuitBreakerFilterFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/SpringCloudCircuitBreakerFilterFactory.java index 0cbc3eca..455da438 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/SpringCloudCircuitBreakerFilterFactory.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/SpringCloudCircuitBreakerFilterFactory.java @@ -89,6 +89,7 @@ public abstract class SpringCloudCircuitBreakerFilterFactory @Override public GatewayFilter apply(Config config) { + enableBodyCaching(config.getRouteId()); ReactiveCircuitBreaker cb = reactiveCircuitBreakerFactory.create(config.getId()); Set statuses = config.getStatusCodes() .stream() diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/SpringCloudCircuitBreakerFilterFactoryTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/SpringCloudCircuitBreakerFilterFactoryTests.java index 1966fa9e..30eb9a8b 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/SpringCloudCircuitBreakerFilterFactoryTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/SpringCloudCircuitBreakerFilterFactoryTests.java @@ -21,6 +21,7 @@ import org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable; import org.springframework.cloud.gateway.test.BaseWebClientTests; import org.springframework.http.HttpStatus; +import org.springframework.web.reactive.function.BodyInserters; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.http.MediaType.APPLICATION_JSON; @@ -243,4 +244,11 @@ public abstract class SpringCloudCircuitBreakerFilterFactoryTests extends BaseWe .valueEquals(ROUTE_ID_HEADER, "circuitbreaker_resume_without_error"); } + @Test + public void filterPostFallback() { + testClient.post().uri("/post").body(BodyInserters.fromValue("hello")) + .header("Host", "www.circuitbreakerfallbackpost.org").exchange().expectStatus() + .isOk().expectBody().json("{\"body\":\"hello\"}"); + } + } diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/SpringCloudCircuitBreakerTestConfig.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/SpringCloudCircuitBreakerTestConfig.java index 1b481ee2..fb31edcc 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/SpringCloudCircuitBreakerTestConfig.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/SpringCloudCircuitBreakerTestConfig.java @@ -38,6 +38,8 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @@ -68,6 +70,11 @@ public class SpringCloudCircuitBreakerTestConfig { return Collections.singletonMap("from", "circuitbreakerfallbackcontroller"); } + @PostMapping("/circuitbreakerPostFallbackController") + public Map postFallbackController(@RequestBody String body) { + return Collections.singletonMap("body", body); + } + @GetMapping("/circuitbreakerUriFallbackController/**") public Map uriFallbackcontroller(ServerWebExchange exchange, @RequestParam("a") String a) { return Collections.singletonMap("uri", exchange.getRequest().getURI().toString()); diff --git a/spring-cloud-gateway-server/src/test/resources/application.yml b/spring-cloud-gateway-server/src/test/resources/application.yml index 2b1fde13..a08f82da 100644 --- a/spring-cloud-gateway-server/src/test/resources/application.yml +++ b/spring-cloud-gateway-server/src/test/resources/application.yml @@ -104,6 +104,19 @@ spring: name: fallbackcmd fallbackUri: forward:/circuitbreakerFallbackController + # ===================================== + - id: circuitbreaker_fallback_test_post + uri: ${test.uri} + predicates: + - Host=**.circuitbreakerfallbackpost.org + filters: + - name: CircuitBreaker + args: + name: fallbackcmd + statusCodes: + - 200 + fallbackUri: forward:/circuitbreakerPostFallbackController + # ===================================== - id: circuitbreaker_fallback_test_variables uri: ${test.uri} From 12d48d415db6e94efe25cb87dda13f770cc4e4dc Mon Sep 17 00:00:00 2001 From: Andrei Date: Thu, 6 Mar 2025 20:35:51 -0800 Subject: [PATCH 83/90] Fix keyStore bug in GatewayAutoConfiguration Fixes java.security.KeyStoreException: Uninitialized keystore Per JDK documentation keystore should be initialized before use. Signed-off-by: Andrei --- .../cloud/gateway/config/GatewayAutoConfiguration.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java index c1906e3f..3a63edaf 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java @@ -16,9 +16,11 @@ package org.springframework.cloud.gateway.config; +import java.io.IOException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; import java.util.List; import java.util.Set; import java.util.function.Supplier; @@ -354,10 +356,12 @@ public class GatewayAutoConfiguration { @ConditionalOnMissingBean(GrpcSslConfigurer.class) @ConditionalOnClass(name = "io.grpc.Channel") public GrpcSslConfigurer grpcSslConfigurer(HttpClientProperties properties) - throws KeyStoreException, NoSuchAlgorithmException { + throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException { TrustManagerFactory trustManagerFactory = TrustManagerFactory .getInstance(TrustManagerFactory.getDefaultAlgorithm()); - trustManagerFactory.init(KeyStore.getInstance(KeyStore.getDefaultType())); + KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + keyStore.load(null); + trustManagerFactory.init(keyStore); return new GrpcSslConfigurer(properties.getSsl()); } From 5b08d91a8b157bd2150c764dabed880468485c32 Mon Sep 17 00:00:00 2001 From: Ryan Baxter Date: Fri, 14 Mar 2025 09:07:30 -0400 Subject: [PATCH 84/90] Adding 4.1.x to GitHub build action --- .github/workflows/maven.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 6aceddb1..d9851135 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -5,9 +5,9 @@ name: Build on: push: - branches: [ main, 3.1.x ] + branches: [ main, 4.1.x, 3.1.x ] pull_request: - branches: [ main, 3.1.x ] + branches: [ main, 4.1.x, 3.1.x ] jobs: build: From 470068bc0b56582a35d974d87eb3e3b1f6dced67 Mon Sep 17 00:00:00 2001 From: Abel Salgado Romero Date: Fri, 14 Mar 2025 10:14:44 +0100 Subject: [PATCH 85/90] Add enum values to JavaDocs to show in docs Signed-off-by: Abel Salgado Romero --- .../cloud/gateway/config/HttpClientProperties.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/HttpClientProperties.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/HttpClientProperties.java index 85b3b027..0f586c5a 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/HttpClientProperties.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/HttpClientProperties.java @@ -169,7 +169,7 @@ public class HttpClientProperties { public static class Pool { - /** Type of pool for HttpClient to use, defaults to ELASTIC. */ + /** Type of pool for HttpClient to use (elastic, fixed or disabled). */ private PoolType type = PoolType.ELASTIC; /** The channel pool map name, defaults to proxy. */ @@ -302,7 +302,10 @@ public class HttpClientProperties { public static class Proxy { - /** proxyType for proxy configuration of Netty HttpClient. */ + /** + * proxyType for proxy configuration of Netty HttpClient (http, socks4 or + * socks5). + */ private ProxyProvider.Proxy type = ProxyProvider.Proxy.HTTP; /** Hostname for proxy configuration of Netty HttpClient. */ From 611ae85b2d19a7153351f8d0ea2ade863601a3d2 Mon Sep 17 00:00:00 2001 From: qnnn <65326092+qnnn@users.noreply.github.com> Date: Sat, 15 Mar 2025 14:02:32 +0800 Subject: [PATCH 86/90] enable body caching if fallbackUri is configured Signed-off-by: qnnn <65326092+qnnn@users.noreply.github.com> --- .../factory/SpringCloudCircuitBreakerFilterFactory.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/SpringCloudCircuitBreakerFilterFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/SpringCloudCircuitBreakerFilterFactory.java index 455da438..823eac83 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/SpringCloudCircuitBreakerFilterFactory.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/SpringCloudCircuitBreakerFilterFactory.java @@ -89,7 +89,9 @@ public abstract class SpringCloudCircuitBreakerFilterFactory @Override public GatewayFilter apply(Config config) { - enableBodyCaching(config.getRouteId()); + if (config.getFallbackUri() != null) { + enableBodyCaching(config.getRouteId()); + } ReactiveCircuitBreaker cb = reactiveCircuitBreakerFactory.create(config.getId()); Set statuses = config.getStatusCodes() .stream() From 160c82057ee06c90d249bddf405a2ffd01d228d7 Mon Sep 17 00:00:00 2001 From: spring-builds Date: Sat, 15 Mar 2025 13:26:33 +0000 Subject: [PATCH 87/90] Bumping versions --- docs/modules/ROOT/partials/_configprops.adoc | 4 ++-- .../cloud/gateway/config/HttpClientProperties.java | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/modules/ROOT/partials/_configprops.adoc b/docs/modules/ROOT/partials/_configprops.adoc index 98a11c38..81866e90 100644 --- a/docs/modules/ROOT/partials/_configprops.adoc +++ b/docs/modules/ROOT/partials/_configprops.adoc @@ -89,12 +89,12 @@ |spring.cloud.gateway.httpclient.pool.max-life-time | | Duration after which the channel will be closed. If NULL, there is no max life time. |spring.cloud.gateway.httpclient.pool.metrics | `+++false+++` | Enables channel pools metrics to be collected and registered in Micrometer. Disabled by default. |spring.cloud.gateway.httpclient.pool.name | `+++proxy+++` | The channel pool map name, defaults to proxy. -|spring.cloud.gateway.httpclient.pool.type | | Type of pool for HttpClient to use, defaults to ELASTIC. +|spring.cloud.gateway.httpclient.pool.type | | Type of pool for HttpClient to use (elastic, fixed or disabled). |spring.cloud.gateway.httpclient.proxy.host | | Hostname for proxy configuration of Netty HttpClient. |spring.cloud.gateway.httpclient.proxy.non-proxy-hosts-pattern | | Regular expression (Java) for a configured list of hosts. that should be reached directly, bypassing the proxy |spring.cloud.gateway.httpclient.proxy.password | | Password for proxy configuration of Netty HttpClient. |spring.cloud.gateway.httpclient.proxy.port | | Port for proxy configuration of Netty HttpClient. -|spring.cloud.gateway.httpclient.proxy.type | | proxyType for proxy configuration of Netty HttpClient. +|spring.cloud.gateway.httpclient.proxy.type | | proxyType for proxy configuration of Netty HttpClient (http, socks4 or socks5). |spring.cloud.gateway.httpclient.proxy.username | | Username for proxy configuration of Netty HttpClient. |spring.cloud.gateway.httpclient.response-timeout | | The response timeout. |spring.cloud.gateway.httpclient.ssl.close-notify-flush-timeout | `+++3000ms+++` | SSL close_notify flush timeout. Default to 3000 ms. diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/HttpClientProperties.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/HttpClientProperties.java index 0f586c5a..8631cf2b 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/HttpClientProperties.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/HttpClientProperties.java @@ -303,8 +303,7 @@ public class HttpClientProperties { public static class Proxy { /** - * proxyType for proxy configuration of Netty HttpClient (http, socks4 or - * socks5). + * proxyType for proxy configuration of Netty HttpClient (http, socks4 or socks5). */ private ProxyProvider.Proxy type = ProxyProvider.Proxy.HTTP; From f6e68fac8ecf5ce645eafa108aaf0a0e2e9126fb Mon Sep 17 00:00:00 2001 From: Jonathon Henderson Date: Sat, 29 Jun 2024 18:32:26 +0100 Subject: [PATCH 88/90] Adds support for saving original request URL to request attributes This enables X-Forwarded-Prefix. Fixes gh-3354 Fixes gh-3443 --- .../gateway/server/mvc/common/MvcUtils.java | 15 +++++++++++++ .../mvc/filter/BeforeFilterFunctions.java | 3 +++ .../XForwardedRequestHeadersFilter.java | 10 ++++----- .../server/mvc/ServerMvcIntegrationTests.java | 20 ++++++++++++++++-- .../server/mvc/test/HttpbinUriResolver.java | 21 +++++++++++++++++++ 5 files changed, 61 insertions(+), 8 deletions(-) diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/MvcUtils.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/MvcUtils.java index 72f30289..5eec2dd6 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/MvcUtils.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/MvcUtils.java @@ -25,6 +25,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Optional; @@ -72,6 +73,11 @@ public abstract class MvcUtils { */ public static final String GATEWAY_ATTRIBUTES_ATTR = qualify("gatewayAttributes"); + /** + * Gateway original request URL attribute name. + */ + public static final String GATEWAY_ORIGINAL_REQUEST_URL_ATTR = qualify("gatewayOriginalRequestUrl"); + /** * Gateway request URL attribute name. */ @@ -250,6 +256,15 @@ public abstract class MvcUtils { request.servletRequest().setAttribute(GATEWAY_REQUEST_URL_ATTR, url); } + public static void addOriginalRequestUrl(ServerRequest request, URI url) { + LinkedHashSet urls = getAttribute(request, GATEWAY_ORIGINAL_REQUEST_URL_ATTR); + if (urls == null) { + urls = new LinkedHashSet<>(); + } + urls.add(url); + putAttribute(request, GATEWAY_ORIGINAL_REQUEST_URL_ATTR, urls); + } + private record ByteArrayInputMessage(ServerRequest request, ByteArrayInputStream body) implements HttpInputMessage { @Override diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/BeforeFilterFunctions.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/BeforeFilterFunctions.java index 650f9ca6..352bacbb 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/BeforeFilterFunctions.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/BeforeFilterFunctions.java @@ -389,6 +389,7 @@ public abstract class BeforeFilterFunctions { public static Function stripPrefix(int parts) { return request -> { + MvcUtils.addOriginalRequestUrl(request, request.uri()); // TODO: gateway url attributes String path = request.uri().getRawPath(); // TODO: begin duplicate code from StripPrefixGatewayFilterFactory @@ -414,6 +415,8 @@ public abstract class BeforeFilterFunctions { .replacePath(newPath.toString()) .build(true) .toUri(); + MvcUtils.setRequestUrl(request, prefixedUri); + return ServerRequest.from(request).uri(prefixedUri).build(); }; } diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/XForwardedRequestHeadersFilter.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/XForwardedRequestHeadersFilter.java index db5e0539..9b8b9bcb 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/XForwardedRequestHeadersFilter.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/XForwardedRequestHeadersFilter.java @@ -25,6 +25,7 @@ import java.util.Map; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.DeprecatedConfigurationProperty; import org.springframework.boot.context.properties.PropertyMapper; +import org.springframework.cloud.gateway.server.mvc.common.MvcUtils; import org.springframework.core.Ordered; import org.springframework.http.HttpHeaders; import org.springframework.util.ObjectUtils; @@ -397,18 +398,15 @@ public class XForwardedRequestHeadersFilter implements HttpHeadersFilter.Request // - see XForwardedHeadersFilterTests, so first get uris, then extract paths // and remove one from another if it's the ending part. - LinkedHashSet originalUris = null; // TODO: - // exchange.getAttribute(GATEWAY_ORIGINAL_REQUEST_URL_ATTR); - URI requestUri = null; // TODO: - // exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR); + LinkedHashSet originalUris = MvcUtils.getAttribute(request, + MvcUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR); + URI requestUri = MvcUtils.getAttribute(request, MvcUtils.GATEWAY_REQUEST_URL_ATTR); if (originalUris != null && requestUri != null) { originalUris.forEach(originalUri -> { if (originalUri != null && originalUri.getPath() != null) { - String prefix = originalUri.getPath(); - // strip trailing slashes before checking if request path is end // of original path String originalUriPath = stripTrailingSlash(originalUri); diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/ServerMvcIntegrationTests.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/ServerMvcIntegrationTests.java index 9deff0ed..d5f46144 100644 --- a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/ServerMvcIntegrationTests.java +++ b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/ServerMvcIntegrationTests.java @@ -242,6 +242,14 @@ public class ServerMvcIntegrationTests { .consumeWith(res -> { Map map = res.getResponseBody(); Map headers = getMap(map, "headers"); + assertThat(headers).containsKeys( + XForwardedRequestHeadersFilter.X_FORWARDED_PREFIX_HEADER, + XForwardedRequestHeadersFilter.X_FORWARDED_HOST_HEADER, + XForwardedRequestHeadersFilter.X_FORWARDED_PORT_HEADER, + XForwardedRequestHeadersFilter.X_FORWARDED_PROTO_HEADER, + XForwardedRequestHeadersFilter.X_FORWARDED_FOR_HEADER); + assertThat(headers).containsEntry( + XForwardedRequestHeadersFilter.X_FORWARDED_PREFIX_HEADER, "/long/path/to"); assertThat(headers).containsEntry("X-Test", "stripPrefix"); }); } @@ -260,6 +268,14 @@ public class ServerMvcIntegrationTests { Map map = res.getResponseBody(); assertThat(map).containsEntry("data", "hello"); Map headers = getMap(map, "headers"); + assertThat(headers).containsKeys( + XForwardedRequestHeadersFilter.X_FORWARDED_PREFIX_HEADER, + XForwardedRequestHeadersFilter.X_FORWARDED_HOST_HEADER, + XForwardedRequestHeadersFilter.X_FORWARDED_PORT_HEADER, + XForwardedRequestHeadersFilter.X_FORWARDED_PROTO_HEADER, + XForwardedRequestHeadersFilter.X_FORWARDED_FOR_HEADER); + assertThat(headers).containsEntry( + XForwardedRequestHeadersFilter.X_FORWARDED_PREFIX_HEADER, "/long/path/to"); assertThat(headers).containsEntry("X-Test", "stripPrefixPost"); }); } @@ -1068,9 +1084,9 @@ public class ServerMvcIntegrationTests { public RouterFunction gatewayRouterFunctionsStripPrefix() { // @formatter:off return route(GET("/long/path/to/get"), http()) - .filter(new HttpbinUriResolver()) .filter(stripPrefix(3)) .filter(addRequestHeader("X-Test", "stripPrefix")) + .filter(new HttpbinUriResolver(true)) .withAttribute(MvcUtils.GATEWAY_ROUTE_ID_ATTR, "teststripprefix"); // @formatter:on } @@ -1080,9 +1096,9 @@ public class ServerMvcIntegrationTests { // @formatter:off return route("teststripprefixpost") .route(POST("/long/path/to/post").and(host("**.stripprefixpost.org")), http()) - .filter(new HttpbinUriResolver()) .filter(stripPrefix(3)) .filter(addRequestHeader("X-Test", "stripPrefixPost")) + .filter(new HttpbinUriResolver(true)) .build(); // @formatter:on } diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/HttpbinUriResolver.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/HttpbinUriResolver.java index c46e1f25..0d327185 100644 --- a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/HttpbinUriResolver.java +++ b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/HttpbinUriResolver.java @@ -17,6 +17,7 @@ package org.springframework.cloud.gateway.server.mvc.test; import java.net.URI; +import java.net.URISyntaxException; import java.util.function.Function; import org.springframework.cloud.gateway.server.mvc.common.MvcUtils; @@ -30,12 +31,32 @@ import org.springframework.web.servlet.function.ServerResponse; public class HttpbinUriResolver implements Function, HandlerFilterFunction { + private final boolean preservePath; + + public HttpbinUriResolver(boolean preservePath) { + this.preservePath = preservePath; + } + + public HttpbinUriResolver() { + this(false); + } + protected URI uri(ServerRequest request) { ApplicationContext context = MvcUtils.getApplicationContext(request); Integer port = context.getEnvironment().getProperty("httpbin.port", Integer.class); String host = context.getEnvironment().getProperty("httpbin.host"); Assert.hasText(host, "httpbin.host is not set, did you initialize HttpbinTestcontainers?"); Assert.notNull(port, "httpbin.port is not set, did you initialize HttpbinTestcontainers?"); + if (preservePath) { + URI original = request.uri(); + try { + return new URI("http", original.getUserInfo(), host, port, original.getPath(), + original.getQuery(), original.getFragment()); + } catch (URISyntaxException e) { + throw new IllegalArgumentException(e.getMessage(), e); + } + } + return URI.create(String.format("http://%s:%d", host, port)); } From 9843fdf1478ca7c310d7ca5e8221336d3aecaacc Mon Sep 17 00:00:00 2001 From: spencergibb Date: Mon, 17 Mar 2025 15:44:11 -0400 Subject: [PATCH 89/90] Updates for forwarded prefix header. Adjusts all path related filters to add the original uri and set the request url See gh-3443 --- .../gateway/server/mvc/common/MvcUtils.java | 8 +-- .../mvc/filter/BeforeFilterFunctions.java | 16 ++--- .../filter/LoadBalancerFilterFunctions.java | 2 + .../server/mvc/ServerMvcIntegrationTests.java | 63 ++++++++++++++----- .../server/mvc/test/HttpbinUriResolver.java | 27 +++----- 5 files changed, 70 insertions(+), 46 deletions(-) diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/MvcUtils.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/MvcUtils.java index 5eec2dd6..05c073ed 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/MvcUtils.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/MvcUtils.java @@ -256,13 +256,11 @@ public abstract class MvcUtils { request.servletRequest().setAttribute(GATEWAY_REQUEST_URL_ATTR, url); } + @SuppressWarnings("unchecked") public static void addOriginalRequestUrl(ServerRequest request, URI url) { - LinkedHashSet urls = getAttribute(request, GATEWAY_ORIGINAL_REQUEST_URL_ATTR); - if (urls == null) { - urls = new LinkedHashSet<>(); - } + LinkedHashSet urls = (LinkedHashSet) request.attributes() + .computeIfAbsent(GATEWAY_ORIGINAL_REQUEST_URL_ATTR, s -> new LinkedHashSet<>()); urls.add(url); - putAttribute(request, GATEWAY_ORIGINAL_REQUEST_URL_ATTR, urls); } private record ByteArrayInputMessage(ServerRequest request, ByteArrayInputStream body) implements HttpInputMessage { diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/BeforeFilterFunctions.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/BeforeFilterFunctions.java index 352bacbb..96dac3d1 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/BeforeFilterFunctions.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/BeforeFilterFunctions.java @@ -189,12 +189,14 @@ public abstract class BeforeFilterFunctions { final UriTemplate uriTemplate = new UriTemplate(prefix); return request -> { + MvcUtils.addOriginalRequestUrl(request, request.uri()); Map uriVariables = MvcUtils.getUriTemplateVariables(request); URI uri = uriTemplate.expand(uriVariables); String newPath = uri.getRawPath() + request.uri().getRawPath(); URI prefixedUri = UriComponentsBuilder.fromUri(request.uri()).replacePath(newPath).build().toUri(); + MvcUtils.setRequestUrl(request, prefixedUri); return ServerRequest.from(request).uri(prefixedUri).build(); }; } @@ -326,7 +328,7 @@ public abstract class BeforeFilterFunctions { String normalizedReplacement = replacement.replace("$\\", "$"); Pattern pattern = Pattern.compile(regexp); return request -> { - // TODO: original request url + MvcUtils.addOriginalRequestUrl(request, request.uri()); String path = request.uri().getRawPath(); String newPath = pattern.matcher(path).replaceAll(normalizedReplacement); @@ -334,8 +336,7 @@ public abstract class BeforeFilterFunctions { ServerRequest modified = ServerRequest.from(request).uri(rewrittenUri).build(); - // TODO: can this be restored at some point? - // MvcUtils.setRequestUrl(modified, modified.uri()); + MvcUtils.setRequestUrl(request, rewrittenUri); return modified; }; } @@ -351,14 +352,13 @@ public abstract class BeforeFilterFunctions { UriTemplate uriTemplate = new UriTemplate(path); return request -> { + MvcUtils.addOriginalRequestUrl(request, request.uri()); Map uriVariables = MvcUtils.getUriTemplateVariables(request); URI uri = uriTemplate.expand(uriVariables); - URI prefixedUri = UriComponentsBuilder.fromUri(request.uri()) - .replacePath(uri.getRawPath()) - .build(true) - .toUri(); - return ServerRequest.from(request).uri(prefixedUri).build(); + URI newUri = UriComponentsBuilder.fromUri(request.uri()).replacePath(uri.getRawPath()).build(true).toUri(); + MvcUtils.setRequestUrl(request, newUri); + return ServerRequest.from(request).uri(newUri).build(); }; } diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/LoadBalancerFilterFunctions.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/LoadBalancerFilterFunctions.java index ab6d47dc..db7b1464 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/LoadBalancerFilterFunctions.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/LoadBalancerFilterFunctions.java @@ -63,6 +63,8 @@ public abstract class LoadBalancerFilterFunctions { public static HandlerFilterFunction lb(String serviceId, BiFunction reconstructUriFunction) { return (request, next) -> { + MvcUtils.addOriginalRequestUrl(request, request.uri()); + LoadBalancerClientFactory clientFactory = getApplicationContext(request) .getBean(LoadBalancerClientFactory.class); Set supportedLifecycleProcessors = LoadBalancerLifecycleValidator diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/ServerMvcIntegrationTests.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/ServerMvcIntegrationTests.java index d5f46144..4188d73a 100644 --- a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/ServerMvcIntegrationTests.java +++ b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/ServerMvcIntegrationTests.java @@ -235,6 +235,7 @@ public class ServerMvcIntegrationTests { public void stripPrefixWorks() { restClient.get() .uri("/long/path/to/get") + .header("Host", "www.stripprefix.org") .exchange() .expectStatus() .isOk() @@ -242,14 +243,13 @@ public class ServerMvcIntegrationTests { .consumeWith(res -> { Map map = res.getResponseBody(); Map headers = getMap(map, "headers"); - assertThat(headers).containsKeys( - XForwardedRequestHeadersFilter.X_FORWARDED_PREFIX_HEADER, + assertThat(headers).containsKeys(XForwardedRequestHeadersFilter.X_FORWARDED_PREFIX_HEADER, XForwardedRequestHeadersFilter.X_FORWARDED_HOST_HEADER, XForwardedRequestHeadersFilter.X_FORWARDED_PORT_HEADER, XForwardedRequestHeadersFilter.X_FORWARDED_PROTO_HEADER, XForwardedRequestHeadersFilter.X_FORWARDED_FOR_HEADER); - assertThat(headers).containsEntry( - XForwardedRequestHeadersFilter.X_FORWARDED_PREFIX_HEADER, "/long/path/to"); + assertThat(headers).containsEntry(XForwardedRequestHeadersFilter.X_FORWARDED_PREFIX_HEADER, + "/long/path/to"); assertThat(headers).containsEntry("X-Test", "stripPrefix"); }); } @@ -268,18 +268,40 @@ public class ServerMvcIntegrationTests { Map map = res.getResponseBody(); assertThat(map).containsEntry("data", "hello"); Map headers = getMap(map, "headers"); - assertThat(headers).containsKeys( - XForwardedRequestHeadersFilter.X_FORWARDED_PREFIX_HEADER, + assertThat(headers).containsKeys(XForwardedRequestHeadersFilter.X_FORWARDED_PREFIX_HEADER, XForwardedRequestHeadersFilter.X_FORWARDED_HOST_HEADER, XForwardedRequestHeadersFilter.X_FORWARDED_PORT_HEADER, XForwardedRequestHeadersFilter.X_FORWARDED_PROTO_HEADER, XForwardedRequestHeadersFilter.X_FORWARDED_FOR_HEADER); - assertThat(headers).containsEntry( - XForwardedRequestHeadersFilter.X_FORWARDED_PREFIX_HEADER, "/long/path/to"); + assertThat(headers).containsEntry(XForwardedRequestHeadersFilter.X_FORWARDED_PREFIX_HEADER, + "/long/path/to"); assertThat(headers).containsEntry("X-Test", "stripPrefixPost"); }); } + @Test + public void stripPrefixLbWorks() { + restClient.get() + .uri("/long/path/to/get") + .header("Host", "www.stripprefixlb.org") + .exchange() + .expectStatus() + .isOk() + .expectBody(Map.class) + .consumeWith(res -> { + Map map = res.getResponseBody(); + Map headers = getMap(map, "headers"); + assertThat(headers).containsKeys(XForwardedRequestHeadersFilter.X_FORWARDED_PREFIX_HEADER, + XForwardedRequestHeadersFilter.X_FORWARDED_HOST_HEADER, + XForwardedRequestHeadersFilter.X_FORWARDED_PORT_HEADER, + XForwardedRequestHeadersFilter.X_FORWARDED_PROTO_HEADER, + XForwardedRequestHeadersFilter.X_FORWARDED_FOR_HEADER); + assertThat(headers).containsEntry(XForwardedRequestHeadersFilter.X_FORWARDED_PREFIX_HEADER, + "/long/path/to"); + assertThat(headers).containsEntry("X-Test", "stripPrefix"); + }); + } + @Test public void setStatusGatewayRouterFunctionWorks() { restClient.get() @@ -1074,8 +1096,8 @@ public class ServerMvcIntegrationTests { // @formatter:off return route("testsetpath") .route(POST("/mycustompath{extra}").and(host("**.setpathpost.org")), http()) - .filter(new HttpbinUriResolver()) .filter(setPath("/{extra}")) + .filter(new HttpbinUriResolver()) .build(); // @formatter:on } @@ -1083,11 +1105,12 @@ public class ServerMvcIntegrationTests { @Bean public RouterFunction gatewayRouterFunctionsStripPrefix() { // @formatter:off - return route(GET("/long/path/to/get"), http()) + return route("teststripprefix") + .route(GET("/long/path/to/get").and(host("**.stripprefix.org")), http()) .filter(stripPrefix(3)) .filter(addRequestHeader("X-Test", "stripPrefix")) - .filter(new HttpbinUriResolver(true)) - .withAttribute(MvcUtils.GATEWAY_ROUTE_ID_ATTR, "teststripprefix"); + .filter(new HttpbinUriResolver()) + .build(); // @formatter:on } @@ -1098,7 +1121,19 @@ public class ServerMvcIntegrationTests { .route(POST("/long/path/to/post").and(host("**.stripprefixpost.org")), http()) .filter(stripPrefix(3)) .filter(addRequestHeader("X-Test", "stripPrefixPost")) - .filter(new HttpbinUriResolver(true)) + .filter(new HttpbinUriResolver()) + .build(); + // @formatter:on + } + + @Bean + public RouterFunction gatewayRouterFunctionsStripPrefixLb() { + // @formatter:off + return route("teststripprefix") + .route(GET("/long/path/to/get").and(host("**.stripprefixlb.org")), http()) + .filter(stripPrefix(3)) + .filter(addRequestHeader("X-Test", "stripPrefix")) + .filter(lb("httpbin")) .build(); // @formatter:on } @@ -1433,8 +1468,8 @@ public class ServerMvcIntegrationTests { return route("requestheadertorequesturi") .route(cloudFoundryRouteService().and(host("**.requestheadertorequesturi.org")), http()) //.before(new HttpbinUriResolver()) NO URI RESOLVER! - .before(requestHeaderToRequestUri("X-CF-Forwarded-Url")) .filter(setPath("/hello")) + .before(requestHeaderToRequestUri("X-CF-Forwarded-Url")) .build(); // @formatter:on } diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/HttpbinUriResolver.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/HttpbinUriResolver.java index 0d327185..93b6bb51 100644 --- a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/HttpbinUriResolver.java +++ b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/HttpbinUriResolver.java @@ -16,6 +16,7 @@ package org.springframework.cloud.gateway.server.mvc.test; +import java.lang.reflect.UndeclaredThrowableException; import java.net.URI; import java.net.URISyntaxException; import java.util.function.Function; @@ -31,33 +32,21 @@ import org.springframework.web.servlet.function.ServerResponse; public class HttpbinUriResolver implements Function, HandlerFilterFunction { - private final boolean preservePath; - - public HttpbinUriResolver(boolean preservePath) { - this.preservePath = preservePath; - } - - public HttpbinUriResolver() { - this(false); - } - protected URI uri(ServerRequest request) { ApplicationContext context = MvcUtils.getApplicationContext(request); Integer port = context.getEnvironment().getProperty("httpbin.port", Integer.class); String host = context.getEnvironment().getProperty("httpbin.host"); Assert.hasText(host, "httpbin.host is not set, did you initialize HttpbinTestcontainers?"); Assert.notNull(port, "httpbin.port is not set, did you initialize HttpbinTestcontainers?"); - if (preservePath) { - URI original = request.uri(); - try { - return new URI("http", original.getUserInfo(), host, port, original.getPath(), - original.getQuery(), original.getFragment()); - } catch (URISyntaxException e) { - throw new IllegalArgumentException(e.getMessage(), e); - } + URI original = request.uri(); + try { + return new URI("http", original.getUserInfo(), host, port, original.getPath(), original.getQuery(), + original.getFragment()); + } + catch (URISyntaxException e) { + throw new UndeclaredThrowableException(e); } - return URI.create(String.format("http://%s:%d", host, port)); } @Override From c1c741d0227850ac8e2cfd0f0015db788e4ddf20 Mon Sep 17 00:00:00 2001 From: spencergibb Date: Mon, 17 Mar 2025 16:12:09 -0400 Subject: [PATCH 90/90] For lb: scheme, put associated filters last. This makes sure the lb filter runs after path modifying filters like stripPrefix. See gh-3443 --- .../filters/loadbalancer.adoc | 2 ++ .../filters/prefixpath.adoc | 3 ++- .../filters/rewritepath.adoc | 1 + .../spring-cloud-gateway-server-mvc/filters/setpath.adoc | 1 + .../filters/stripprefix.adoc | 1 + .../server/mvc/config/RouterFunctionHolderFactory.java | 9 ++++++++- ...GatewayMvcPropertiesBeanDefinitionRegistrarTests.java | 4 ++-- ...pplication-propertiesbeandefinitionregistrartests.yml | 5 ++++- 8 files changed, 21 insertions(+), 5 deletions(-) diff --git a/docs/modules/ROOT/pages/spring-cloud-gateway-server-mvc/filters/loadbalancer.adoc b/docs/modules/ROOT/pages/spring-cloud-gateway-server-mvc/filters/loadbalancer.adoc index 58859f9c..2135aaf2 100644 --- a/docs/modules/ROOT/pages/spring-cloud-gateway-server-mvc/filters/loadbalancer.adoc +++ b/docs/modules/ROOT/pages/spring-cloud-gateway-server-mvc/filters/loadbalancer.adoc @@ -43,6 +43,8 @@ spring: - Path=/api/** ---- +WARNING: If using the `lb()` filter, it needs to be after any filter that manipulates the path such as `setPath()` or `stripPrefix()`, otherwise the resulting url could be incorrect. The `lb:` scheme handler in configuration, automatically puts the filter in the highest precedence order. + NOTE: By default, when a service instance cannot be found by the `ReactorLoadBalancer`, a `503` is returned. // TODO: implement use404 // You can configure the gateway to return a `404` by setting `spring.cloud.gateway.loadbalancer.use404=true`. diff --git a/docs/modules/ROOT/pages/spring-cloud-gateway-server-mvc/filters/prefixpath.adoc b/docs/modules/ROOT/pages/spring-cloud-gateway-server-mvc/filters/prefixpath.adoc index 2313b6e5..5bc8b5e8 100644 --- a/docs/modules/ROOT/pages/spring-cloud-gateway-server-mvc/filters/prefixpath.adoc +++ b/docs/modules/ROOT/pages/spring-cloud-gateway-server-mvc/filters/prefixpath.adoc @@ -33,7 +33,7 @@ class RouteConfiguration { public RouterFunction gatewayRouterFunctionsPrefixPath() { return route("prefixpath_route") .GET("/**", http("https://example.org")) - .before("/mypath") + .before(prefixPath("/mypath")) .build(); } } @@ -42,3 +42,4 @@ class RouteConfiguration { This prefixes `/mypath` to the path of all matching requests. So a request to `/hello` is sent to `/mypath/hello`. +WARNING: If using the `lb()` filter, it needs to be after the `prefixPath()` filter, otherwise the resulting url could be incorrect. The `lb:` scheme handler in configuration, automatically puts the filter in the highest precedence order. diff --git a/docs/modules/ROOT/pages/spring-cloud-gateway-server-mvc/filters/rewritepath.adoc b/docs/modules/ROOT/pages/spring-cloud-gateway-server-mvc/filters/rewritepath.adoc index 7247db46..3b50b615 100644 --- a/docs/modules/ROOT/pages/spring-cloud-gateway-server-mvc/filters/rewritepath.adoc +++ b/docs/modules/ROOT/pages/spring-cloud-gateway-server-mvc/filters/rewritepath.adoc @@ -43,3 +43,4 @@ class RouteConfiguration { For a request path of `/red/blue`, this sets the path to `/blue` before making the downstream request. Note that in `application.yml` the `$` should be replaced with `$\` because of the YAML specification. +WARNING: If using the `lb()` filter, it needs to be after the `rewritePath()` filter, otherwise the resulting url could be incorrect. The `lb:` scheme handler in configuration, automatically puts the filter in the highest precedence order. \ No newline at end of file diff --git a/docs/modules/ROOT/pages/spring-cloud-gateway-server-mvc/filters/setpath.adoc b/docs/modules/ROOT/pages/spring-cloud-gateway-server-mvc/filters/setpath.adoc index 261345e6..9355a068 100644 --- a/docs/modules/ROOT/pages/spring-cloud-gateway-server-mvc/filters/setpath.adoc +++ b/docs/modules/ROOT/pages/spring-cloud-gateway-server-mvc/filters/setpath.adoc @@ -45,3 +45,4 @@ class RouteConfiguration { For a request path of `/red/blue`, this sets the path to `/blue` before making the downstream request. +WARNING: If using the `lb()` filter, it needs to be after the `setPath()` filter, otherwise the resulting url could be incorrect. The `lb:` scheme handler in configuration, automatically puts the filter in the highest precedence order. \ No newline at end of file diff --git a/docs/modules/ROOT/pages/spring-cloud-gateway-server-mvc/filters/stripprefix.adoc b/docs/modules/ROOT/pages/spring-cloud-gateway-server-mvc/filters/stripprefix.adoc index ee2d2efa..dc2be798 100644 --- a/docs/modules/ROOT/pages/spring-cloud-gateway-server-mvc/filters/stripprefix.adoc +++ b/docs/modules/ROOT/pages/spring-cloud-gateway-server-mvc/filters/stripprefix.adoc @@ -43,3 +43,4 @@ class RouteConfiguration { When a request is made through the gateway to `/name/blue/red`, the request made to `nameservice` looks like `https://nameservice/red`. +WARNING: If using the `lb()` filter, it needs to be after the `stripPrefix()` filter, otherwise the resulting url could be incorrect. The `lb:` scheme handler in configuration, automatically puts the filter in the highest precedence order. \ No newline at end of file diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/RouterFunctionHolderFactory.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/RouterFunctionHolderFactory.java index 687bdec8..dcebc7e7 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/RouterFunctionHolderFactory.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/RouterFunctionHolderFactory.java @@ -16,6 +16,7 @@ package org.springframework.cloud.gateway.server.mvc.config; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; @@ -178,12 +179,15 @@ public class RouterFunctionHolderFactory { NormalizedOperationMethod normalizedOpMethod = handlerOperationMethod.get(); Object response = invokeOperation(normalizedOpMethod, normalizedOpMethod.getNormalizedArgs()); HandlerFunction handlerFunction = null; + + // filters added by HandlerDiscoverer need to go last, so save them + List> handlerFilterFunctionFilters = new ArrayList<>(); if (response instanceof HandlerFunction) { handlerFunction = (HandlerFunction) response; } else if (response instanceof HandlerDiscoverer.Result result) { handlerFunction = result.getHandlerFunction(); - result.getFilters().forEach(builder::filter); + handlerFilterFunctionFilters.addAll(result.getFilters()); } if (handlerFunction == null) { throw new IllegalStateException( @@ -221,6 +225,9 @@ public class RouterFunctionHolderFactory { translate(filterOperations, filterProperties.getName(), args, HandlerFilterFunction.class, builder::filter); }); + // HandlerDiscoverer filters need higher priority, so put them last + handlerFilterFunctionFilters.forEach(builder::filter); + builder.withAttribute(MvcUtils.GATEWAY_ROUTE_ID_ATTR, routeId); return builder.build(); diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/config/GatewayMvcPropertiesBeanDefinitionRegistrarTests.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/config/GatewayMvcPropertiesBeanDefinitionRegistrarTests.java index f4f7675c..861b1694 100644 --- a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/config/GatewayMvcPropertiesBeanDefinitionRegistrarTests.java +++ b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/config/GatewayMvcPropertiesBeanDefinitionRegistrarTests.java @@ -122,7 +122,7 @@ public class GatewayMvcPropertiesBeanDefinitionRegistrarTests { predicate.accept(new AbstractRequestPredicatesVisitor() { @Override public void path(String pattern) { - assertThat(pattern).isEqualTo("/anything/listRoute3"); + assertThat(pattern).isEqualTo("/extra/anything/listRoute3"); } @Override @@ -181,7 +181,7 @@ public class GatewayMvcPropertiesBeanDefinitionRegistrarTests { @SuppressWarnings("unchecked") public void lbRouteWorks() { restClient.get() - .uri("/anything/listRoute3") + .uri("/extra/anything/listRoute3") .header("MyHeaderName", "MyHeaderVal") .exchange() .expectStatus() diff --git a/spring-cloud-gateway-server-mvc/src/test/resources/application-propertiesbeandefinitionregistrartests.yml b/spring-cloud-gateway-server-mvc/src/test/resources/application-propertiesbeandefinitionregistrartests.yml index 5270b8cb..a91fe14d 100644 --- a/spring-cloud-gateway-server-mvc/src/test/resources/application-propertiesbeandefinitionregistrartests.yml +++ b/spring-cloud-gateway-server-mvc/src/test/resources/application-propertiesbeandefinitionregistrartests.yml @@ -38,9 +38,12 @@ spring.cloud.gateway.mvc: - id: listRoute3 uri: lb://httpbin predicates: - - Path=/anything/listRoute3 + - Path=/extra/anything/listRoute3 - Header=MyHeaderName,MyHeader.* filters: + - name: StripPrefix + args: + parts: 1 - name: AddRequestHeader args: name: X-Test