Provide more Zuul route details in the routes actuator endpoint
Add a parameter to control the level of details returned with the routes actuator endpoint. If requested, return all available Zuul route details including ID, prefix, sensitive headers, etc. Signed-off-by: Gregor Zurowski <gregor@zurowski.org>
This commit is contained in:
@@ -18,7 +18,11 @@ package org.springframework.cloud.netflix.zuul;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
|
||||
import lombok.Data;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.actuate.endpoint.AbstractEndpoint;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
@@ -34,6 +38,7 @@ import org.springframework.jmx.export.annotation.ManagedResource;
|
||||
* @author Spencer Gibb
|
||||
* @author Dave Syer
|
||||
* @author Ryan Baxter
|
||||
* @author Gregor Zurowski
|
||||
*/
|
||||
@ManagedResource(description = "Can be used to list the reverse proxy routes")
|
||||
@ConfigurationProperties(prefix = "endpoints.routes")
|
||||
@@ -59,4 +64,55 @@ public class RoutesEndpoint extends AbstractEndpoint<Map<String, String>> {
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
Map<String, RouteDetails> invokeRouteDetails() {
|
||||
Map<String, RouteDetails> map = new LinkedHashMap<>();
|
||||
for (Route route : this.routes.getRoutes()) {
|
||||
map.put(route.getFullPath(), new RouteDetails(route));
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Container for exposing Zuul {@link Route} details as JSON.
|
||||
*/
|
||||
@JsonPropertyOrder({ "id", "fullPath", "location" })
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
@Data
|
||||
public static class RouteDetails {
|
||||
|
||||
private String id;
|
||||
|
||||
private String fullPath;
|
||||
|
||||
private String path;
|
||||
|
||||
private String location;
|
||||
|
||||
private String prefix;
|
||||
|
||||
private Boolean retryable;
|
||||
|
||||
private Set<String> sensitiveHeaders;
|
||||
|
||||
private boolean customSensitiveHeaders;
|
||||
|
||||
private boolean prefixStripped;
|
||||
|
||||
public RouteDetails() {
|
||||
}
|
||||
|
||||
RouteDetails(final Route route) {
|
||||
this.id = route.getId();
|
||||
this.fullPath = route.getFullPath();
|
||||
this.path = route.getPath();
|
||||
this.location = route.getLocation();
|
||||
this.prefix = route.getPrefix();
|
||||
this.retryable = route.getRetryable();
|
||||
this.sensitiveHeaders = route.getSensitiveHeaders();
|
||||
this.customSensitiveHeaders = route.isCustomSensitiveHeaders();
|
||||
this.prefixStripped = route.isPrefixStripped();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -18,28 +18,38 @@
|
||||
|
||||
package org.springframework.cloud.netflix.zuul;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.mvc.ActuatorMediaTypes;
|
||||
import org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter;
|
||||
import org.springframework.cloud.netflix.zuul.filters.Route;
|
||||
import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.ApplicationEventPublisherAware;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.jmx.export.annotation.ManagedOperation;
|
||||
import org.springframework.jmx.export.annotation.ManagedResource;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
/**
|
||||
* Endpoint used to reset the reverse proxy routes
|
||||
* @author Ryan Baxter
|
||||
* @author Gregor Zurowski
|
||||
*/
|
||||
@ManagedResource(description = "Can be used to reset the reverse proxy routes")
|
||||
public class RoutesMvcEndpoint extends EndpointMvcAdapter implements ApplicationEventPublisherAware {
|
||||
|
||||
static final String FORMAT_DETAILS = "details";
|
||||
|
||||
private final RoutesEndpoint endpoint;
|
||||
private RouteLocator routes;
|
||||
private ApplicationEventPublisher publisher;
|
||||
|
||||
public RoutesMvcEndpoint(RoutesEndpoint endpoint, RouteLocator routes) {
|
||||
super(endpoint);
|
||||
this.endpoint = endpoint;
|
||||
this.routes = routes;
|
||||
}
|
||||
|
||||
@@ -55,4 +65,18 @@ public class RoutesMvcEndpoint extends EndpointMvcAdapter implements Application
|
||||
this.publisher.publishEvent(new RoutesRefreshedEvent(this.routes));
|
||||
return super.invoke();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Expose Zuul {@link Route} information with details.
|
||||
*/
|
||||
@GetMapping(params = "format", produces = { ActuatorMediaTypes.APPLICATION_ACTUATOR_V1_JSON_VALUE,
|
||||
MediaType.APPLICATION_JSON_VALUE })
|
||||
@ResponseBody
|
||||
public Object invokeRouteDetails(@RequestParam String format) {
|
||||
if (FORMAT_DETAILS.equalsIgnoreCase(format)) {
|
||||
return endpoint.invokeRouteDetails();
|
||||
} else {
|
||||
return super.invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,16 +27,23 @@ import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.web.client.TestRestTemplate;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* @author Ryan Baxter
|
||||
* @author Gregor Zurowski
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringBootTest(
|
||||
@@ -64,6 +71,22 @@ public class RoutesEndpointIntegrationTests {
|
||||
assertTrue(refreshListener.wasCalled());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getRouteDetailsTest() {
|
||||
ResponseEntity<Map<String, RoutesEndpoint.RouteDetails>> responseEntity = restTemplate.exchange(
|
||||
"/admin/routes?format=details", HttpMethod.GET, null, new ParameterizedTypeReference<Map<String, RoutesEndpoint.RouteDetails>>() {
|
||||
});
|
||||
|
||||
assertThat(responseEntity.getStatusCode(), is(HttpStatus.OK));
|
||||
|
||||
RoutesEndpoint.RouteDetails details = responseEntity.getBody().get("/sslservice/**");
|
||||
assertThat(details.getPath(), is("/**"));
|
||||
assertThat(details.getFullPath(), is("/sslservice/**"));
|
||||
assertThat(details.getLocation(), is("https://localhost:8443"));
|
||||
assertThat(details.getPrefix(), is("/sslservice"));
|
||||
assertTrue(details.isPrefixStripped());
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableAutoConfiguration
|
||||
@RestController
|
||||
|
||||
@@ -34,6 +34,7 @@ import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* @author Ryan Baxter
|
||||
* @author Gregor Zurowski
|
||||
*/
|
||||
public class RoutesEndpointTests {
|
||||
|
||||
@@ -51,7 +52,7 @@ public class RoutesEndpointTests {
|
||||
public List<Route> getRoutes() {
|
||||
List<Route> routes = new ArrayList<>();
|
||||
routes.add(new Route("foo", "foopath", "foolocation", null, true, Collections.EMPTY_SET));
|
||||
routes.add(new Route("bar", "barpath", "barlocation", null, true, Collections.EMPTY_SET));
|
||||
routes.add(new Route("bar", "barpath", "barlocation", "/bar-prefix", true, Collections.EMPTY_SET));
|
||||
return routes;
|
||||
}
|
||||
|
||||
@@ -72,6 +73,16 @@ public class RoutesEndpointTests {
|
||||
assertEquals(result , endpoint.invoke());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvokeRouteDetails() {
|
||||
RoutesEndpoint endpoint = new RoutesEndpoint(locator);
|
||||
Map<String, RoutesEndpoint.RouteDetails> results = new HashMap<>();
|
||||
for (Route route : locator.getRoutes()) {
|
||||
results.put(route.getFullPath(), new RoutesEndpoint.RouteDetails(route));
|
||||
}
|
||||
assertEquals(results, endpoint.invokeRouteDetails());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testId() {
|
||||
RoutesEndpoint endpoint = new RoutesEndpoint(locator);
|
||||
|
||||
@@ -42,6 +42,7 @@ import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* @author Ryan Baxter
|
||||
* @author Gregor Zurowski
|
||||
*/
|
||||
@SpringBootTest
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
@@ -63,7 +64,7 @@ public class RoutesMvcEndpointTests {
|
||||
public List<Route> getRoutes() {
|
||||
List<Route> routes = new ArrayList<>();
|
||||
routes.add(new Route("foo", "foopath", "foolocation", null, true, Collections.EMPTY_SET));
|
||||
routes.add(new Route("bar", "barpath", "barlocation", null, true, Collections.EMPTY_SET));
|
||||
routes.add(new Route("bar", "barpath", "barlocation", "bar-prefix", true, Collections.EMPTY_SET));
|
||||
return routes;
|
||||
}
|
||||
|
||||
@@ -88,4 +89,15 @@ public class RoutesMvcEndpointTests {
|
||||
verify(publisher, times(1)).publishEvent(isA(RoutesRefreshedEvent.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void routeDetails() throws Exception {
|
||||
RoutesMvcEndpoint mvcEndpoint = new RoutesMvcEndpoint(endpoint, locator);
|
||||
Map<String, RoutesEndpoint.RouteDetails> results = new HashMap<>();
|
||||
for (Route route : locator.getRoutes()) {
|
||||
results.put(route.getFullPath(), new RoutesEndpoint.RouteDetails(route));
|
||||
}
|
||||
assertEquals(results, mvcEndpoint.invokeRouteDetails(RoutesMvcEndpoint.FORMAT_DETAILS));
|
||||
verify(endpoint, times(1)).invokeRouteDetails();
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user