From 50632cba6baa54215e0a2f3ff8cbe9e0d39bd42c Mon Sep 17 00:00:00 2001 From: Alberto Gisbert Date: Mon, 16 Sep 2019 16:54:40 +0200 Subject: [PATCH 1/3] FIX #1282 Rewrite filter example (#1284) The regexp now treats the possible root trailing slash as optional. Fixes gh-1282 --- docs/src/main/asciidoc/spring-cloud-gateway.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/spring-cloud-gateway.adoc b/docs/src/main/asciidoc/spring-cloud-gateway.adoc index 2ffeb37e..4dd9eedc 100644 --- a/docs/src/main/asciidoc/spring-cloud-gateway.adoc +++ b/docs/src/main/asciidoc/spring-cloud-gateway.adoc @@ -814,7 +814,7 @@ spring: predicates: - Path=/foo/** filters: - - RewritePath=/foo/(?.*), /$\{segment} + - RewritePath=/foo(?/?.*), $\{segment} ---- For a request path of `/foo/bar`, this will set the path to `/bar` before making the downstream request. Notice the `$\` which is replaced with `$` because of the YAML spec. From 29296eda41d9d03840c5117085b2dbf05eb5e88a Mon Sep 17 00:00:00 2001 From: Dennis Menge Date: Mon, 16 Sep 2019 18:30:45 +0200 Subject: [PATCH 2/3] =?UTF-8?q?relates=20to=20gh-600=20Add=20Predicate=20a?= =?UTF-8?q?nd=20Filter=20validation=20to=20Route=E2=80=A6=20(#1212)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * relates to gh-600 Add Predicate and Filter validation to Route Creation Actuator Endpoint * renamed a variable in validateRouteDefinition(RouteDefinition) to better fit the injected parameter --- .../AbstractGatewayControllerEndpoint.java | 47 +++++- .../actuate/GatewayControllerEndpoint.java | 5 +- .../GatewayLegacyControllerEndpoint.java | 4 +- .../config/GatewayAutoConfiguration.java | 6 +- .../GatewayControllerEndpointTests.java | 140 +++++++++++++++++- 5 files changed, 186 insertions(+), 16 deletions(-) diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/actuate/AbstractGatewayControllerEndpoint.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/actuate/AbstractGatewayControllerEndpoint.java index a56c1a0a..dcda3e57 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/actuate/AbstractGatewayControllerEndpoint.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/actuate/AbstractGatewayControllerEndpoint.java @@ -28,6 +28,7 @@ import reactor.core.publisher.Mono; import org.springframework.cloud.gateway.event.RefreshRoutesEvent; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.cloud.gateway.filter.factory.GatewayFilterFactory; +import org.springframework.cloud.gateway.handler.predicate.RoutePredicateFactory; import org.springframework.cloud.gateway.route.RouteDefinition; import org.springframework.cloud.gateway.route.RouteDefinitionLocator; import org.springframework.cloud.gateway.route.RouteDefinitionWriter; @@ -56,6 +57,8 @@ public class AbstractGatewayControllerEndpoint implements ApplicationEventPublis protected List GatewayFilters; + protected List routePredicates; + protected RouteDefinitionWriter routeDefinitionWriter; protected RouteLocator routeLocator; @@ -65,10 +68,12 @@ public class AbstractGatewayControllerEndpoint implements ApplicationEventPublis public AbstractGatewayControllerEndpoint( RouteDefinitionLocator routeDefinitionLocator, List globalFilters, List GatewayFilters, + List routePredicates, RouteDefinitionWriter routeDefinitionWriter, RouteLocator routeLocator) { this.routeDefinitionLocator = routeDefinitionLocator; this.globalFilters = globalFilters; this.GatewayFilters = GatewayFilters; + this.routePredicates = routePredicates; this.routeDefinitionWriter = routeDefinitionWriter; this.routeLocator = routeLocator; } @@ -96,6 +101,11 @@ public class AbstractGatewayControllerEndpoint implements ApplicationEventPublis return getNamesToOrders(this.GatewayFilters); } + @GetMapping("/routepredicates") + public Mono> routepredicates() { + return getNamesToOrders(this.routePredicates); + } + private Mono> getNamesToOrders(List list) { return Flux.fromIterable(list).reduce(new HashMap<>(), this::putItem); } @@ -117,14 +127,35 @@ public class AbstractGatewayControllerEndpoint implements ApplicationEventPublis */ @PostMapping("/routes/{id}") @SuppressWarnings("unchecked") - public Mono> save(@PathVariable String id, - @RequestBody Mono route) { - return this.routeDefinitionWriter.save(route.map(r -> { - r.setId(id); - log.debug("Saving route: " + route); - return r; - })).then(Mono.defer(() -> Mono - .just(ResponseEntity.created(URI.create("/routes/" + id)).build()))); + public Mono> save(@PathVariable String id, + @RequestBody RouteDefinition route) { + + return Mono.just(route).filter(this::validateRouteDefinition) + .flatMap(routeDefinition -> this.routeDefinitionWriter + .save(Mono.just(routeDefinition).map(r -> { + r.setId(id); + log.debug("Saving route: " + route); + return r; + })) + .then(Mono.defer(() -> Mono.just(ResponseEntity + .created(URI.create("/routes/" + id)).build())))) + .switchIfEmpty( + Mono.defer(() -> Mono.just(ResponseEntity.badRequest().build()))); + } + + private boolean validateRouteDefinition(RouteDefinition routeDefinition) { + boolean hasValidFilterDefinitions = routeDefinition.getFilters().stream() + .allMatch(filterDefinition -> GatewayFilters.stream() + .anyMatch(gatewayFilterFactory -> filterDefinition.getName() + .equals(gatewayFilterFactory.name()))); + + boolean hasValidPredicateDefinitions = routeDefinition.getPredicates().stream() + .allMatch(predicateDefinition -> routePredicates.stream() + .anyMatch(routePredicate -> predicateDefinition.getName() + .equals(routePredicate.name()))); + log.debug("FilterDefinitions valid: " + hasValidFilterDefinitions); + log.debug("PredicateDefinitions valid: " + hasValidPredicateDefinitions); + return hasValidFilterDefinitions && hasValidPredicateDefinitions; } @DeleteMapping("/routes/{id}") diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/actuate/GatewayControllerEndpoint.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/actuate/GatewayControllerEndpoint.java index 244d976a..d8935afc 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/actuate/GatewayControllerEndpoint.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/actuate/GatewayControllerEndpoint.java @@ -28,6 +28,7 @@ import org.springframework.boot.actuate.endpoint.web.annotation.RestControllerEn import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.cloud.gateway.filter.factory.GatewayFilterFactory; +import org.springframework.cloud.gateway.handler.predicate.RoutePredicateFactory; import org.springframework.cloud.gateway.route.Route; import org.springframework.cloud.gateway.route.RouteDefinitionWriter; import org.springframework.cloud.gateway.route.RouteLocator; @@ -43,8 +44,10 @@ public class GatewayControllerEndpoint extends AbstractGatewayControllerEndpoint public GatewayControllerEndpoint(List globalFilters, List gatewayFilters, + List routePredicates, RouteDefinitionWriter routeDefinitionWriter, RouteLocator routeLocator) { - super(null, globalFilters, gatewayFilters, routeDefinitionWriter, routeLocator); + super(null, globalFilters, gatewayFilters, routePredicates, routeDefinitionWriter, + routeLocator); } // TODO: Flush out routes without a definition diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/actuate/GatewayLegacyControllerEndpoint.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/actuate/GatewayLegacyControllerEndpoint.java index 69c65152..9a78b585 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/actuate/GatewayLegacyControllerEndpoint.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/actuate/GatewayLegacyControllerEndpoint.java @@ -27,6 +27,7 @@ import org.springframework.boot.actuate.endpoint.web.annotation.RestControllerEn import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.cloud.gateway.filter.factory.GatewayFilterFactory; +import org.springframework.cloud.gateway.handler.predicate.RoutePredicateFactory; import org.springframework.cloud.gateway.route.Route; import org.springframework.cloud.gateway.route.RouteDefinition; import org.springframework.cloud.gateway.route.RouteDefinitionLocator; @@ -44,8 +45,9 @@ public class GatewayLegacyControllerEndpoint extends AbstractGatewayControllerEn public GatewayLegacyControllerEndpoint(RouteDefinitionLocator routeDefinitionLocator, List globalFilters, List GatewayFilters, + List routePredicates, RouteDefinitionWriter routeDefinitionWriter, RouteLocator routeLocator) { - super(routeDefinitionLocator, globalFilters, GatewayFilters, + super(routeDefinitionLocator, globalFilters, GatewayFilters, routePredicates, routeDefinitionWriter, routeLocator); } diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java index b11734fb..3013e3a0 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java @@ -688,8 +688,9 @@ public class GatewayAutoConfiguration { public GatewayControllerEndpoint gatewayControllerEndpoint( List globalFilters, List gatewayFilters, + List routePredicates, RouteDefinitionWriter routeDefinitionWriter, RouteLocator routeLocator) { - return new GatewayControllerEndpoint(globalFilters, gatewayFilters, + return new GatewayControllerEndpoint(globalFilters, gatewayFilters, routePredicates, routeDefinitionWriter, routeLocator); } @@ -700,9 +701,10 @@ public class GatewayAutoConfiguration { RouteDefinitionLocator routeDefinitionLocator, List globalFilters, List gatewayFilters, + List routePredicates, RouteDefinitionWriter routeDefinitionWriter, RouteLocator routeLocator) { return new GatewayLegacyControllerEndpoint(routeDefinitionLocator, - globalFilters, gatewayFilters, routeDefinitionWriter, routeLocator); + globalFilters, gatewayFilters, routePredicates, routeDefinitionWriter, routeLocator); } } diff --git a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/actuate/GatewayControllerEndpointTests.java b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/actuate/GatewayControllerEndpointTests.java index ff7e7730..9d263b79 100644 --- a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/actuate/GatewayControllerEndpointTests.java +++ b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/actuate/GatewayControllerEndpointTests.java @@ -16,8 +16,12 @@ package org.springframework.cloud.gateway.actuate; +import java.net.URI; +import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.function.Predicate; import org.junit.Test; import org.junit.runner.RunWith; @@ -27,20 +31,30 @@ import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.cloud.gateway.filter.FilterDefinition; +import org.springframework.cloud.gateway.filter.GatewayFilter; +import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; +import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory; +import org.springframework.cloud.gateway.handler.predicate.GatewayPredicate; +import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition; +import org.springframework.cloud.gateway.route.RouteDefinition; import org.springframework.cloud.gateway.route.RouteLocator; import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; import org.springframework.cloud.gateway.test.PermitAllSecurityConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; +import org.springframework.http.MediaType; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.reactive.server.WebTestClient; +import org.springframework.web.reactive.function.BodyInserters; +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; @RunWith(SpringRunner.class) -@SpringBootTest(properties = { "management.endpoints.web.exposure.include=*", - "spring.cloud.gateway.actuator.verbose.enabled=true" }, webEnvironment = RANDOM_PORT) +@SpringBootTest(properties = {"management.endpoints.web.exposure.include=*", + "spring.cloud.gateway.actuator.verbose.enabled=true"}, webEnvironment = RANDOM_PORT) public class GatewayControllerEndpointTests { @Autowired @@ -78,6 +92,92 @@ public class GatewayControllerEndpointTests { }); } + @Test + public void testRouteFilters() { + testClient.get() + .uri("http://localhost:" + port + "/actuator/gateway/routefilters") + .exchange().expectStatus().isOk().expectBody(Map.class) + .consumeWith(result -> { + Map responseBody = result.getResponseBody(); + assertThat(responseBody).isNotEmpty(); + }); + } + + @Test + public void testRoutePredicates() { + testClient.get() + .uri("http://localhost:" + port + "/actuator/gateway/routepredicates") + .exchange().expectStatus().isOk().expectBody(Map.class) + .consumeWith(result -> { + Map responseBody = result.getResponseBody(); + assertThat(responseBody).isNotEmpty(); + }); + } + + @Test + public void testPostValidRouteDefinition() { + + RouteDefinition testRouteDefinition = new RouteDefinition(); + testRouteDefinition.setUri(URI.create("http://example.org")); + + FilterDefinition prefixPathFilterDefinition = new FilterDefinition( + "PrefixPath=/test-path"); + FilterDefinition redirectToFilterDefinition = new FilterDefinition( + "RemoveResponseHeader=Sensitive-Header"); + FilterDefinition testFilterDefinition = new FilterDefinition("TestFilter"); + testRouteDefinition.setFilters(Arrays.asList(prefixPathFilterDefinition, + redirectToFilterDefinition, testFilterDefinition)); + + PredicateDefinition hostRoutePredicateDefinition = new PredicateDefinition( + "Host=myhost.org"); + PredicateDefinition methodRoutePredicateDefinition = new PredicateDefinition( + "Method=GET"); + PredicateDefinition testPredicateDefinition = new PredicateDefinition( + "Test=value"); + testRouteDefinition.setPredicates(Arrays.asList(hostRoutePredicateDefinition, + methodRoutePredicateDefinition, testPredicateDefinition)); + + testClient.post() + .uri("http://localhost:" + port + "/actuator/gateway/routes/test-route") + .accept(MediaType.APPLICATION_JSON_UTF8) + .body(BodyInserters.fromObject(testRouteDefinition)).exchange() + .expectStatus().isCreated(); + } + + @Test + public void testPostRouteWithNotExistingFilter() { + + RouteDefinition testRouteDefinition = new RouteDefinition(); + testRouteDefinition.setUri(URI.create("http://example.org")); + + FilterDefinition filterDefinition = new FilterDefinition( + "NotExistingFilter=test-config"); + testRouteDefinition.setFilters(Collections.singletonList(filterDefinition)); + + testClient.post() + .uri("http://localhost:" + port + "/actuator/gateway/routes/test-route") + .accept(MediaType.APPLICATION_JSON_UTF8) + .body(BodyInserters.fromObject(testRouteDefinition)).exchange() + .expectStatus().isBadRequest(); + } + + @Test + public void testPostRouteWithNotExistingPredicate() { + + RouteDefinition testRouteDefinition = new RouteDefinition(); + testRouteDefinition.setUri(URI.create("http://example.org")); + + PredicateDefinition predicateDefinition = new PredicateDefinition( + "NotExistingPredicate=test-config"); + testRouteDefinition.setPredicates(Collections.singletonList(predicateDefinition)); + + testClient.post() + .uri("http://localhost:" + port + "/actuator/gateway/routes/test-route") + .accept(MediaType.APPLICATION_JSON_UTF8) + .body(BodyInserters.fromObject(testRouteDefinition)).exchange() + .expectStatus().isBadRequest(); + } + @SpringBootConfiguration @EnableAutoConfiguration @Import(PermitAllSecurityConfiguration.class) @@ -86,11 +186,43 @@ public class GatewayControllerEndpointTests { @Bean RouteLocator testRouteLocator(RouteLocatorBuilder routeLocatorBuilder) { return routeLocatorBuilder.routes() - .route("test-service", - r -> r.path("/test-service/**").uri("lb://test-service")) + .route("test-service", r -> r.path("/test-service/**").uri("lb://test-service")) .build(); } + @Bean + public TestFilterGatewayFilterFactory customGatewayFilterFactory() { + return new TestFilterGatewayFilterFactory(); + } + + @Bean + public TestRoutePredicateFactory customGatewayPredicateFactory() { + return new TestRoutePredicateFactory(Object.class); + } + + } + + private static class TestFilterGatewayFilterFactory + extends AbstractGatewayFilterFactory { + + @Override + public GatewayFilter apply(Object config) { + return null; + } + + } + + private static class TestRoutePredicateFactory extends AbstractRoutePredicateFactory { + + TestRoutePredicateFactory(Class configClass) { + super(configClass); + } + + @Override + public Predicate apply(Object config) { + return (GatewayPredicate) serverWebExchange -> true; + } + } } From bb517a8d57c44a078b5da669c97e13186b44c00f Mon Sep 17 00:00:00 2001 From: Olga Maciaszek-Sharma Date: Wed, 18 Sep 2019 12:33:04 +0200 Subject: [PATCH 3/3] Remove spring.provides. --- .../src/main/resources/META-INF/spring.provides | 1 - 1 file changed, 1 deletion(-) delete mode 100644 spring-cloud-starter-gateway/src/main/resources/META-INF/spring.provides diff --git a/spring-cloud-starter-gateway/src/main/resources/META-INF/spring.provides b/spring-cloud-starter-gateway/src/main/resources/META-INF/spring.provides deleted file mode 100644 index ff520021..00000000 --- a/spring-cloud-starter-gateway/src/main/resources/META-INF/spring.provides +++ /dev/null @@ -1 +0,0 @@ -provides: spring-cloud-gateway-core \ No newline at end of file