From 33a86df5c91074e8458aa0f6be57624ed47fb4d5 Mon Sep 17 00:00:00 2001 From: Alpha <2748109647@qq.com> Date: Thu, 29 Oct 2026 06:44:15 +0800 Subject: [PATCH] Optimize literal host predicate matching Signed-off-by: Alpha <2748109647@qq.com> --- .../predicate/HostRoutePredicateFactory.java | 48 +++++++++++++-- .../HostRoutePredicateFactoryTests.java | 61 +++++++++++++++++++ 2 files changed, 103 insertions(+), 6 deletions(-) diff --git a/spring-cloud-gateway-server-webflux/src/main/java/org/springframework/cloud/gateway/handler/predicate/HostRoutePredicateFactory.java b/spring-cloud-gateway-server-webflux/src/main/java/org/springframework/cloud/gateway/handler/predicate/HostRoutePredicateFactory.java index f71cbe16cd..7b3242a825 100644 --- a/spring-cloud-gateway-server-webflux/src/main/java/org/springframework/cloud/gateway/handler/predicate/HostRoutePredicateFactory.java +++ b/spring-cloud-gateway-server-webflux/src/main/java/org/springframework/cloud/gateway/handler/predicate/HostRoutePredicateFactory.java @@ -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; @@ -31,6 +33,7 @@ /** * @author Spencer Gibb + * @author Alpha */ public class HostRoutePredicateFactory extends AbstractRoutePredicateFactory { @@ -38,6 +41,8 @@ public class HostRoutePredicateFactory extends AbstractRoutePredicateFactory apply(Config config) { + List patterns = new ArrayList<>(config.getPatterns()); + Set literalPatterns = new HashSet<>(); + List 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) { @@ -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 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 variables = pathMatcher.extractUriTemplateVariables(match, host); + Map variables = !useFastPath || match.indexOf('{') >= 0 + ? matcher.extractUriTemplateVariables(match, host) : Collections.emptyMap(); ServerWebExchangeUtils.putUriTemplateVariables(exchange, variables); return true; } @@ -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 patterns = new ArrayList<>(); diff --git a/spring-cloud-gateway-server-webflux/src/test/java/org/springframework/cloud/gateway/handler/predicate/HostRoutePredicateFactoryTests.java b/spring-cloud-gateway-server-webflux/src/test/java/org/springframework/cloud/gateway/handler/predicate/HostRoutePredicateFactoryTests.java index 7030494e2f..09f98a3284 100644 --- a/spring-cloud-gateway-server-webflux/src/test/java/org/springframework/cloud/gateway/handler/predicate/HostRoutePredicateFactoryTests.java +++ b/spring-cloud-gateway-server-webflux/src/test/java/org/springframework/cloud/gateway/handler/predicate/HostRoutePredicateFactoryTests.java @@ -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; @@ -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; @@ -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 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 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 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 withPortPredicate = new HostRoutePredicateFactory(true).apply(withPort); + Config withoutPort = new Config().setPatterns(List.of("www.example.org")); + Predicate 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"));