Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,8 @@ This route matches if the request path was, for example: `/red/1` or `/red/1/` o

If `matchTrailingSlash` is set to `false`, then request path `/red/1/` will not be matched.

If you have set `spring.webflux.base-path` property, this will influence the path matching. The property value will be automatically prepended to the path patterns. For example, with `spring.webflux.base-path=/app` and a path pattern of `/red/{segment}`, the full pattern used for matching would be `/app/red/{segment}`.

This predicate extracts the URI template variables (such as `segment`, defined in the preceding example) as a map of names and values and places it in the `ServerWebExchange.getAttributes()` with a key defined in `ServerWebExchangeUtils.URI_TEMPLATE_VARIABLES_ATTRIBUTE`.
Those values are then available for use by <<gateway-route-filters,`GatewayFilter` factories>>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
import org.springframework.boot.autoconfigure.web.embedded.NettyWebServerFactoryCustomizer;
import org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration;
import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration;
import org.springframework.boot.autoconfigure.web.reactive.WebFluxProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.boot.ssl.SslBundles;
Expand Down Expand Up @@ -190,6 +191,7 @@
* @author Mete Alpaslan Katırcıoğlu
* @author Alberto C. Ríos
* @author Olga Maciaszek-Sharma
* @author FuYiNan Guo
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = "spring.cloud.gateway.enabled", matchIfMissing = true)
Expand Down Expand Up @@ -471,8 +473,8 @@ public MethodRoutePredicateFactory methodRoutePredicateFactory() {

@Bean
@ConditionalOnEnabledPredicate
public PathRoutePredicateFactory pathRoutePredicateFactory() {
return new PathRoutePredicateFactory();
public PathRoutePredicateFactory pathRoutePredicateFactory(WebFluxProperties webFluxProperties) {
return new PathRoutePredicateFactory(webFluxProperties);
}

@Bean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.boot.autoconfigure.web.reactive.WebFluxProperties;
import org.springframework.core.style.ToStringCreator;
import org.springframework.http.server.PathContainer;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.pattern.PathPattern;
import org.springframework.web.util.pattern.PathPattern.PathMatchInfo;
Expand All @@ -41,6 +43,7 @@
/**
* @author Spencer Gibb
* @author Dhawal Kapil
* @author FuYiNan Guo
*/
public class PathRoutePredicateFactory extends AbstractRoutePredicateFactory<PathRoutePredicateFactory.Config> {

Expand All @@ -50,8 +53,20 @@ public class PathRoutePredicateFactory extends AbstractRoutePredicateFactory<Pat

private PathPatternParser pathPatternParser = new PathPatternParser();

private final WebFluxProperties webFluxProperties;

/**
* @deprecated {@link #PathRoutePredicateFactory(WebFluxProperties)}
*/
@Deprecated
public PathRoutePredicateFactory() {
super(Config.class);
this.webFluxProperties = new WebFluxProperties();
}

public PathRoutePredicateFactory(WebFluxProperties webFluxProperties) {
super(Config.class);
this.webFluxProperties = webFluxProperties;
}

private static void traceMatch(String prefix, Object desired, Object actual, boolean match) {
Expand Down Expand Up @@ -82,7 +97,16 @@ public Predicate<ServerWebExchange> apply(Config config) {
synchronized (this.pathPatternParser) {
pathPatternParser.setMatchOptionalTrailingSeparator(config.isMatchTrailingSlash());
config.getPatterns().forEach(pattern -> {
PathPattern pathPattern = this.pathPatternParser.parse(pattern);
String basePath = webFluxProperties.getBasePath();
boolean basePathIsNotBlank = StringUtils.hasText(basePath);
String pathPatternStr = pattern;
if (basePathIsNotBlank) {
if (pattern.length() > 1 && !pattern.startsWith("/")) {
basePath += ("/");
}
pathPatternStr = basePath + pattern;
}
PathPattern pathPattern = this.pathPatternParser.parse(pathPatternStr);
pathPatterns.add(pathPattern);
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,41 @@

package org.springframework.cloud.gateway.handler.predicate;

import java.net.URI;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.function.Predicate;

import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import reactor.core.publisher.Mono;

import org.springframework.boot.autoconfigure.web.reactive.WebFluxProperties;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.StripPrefixGatewayFilterFactory;
import org.springframework.cloud.gateway.handler.AsyncPredicate;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.web.server.MockServerWebExchange;
import org.springframework.web.server.ServerWebExchange;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR;

/**
* @author Spencer Gibb
* @author FuYiNan Guo
*/
public class GatewayPredicateVisitorTests {

@Test
public void asyncPredicateVisitVisitsEachNode() {
PathRoutePredicateFactory pathRoutePredicateFactory = new PathRoutePredicateFactory();
PathRoutePredicateFactory pathRoutePredicateFactory = new PathRoutePredicateFactory(new WebFluxProperties());
HostRoutePredicateFactory hostRoutePredicateFactory = new HostRoutePredicateFactory();
ReadBodyRoutePredicateFactory readBodyRoutePredicateFactory1 = new ReadBodyRoutePredicateFactory();
ReadBodyRoutePredicateFactory readBodyRoutePredicateFactory2 = new ReadBodyRoutePredicateFactory();
Expand All @@ -55,7 +71,7 @@ public void asyncPredicateVisitVisitsEachNode() {

@Test
public void predicateVisitVisitsEachNode() {
PathRoutePredicateFactory pathRoutePredicateFactory = new PathRoutePredicateFactory();
PathRoutePredicateFactory pathRoutePredicateFactory = new PathRoutePredicateFactory(new WebFluxProperties());
HostRoutePredicateFactory hostRoutePredicateFactory = new HostRoutePredicateFactory();
Predicate<ServerWebExchange> predicate = pathRoutePredicateFactory.apply(pathRoutePredicateFactory.newConfig())
.and(hostRoutePredicateFactory.apply(hostRoutePredicateFactory.newConfig()));
Expand All @@ -68,4 +84,59 @@ public void predicateVisitVisitsEachNode() {
.hasExactlyElementsOfTypes(PathRoutePredicateFactory.Config.class, HostRoutePredicateFactory.Config.class);
}

@Test
public void pathRoutePredicateVisitWithSetWebfluxBasePath() {
WebFluxProperties webFluxProperties = new WebFluxProperties();
webFluxProperties.setBasePath("/gw/api/v1");

PathRoutePredicateFactory pathRoutePredicateFactory = new PathRoutePredicateFactory(webFluxProperties);
PathRoutePredicateFactory.Config config = new PathRoutePredicateFactory.Config()
.setPatterns(List.of("/temp/**"))
.setMatchTrailingSlash(true);

Predicate<ServerWebExchange> predicate = pathRoutePredicateFactory.apply(config);

ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("http://127.0.0.1:8080/gw/api/v1/temp/test")
.build());

assertThat(predicate.test(exchange)).isEqualTo(true);
}

@Test
public void pathRoutePredicateVisitWithSetWebfluxBasePathStripPrefix() {
WebFluxProperties webFluxProperties = new WebFluxProperties();
webFluxProperties.setBasePath("/gw/api/v1");

PathRoutePredicateFactory pathRoutePredicateFactory = new PathRoutePredicateFactory(webFluxProperties);
PathRoutePredicateFactory.Config config = new PathRoutePredicateFactory.Config()
.setPatterns(List.of("/temp/**"))
.setMatchTrailingSlash(true);

Predicate<ServerWebExchange> predicate = pathRoutePredicateFactory.apply(config);

ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("http://127.0.0.1:8080/gw/api/v1/temp/test")
.build());

assertThat(predicate.test(exchange)).isEqualTo(true);

// webflux base path strips prefix is 3
GatewayFilter filter = new StripPrefixGatewayFilterFactory().apply(c -> c.setParts(3));

GatewayFilterChain filterChain = mock(GatewayFilterChain.class);

ArgumentCaptor<ServerWebExchange> captor = ArgumentCaptor.forClass(ServerWebExchange.class);
when(filterChain.filter(captor.capture())).thenReturn(Mono.empty());

filter.filter(exchange, filterChain);

ServerWebExchange webExchange = captor.getValue();

assertThat(webExchange.getRequest().getURI()).hasPath("/temp/test");

URI requestUrl = webExchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR);
assertThat(requestUrl).hasScheme("http").hasHost("127.0.0.1").hasPort(8080).hasPath("/temp/test");

LinkedHashSet<URI> uris = webExchange.getRequiredAttribute(GATEWAY_ORIGINAL_REQUEST_URL_ATTR);
assertThat(uris).contains(exchange.getRequest().getURI());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.web.reactive.WebFluxProperties;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping;
import org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory.Config;
Expand Down Expand Up @@ -133,14 +134,14 @@ public void matchOptionalTrailingSeparatorCopiedToMatchTrailingSlash() {
@Test
public void toStringFormat() {
Config config = new Config().setPatterns(Arrays.asList("patternA", "patternB")).setMatchTrailingSlash(false);
Predicate predicate = new PathRoutePredicateFactory().apply(config);
Predicate predicate = new PathRoutePredicateFactory(new WebFluxProperties()).apply(config);
assertThat(predicate.toString()).contains("patternA").contains("patternB").contains("false");
}

@Test
public void toStringFormatMatchTrailingSlashTrue() {
Config config = new Config().setPatterns(Arrays.asList("patternA", "patternB")).setMatchTrailingSlash(true);
Predicate<ServerWebExchange> predicate = new PathRoutePredicateFactory().apply(config);
Predicate<ServerWebExchange> predicate = new PathRoutePredicateFactory(new WebFluxProperties()).apply(config);
assertThat(predicate.toString()).contains("patternA").contains("patternB").contains("true");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import org.openjdk.jmh.annotations.Threads;
import org.openjdk.jmh.annotations.Warmup;

import org.springframework.boot.autoconfigure.web.reactive.WebFluxProperties;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.web.server.MockServerWebExchange;
import org.springframework.web.server.ServerWebExchange;
Expand All @@ -54,7 +55,7 @@ public class PathRoutePredicatePathContainerAttrBenchMarkTests {
PathRoutePredicateFactory.Config config = new PathRoutePredicateFactory.Config()
.setPatterns(Collections.singletonList(PATH_PATTERN_PREFIX + i))
.setMatchTrailingSlash(true);
Predicate<ServerWebExchange> predicate = new PathRoutePredicateFactory().apply(config);
Predicate<ServerWebExchange> predicate = new PathRoutePredicateFactory(new WebFluxProperties()).apply(config);
predicates.add(predicate);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import io.micrometer.core.instrument.Tags;
import org.junit.jupiter.api.Test;

import org.springframework.boot.autoconfigure.web.reactive.WebFluxProperties;
import org.springframework.cloud.gateway.handler.predicate.HostRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.MethodRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory;
Expand Down Expand Up @@ -56,7 +57,7 @@ void addPathToRoutes() {
Route route = Route.async()
.id("git")
.uri(ROUTE_URI)
.predicate(new PathRoutePredicateFactory().apply(pathConfig)
.predicate(new PathRoutePredicateFactory(new WebFluxProperties()).apply(pathConfig)
.and(new HostRoutePredicateFactory().apply(hostConfig)))
.build();

Expand All @@ -81,8 +82,8 @@ void addsMultiplePathToRoutes() {
Route route = Route.async()
.id("git")
.uri(ROUTE_URI)
.predicate(new PathRoutePredicateFactory().apply(pathConfig)
.or(new PathRoutePredicateFactory().apply(pathConfig2)))
.predicate(new PathRoutePredicateFactory(new WebFluxProperties()).apply(pathConfig)
.or(new PathRoutePredicateFactory(new WebFluxProperties()).apply(pathConfig2)))
.build();

ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get(ROUTE_URI).build());
Expand Down
Loading