Skip to content

Commit c8ada1b

Browse files
committed
Merge branch '4.0.x'
2 parents 6157322 + b3abd52 commit c8ada1b

File tree

6 files changed

+185
-9
lines changed

6 files changed

+185
-9
lines changed

spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/actuate/AbstractGatewayControllerEndpoint.java

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,24 +16,30 @@
1616

1717
package org.springframework.cloud.gateway.actuate;
1818

19+
import java.io.IOException;
1920
import java.net.URI;
21+
import java.util.ArrayList;
22+
import java.util.Arrays;
2023
import java.util.HashMap;
2124
import java.util.List;
2225
import java.util.Map;
2326
import java.util.Set;
2427
import java.util.stream.Collectors;
28+
import java.util.stream.Stream;
2529

2630
import org.apache.commons.logging.Log;
2731
import org.apache.commons.logging.LogFactory;
2832
import reactor.core.publisher.Flux;
2933
import reactor.core.publisher.Mono;
3034

35+
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
3136
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
3237
import org.springframework.cloud.gateway.filter.FilterDefinition;
3338
import org.springframework.cloud.gateway.filter.GlobalFilter;
3439
import org.springframework.cloud.gateway.filter.factory.GatewayFilterFactory;
3540
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
3641
import org.springframework.cloud.gateway.handler.predicate.RoutePredicateFactory;
42+
import org.springframework.cloud.gateway.route.Route;
3743
import org.springframework.cloud.gateway.route.RouteDefinition;
3844
import org.springframework.cloud.gateway.route.RouteDefinitionLocator;
3945
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
@@ -42,6 +48,9 @@
4248
import org.springframework.context.ApplicationEventPublisher;
4349
import org.springframework.context.ApplicationEventPublisherAware;
4450
import org.springframework.core.Ordered;
51+
import org.springframework.core.type.MethodMetadata;
52+
import org.springframework.core.type.classreading.MetadataReader;
53+
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
4554
import org.springframework.http.HttpStatus;
4655
import org.springframework.http.ResponseEntity;
4756
import org.springframework.util.CollectionUtils;
@@ -51,6 +60,8 @@
5160
import org.springframework.web.bind.annotation.PathVariable;
5261
import org.springframework.web.bind.annotation.PostMapping;
5362
import org.springframework.web.bind.annotation.RequestBody;
63+
import org.springframework.web.bind.annotation.RequestMapping;
64+
import org.springframework.web.bind.annotation.RequestMethod;
5465
import org.springframework.web.bind.annotation.RequestParam;
5566
import org.springframework.web.server.ResponseStatusException;
5667

@@ -76,16 +87,77 @@ public class AbstractGatewayControllerEndpoint implements ApplicationEventPublis
7687

7788
protected ApplicationEventPublisher publisher;
7889

90+
protected WebEndpointProperties webEndpointProperties;
91+
92+
private final SimpleMetadataReaderFactory simpleMetadataReaderFactory = new SimpleMetadataReaderFactory();
93+
94+
@Deprecated
7995
public AbstractGatewayControllerEndpoint(RouteDefinitionLocator routeDefinitionLocator,
8096
List<GlobalFilter> globalFilters, List<GatewayFilterFactory> gatewayFilters,
8197
List<RoutePredicateFactory> routePredicates, RouteDefinitionWriter routeDefinitionWriter,
8298
RouteLocator routeLocator) {
99+
this(routeDefinitionLocator, globalFilters, gatewayFilters, routePredicates,
100+
routeDefinitionWriter, routeLocator, new WebEndpointProperties());
101+
}
102+
103+
public AbstractGatewayControllerEndpoint(RouteDefinitionLocator routeDefinitionLocator,
104+
List<GlobalFilter> globalFilters, List<GatewayFilterFactory> gatewayFilters,
105+
List<RoutePredicateFactory> routePredicates, RouteDefinitionWriter routeDefinitionWriter,
106+
RouteLocator routeLocator, WebEndpointProperties webEndpointProperties) {
83107
this.routeDefinitionLocator = routeDefinitionLocator;
84108
this.globalFilters = globalFilters;
85109
this.GatewayFilters = gatewayFilters;
86110
this.routePredicates = routePredicates;
87111
this.routeDefinitionWriter = routeDefinitionWriter;
88112
this.routeLocator = routeLocator;
113+
this.webEndpointProperties = webEndpointProperties;
114+
}
115+
116+
@GetMapping("/")
117+
Mono<List<GatewayEndpointInfo>> getEndpoints() {
118+
List<GatewayEndpointInfo> endpoints = mergeEndpoints(
119+
getAvailableEndpointsForClass(AbstractGatewayControllerEndpoint.class.getName()),
120+
getAvailableEndpointsForClass(GatewayControllerEndpoint.class.getName()));
121+
122+
return Flux.fromIterable(endpoints).map(p -> p)
123+
.flatMap(path -> this.routeLocator.getRoutes().map(r -> generateHref(r, path)).distinct().collectList()
124+
.flatMapMany(Flux::fromIterable))
125+
.distinct() // Ensure overall uniqueness
126+
.collectList();
127+
}
128+
129+
private List<GatewayEndpointInfo> mergeEndpoints(List<GatewayEndpointInfo> listA,
130+
List<GatewayEndpointInfo> listB) {
131+
Map<String, List<String>> mergedMap = new HashMap<>();
132+
133+
Stream.concat(listA.stream(), listB.stream()).forEach(e -> mergedMap
134+
.computeIfAbsent(e.getHref(), k -> new ArrayList<>()).addAll(Arrays.asList(e.getMethods())));
135+
136+
return mergedMap.entrySet().stream().map(entry -> new GatewayEndpointInfo(entry.getKey(), entry.getValue()))
137+
.collect(Collectors.toList());
138+
}
139+
140+
private List<GatewayEndpointInfo> getAvailableEndpointsForClass(String className) {
141+
try {
142+
MetadataReader metadataReader = simpleMetadataReaderFactory.getMetadataReader(className);
143+
Set<MethodMetadata> annotatedMethods = metadataReader.getAnnotationMetadata()
144+
.getAnnotatedMethods(RequestMapping.class.getName());
145+
146+
String gatewayActuatorPath = webEndpointProperties.getBasePath() + "/gateway";
147+
return annotatedMethods.stream().map(method -> new GatewayEndpointInfo(gatewayActuatorPath
148+
+ ((String[]) method.getAnnotationAttributes(RequestMapping.class.getName()).get("path"))[0],
149+
((RequestMethod[]) method.getAnnotationAttributes(RequestMapping.class.getName()).get("method"))[0]
150+
.name()))
151+
.collect(Collectors.toList());
152+
}
153+
catch (IOException exception) {
154+
log.warn(exception.getMessage());
155+
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, exception.getMessage());
156+
}
157+
}
158+
159+
private GatewayEndpointInfo generateHref(Route r, GatewayEndpointInfo path) {
160+
return new GatewayEndpointInfo(path.getHref().replace("{id}", r.getId()), Arrays.asList(path.getMethods()));
89161
}
90162

