Adds metadata field on Route and RouteDefinition
Fixes gh-1021
This commit is contained in:
committed by
Spencer Gibb
parent
73eee9c559
commit
5c83f45ed9
@@ -1476,6 +1476,34 @@ spring:
|
||||
|
||||
For some usages of the gateway, properties will be adequate, but some production use cases will benefit from loading configuration from an external source, such as a database. Future milestone versions will have `RouteDefinitionLocator` implementations based off of Spring Data Repositories such as: Redis, MongoDB and Cassandra.
|
||||
|
||||
== Route metadata configuration
|
||||
Additional parameters can be configured for each route using metadata:
|
||||
|
||||
.application.yml
|
||||
[source,yaml]
|
||||
----
|
||||
spring:
|
||||
cloud:
|
||||
gateway:
|
||||
routes:
|
||||
- id: route_with_metadata
|
||||
uri: https://example.org
|
||||
metadata:
|
||||
optionName: "OptionValue"
|
||||
compositeObject:
|
||||
name: "value"
|
||||
iAmNumber: 1
|
||||
----
|
||||
|
||||
All metadata properties could be acquired from exchange:
|
||||
```
|
||||
Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);
|
||||
// get all metadata properties
|
||||
route.getMetadata();
|
||||
// get a single metadata property
|
||||
route.getMetadata(someKey);
|
||||
```
|
||||
|
||||
=== Fluent Java Routes API
|
||||
To allow for simple configuration in Java, there is a fluent API defined in the `RouteLocatorBuilder` bean.
|
||||
|
||||
@@ -1495,6 +1523,7 @@ public RouteLocator customRouteLocator(RouteLocatorBuilder builder, ThrottleGate
|
||||
.filters(f ->
|
||||
f.addResponseHeader("X-AnotherHeader", "baz"))
|
||||
.uri("http://httpbin.org:80")
|
||||
.metadata("key", "value")
|
||||
)
|
||||
.route(r -> r.order(-1)
|
||||
.host("**.throttle.org").and().path("/get")
|
||||
@@ -1503,6 +1532,7 @@ public RouteLocator customRouteLocator(RouteLocatorBuilder builder, ThrottleGate
|
||||
10,
|
||||
TimeUnit.SECONDS)))
|
||||
.uri("http://httpbin.org:80")
|
||||
.metadata("key", "value")
|
||||
)
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ import org.springframework.cloud.gateway.route.Route;
|
||||
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
|
||||
import org.springframework.cloud.gateway.route.RouteLocator;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
|
||||
@@ -59,6 +60,9 @@ public class GatewayControllerEndpoint extends AbstractGatewayControllerEndpoint
|
||||
r.put("uri", route.getUri().toString());
|
||||
r.put("order", route.getOrder());
|
||||
r.put("predicate", route.getPredicate().toString());
|
||||
if (!CollectionUtils.isEmpty(route.getMetadata())) {
|
||||
r.put("metadata", route.getMetadata());
|
||||
}
|
||||
|
||||
ArrayList<String> filters = new ArrayList<>();
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ import org.springframework.cloud.gateway.route.RouteDefinitionLocator;
|
||||
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
|
||||
import org.springframework.cloud.gateway.route.RouteLocator;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
|
||||
@@ -81,6 +82,10 @@ public class GatewayLegacyControllerEndpoint extends AbstractGatewayControllerEn
|
||||
obj.put("filters", filters);
|
||||
}
|
||||
|
||||
if (!CollectionUtils.isEmpty(route.getMetadata())) {
|
||||
obj.put("metadata", route.getMetadata());
|
||||
}
|
||||
|
||||
if (!obj.isEmpty()) {
|
||||
r.put("route_object", obj);
|
||||
}
|
||||
|
||||
@@ -21,7 +21,9 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
@@ -50,14 +52,24 @@ public class Route implements Ordered {
|
||||
|
||||
private final List<GatewayFilter> gatewayFilters;
|
||||
|
||||
private final Map<String, Object> metadata;
|
||||
|
||||
@Deprecated
|
||||
private Route(String id, URI uri, int order,
|
||||
AsyncPredicate<ServerWebExchange> predicate,
|
||||
List<GatewayFilter> gatewayFilters) {
|
||||
this(id, uri, order, predicate, gatewayFilters, new HashMap<>());
|
||||
}
|
||||
|
||||
private Route(String id, URI uri, int order,
|
||||
AsyncPredicate<ServerWebExchange> predicate,
|
||||
List<GatewayFilter> gatewayFilters, Map<String, Object> metadata) {
|
||||
this.id = id;
|
||||
this.uri = uri;
|
||||
this.order = order;
|
||||
this.predicate = predicate;
|
||||
this.gatewayFilters = gatewayFilters;
|
||||
this.metadata = metadata;
|
||||
}
|
||||
|
||||
public static Builder builder() {
|
||||
@@ -65,8 +77,12 @@ public class Route implements Ordered {
|
||||
}
|
||||
|
||||
public static Builder builder(RouteDefinition routeDefinition) {
|
||||
return new Builder().id(routeDefinition.getId()).uri(routeDefinition.getUri())
|
||||
.order(routeDefinition.getOrder());
|
||||
// @formatter:off
|
||||
return new Builder().id(routeDefinition.getId())
|
||||
.uri(routeDefinition.getUri())
|
||||
.order(routeDefinition.getOrder())
|
||||
.metadata(routeDefinition.getMetadata());
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
public static AsyncBuilder async() {
|
||||
@@ -74,8 +90,12 @@ public class Route implements Ordered {
|
||||
}
|
||||
|
||||
public static AsyncBuilder async(RouteDefinition routeDefinition) {
|
||||
// @formatter:off
|
||||
return new AsyncBuilder().id(routeDefinition.getId())
|
||||
.uri(routeDefinition.getUri()).order(routeDefinition.getOrder());
|
||||
.uri(routeDefinition.getUri())
|
||||
.order(routeDefinition.getOrder())
|
||||
.metadata(routeDefinition.getMetadata());
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
@@ -98,6 +118,10 @@ public class Route implements Ordered {
|
||||
return Collections.unmodifiableList(this.gatewayFilters);
|
||||
}
|
||||
|
||||
public Map<String, Object> getMetadata() {
|
||||
return Collections.unmodifiableMap(metadata);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
@@ -107,15 +131,17 @@ public class Route implements Ordered {
|
||||
return false;
|
||||
}
|
||||
Route route = (Route) o;
|
||||
return Objects.equals(id, route.id) && Objects.equals(uri, route.uri)
|
||||
&& Objects.equals(order, route.order)
|
||||
&& Objects.equals(predicate, route.predicate)
|
||||
&& Objects.equals(gatewayFilters, route.gatewayFilters);
|
||||
return this.order == route.order && Objects.equals(this.id, route.id)
|
||||
&& Objects.equals(this.uri, route.uri)
|
||||
&& Objects.equals(this.predicate, route.predicate)
|
||||
&& Objects.equals(this.gatewayFilters, route.gatewayFilters)
|
||||
&& Objects.equals(this.metadata, route.metadata);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(id, uri, predicate, gatewayFilters);
|
||||
return Objects.hash(this.id, this.uri, this.order, this.predicate,
|
||||
this.gatewayFilters, this.metadata);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -126,6 +152,7 @@ public class Route implements Ordered {
|
||||
sb.append(", order=").append(order);
|
||||
sb.append(", predicate=").append(predicate);
|
||||
sb.append(", gatewayFilters=").append(gatewayFilters);
|
||||
sb.append(", metadata=").append(metadata);
|
||||
sb.append('}');
|
||||
return sb.toString();
|
||||
}
|
||||
@@ -140,6 +167,8 @@ public class Route implements Ordered {
|
||||
|
||||
protected List<GatewayFilter> gatewayFilters = new ArrayList<>();
|
||||
|
||||
protected Map<String, Object> metadata = new HashMap<>();
|
||||
|
||||
protected AbstractBuilder() {
|
||||
}
|
||||
|
||||
@@ -177,6 +206,21 @@ public class Route implements Ordered {
|
||||
return getThis();
|
||||
}
|
||||
|
||||
public B replaceMetadata(Map<String, Object> metadata) {
|
||||
this.metadata = metadata;
|
||||
return getThis();
|
||||
}
|
||||
|
||||
public B metadata(Map<String, Object> metadata) {
|
||||
this.metadata.putAll(metadata);
|
||||
return getThis();
|
||||
}
|
||||
|
||||
public B metadata(String key, Object value) {
|
||||
this.metadata.put(key, value);
|
||||
return getThis();
|
||||
}
|
||||
|
||||
public abstract AsyncPredicate<ServerWebExchange> getPredicate();
|
||||
|
||||
public B replaceFilters(List<GatewayFilter> gatewayFilters) {
|
||||
@@ -205,7 +249,7 @@ public class Route implements Ordered {
|
||||
Assert.notNull(predicate, "predicate can not be null");
|
||||
|
||||
return new Route(this.id, this.uri, this.order, predicate,
|
||||
this.gatewayFilters);
|
||||
this.gatewayFilters, this.metadata);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -18,7 +18,9 @@ package org.springframework.cloud.gateway.route;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -52,6 +54,8 @@ public class RouteDefinition {
|
||||
@NotNull
|
||||
private URI uri;
|
||||
|
||||
private Map<String, Object> metadata = new HashMap<>();
|
||||
|
||||
private int order = 0;
|
||||
|
||||
public RouteDefinition() {
|
||||
@@ -115,6 +119,14 @@ public class RouteDefinition {
|
||||
this.order = order;
|
||||
}
|
||||
|
||||
public Map<String, Object> getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
public void setMetadata(Map<String, Object> metadata) {
|
||||
this.metadata = metadata;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
@@ -123,22 +135,25 @@ public class RouteDefinition {
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
RouteDefinition routeDefinition = (RouteDefinition) o;
|
||||
return Objects.equals(id, routeDefinition.id)
|
||||
&& Objects.equals(predicates, routeDefinition.predicates)
|
||||
&& Objects.equals(order, routeDefinition.order)
|
||||
&& Objects.equals(uri, routeDefinition.uri);
|
||||
RouteDefinition that = (RouteDefinition) o;
|
||||
return this.order == that.order && Objects.equals(this.id, that.id)
|
||||
&& Objects.equals(this.predicates, that.predicates)
|
||||
&& Objects.equals(this.filters, that.filters)
|
||||
&& Objects.equals(this.uri, that.uri)
|
||||
&& Objects.equals(this.metadata, that.metadata);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(id, predicates, uri);
|
||||
return Objects.hash(this.id, this.predicates, this.filters, this.uri,
|
||||
this.metadata, this.order);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "RouteDefinition{" + "id='" + id + '\'' + ", predicates=" + predicates
|
||||
+ ", filters=" + filters + ", uri=" + uri + ", order=" + order + '}';
|
||||
+ ", filters=" + filters + ", uri=" + uri + ", order=" + order
|
||||
+ ", metadata=" + metadata + '}';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ package org.springframework.cloud.gateway.actuate;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.assertj.core.util.Maps;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@@ -80,6 +81,18 @@ public class GatewayControllerEndpointTests {
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRouteReturnsMetadata() {
|
||||
testClient.get()
|
||||
.uri("http://localhost:" + port
|
||||
+ "/actuator/gateway/routes/route_with_metadata")
|
||||
.exchange().expectStatus().isOk().expectBody().jsonPath("$.metadata")
|
||||
.value(map -> assertThat((Map<String, Object>) map).hasSize(3)
|
||||
.containsEntry("optionName", "OptionValue")
|
||||
.containsEntry("iAmNumber", 1).containsEntry("compositeObject",
|
||||
Maps.newHashMap("name", "value")));
|
||||
}
|
||||
|
||||
@SpringBootConfiguration
|
||||
@EnableAutoConfiguration
|
||||
@Import(PermitAllSecurityConfiguration.class)
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2013-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.gateway.route;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.assertj.core.util.Maps;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Stefan Stus
|
||||
*/
|
||||
public class RouteDefinitionTest {
|
||||
|
||||
@Test
|
||||
public void addRouteDefinitionKeepsExistingMetadata() {
|
||||
Map<String, Object> originalMetadata = Maps.newHashMap("key", "value");
|
||||
Map<String, Object> newMetadata = Maps.newHashMap("key2", "value2");
|
||||
|
||||
RouteDefinition routeDefinition = new RouteDefinition();
|
||||
routeDefinition.setMetadata(originalMetadata);
|
||||
routeDefinition.getMetadata().putAll(newMetadata);
|
||||
|
||||
assertThat(routeDefinition.getMetadata()).hasSize(2)
|
||||
.containsAllEntriesOf(originalMetadata).containsAllEntriesOf(newMetadata);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setRouteDefinitionReplacesExistingMetadata() {
|
||||
Map<String, Object> originalMetadata = Maps.newHashMap("key", "value");
|
||||
Map<String, Object> newMetadata = Maps.newHashMap("key2", "value2");
|
||||
|
||||
RouteDefinition routeDefinition = new RouteDefinition();
|
||||
routeDefinition.setMetadata(originalMetadata);
|
||||
routeDefinition.setMetadata(newMetadata);
|
||||
|
||||
assertThat(routeDefinition.getMetadata()).isEqualTo(newMetadata);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addSingleMetadataEntryKeepsOriginalMetadata() {
|
||||
Map<String, Object> originalMetadata = Maps.newHashMap("key", "value");
|
||||
|
||||
RouteDefinition routeDefinition = new RouteDefinition();
|
||||
routeDefinition.setMetadata(originalMetadata);
|
||||
routeDefinition.getMetadata().put("key2", "value2");
|
||||
|
||||
assertThat(routeDefinition.getMetadata()).hasSize(2)
|
||||
.containsAllEntriesOf(originalMetadata).containsEntry("key2", "value2");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,6 +16,10 @@
|
||||
|
||||
package org.springframework.cloud.gateway.route;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.HashMap;
|
||||
|
||||
import org.assertj.core.util.Maps;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
@@ -57,4 +61,37 @@ public class RouteTests {
|
||||
Route.async().id("1").predicate(exchange -> true).uri("/pathonly");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void defaultMetadataToEmpty() {
|
||||
Route route = Route.async().id("1").predicate(exchange -> true)
|
||||
.uri("http://acme.com:8080").build();
|
||||
|
||||
assertThat(route.getMetadata()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isAbleToAddMetadata() {
|
||||
Route route = Route.async().id("1").predicate(exchange -> true)
|
||||
.uri("http://acme.com:8080").metadata(Maps.newHashMap("key", "value"))
|
||||
.metadata("key2", "value2").build();
|
||||
|
||||
assertThat(route.getMetadata()).hasSize(2).containsEntry("key", "value")
|
||||
.containsEntry("key2", "value2");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void metadataIsAddedFromDefinition() {
|
||||
RouteDefinition definition = new RouteDefinition();
|
||||
definition.setId("1");
|
||||
definition.setUri(URI.create("http://acme.com:8080"));
|
||||
HashMap<String, Object> metadata = new HashMap<>();
|
||||
metadata.put("key", "value");
|
||||
metadata.put("key2", "value2");
|
||||
definition.setMetadata(metadata);
|
||||
Route route = Route.async(definition).predicate(exchange -> true).build();
|
||||
|
||||
assertThat(route.getMetadata()).hasSize(2).containsEntry("key", "value")
|
||||
.containsEntry("key2", "value2");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,9 +17,12 @@
|
||||
package org.springframework.cloud.gateway.route.builder;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Map;
|
||||
|
||||
import org.assertj.core.util.Maps;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -63,6 +66,31 @@ public class RouteBuilderTests {
|
||||
.expectComplete().verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRouteOptionsPropagatedToRoute() {
|
||||
Map<String, Object> routeMetadata = Maps.newHashMap("key", "value");
|
||||
RouteLocator routeLocator = this.routeLocatorBuilder.routes()
|
||||
.route("test1", r -> r.host("*.somehost.org").and().path("/somepath")
|
||||
.filters(f -> f.addRequestHeader("header1", "header-value-1"))
|
||||
.uri("http://someuri").metadata("key", "value"))
|
||||
.route("test2", r -> r.host("*.somehost2.org")
|
||||
.filters(f -> f.addResponseHeader("header-response-1",
|
||||
"header-response-1"))
|
||||
.uri("https://httpbin.org:9090"))
|
||||
.build();
|
||||
|
||||
StepVerifier.create(routeLocator.getRoutes())
|
||||
.expectNextMatches(
|
||||
r -> r.getId().equals("test1") && r.getFilters().size() == 1
|
||||
&& r.getUri().equals(URI.create("http://someuri:80"))
|
||||
&& r.getMetadata().equals(routeMetadata))
|
||||
.expectNextMatches(r -> r.getId().equals("test2")
|
||||
&& r.getFilters().size() == 1
|
||||
&& r.getUri().equals(URI.create("https://httpbin.org:9090"))
|
||||
&& r.getMetadata().isEmpty())
|
||||
.expectComplete().verify();
|
||||
}
|
||||
|
||||
@EnableAutoConfiguration
|
||||
@Configuration
|
||||
public static class SpringConfig {
|
||||
|
||||
@@ -387,6 +387,19 @@ spring:
|
||||
- name: Path
|
||||
args:
|
||||
pattern: /**
|
||||
# =====================================
|
||||
- id: route_with_metadata
|
||||
uri: ${test.uri}
|
||||
order: 10000
|
||||
predicates:
|
||||
- name: Path
|
||||
args:
|
||||
pattern: /route_with_metadata
|
||||
metadata:
|
||||
optionName: "OptionValue"
|
||||
compositeObject:
|
||||
name: "value"
|
||||
iAmNumber: 1
|
||||
|
||||
hystrix.command.successcmd.execution.isolation.thread.timeoutInMilliseconds: 5000
|
||||
|
||||
|
||||
Reference in New Issue
Block a user