Skip to content

Commit 63fc79c

Browse files
author
Marta Medio
committed
Add links to child paths for /actuator/gateway endpoint
1 parent cba61f7 commit 63fc79c

File tree

3 files changed

+139
-0
lines changed

3 files changed

+139
-0
lines changed

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

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,16 @@
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;
@@ -34,6 +38,7 @@
3438
import org.springframework.cloud.gateway.filter.factory.GatewayFilterFactory;
3539
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
3640
import org.springframework.cloud.gateway.handler.predicate.RoutePredicateFactory;
41+
import org.springframework.cloud.gateway.route.Route;
3742
import org.springframework.cloud.gateway.route.RouteDefinition;
3843
import org.springframework.cloud.gateway.route.RouteDefinitionLocator;
3944
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
@@ -42,6 +47,9 @@
4247
import org.springframework.context.ApplicationEventPublisher;
4348
import org.springframework.context.ApplicationEventPublisherAware;
4449
import org.springframework.core.Ordered;
50+
import org.springframework.core.type.MethodMetadata;
51+
import org.springframework.core.type.classreading.MetadataReader;
52+
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
4553
import org.springframework.http.HttpStatus;
4654
import org.springframework.http.ResponseEntity;
4755
import org.springframework.util.CollectionUtils;
@@ -51,6 +59,8 @@
5159
import org.springframework.web.bind.annotation.PathVariable;
5260
import org.springframework.web.bind.annotation.PostMapping;
5361
import org.springframework.web.bind.annotation.RequestBody;
62+
import org.springframework.web.bind.annotation.RequestMapping;
63+
import org.springframework.web.bind.annotation.RequestMethod;
5464
import org.springframework.web.bind.annotation.RequestParam;
5565
import org.springframework.web.server.ResponseStatusException;
5666

@@ -61,6 +71,8 @@ public class AbstractGatewayControllerEndpoint implements ApplicationEventPublis
6171

6272
private static final Log log = LogFactory.getLog(GatewayControllerEndpoint.class);
6373

74+
private static final String ENDPOINT_PREFIX = "/actuator/gateway";
75+
6476
protected RouteDefinitionLocator routeDefinitionLocator;
6577

6678
protected List<GlobalFilter> globalFilters;
@@ -88,6 +100,53 @@ public AbstractGatewayControllerEndpoint(RouteDefinitionLocator routeDefinitionL
88100
this.routeLocator = routeLocator;
89101
}
90102

103+
private List<GatewayEndpointInfo> getAvailableEndpointsForClass(String className) {
104+
try {
105+
MetadataReader metadataReader = new SimpleMetadataReaderFactory().getMetadataReader(className);
106+
Set<MethodMetadata> annotatedMethods = metadataReader.getAnnotationMetadata()
107+
.getAnnotatedMethods(RequestMapping.class.getName());
108+
109+
return annotatedMethods.stream().map(method -> new GatewayEndpointInfo(ENDPOINT_PREFIX
110+
+ ((String[]) method.getAnnotationAttributes(RequestMapping.class.getName()).get("path"))[0],
111+
((RequestMethod[]) method.getAnnotationAttributes(RequestMapping.class.getName()).get("method"))[0]
112+
.name()))
113+
.collect(Collectors.toList());
114+
}
115+
catch (IOException exception) {
116+
log.warn(exception.getMessage());
117+
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, exception.getMessage());
118+
}
119+
}
120+
121+
public static List<GatewayEndpointInfo> mergeEndpoints(List<GatewayEndpointInfo> listA,
122+
List<GatewayEndpointInfo> listB) {
123+
Map<String, List<String>> mergedMap = new HashMap<>();
124+
125+
Stream.concat(listA.stream(), listB.stream()).forEach(e -> mergedMap
126+
.computeIfAbsent(e.getHref(), k -> new ArrayList<>()).addAll(Arrays.asList(e.getMethods())));
127+
128+
return mergedMap.entrySet().stream().map(entry -> new GatewayEndpointInfo(entry.getKey(), entry.getValue()))
129+
.collect(Collectors.toList());
130+
}
131+
132+
GatewayEndpointInfo generateHref(Route r, GatewayEndpointInfo path) {
133+
return new GatewayEndpointInfo(path.getHref().replace("{id}", r.getId()), Arrays.asList(path.getMethods()));
134+
}
135+
136+
@GetMapping("/")
137+
public Mono<List<GatewayEndpointInfo>> getEndpoints() {
138+
List<GatewayEndpointInfo> endpoints = mergeEndpoints(
139+
getAvailableEndpointsForClass(AbstractGatewayControllerEndpoint.class.getName()),
140+
getAvailableEndpointsForClass(GatewayControllerEndpoint.class.getName()));
141+
142+
return Flux.fromIterable(endpoints).map(p -> p)
143+
.flatMap(path -> this.routeLocator.getRoutes().map(r -> generateHref(r, path)).distinct().collectList()
144+
.flatMapMany(Flux::fromIterable))
145+
.distinct() // Ensure overall uniqueness
146+
.collectList();
147+
148+
}
149+
91150
@Override
92151
public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
93152
this.publisher = publisher;
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/test/java/org/springframework/cloud/gateway/actuate/GatewayControllerEndpointTests.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,15 @@ 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().expectStatus().isOk()
74+
.expectBody(List.class).consumeWith(result -> {
75+
List<String> responseBody = result.getResponseBody();
76+
assertThat(responseBody).isNotEmpty();
77+
});
78+
}
79+
7180
@Test
7281
public void testRefresh() {
7382
testClient.post().uri("http://localhost:" + port + "/actuator/gateway/refresh").exchange().expectStatus()

0 commit comments

Comments
 (0)