91163
@Override

spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/actuate/GatewayControllerEndpoint.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import reactor.core.publisher.Flux;
2525
import reactor.core.publisher.Mono;
2626

27+
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
2728
import org.springframework.boot.actuate.endpoint.web.annotation.RestControllerEndpoint;
2829
import org.springframework.cloud.gateway.filter.GatewayFilter;
2930
import org.springframework.cloud.gateway.filter.GlobalFilter;
@@ -47,9 +48,10 @@ public class GatewayControllerEndpoint extends AbstractGatewayControllerEndpoint
4748

4849
public GatewayControllerEndpoint(List<GlobalFilter> globalFilters, List<GatewayFilterFactory> gatewayFilters,
4950
List<RoutePredicateFactory> routePredicates, RouteDefinitionWriter routeDefinitionWriter,
50-
RouteLocator routeLocator, RouteDefinitionLocator routeDefinitionLocator) {
51-
super(routeDefinitionLocator, globalFilters, gatewayFilters, routePredicates, routeDefinitionWriter,
52-
routeLocator);
51+
RouteLocator routeLocator, RouteDefinitionLocator routeDefinitionLocator,
52+
WebEndpointProperties webEndpointProperties) {
53+
super(routeDefinitionLocator, globalFilters, gatewayFilters, routePredicates,
54+
routeDefinitionWriter, routeLocator, webEndpointProperties);
5355
}
5456

5557
@GetMapping("/routedefinitions")
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* Copyright 2013-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.cloud.gateway.actuate;
18+
19+
import java.util.Collections;
20+
import java.util.List;
21+
import java.util.Objects;
22+
23+
/**
24+
* @author Marta Medio
25+
*/
26+
class GatewayEndpointInfo {
27+
28+
private String href;
29+
30+
private List<String> methods;
31+
32+
public String getHref() {
33+
return href;
34+
}
35+
36+
public void setHref(String href) {
37+
this.href = href;
38+
}
39+
40+
public String[] getMethods() {
41+
return methods.stream().toArray(String[]::new);
42+
}
43+
44+
GatewayEndpointInfo(String href, String method) {
45+
this.href = href;
46+
this.methods = Collections.singletonList(method);
47+
}
48+
49+
GatewayEndpointInfo(String href, List<String> methods) {
50+
this.href = href;
51+
this.methods = methods;
52+
}
53+
54+
@Override
55+
public boolean equals(Object o) {
56+
if (this == o) {
57+
return true;
58+
}
59+
if (o == null || getClass() != o.getClass()) {
60+
return false;
61+
}
62+
GatewayEndpointInfo that = (GatewayEndpointInfo) o;
63+
return Objects.equals(href, that.href) && Objects.equals(methods, that.methods);
64+
}
65+
66+
@Override
67+
public int hashCode() {
68+
return Objects.hash(href, methods);
69+
}
70+
71+
}

spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/actuate/GatewayLegacyControllerEndpoint.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
import reactor.core.publisher.Mono;
2525

