Add a new endpoint POST /routes (#2915)

* Add a new endpoint POST /routes

A list of RouteDefinition can be sent to /routes using POST method to persist multiple routes by a single request.

Validation is done before persisting any RouteDefinition
This commit is contained in:
Ignacio Lozano
2023-04-26 19:32:25 +02:00
committed by GitHub
parent 964cb81932
commit 23ca3721a6
3 changed files with 117 additions and 4 deletions

View File

@@ -2952,11 +2952,17 @@ The following table describes the structure of the response:
|===
=== Creating and Deleting a Particular Route
=== Creating and Deleting a Particular Route Definition
To create a route, make a `POST` request to `/gateway/routes/{id_route_to_create}` with a JSON body that specifies the fields of the route (see <<gateway-retrieving-information-about-a-particular-route>>).
To create a route definition, make a `POST` request to `/gateway/routes/{id_route_to_create}` with a JSON body that specifies the fields of the route (see <<gateway-retrieving-information-about-a-particular-route>>).
To delete a route, make a `DELETE` request to `/gateway/routes/{id_route_to_delete}`.
To delete a route definition, make a `DELETE` request to `/gateway/routes/{id_route_to_delete}`.
=== Creating multiple Route Definitions
To create multiple route definitions in a single request, make a `POST` request to `/gateway/routes` with a JSON body that specifies the fields of the route, including the route id (see <<gateway-retrieving-information-about-a-particular-route>>).
The route definitions will be discarded if any route raises an error during the creation of the routes.
=== Recap: The List of All endpoints

View File

@@ -145,6 +145,28 @@ public class AbstractGatewayControllerEndpoint implements ApplicationEventPublis
.switchIfEmpty(Mono.defer(() -> Mono.just(ResponseEntity.badRequest().build())));
}
@PostMapping("/routes")
@SuppressWarnings("unchecked")
public Mono<ResponseEntity<Object>> save(@RequestBody List<RouteDefinition> routes) {
routes.stream().forEach(routeDef -> {
validateRouteDefinition(routeDef);
validateRouteId(routeDef);
});
return Flux.fromIterable(routes)
.flatMap(routeDefinition -> this.routeDefinitionWriter.save(Mono.just(routeDefinition).map(r -> {
log.debug("Saving route: " + routeDefinition);
return r;
}))).then(Mono.defer(() -> Mono.just(ResponseEntity.ok().build())))
.switchIfEmpty(Mono.defer(() -> Mono.just(ResponseEntity.badRequest().build())));
}
private void validateRouteId(RouteDefinition routeDefinition) {
if(routeDefinition.getId() == null) {
handleError("Saving multiple routes require specifying the ID for every route");
}
}
private void validateRouteDefinition(RouteDefinition routeDefinition) {
Set<String> unavailableFilterDefinitions = routeDefinition.getFilters().stream().filter(rd -> !isAvailable(rd))
.map(FilterDefinition::getName).collect(Collectors.toSet());

View File

@@ -21,6 +21,7 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.function.Predicate;
import org.assertj.core.util.Maps;
@@ -141,7 +142,6 @@ public class GatewayControllerEndpointTests {
@Test
public void testPostValidRouteDefinition() {
RouteDefinition testRouteDefinition = new RouteDefinition();
testRouteDefinition.setUri(URI.create("http://example.org"));
@@ -162,6 +162,91 @@ public class GatewayControllerEndpointTests {
.expectStatus().isCreated();
}
@Test
public void testPostMultipleValidRouteDefinitions() {
RouteDefinition testRouteDefinition = new RouteDefinition();
testRouteDefinition.setUri(URI.create("http://example.org"));
String routeId1 = UUID.randomUUID().toString();
testRouteDefinition.setId(routeId1);
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));
RouteDefinition testRouteDefinition2 = new RouteDefinition();
testRouteDefinition2.setUri(URI.create("http://example-2.org"));
String routeId2 = UUID.randomUUID().toString();
testRouteDefinition2.setId(routeId2);
FilterDefinition prefixPathFilterDefinition2 = new FilterDefinition("PrefixPath=/test-path-2");
FilterDefinition redirectToFilterDefinition2 = new FilterDefinition("RemoveResponseHeader=Sensitive-Header-2");
FilterDefinition testFilterDefinition2 = new FilterDefinition("TestFilter");
testRouteDefinition2.setFilters(
Arrays.asList(prefixPathFilterDefinition2, redirectToFilterDefinition2, testFilterDefinition2));
PredicateDefinition hostRoutePredicateDefinition2 = new PredicateDefinition("Host=myhost-2.org");
PredicateDefinition methodRoutePredicateDefinition2 = new PredicateDefinition("Method=GET");
PredicateDefinition testPredicateDefinition2 = new PredicateDefinition("Test=value-2");
testRouteDefinition2.setPredicates(
Arrays.asList(hostRoutePredicateDefinition2, methodRoutePredicateDefinition2, testPredicateDefinition2)
);
List<RouteDefinition> multipleRouteDefs = List.of(testRouteDefinition, testRouteDefinition2);
testClient.post().uri("http://localhost:" + port + "/actuator/gateway/routes")
.accept(MediaType.APPLICATION_JSON).body(BodyInserters.fromValue(multipleRouteDefs)).exchange()
.expectStatus().isOk();
testClient.get().uri("http://localhost:" + port + "/actuator/gateway/routedefinitions")
.accept(MediaType.APPLICATION_JSON).exchange()
.expectBody()
.jsonPath("[?(@.id in ['%s','%s'])].id".formatted(routeId1, routeId2)).exists();
}
@Test
public void testPostMultipleRoutesWithOneWrong_doesntPersistRouteDefinitions() {
RouteDefinition testRouteDefinition = new RouteDefinition();
testRouteDefinition.setUri(URI.create("http://example.org"));
String routeId1 = UUID.randomUUID().toString();
testRouteDefinition.setId(routeId1);
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));
RouteDefinition testRouteDefinition2 = new RouteDefinition();
testRouteDefinition2.setUri(URI.create("this-is-wrong"));
String routeId2 = UUID.randomUUID().toString();
testRouteDefinition2.setId(routeId2);
List<RouteDefinition> multipleRouteDefs = List.of(testRouteDefinition, testRouteDefinition2);
testClient.post().uri("http://localhost:" + port + "/actuator/gateway/routes")
.accept(MediaType.APPLICATION_JSON).body(BodyInserters.fromValue(multipleRouteDefs)).exchange()
.expectStatus().is4xxClientError();
testClient.get().uri("http://localhost:" + port + "/actuator/gateway/routedefinitions")
.accept(MediaType.APPLICATION_JSON).exchange()
.expectBody()
.jsonPath("[?(@.id in ['%s','%s'])].id".formatted(routeId1, routeId2)).doesNotExist();
}
@Test
public void testPostValidShortcutRouteDefinition() {
RouteDefinition testRouteDefinition = new RouteDefinition();