Skip to content
Open
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 @@ -19,8 +19,10 @@
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;

import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
Expand All @@ -31,13 +33,16 @@

/**
* @author Spencer Gibb
* @author Alpha
*/
public class HostRoutePredicateFactory extends AbstractRoutePredicateFactory<HostRoutePredicateFactory.Config> {

private boolean includePort = true;

private PathMatcher pathMatcher = new AntPathMatcher(".");

private boolean pathMatcherCustomized;

public HostRoutePredicateFactory() {
this(true);
}
Expand All @@ -49,6 +54,7 @@ public HostRoutePredicateFactory(boolean includePort) {

public void setPathMatcher(PathMatcher pathMatcher) {
this.pathMatcher = pathMatcher;
this.pathMatcherCustomized = true;
}

/* for testing */ void setIncludePort(boolean includePort) {
Expand All @@ -67,6 +73,21 @@ public ShortcutType shortcutType() {

@Override
public Predicate<ServerWebExchange> apply(Config config) {
List<String> patterns = new ArrayList<>(config.getPatterns());
Set<String> literalPatterns = new HashSet<>();
List<String> nonLiteralPatterns = new ArrayList<>();
boolean containsUriTemplateVariables = false;
for (String pattern : patterns) {
if (isLiteralPattern(pattern)) {
literalPatterns.add(pattern);
}
else {
nonLiteralPatterns.add(pattern);
containsUriTemplateVariables |= pattern.indexOf('{') >= 0;
}
}
boolean hasUriTemplateVariables = containsUriTemplateVariables;

return new GatewayPredicate() {
@Override
public boolean test(ServerWebExchange exchange) {
Expand All @@ -88,17 +109,28 @@ public boolean test(ServerWebExchange exchange) {
return false;
}

PathMatcher matcher = pathMatcher;
boolean useFastPath = !pathMatcherCustomized;
String match = null;
for (int i = 0; i < config.getPatterns().size(); i++) {
String pattern = config.getPatterns().get(i);
if (pathMatcher.match(pattern, host)) {
match = pattern;
break;
if (useFastPath && !hasUriTemplateVariables && literalPatterns.contains(host)) {
match = host;
}
else {
List<String> patternsToCheck = useFastPath && !hasUriTemplateVariables ? nonLiteralPatterns
: patterns;
for (String pattern : patternsToCheck) {
boolean matches = useFastPath && isLiteralPattern(pattern) ? pattern.equals(host)
: matcher.match(pattern, host);
if (matches) {
match = pattern;
break;
}
}
}

if (match != null) {
Map<String, String> variables = pathMatcher.extractUriTemplateVariables(match, host);
Map<String, String> variables = !useFastPath || match.indexOf('{') >= 0
? matcher.extractUriTemplateVariables(match, host) : Collections.emptyMap();
ServerWebExchangeUtils.putUriTemplateVariables(exchange, variables);
return true;
}
Expand All @@ -118,6 +150,10 @@ public String toString() {
};
}

private static boolean isLiteralPattern(String pattern) {
return pattern.indexOf('*') < 0 && pattern.indexOf('?') < 0 && pattern.indexOf('{') < 0;
}

public static class Config {

private List<String> patterns = new ArrayList<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
package org.springframework.cloud.gateway.handler.predicate;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;

import org.junit.jupiter.api.Test;
Expand All @@ -30,10 +32,15 @@
import org.springframework.cloud.gateway.handler.predicate.HostRoutePredicateFactory.Config;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.cloud.gateway.test.BaseWebClientTests;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.web.server.MockServerWebExchange;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;

import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
Expand Down Expand Up @@ -94,6 +101,60 @@ public void sameHostWithPort() {
hostPredicate.setIncludePort(true);
}

@Test
public void literalHostMatchKeepsExistingUriTemplateVariables() {
Config config = new Config().setPatterns(List.of("www.example.org"));
Predicate<ServerWebExchange> predicate = new HostRoutePredicateFactory().apply(config);
ServerWebExchange exchange = MockServerWebExchange
.from(MockServerHttpRequest.get("/").header("Host", "www.example.org").build());
ServerWebExchangeUtils.putUriTemplateVariables(exchange, Map.of("existing", "value"));

assertThat(predicate.test(exchange)).isTrue();
assertThat(ServerWebExchangeUtils.getUriTemplateVariables(exchange)).containsEntry("existing", "value")
.hasSize(1);
}

@Test
public void hostTemplateVariablesRetainPatternOrder() {
Config config = new Config().setPatterns(List.of("{tenant}.example.org", "api.example.org"));
Predicate<ServerWebExchange> predicate = new HostRoutePredicateFactory().apply(config);
ServerWebExchange exchange = MockServerWebExchange
.from(MockServerHttpRequest.get("/").header("Host", "api.example.org").build());

assertThat(predicate.test(exchange)).isTrue();
assertThat(ServerWebExchangeUtils.getUriTemplateVariables(exchange)).containsEntry("tenant", "api");
}

@Test
public void customPathMatcherStillHandlesLiteralPatterns() {
HostRoutePredicateFactory factory = new HostRoutePredicateFactory();
factory.setPathMatcher(new AntPathMatcher(".") {
@Override
public boolean match(String pattern, String path) {
return false;
}
});
Config config = new Config().setPatterns(List.of("www.example.org"));
Predicate<ServerWebExchange> predicate = factory.apply(config);
ServerWebExchange exchange = MockServerWebExchange
.from(MockServerHttpRequest.get("/").header("Host", "www.example.org").build());

assertThat(predicate.test(exchange)).isFalse();
}

@Test
public void exactHostMatchRespectsIncludePortSetting() {
Config withPort = new Config().setPatterns(List.of("www.example.org:8080"));
Predicate<ServerWebExchange> withPortPredicate = new HostRoutePredicateFactory(true).apply(withPort);
Config withoutPort = new Config().setPatterns(List.of("www.example.org"));
Predicate<ServerWebExchange> withoutPortPredicate = new HostRoutePredicateFactory(false).apply(withoutPort);
ServerWebExchange exchange = MockServerWebExchange
.from(MockServerHttpRequest.get("/").header("Host", "www.example.org:8080").build());

assertThat(withPortPredicate.test(exchange)).isTrue();
assertThat(withoutPortPredicate.test(exchange)).isTrue();
}

@Test
public void toStringFormat() {
Config config = new Config().setPatterns(Arrays.asList("pattern1", "pattern2"));
Expand Down
Loading