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