Adds support for saving original request URL to request attributes

This enables X-Forwarded-Prefix.

Fixes gh-3354
Fixes gh-3443
This commit is contained in:
Jonathon Henderson
2024-06-29 18:32:26 +01:00
committed by spencergibb
parent 88fce67f9d
commit f6e68fac8e
5 changed files with 61 additions and 8 deletions

View File

@@ -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<URI> 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

View File

@@ -389,6 +389,7 @@ public abstract class BeforeFilterFunctions {
public static Function<ServerRequest, ServerRequest> 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();
};
}

View File

@@ -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<URI> originalUris = null; // TODO:
// exchange.getAttribute(GATEWAY_ORIGINAL_REQUEST_URL_ATTR);
URI requestUri = null; // TODO:
// exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR);
LinkedHashSet<URI> 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);

View File

@@ -242,6 +242,14 @@ public class ServerMvcIntegrationTests {
.consumeWith(res -> {
Map<String, Object> map = res.getResponseBody();
Map<String, Object> 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<String, Object> map = res.getResponseBody();
assertThat(map).containsEntry("data", "hello");
Map<String, Object> 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<ServerResponse> 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
}

View File

@@ -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<ServerRequest, ServerRequest>, HandlerFilterFunction<ServerResponse, ServerResponse> {
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));
}