Combine /links and /hal into a single /actuator endpoint

This commit provides a single endpoint, /actuator, that serves HTML
(the HAL browser) or JSON depending on the request’s accept header
that enables discovery of all of the actuator’s other endpoints.

When the management context path is configured, the /actuator endpoint
moves to the configured path, e.g. if the management context path is
set to /management, the actuator endpoint will be available from
/management.

Closes gh-3696
This commit is contained in:
Andy Wilkinson 2015-08-06 15:56:06 +01:00
parent ce512a18f3
commit 58db5a3889
16 changed files with 301 additions and 508 deletions

View File

@ -61,7 +61,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
@SpringApplicationConfiguration(classes = SpringBootHypermediaApplication.class)
@WebAppConfiguration
@TestPropertySource(properties = { "spring.jackson.serialization.indent_output=true",
"endpoints.health.sensitive=true", "endpoints.links.enabled=false" })
"endpoints.health.sensitive=true", "endpoints.actuator.enabled=false" })
@DirtiesContext
public class EndpointDocumentation {

View File

@ -82,7 +82,7 @@ public class HypermediaEndpointDocumentation {
@Test
public void home() throws Exception {
this.mockMvc.perform(get("/links").accept(MediaType.APPLICATION_JSON))
this.mockMvc.perform(get("/actuator").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()).andDo(document("admin"));
}

View File

@ -25,32 +25,23 @@ import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.endpoint.mvc.ActuatorDocsEndpoint;
import org.springframework.boot.actuate.endpoint.mvc.HalBrowserMvcEndpoint;
import org.springframework.boot.actuate.endpoint.mvc.HalBrowserMvcEndpoint.HalBrowserLocation;
import org.springframework.boot.actuate.endpoint.mvc.ActuatorMvcEndpoint;
import org.springframework.boot.actuate.endpoint.mvc.HypermediaDisabled;
import org.springframework.boot.actuate.endpoint.mvc.LinksMvcEndpoint;
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint;
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoints;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnResource;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.boot.autoconfigure.web.HttpMessageConverters;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.Conditional;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.Resource;
import org.springframework.hateoas.ResourceSupport;
@ -75,7 +66,6 @@ import com.fasterxml.jackson.annotation.JsonUnwrapped;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import static org.springframework.hateoas.mvc.BasicLinkBuilder.linkToCurrentMapping;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
/**
@ -83,6 +73,7 @@ import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
*
* @author Dave Syer
* @author Phillip Webb
* @author Andy Wilkinson
* @since 1.3.0
*/
@ManagementContextConfiguration
@ -93,16 +84,15 @@ import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
@EnableConfigurationProperties(ResourceProperties.class)
public class EndpointWebMvcHypermediaManagementContextConfiguration {
@ConditionalOnProperty(value = "endpoints.hal.enabled", matchIfMissing = true)
@Conditional(HalBrowserCondition.class)
@ConditionalOnProperty(prefix = "endpoints.actuator", name = "enabled", matchIfMissing = true)
@Bean
public HalBrowserMvcEndpoint halBrowserMvcEndpoint(
ManagementServerProperties management, ResourceProperties resources) {
return new HalBrowserMvcEndpoint(management);
public ActuatorMvcEndpoint actuatorMvcEndpoint(ManagementServerProperties management,
ResourceProperties resources) {
return new ActuatorMvcEndpoint(management);
}
@Bean
@ConditionalOnProperty(value = "endpoints.docs.enabled", matchIfMissing = true)
@ConditionalOnProperty(prefix = "endpoints.docs", name = "enabled", matchIfMissing = true)
@ConditionalOnResource(resources = "classpath:/META-INF/resources/spring-boot-actuator/docs/index.html")
public ActuatorDocsEndpoint actuatorDocsEndpoint(ManagementServerProperties management) {
return new ActuatorDocsEndpoint(management);
@ -111,7 +101,7 @@ public class EndpointWebMvcHypermediaManagementContextConfiguration {
@Bean
@ConditionalOnBean(ActuatorDocsEndpoint.class)
@ConditionalOnMissingBean(CurieProvider.class)
@ConditionalOnProperty(value = "endpoints.docs.curies.enabled", matchIfMissing = false)
@ConditionalOnProperty(prefix = "endpoints.docs.curies", name = "enabled", matchIfMissing = false)
public DefaultCurieProvider curieProvider(ServerProperties server,
ManagementServerProperties management, ActuatorDocsEndpoint endpoint) {
String path = management.getContextPath() + endpoint.getPath()
@ -124,217 +114,167 @@ public class EndpointWebMvcHypermediaManagementContextConfiguration {
}
/**
* {@link SpringBootCondition} to detect the HAL browser.
* Controller advice that adds links to the actuator endpoint's path.
*/
protected static class HalBrowserCondition extends SpringBootCondition {
@ControllerAdvice
public static class ActuatorEndpointLinksAdvice implements ResponseBodyAdvice<Object> {
@Autowired
private MvcEndpoints endpoints;
@Autowired(required = false)
private ActuatorMvcEndpoint actuatorEndpoint;
@Autowired
private ManagementServerProperties management;
private LinksEnhancer linksEnhancer;
@PostConstruct
public void init() {
this.linksEnhancer = new LinksEnhancer(this.management.getContextPath(),
this.endpoints);
}
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
ResourceLoader loader = context.getResourceLoader();
loader = (loader == null ? new DefaultResourceLoader() : loader);
HalBrowserLocation found = HalBrowserMvcEndpoint
.getHalBrowserLocation(loader);
return new ConditionOutcome(found != null, "HAL Browser "
+ (found == null ? "not found" : "at " + found));
public boolean supports(MethodParameter returnType,
Class<? extends HttpMessageConverter<?>> converterType) {
returnType.increaseNestingLevel();
Type nestedType = returnType.getNestedGenericParameterType();
returnType.decreaseNestingLevel();
return ResourceSupport.class.isAssignableFrom(returnType.getParameterType())
|| TypeUtils.isAssignable(ResourceSupport.class, nestedType);
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType,
MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response) {
if (request instanceof ServletServerHttpRequest) {
beforeBodyWrite(body, (ServletServerHttpRequest) request);
}
return body;
}
private void beforeBodyWrite(Object body, ServletServerHttpRequest request) {
Object pattern = request.getServletRequest().getAttribute(
HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
if (pattern != null && body instanceof ResourceSupport) {
beforeBodyWrite(pattern.toString(), (ResourceSupport) body);
}
}
private void beforeBodyWrite(String path, ResourceSupport body) {
if (isActuatorEndpointPath(path)) {
this.linksEnhancer
.addEndpointLinks(body, this.actuatorEndpoint.getPath());
}
}
private boolean isActuatorEndpointPath(String path) {
return this.actuatorEndpoint != null
&& (this.management.getContextPath() + this.actuatorEndpoint
.getPath()).equals(path);
}
}
/**
* Configuration for Endpoint links.
* Controller advice that adds links to the existing Actuator endpoints. By default
* all the top-level resources are enhanced with a "self" link. Those resources that
* could not be enhanced (e.g. "/env/{name}") because their values are "primitive" are
* ignored. Those that have values of type Collection (e.g. /trace) are transformed in
* to maps, and the original collection value is added with a key equal to the
* endpoint name.
*/
@ConditionalOnProperty(value = "endpoints.links.enabled", matchIfMissing = true)
public static class LinksConfiguration {
@ControllerAdvice(assignableTypes = MvcEndpoint.class)
public static class MvcEndpointAdvice implements ResponseBodyAdvice<Object> {
@Bean
public LinksMvcEndpoint linksMvcEndpoint(ResourceProperties resources) {
return new LinksMvcEndpoint();
@Autowired
private ManagementServerProperties management;
@Autowired
private HttpMessageConverters converters;
private Map<MediaType, HttpMessageConverter<?>> converterCache = new ConcurrentHashMap<MediaType, HttpMessageConverter<?>>();
@Autowired
private ObjectMapper mapper;
@Override
public boolean supports(MethodParameter returnType,
Class<? extends HttpMessageConverter<?>> converterType) {
Class<?> controllerType = returnType.getDeclaringClass();
return !ActuatorMvcEndpoint.class.isAssignableFrom(controllerType);
}
/**
* Controller advice that adds links to the home page and/or the management
* context path. The home page is enhanced if it is composed already of a
* {@link ResourceSupport} (e.g. when using Spring Data REST).
*/
@ControllerAdvice
public static class HomePageLinksAdvice implements ResponseBodyAdvice<Object> {
@Autowired
private MvcEndpoints endpoints;
@Autowired(required = false)
private LinksMvcEndpoint linksEndpoint;
@Autowired
private ManagementServerProperties management;
private LinksEnhancer linksEnhancer;
@PostConstruct
public void init() {
this.linksEnhancer = new LinksEnhancer(this.management.getContextPath(),
this.endpoints);
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType,
MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response) {
if (request instanceof ServletServerHttpRequest) {
return beforeBodyWrite(body, returnType, selectedContentType,
selectedConverterType, (ServletServerHttpRequest) request,
response);
}
@Override
public boolean supports(MethodParameter returnType,
Class<? extends HttpMessageConverter<?>> converterType) {
Class<?> controllerType = returnType.getDeclaringClass();
if (!LinksMvcEndpoint.class.isAssignableFrom(controllerType)
&& MvcEndpoint.class.isAssignableFrom(controllerType)) {
return false;
}
returnType.increaseNestingLevel();
Type nestedType = returnType.getNestedGenericParameterType();
returnType.decreaseNestingLevel();
return ResourceSupport.class.isAssignableFrom(returnType
.getParameterType())
|| TypeUtils.isAssignable(ResourceSupport.class, nestedType);
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType,
MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response) {
if (request instanceof ServletServerHttpRequest) {
beforeBodyWrite(body, (ServletServerHttpRequest) request);
}
return body;
}
private void beforeBodyWrite(Object body, ServletServerHttpRequest request) {
Object pattern = request.getServletRequest().getAttribute(
HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
if (pattern != null && body instanceof ResourceSupport) {
beforeBodyWrite(pattern.toString(), (ResourceSupport) body);
}
}
private void beforeBodyWrite(String path, ResourceSupport body) {
if (isLinksPath(path)) {
this.linksEnhancer.addEndpointLinks(body,
this.linksEndpoint.getPath());
}
else if (isHomePage(path)) {
body.add(linkToCurrentMapping()
.slash(this.management.getContextPath())
.slash(this.linksEndpoint.getPath()).withRel("actuator"));
}
}
private boolean isLinksPath(String path) {
return this.linksEndpoint != null
&& (this.management.getContextPath() + this.linksEndpoint
.getPath()).equals(path);
}
private boolean isHomePage(String path) {
return "".equals(path) || "/".equals(path);
}
return body;
}
/**
* Controller advice that adds links to the existing Actuator endpoints. By
* default all the top-level resources are enhanced with a "self" link. Those
* resources that could not be enhanced (e.g. "/env/{name}") because their values
* are "primitive" are ignored. Those that have values of type Collection (e.g.
* /trace) are transformed in to maps, and the original collection value is added
* with a key equal to the endpoint name.
*/
@ControllerAdvice(assignableTypes = MvcEndpoint.class)
public static class MvcEndpointAdvice implements ResponseBodyAdvice<Object> {
@Autowired
private ManagementServerProperties management;
@Autowired
private HttpMessageConverters converters;
private Map<MediaType, HttpMessageConverter<?>> converterCache = new ConcurrentHashMap<MediaType, HttpMessageConverter<?>>();
@Autowired
private ObjectMapper mapper;
@Override
public boolean supports(MethodParameter returnType,
Class<? extends HttpMessageConverter<?>> converterType) {
Class<?> controllerType = returnType.getDeclaringClass();
return !LinksMvcEndpoint.class.isAssignableFrom(controllerType)
&& !HalBrowserMvcEndpoint.class.isAssignableFrom(controllerType);
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType,
MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response) {
if (request instanceof ServletServerHttpRequest) {
return beforeBodyWrite(body, returnType, selectedContentType,
selectedConverterType, (ServletServerHttpRequest) request,
response);
}
private Object beforeBodyWrite(Object body, MethodParameter returnType,
MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServletServerHttpRequest request, ServerHttpResponse response) {
if (body == null || body instanceof Resource) {
// Assume it already was handled or it already has its links
return body;
}
private Object beforeBodyWrite(Object body, MethodParameter returnType,
MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServletServerHttpRequest request, ServerHttpResponse response) {
if (body == null || body instanceof Resource) {
// Assume it already was handled or it already has its links
return body;
}
HttpMessageConverter<Object> converter = findConverter(
selectedConverterType, selectedContentType);
if (converter == null || isHypermediaDisabled(returnType)) {
// Not a resource that can be enhanced with a link
return body;
}
String path = getPath(request);
try {
converter.write(new EndpointResource(body, path),
selectedContentType, response);
}
catch (IOException ex) {
throw new HttpMessageNotWritableException("Cannot write response", ex);
}
return null;
HttpMessageConverter<Object> converter = findConverter(selectedConverterType,
selectedContentType);
if (converter == null || isHypermediaDisabled(returnType)) {
// Not a resource that can be enhanced with a link
return body;
}
String path = getPath(request);
try {
converter.write(new EndpointResource(body, path), selectedContentType,
response);
}
catch (IOException ex) {
throw new HttpMessageNotWritableException("Cannot write response", ex);
}
return null;
}
@SuppressWarnings("unchecked")
private HttpMessageConverter<Object> findConverter(
Class<? extends HttpMessageConverter<?>> selectedConverterType,
MediaType mediaType) {
if (this.converterCache.containsKey(mediaType)) {
return (HttpMessageConverter<Object>) this.converterCache
.get(mediaType);
@SuppressWarnings("unchecked")
private HttpMessageConverter<Object> findConverter(
Class<? extends HttpMessageConverter<?>> selectedConverterType,
MediaType mediaType) {
if (this.converterCache.containsKey(mediaType)) {
return (HttpMessageConverter<Object>) this.converterCache.get(mediaType);
}
for (HttpMessageConverter<?> converter : this.converters) {
if (selectedConverterType.isAssignableFrom(converter.getClass())
&& converter.canWrite(EndpointResource.class, mediaType)) {
this.converterCache.put(mediaType, converter);
return (HttpMessageConverter<Object>) converter;
}
for (HttpMessageConverter<?> converter : this.converters) {
if (selectedConverterType.isAssignableFrom(converter.getClass())
&& converter.canWrite(EndpointResource.class, mediaType)) {
this.converterCache.put(mediaType, converter);
return (HttpMessageConverter<Object>) converter;
}
}
return null;
}
return null;
}
private boolean isHypermediaDisabled(MethodParameter returnType) {
return AnnotationUtils.findAnnotation(returnType.getMethod(),
HypermediaDisabled.class) != null
|| AnnotationUtils.findAnnotation(returnType.getMethod()
.getDeclaringClass(), HypermediaDisabled.class) != null;
}
private String getPath(ServletServerHttpRequest request) {
String path = (String) request.getServletRequest().getAttribute(
HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
return (path == null ? "" : path);
}
private boolean isHypermediaDisabled(MethodParameter returnType) {
return AnnotationUtils.findAnnotation(returnType.getMethod(),
HypermediaDisabled.class) != null
|| AnnotationUtils.findAnnotation(returnType.getMethod()
.getDeclaringClass(), HypermediaDisabled.class) != null;
}
private String getPath(ServletServerHttpRequest request) {
String path = (String) request.getServletRequest().getAttribute(
HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
return (path == null ? "" : path);
}
}

View File

@ -27,7 +27,7 @@ import org.springframework.util.StringUtils;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
/**
* Adds endpoints links to {@link ResourceSupport}.
* Adds endpoint links to {@link ResourceSupport}.
*
* @author Dave Syer
*/

View File

@ -23,16 +23,20 @@ import javax.servlet.http.HttpServletRequest;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.autoconfigure.ManagementServerProperties;
import org.springframework.boot.actuate.endpoint.Endpoint;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.hateoas.ResourceSupport;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.resource.ResourceTransformer;
@ -40,15 +44,16 @@ import org.springframework.web.servlet.resource.ResourceTransformerChain;
import org.springframework.web.servlet.resource.TransformedResource;
/**
* {@link MvcEndpoint} to support the HAL browser.
* {@link MvcEndpoint} for the actuator. Uses content negotiation to provide access to the
* HAL browser (when on the classpath), and to HAL-formatted JSON.
*
* @author Dave Syer
* @author Phillip Webb
* @since 1.3.0
* @author Phil Webb
* @author Andy Wilkinson
*/
@ConfigurationProperties("endpoints.hal")
public class HalBrowserMvcEndpoint extends WebMvcConfigurerAdapter implements
MvcEndpoint, ResourceLoaderAware {
@ConfigurationProperties("endpoints.actuator")
public class ActuatorMvcEndpoint extends WebMvcConfigurerAdapter implements MvcEndpoint,
ResourceLoaderAware {
private static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
@ -63,8 +68,8 @@ public class HalBrowserMvcEndpoint extends WebMvcConfigurerAdapter implements
* Endpoint URL path.
*/
@NotNull
@Pattern(regexp = "/[^/]*", message = "Path must start with /")
private String path = "/hal";
@Pattern(regexp = "^$|/[^/]*", message = "Path must be empty or start with /")
private String path;
/**
* Enable security on the endpoint.
@ -78,13 +83,16 @@ public class HalBrowserMvcEndpoint extends WebMvcConfigurerAdapter implements
private final ManagementServerProperties management;
@Autowired(required = false)
private LinksMvcEndpoint linksMvcEndpoint;
private HalBrowserLocation location;
public HalBrowserMvcEndpoint(ManagementServerProperties management) {
public ActuatorMvcEndpoint(ManagementServerProperties management) {
this.management = management;
if (StringUtils.hasText(management.getContextPath())) {
this.path = "";
}
else {
this.path = "/actuator";
}
}
@Override
@ -94,22 +102,34 @@ public class HalBrowserMvcEndpoint extends WebMvcConfigurerAdapter implements
@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
public String browse(HttpServletRequest request) {
String contextPath = this.management.getContextPath() + this.path + "/";
if (this.location == null) {
throw new HalBrowserUnavailableException();
}
String contextPath = this.management.getContextPath()
+ (this.path.endsWith("/") ? this.path : this.path + "/");
if (request.getRequestURI().endsWith("/")) {
return "forward:" + contextPath + this.location.getHtmlFile();
}
return "redirect:" + contextPath;
}
@RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public ResourceSupport links() {
return new ResourceSupport();
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// Make sure the root path is not cached so the browser comes back for the JSON
// and add a transformer to set the initial link
String start = this.management.getContextPath() + this.path;
registry.addResourceHandler(start + "/", start + "/**")
.addResourceLocations(this.location.getResourceLocation())
.setCachePeriod(0).resourceChain(true)
.addTransformer(new InitialUrlTransformer());
if (this.location != null) {
String start = this.management.getContextPath() + this.path;
registry.addResourceHandler(start + "/", start + "/**")
.addResourceLocations(this.location.getResourceLocation())
.setCachePeriod(0).resourceChain(true)
.addTransformer(new InitialUrlTransformer());
}
}
public void setPath(String path) {
@ -121,23 +141,23 @@ public class HalBrowserMvcEndpoint extends WebMvcConfigurerAdapter implements
return this.path;
}
public void setSensitive(boolean sensitive) {
this.sensitive = sensitive;
}
@Override
public boolean isSensitive() {
return this.sensitive;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
public void setSensitive(boolean sensitive) {
this.sensitive = sensitive;
}
public boolean isEnabled() {
return this.enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
@Override
public Class<? extends Endpoint<?>> getEndpointType() {
return null;
@ -167,21 +187,17 @@ public class HalBrowserMvcEndpoint extends WebMvcConfigurerAdapter implements
ResourceTransformerChain transformerChain) throws IOException {
resource = transformerChain.transform(request, resource);
if (resource.getFilename().equalsIgnoreCase(
HalBrowserMvcEndpoint.this.location.getHtmlFile())) {
ActuatorMvcEndpoint.this.location.getHtmlFile())) {
return replaceInitialLink(resource);
}
return resource;
}
private Resource replaceInitialLink(Resource resource) throws IOException {
LinksMvcEndpoint linksEndpoint = HalBrowserMvcEndpoint.this.linksMvcEndpoint;
if (linksEndpoint == null) {
return resource;
}
byte[] bytes = FileCopyUtils.copyToByteArray(resource.getInputStream());
String content = new String(bytes, DEFAULT_CHARSET);
String initialLink = HalBrowserMvcEndpoint.this.management.getContextPath()
+ linksEndpoint.getPath();
String initialLink = ActuatorMvcEndpoint.this.management.getContextPath()
+ getPath();
content = content.replace("entryPoint: '/'", "entryPoint: '" + initialLink
+ "'");
return new TransformedResource(resource, content.getBytes(DEFAULT_CHARSET));
@ -215,4 +231,9 @@ public class HalBrowserMvcEndpoint extends WebMvcConfigurerAdapter implements
}
@ResponseStatus(HttpStatus.NOT_ACCEPTABLE)
private static class HalBrowserUnavailableException extends RuntimeException {
}
}

View File

@ -25,8 +25,8 @@ import java.lang.annotation.Target;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* Annotation to that {@link MvcEndpoint} class or {@link RequestMapping} method should't
* generate a hypermedia response.
* Annotation to indicate that an {@link MvcEndpoint} class or {@link RequestMapping}
* method should't generate a hypermedia response.
*
* @author Dave Syer
* @since 1.3.0

View File

@ -1,95 +0,0 @@
/*
* Copyright 2013-2015 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
*
* http://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.boot.actuate.endpoint.mvc;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import org.springframework.boot.actuate.endpoint.Endpoint;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.hateoas.ResourceSupport;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* {@link MvcEndpoint} to add hypermedia links.
*
* @author Dave Syer
* @since 1.3.0
*/
@ConfigurationProperties("endpoints.links")
public class LinksMvcEndpoint implements MvcEndpoint {
/**
* Endpoint URL path.
*/
@NotNull
@Pattern(regexp = "/[^/]*", message = "Path must start with /")
private String path = "/links";
/**
* Enable security on the endpoint.
*/
private boolean sensitive = false;
/**
* Enable the endpoint.
*/
private boolean enabled = true;
public LinksMvcEndpoint() {
}
@RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public ResourceSupport links() {
return new ResourceSupport();
}
public void setPath(String path) {
this.path = path;
}
@Override
public String getPath() {
return this.path;
}
@Override
public boolean isSensitive() {
return this.sensitive;
}
public void setSensitive(boolean sensitive) {
this.sensitive = sensitive;
}
public boolean isEnabled() {
return this.enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
@Override
public Class<? extends Endpoint<?>> getEndpointType() {
return null;
}
}

View File

@ -20,8 +20,8 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.actuate.autoconfigure.BrowserPathHypermediaIntegrationTests.SpringBootHypermediaApplication;
import org.springframework.boot.actuate.endpoint.mvc.ActuatorMvcEndpoint;
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoints;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.context.annotation.Configuration;
@ -40,10 +40,16 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Integration tests for {@link ActuatorMvcEndpoint}'s HAL Browser support
*
* @author Dave Syer
* @author Andy Wilkinson
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = SpringBootHypermediaApplication.class)
@WebAppConfiguration
@TestPropertySource(properties = "endpoints.hal.path=/hal")
@TestPropertySource(properties = "endpoints.actuator.path=/actuator")
@DirtiesContext
public class BrowserPathHypermediaIntegrationTests {
@ -63,26 +69,22 @@ public class BrowserPathHypermediaIntegrationTests {
@Test
public void browser() throws Exception {
MvcResult response = this.mockMvc
.perform(get("/hal/").accept(MediaType.TEXT_HTML))
.perform(get("/actuator/").accept(MediaType.TEXT_HTML))
.andExpect(status().isOk()).andReturn();
assertEquals("/hal/browser.html", response.getResponse().getForwardedUrl());
assertEquals("/actuator/browser.html", response.getResponse().getForwardedUrl());
}
@Test
public void redirect() throws Exception {
this.mockMvc.perform(get("/hal").accept(MediaType.TEXT_HTML))
this.mockMvc.perform(get("/actuator").accept(MediaType.TEXT_HTML))
.andExpect(status().isFound())
.andExpect(header().string("location", "/hal/"));
.andExpect(header().string("location", "/actuator/"));
}
@MinimalActuatorHypermediaApplication
@Configuration
public static class SpringBootHypermediaApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootHypermediaApplication.class, args);
}
}
}

View File

@ -1,101 +0,0 @@
/*
* Copyright 2012-2015 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
*
* http://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.boot.actuate.autoconfigure;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.actuate.autoconfigure.CustomHomepageHypermediaIntegrationTests.SpringBootHypermediaApplication;
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint;
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoints;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.hateoas.ResourceSupport;
import org.springframework.http.MediaType;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.WebApplicationContext;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = SpringBootHypermediaApplication.class)
@WebAppConfiguration
@DirtiesContext
public class CustomHomepageHypermediaIntegrationTests {
@Autowired
private WebApplicationContext context;
@Autowired
private MvcEndpoints mvcEndpoints;
private MockMvc mockMvc;
@Before
public void setUp() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build();
}
@Test
public void links() throws Exception {
this.mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()).andExpect(jsonPath("$._links").exists());
}
@Test
public void endpointsAllListed() throws Exception {
for (MvcEndpoint endpoint : this.mvcEndpoints.getEndpoints()) {
String path = endpoint.getPath();
if ("/links".equals(path)) {
continue;
}
path = path.startsWith("/") ? path.substring(1) : path;
this.mockMvc.perform(get("/links").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$._links.%s.href", path).exists());
}
}
@MinimalActuatorHypermediaApplication
@RestController
public static class SpringBootHypermediaApplication {
@RequestMapping("")
public ResourceSupport home() {
ResourceSupport resource = new ResourceSupport();
resource.add(linkTo(SpringBootHypermediaApplication.class).slash("/")
.withSelfRel());
return resource;
}
public static void main(String[] args) {
SpringApplication.run(SpringBootHypermediaApplication.class, args);
}
}
}

View File

@ -292,9 +292,9 @@ public class EndpointWebMvcAutoConfigurationTests {
this.applicationContext.register(RootConfig.class, BaseConfiguration.class,
ServerPortConfig.class, EndpointWebMvcAutoConfiguration.class);
this.applicationContext.refresh();
// /health, /metrics, /env (/shutdown is disabled by default)
// /health, /metrics, /env, /actuator (/shutdown is disabled by default)
assertThat(this.applicationContext.getBeansOfType(MvcEndpoint.class).size(),
is(equalTo(5)));
is(equalTo(4)));
}
@Test

View File

@ -20,10 +20,10 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.autoconfigure.ContextPathHypermediaIntegrationTests.SpringBootHypermediaApplication;
import org.springframework.boot.actuate.autoconfigure.ManagementContextPathHypermediaIntegrationTests.SpringBootHypermediaApplication;
import org.springframework.boot.actuate.endpoint.mvc.ActuatorMvcEndpoint;
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint;
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoints;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.hateoas.ResourceSupport;
import org.springframework.http.MediaType;
@ -39,15 +39,23 @@ import org.springframework.web.context.WebApplicationContext;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.forwardedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Integration tests for {@link ActuatorMvcEndpoint} when a custom management context path
* has been configured.
*
* @author Dave Syer
* @author Andy Wilkinson
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = SpringBootHypermediaApplication.class)
@WebAppConfiguration
@TestPropertySource(properties = "management.contextPath:/admin")
@DirtiesContext
public class ContextPathHypermediaIntegrationTests {
public class ManagementContextPathHypermediaIntegrationTests {
@Autowired
private WebApplicationContext context;
@ -63,15 +71,16 @@ public class ContextPathHypermediaIntegrationTests {
}
@Test
public void home() throws Exception {
this.mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON))
public void actuatorHomeJson() throws Exception {
this.mockMvc.perform(get("/admin").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()).andExpect(jsonPath("$._links").exists());
}
@Test
public void links() throws Exception {
this.mockMvc.perform(get("/admin/links").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()).andExpect(jsonPath("$._links").exists());
public void actuatorHomeHtml() throws Exception {
this.mockMvc.perform(get("/admin/").accept(MediaType.TEXT_HTML))
.andExpect(status().isOk())
.andExpect(forwardedUrl("/admin/browser.html"));
}
@Test
@ -89,13 +98,13 @@ public class ContextPathHypermediaIntegrationTests {
public void endpointsAllListed() throws Exception {
for (MvcEndpoint endpoint : this.mvcEndpoints.getEndpoints()) {
String path = endpoint.getPath();
if ("/links".equals(path)) {
if ("/actuator".equals(path)) {
continue;
}
path = path.startsWith("/") ? path.substring(1) : path;
path = path.length() > 0 ? path : "self";
this.mockMvc
.perform(get("/admin/links").accept(MediaType.APPLICATION_JSON))
.perform(get("/admin").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(
jsonPath("$._links.%s.href", path).value(
@ -115,11 +124,6 @@ public class ContextPathHypermediaIntegrationTests {
return resource;
}
public static void main(String[] args) {
new SpringApplicationBuilder(SpringBootHypermediaApplication.class)
.properties("management.contextPath:/admin").run(args);
}
}
}

View File

@ -22,7 +22,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.actuate.autoconfigure.ServerContextPathHypermediaIntegrationTests.SpringBootHypermediaApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.actuate.endpoint.mvc.ActuatorMvcEndpoint;
import org.springframework.boot.test.IntegrationTest;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.boot.test.TestRestTemplate;
@ -43,6 +43,13 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
/**
* Integration tests for {@link ActuatorMvcEndpoint} when a custom server context path has
* been configured.
*
* @author Dave Syer
* @author Andy Wilkinson
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = SpringBootHypermediaApplication.class)
@WebAppConfiguration
@ -54,7 +61,7 @@ public class ServerContextPathHypermediaIntegrationTests {
private int port;
@Test
public void links() throws Exception {
public void linksAddedToHomePage() throws Exception {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
ResponseEntity<String> entity = new TestRestTemplate().exchange(
@ -66,16 +73,28 @@ public class ServerContextPathHypermediaIntegrationTests {
}
@Test
public void browser() throws Exception {
public void actuatorBrowser() throws Exception {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.TEXT_HTML));
ResponseEntity<String> entity = new TestRestTemplate().exchange(
"http://localhost:" + this.port + "/spring/hal/", HttpMethod.GET,
"http://localhost:" + this.port + "/spring/actuator/", HttpMethod.GET,
new HttpEntity<Void>(null, headers), String.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertTrue("Wrong body: " + entity.getBody(), entity.getBody().contains("<title"));
}
@Test
public void actuatorLinks() throws Exception {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
ResponseEntity<String> entity = new TestRestTemplate().exchange(
"http://localhost:" + this.port + "/spring/actuator", HttpMethod.GET,
new HttpEntity<Void>(null, headers), String.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertTrue("Wrong body: " + entity.getBody(),
entity.getBody().contains("\"_links\":"));
}
@MinimalActuatorHypermediaApplication
@RestController
public static class SpringBootHypermediaApplication {
@ -88,11 +107,6 @@ public class ServerContextPathHypermediaIntegrationTests {
return resource;
}
public static void main(String[] args) {
new SpringApplicationBuilder(SpringBootHypermediaApplication.class)
.properties("server.contextPath=/spring").run(args);
}
}
}

View File

@ -22,7 +22,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.actuate.autoconfigure.ServerPortHypermediaIntegrationTests.SpringBootHypermediaApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.actuate.endpoint.mvc.ActuatorMvcEndpoint;
import org.springframework.boot.test.IntegrationTest;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.boot.test.TestRestTemplate;
@ -43,6 +43,13 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
/**
* Integration tests for {@link ActuatorMvcEndpoint} when a custom server port has been
* configured.
*
* @author Dave Syer
* @author Andy Wilkinson
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = SpringBootHypermediaApplication.class)
@WebAppConfiguration
@ -58,11 +65,13 @@ public class ServerPortHypermediaIntegrationTests {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
ResponseEntity<String> entity = new TestRestTemplate().exchange(
"http://localhost:" + this.port + "/links", HttpMethod.GET,
"http://localhost:" + this.port + "/actuator", HttpMethod.GET,
new HttpEntity<Void>(null, headers), String.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertTrue("Wrong body: " + entity.getBody(),
entity.getBody().contains("\"_links\":"));
assertTrue("Wrong body: " + entity.getBody(),
entity.getBody().contains(":" + this.port));
}
@Test
@ -70,7 +79,7 @@ public class ServerPortHypermediaIntegrationTests {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.TEXT_HTML));
ResponseEntity<String> entity = new TestRestTemplate().exchange(
"http://localhost:" + this.port + "/hal/", HttpMethod.GET,
"http://localhost:" + this.port + "/actuator/", HttpMethod.GET,
new HttpEntity<Void>(null, headers), String.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertTrue("Wrong body: " + entity.getBody(), entity.getBody().contains("<title"));
@ -88,11 +97,6 @@ public class ServerPortHypermediaIntegrationTests {
return resource;
}
public static void main(String[] args) {
new SpringApplicationBuilder(SpringBootHypermediaApplication.class)
.properties("management.port:9000").run(args);
}
}
}

View File

@ -20,8 +20,8 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.actuate.autoconfigure.VanillaHypermediaIntegrationTests.SpringBootHypermediaApplication;
import org.springframework.boot.actuate.endpoint.mvc.ActuatorMvcEndpoint;
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint;
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoints;
import org.springframework.boot.test.SpringApplicationConfiguration;
@ -41,6 +41,12 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Integration tests for {@link ActuatorMvcEndpoint}
*
* @author Dave Syer
* @author Andy Wilkinson
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = SpringBootHypermediaApplication.class)
@WebAppConfiguration
@ -62,7 +68,7 @@ public class VanillaHypermediaIntegrationTests {
@Test
public void links() throws Exception {
this.mockMvc.perform(get("/links").accept(MediaType.APPLICATION_JSON))
this.mockMvc.perform(get("/actuator").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()).andExpect(jsonPath("$._links").exists())
.andExpect(header().doesNotExist("cache-control"));
}
@ -70,9 +76,9 @@ public class VanillaHypermediaIntegrationTests {
@Test
public void browser() throws Exception {
MvcResult response = this.mockMvc
.perform(get("/hal/").accept(MediaType.TEXT_HTML))
.perform(get("/actuator/").accept(MediaType.TEXT_HTML))
.andExpect(status().isOk()).andReturn();
assertEquals("/hal/browser.html", response.getResponse().getForwardedUrl());
assertEquals("/actuator/browser.html", response.getResponse().getForwardedUrl());
}
@Test
@ -95,11 +101,11 @@ public class VanillaHypermediaIntegrationTests {
public void endpointsAllListed() throws Exception {
for (MvcEndpoint endpoint : this.mvcEndpoints.getEndpoints()) {
String path = endpoint.getPath();
if ("/links".equals(path)) {
if ("/actuator".equals(path)) {
continue;
}
path = path.startsWith("/") ? path.substring(1) : path;
this.mockMvc.perform(get("/links").accept(MediaType.APPLICATION_JSON))
this.mockMvc.perform(get("/actuator").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$._links.%s.href", path).exists());
}
@ -109,9 +115,6 @@ public class VanillaHypermediaIntegrationTests {
public void endpointsEachHaveSelf() throws Exception {
for (MvcEndpoint endpoint : this.mvcEndpoints.getEndpoints()) {
String path = endpoint.getPath();
if ("/hal".equals(path)) {
continue;
}
path = path.length() > 0 ? path : "/";
this.mockMvc
.perform(get(path).accept(MediaType.APPLICATION_JSON))
@ -126,10 +129,6 @@ public class VanillaHypermediaIntegrationTests {
@Configuration
public static class SpringBootHypermediaApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootHypermediaApplication.class, args);
}
}
}

View File

@ -699,6 +699,9 @@ content into your application; rather pick only the properties that you need.
endpoints.trace.enabled=true
# HYPERMEDIA ENDPOINTS
endpoints.actuator.enabled=true
endpoints.actuator.path=/actuator
endpoints.actuator.sensitive=false
endpoints.docs.curies.enabled=false
endpoints.docs.enabled=true
endpoints.docs.path=/docs
@ -709,9 +712,6 @@ content into your application; rather pick only the properties that you need.
endpoints.hal.enabled=true
endpoints.hal.path= # Redirects root HTML traffic to the HAL browser
endpoints.hal.sensitive=false
endpoints.links.enabled=true
endpoints.links.path=/links
endpoints.links.sensitive=false
endpoints.liquibase.enabled=true
endpoints.liquibase.id=liquibase
endpoints.liquibase.sensitive=false

View File

@ -67,6 +67,10 @@ The following endpoints are available:
|===
| ID | Description | Sensitive
|`actuator`
|Provides a hypermedia-based "`discovery page`" for the other endpoints. Requires Spring
HATEOAS to be on the classpath.
|`autoconfig`
|Displays an auto-configuration report showing all auto-configuration candidates and the
reason why they '`were`' or '`were not`' applied.
@ -169,18 +173,19 @@ If http://projects.spring.io/spring-hateoas[Spring HATEOAS] is on the classpath
through the `spring-boot-starter-hateoas` or if you are using
http://projects.spring.io/spring-data-rest[Spring Data REST]) then the HTTP endpoints
from the Actuator are enhanced with hypermedia links, and a "`discovery page`" is added
with links to all the endpoints. The "`discovery page`" is actually an endpoint itself,
so it can be disabled along with the rest of the hypermedia by setting
`endpoints.links.enabled=false`. If it is not explicitly disabled the links
endpoint renders a JSON object with a link for each other endpoint on `/links`. If Spring
Data REST is used, the root endpoint is enhanced with an extra `actuator` links that
points to the "`discovery page`".
with links to all the endpoints. The "`discovery page`" is available on `/actuator` by
default. It is implemented as an endpoint, allowing properties to be used to configure
its path (`endpoints.actuator.path`) and whether or not it is enabled
(`endpoints.actuator.enabled`).
When a custom management context path is configured, the "`discovery page`" will
automatically move from `/actuator` to the root of the management context. For example,
if the management context path is `/management` then the discovery page will be available
from `/management`.
If the https://github.com/mikekelly/hal-browser[HAL Browser] is on the classpath
via its webjar (`org.webjars:hal-browser`), or via the `spring-data-rest-hal-browser` then
the default home page for HTML clients will be the HAL Browser. This is also exposed via
an endpoint ("`hal`") so it can be disabled and have its path explicitly configured like
the other endpoints.
an HTML "`discovery page`", in the form of the HAL Browser, is also provided.