diff --git a/spring-cloud-gateway-server-webflux/src/main/java/org/springframework/cloud/gateway/route/RouteDefinitionRouteLocator.java b/spring-cloud-gateway-server-webflux/src/main/java/org/springframework/cloud/gateway/route/RouteDefinitionRouteLocator.java index e70f46a2b6..504ae819aa 100644 --- a/spring-cloud-gateway-server-webflux/src/main/java/org/springframework/cloud/gateway/route/RouteDefinitionRouteLocator.java +++ b/spring-cloud-gateway-server-webflux/src/main/java/org/springframework/cloud/gateway/route/RouteDefinitionRouteLocator.java @@ -16,6 +16,7 @@ package org.springframework.cloud.gateway.route; +import java.net.URI; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; @@ -179,6 +180,42 @@ List loadGatewayFilters(String id, List filterD } private List getFilters(RouteDefinition routeDefinition) { + + // --- GH-3293: URI Path Syntactic Sugar --- + URI uri = routeDefinition.getUri(); + if (uri != null && org.springframework.util.StringUtils.hasText(uri.getPath()) && !"/".equals(uri.getPath())) { + + String path = uri.getPath(); + + // check if SetPath already exists to avoid duplication + boolean hasSetPath = routeDefinition.getFilters() + .stream() + .anyMatch(f -> "SetPath".equalsIgnoreCase(f.getName())); + + if (!hasSetPath) { + // 1. Create the FilterDefinition programmatically + FilterDefinition sugar = new FilterDefinition(); + sugar.setName("SetPath"); + // SetPath expects a parameter named 'template' + sugar.addArg("template", path); + + // Add it at the beginning of the filter chain + routeDefinition.getFilters().add(0, sugar); + + // 2. Strip the path from the URI to prevent double-pathing + // (e.g., http://example.com/foo -> http://example.com) + URI strippedUri = org.springframework.web.util.UriComponentsBuilder.fromUri(uri) + .replacePath(null) + .build() + .toUri(); + routeDefinition.setUri(strippedUri); + } + } + // --- END GH-3293 --- + + // ... existing code follows (List filters = new ArrayList<>(); + // etc.) + List filters = new ArrayList<>(); Objects.requireNonNull(routeDefinition.getId(), "Route id must be set"); // TODO: support option to apply defaults after route specific filters? diff --git a/spring-cloud-gateway-server-webflux/src/test/java/org/springframework/cloud/gateway/route/RouteDefinitionRouteLocatorTests.java b/spring-cloud-gateway-server-webflux/src/test/java/org/springframework/cloud/gateway/route/RouteDefinitionRouteLocatorTests.java index 3ae0267829..0ab1c264b9 100644 --- a/spring-cloud-gateway-server-webflux/src/test/java/org/springframework/cloud/gateway/route/RouteDefinitionRouteLocatorTests.java +++ b/spring-cloud-gateway-server-webflux/src/test/java/org/springframework/cloud/gateway/route/RouteDefinitionRouteLocatorTests.java @@ -200,6 +200,41 @@ public void contextLoadsAndApplyRouteIdToRetryFilter() { }).expectComplete().verify(); } + @Test + public void uriWithMappingShouldAddSetPathFilter() { + // 1. Setup + List predicates = Arrays.asList(new HostRoutePredicateFactory()); + List gatewayFilterFactories = Arrays + .asList(new org.springframework.cloud.gateway.filter.factory.SetPathGatewayFilterFactory()); + + GatewayProperties gatewayProperties = new GatewayProperties(); + + // Standard way to create the definition + RouteDefinition definition = new RouteDefinition(); + definition.setId("sugar_test"); + definition.setUri(URI.create("https://example.com/foo/bar")); + definition.setPredicates(Arrays.asList(new PredicateDefinition("Host=*.example.com"))); + + gatewayProperties.setRoutes(Arrays.asList(definition)); + + PropertiesRouteDefinitionLocator routeDefinitionLocator = new PropertiesRouteDefinitionLocator( + gatewayProperties); + + // 2. Initialize the Locator + RouteDefinitionRouteLocator routeDefinitionRouteLocator = new RouteDefinitionRouteLocator( + new CompositeRouteDefinitionLocator(Flux.just(routeDefinitionLocator)), predicates, + gatewayFilterFactories, gatewayProperties, new ConfigurationService(null, () -> null, () -> null)); + + // 3. Verify + StepVerifier.create(routeDefinitionRouteLocator.getRoutes()).assertNext(route -> { + assertThat(route.getUri().toString()).isEqualTo("https://example.com:443"); + + List filters = route.getFilters(); + assertThat(filters).hasSize(1); + assertThat(getFilterClassName(filters.get(0))).contains("SetPath"); + }).expectComplete().verify(); + } + private List containsInvalidRoutes() { RouteDefinition foo = new RouteDefinition(); foo.setId("foo");