26+
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
2627
import org.springframework.boot.actuate.endpoint.web.annotation.RestControllerEndpoint;
2728
import org.springframework.cloud.gateway.filter.GatewayFilter;
2829
import org.springframework.cloud.gateway.filter.GlobalFilter;
@@ -47,9 +48,9 @@ public class GatewayLegacyControllerEndpoint extends AbstractGatewayControllerEn
4748
public GatewayLegacyControllerEndpoint(RouteDefinitionLocator routeDefinitionLocator,
4849
List<GlobalFilter> globalFilters, List<GatewayFilterFactory> gatewayFilterFactories,
4950
List<RoutePredicateFactory> routePredicates, RouteDefinitionWriter routeDefinitionWriter,
50-
RouteLocator routeLocator) {
51+
RouteLocator routeLocator, WebEndpointProperties webEndpointProperties) {
5152
super(routeDefinitionLocator, globalFilters, gatewayFilterFactories, routePredicates, routeDefinitionWriter,
52-
routeLocator);
53+
routeLocator, webEndpointProperties);
5354
}
5455

5556
@GetMapping("/routes")

spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import org.springframework.beans.factory.ObjectProvider;
4141
import org.springframework.beans.factory.annotation.Qualifier;
4242
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint;
43+
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
4344
import org.springframework.boot.actuate.health.Health;
4445
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
4546
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
@@ -817,9 +818,9 @@ protected static class GatewayActuatorConfiguration {
817818
public GatewayControllerEndpoint gatewayControllerEndpoint(List<GlobalFilter> globalFilters,
818819
List<GatewayFilterFactory> gatewayFilters, List<RoutePredicateFactory> routePredicates,
819820
RouteDefinitionWriter routeDefinitionWriter, RouteLocator routeLocator,
820-
RouteDefinitionLocator routeDefinitionLocator) {
821+
RouteDefinitionLocator routeDefinitionLocator, WebEndpointProperties webEndpointProperties) {
821822
return new GatewayControllerEndpoint(globalFilters, gatewayFilters, routePredicates, routeDefinitionWriter,
822-
routeLocator, routeDefinitionLocator);
823+
routeLocator, routeDefinitionLocator, webEndpointProperties);
823824
}
824825

825826
@Bean
@@ -828,9 +829,10 @@ public GatewayControllerEndpoint gatewayControllerEndpoint(List<GlobalFilter> gl
828829
public GatewayLegacyControllerEndpoint gatewayLegacyControllerEndpoint(
829830
RouteDefinitionLocator routeDefinitionLocator, List<GlobalFilter> globalFilters,
830831
List<GatewayFilterFactory> gatewayFilters, List<RoutePredicateFactory> routePredicates,
831-
RouteDefinitionWriter routeDefinitionWriter, RouteLocator routeLocator) {
832+
RouteDefinitionWriter routeDefinitionWriter, RouteLocator routeLocator,
833+
WebEndpointProperties webEndpointProperties) {
832834
return new GatewayLegacyControllerEndpoint(routeDefinitionLocator, globalFilters, gatewayFilters,
833-
routePredicates, routeDefinitionWriter, routeLocator);
835+
routePredicates, routeDefinitionWriter, routeLocator, webEndpointProperties);
834836
}
835837

836838
}

spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/actuate/GatewayControllerEndpointTests.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,34 @@ public class GatewayControllerEndpointTests {
6868
@LocalServerPort
6969
int port;
7070

71+
@Test
72+
public void testEndpoints() {
73+
testClient.get().uri("http://localhost:" + port + "/actuator/gateway").exchange()
74+
.expectStatus().isOk().expectBodyList(Map.class).consumeWith(result -> {
75+
List<Map> responseBody = result.getResponseBody();
76+
assertThat(responseBody).isNotEmpty();
77+
assertThat(responseBody).contains(
78+
Map.of("href", "/actuator/gateway/", "methods",
79+
List.of("GET")),
80+
Map.of("href", "/actuator/gateway/globalfilters", "methods",
81+
List.of("GET")),
82+
Map.of("href", "/actuator/gateway/refresh", "methods",
83+
List.of("POST")),
84+
Map.of("href", "/actuator/gateway/routedefinitions",
85+
"methods", List.of("GET")),
86+
Map.of("href", "/actuator/gateway/routefilters", "methods",
87+
List.of("GET")),
88+
Map.of("href", "/actuator/gateway/routepredicates", "methods",
89+
List.of("GET")),
90+
Map.of("href", "/actuator/gateway/routes", "methods",
91+
List.of("POST", "GET")),
92+
Map.of("href", "/actuator/gateway/routes/test-service",
93+
"methods", List.of("POST", "DELETE", "GET")),
94+
Map.of("href", "/actuator/gateway/routes/route_with_metadata",
95+
"methods", List.of("POST", "DELETE", "GET")));
96+
});
97+
}
98+
7199
@Test
72100
public void testRefresh() {
73101
testClient.post().uri("http://localhost:" + port + "/actuator/gateway/refresh").exchange().expectStatus()

0 commit comments

Comments
 (0)