Commit 8e86bcaf authored by Brian Clozel's avatar Brian Clozel

Improve actuator endpoint mapping debug logging

Closes gh-14292
parent 8cdfb6c7
...@@ -38,7 +38,6 @@ import org.springframework.boot.actuate.endpoint.web.reactive.AbstractWebFluxEnd ...@@ -38,7 +38,6 @@ import org.springframework.boot.actuate.endpoint.web.reactive.AbstractWebFluxEnd
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.reactive.result.method.RequestMappingInfoHandlerMapping; import org.springframework.web.reactive.result.method.RequestMappingInfoHandlerMapping;
import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.ServerWebExchange;
...@@ -49,6 +48,7 @@ import org.springframework.web.server.ServerWebExchange; ...@@ -49,6 +48,7 @@ import org.springframework.web.server.ServerWebExchange;
* *
* @author Madhura Bhave * @author Madhura Bhave
* @author Phillip Webb * @author Phillip Webb
* @author Brian Clozel
*/ */
class CloudFoundryWebFluxEndpointHandlerMapping class CloudFoundryWebFluxEndpointHandlerMapping
extends AbstractWebFluxEndpointHandlerMapping { extends AbstractWebFluxEndpointHandlerMapping {
...@@ -75,34 +75,47 @@ class CloudFoundryWebFluxEndpointHandlerMapping ...@@ -75,34 +75,47 @@ class CloudFoundryWebFluxEndpointHandlerMapping
} }
@Override @Override
@ResponseBody protected LinksHandler getLinksHandler() {
protected Publisher<ResponseEntity<Object>> links(ServerWebExchange exchange) { return new CloudFoundryLinksHandler();
ServerHttpRequest request = exchange.getRequest();
return this.securityInterceptor.preHandle(exchange, "")
.map((securityResponse) -> {
if (!securityResponse.getStatus().equals(HttpStatus.OK)) {
return new ResponseEntity<>(securityResponse.getStatus());
}
AccessLevel accessLevel = exchange
.getAttribute(AccessLevel.REQUEST_ATTRIBUTE);
Map<String, Link> links = this.linksResolver
.resolveLinks(request.getURI().toString());
return new ResponseEntity<>(
Collections.singletonMap("_links",
getAccessibleLinks(accessLevel, links)),
HttpStatus.OK);
});
} }
private Map<String, Link> getAccessibleLinks(AccessLevel accessLevel, class CloudFoundryLinksHandler implements LinksHandler {
Map<String, Link> links) {
if (accessLevel == null) { @Override
return new LinkedHashMap<>(); public Publisher<ResponseEntity<Object>> links(ServerWebExchange exchange) {
ServerHttpRequest request = exchange.getRequest();
return CloudFoundryWebFluxEndpointHandlerMapping.this.securityInterceptor
.preHandle(exchange, "").map((securityResponse) -> {
if (!securityResponse.getStatus().equals(HttpStatus.OK)) {
return new ResponseEntity<>(securityResponse.getStatus());
}
AccessLevel accessLevel = exchange
.getAttribute(AccessLevel.REQUEST_ATTRIBUTE);
Map<String, Link> links = CloudFoundryWebFluxEndpointHandlerMapping.this.linksResolver
.resolveLinks(request.getURI().toString());
return new ResponseEntity<>(
Collections.singletonMap("_links",
getAccessibleLinks(accessLevel, links)),
HttpStatus.OK);
});
}
private Map<String, Link> getAccessibleLinks(AccessLevel accessLevel,
Map<String, Link> links) {
if (accessLevel == null) {
return new LinkedHashMap<>();
}
return links.entrySet().stream()
.filter((entry) -> entry.getKey().equals("self")
|| accessLevel.isAccessAllowed(entry.getKey()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
} }
return links.entrySet().stream()
.filter((entry) -> entry.getKey().equals("self") @Override
|| accessLevel.isAccessAllowed(entry.getKey())) public String toString() {
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); return "Actuator root web endpoint";
}
} }
/** /**
......
...@@ -47,6 +47,7 @@ import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMappi ...@@ -47,6 +47,7 @@ import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMappi
* *
* @author Madhura Bhave * @author Madhura Bhave
* @author Phillip Webb * @author Phillip Webb
* @author Brian Clozel
*/ */
class CloudFoundryWebEndpointServletHandlerMapping class CloudFoundryWebEndpointServletHandlerMapping
extends AbstractWebMvcEndpointHandlerMapping { extends AbstractWebMvcEndpointHandlerMapping {
...@@ -73,38 +74,52 @@ class CloudFoundryWebEndpointServletHandlerMapping ...@@ -73,38 +74,52 @@ class CloudFoundryWebEndpointServletHandlerMapping
} }
@Override @Override
@ResponseBody protected LinksHandler getLinksHandler() {
protected Map<String, Map<String, Link>> links(HttpServletRequest request, return new CloudFoundryLinksHandler();
HttpServletResponse response) { }
SecurityResponse securityResponse = this.securityInterceptor.preHandle(request,
null); class CloudFoundryLinksHandler implements LinksHandler {
if (!securityResponse.getStatus().equals(HttpStatus.OK)) {
sendFailureResponse(response, securityResponse); @Override
} @ResponseBody
AccessLevel accessLevel = (AccessLevel) request public Map<String, Map<String, Link>> links(HttpServletRequest request,
.getAttribute(AccessLevel.REQUEST_ATTRIBUTE); HttpServletResponse response) {
Map<String, Link> links = this.linksResolver SecurityResponse securityResponse = CloudFoundryWebEndpointServletHandlerMapping.this.securityInterceptor
.resolveLinks(request.getRequestURL().toString()); .preHandle(request, null);
Map<String, Link> filteredLinks = new LinkedHashMap<>(); if (!securityResponse.getStatus().equals(HttpStatus.OK)) {
if (accessLevel == null) { sendFailureResponse(response, securityResponse);
}
AccessLevel accessLevel = (AccessLevel) request
.getAttribute(AccessLevel.REQUEST_ATTRIBUTE);
Map<String, Link> links = CloudFoundryWebEndpointServletHandlerMapping.this.linksResolver
.resolveLinks(request.getRequestURL().toString());
Map<String, Link> filteredLinks = new LinkedHashMap<>();
if (accessLevel == null) {
return Collections.singletonMap("_links", filteredLinks);
}
filteredLinks = links.entrySet().stream()
.filter((e) -> e.getKey().equals("self")
|| accessLevel.isAccessAllowed(e.getKey()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
return Collections.singletonMap("_links", filteredLinks); return Collections.singletonMap("_links", filteredLinks);
} }
filteredLinks = links.entrySet().stream()
.filter((e) -> e.getKey().equals("self")
|| accessLevel.isAccessAllowed(e.getKey()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
return Collections.singletonMap("_links", filteredLinks);
}
private void sendFailureResponse(HttpServletResponse response, @Override
SecurityResponse securityResponse) { public String toString() {
try { return "Actuator root web endpoint";
response.sendError(securityResponse.getStatus().value(),
securityResponse.getMessage());
} }
catch (Exception ex) {
this.logger.debug("Failed to send error response", ex); private void sendFailureResponse(HttpServletResponse response,
SecurityResponse securityResponse) {
try {
response.sendError(securityResponse.getStatus().value(),
securityResponse.getMessage());
}
catch (Exception ex) {
logger.debug("Failed to send error response", ex);
}
} }
} }
/** /**
......
...@@ -55,6 +55,7 @@ import org.springframework.web.bind.annotation.RequestBody; ...@@ -55,6 +55,7 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.reactive.HandlerMapping; import org.springframework.web.reactive.HandlerMapping;
import org.springframework.web.reactive.result.condition.ConsumesRequestCondition; import org.springframework.web.reactive.result.condition.ConsumesRequestCondition;
import org.springframework.web.reactive.result.condition.PatternsRequestCondition; import org.springframework.web.reactive.result.condition.PatternsRequestCondition;
...@@ -73,6 +74,7 @@ import org.springframework.web.util.pattern.PathPatternParser; ...@@ -73,6 +74,7 @@ import org.springframework.web.util.pattern.PathPatternParser;
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Madhura Bhave * @author Madhura Bhave
* @author Phillip Webb * @author Phillip Webb
* @author Brian Clozel
* @since 2.0.0 * @since 2.0.0
*/ */
public abstract class AbstractWebFluxEndpointHandlerMapping public abstract class AbstractWebFluxEndpointHandlerMapping
...@@ -88,9 +90,6 @@ public abstract class AbstractWebFluxEndpointHandlerMapping ...@@ -88,9 +90,6 @@ public abstract class AbstractWebFluxEndpointHandlerMapping
private final CorsConfiguration corsConfiguration; private final CorsConfiguration corsConfiguration;
private final Method linksMethod = ReflectionUtils.findMethod(getClass(), "links",
ServerWebExchange.class);
private final Method handleWriteMethod = ReflectionUtils.findMethod( private final Method handleWriteMethod = ReflectionUtils.findMethod(
WriteOperationHandler.class, "handle", ServerWebExchange.class, Map.class); WriteOperationHandler.class, "handle", ServerWebExchange.class, Map.class);
...@@ -127,14 +126,17 @@ public abstract class AbstractWebFluxEndpointHandlerMapping ...@@ -127,14 +126,17 @@ public abstract class AbstractWebFluxEndpointHandlerMapping
} }
} }
@Override
protected HandlerMethod createHandlerMethod(Object handler, Method method) {
HandlerMethod handlerMethod = super.createHandlerMethod(handler, method);
return new WebFluxEndpointHandlerMethod(handlerMethod.getBean(),
handlerMethod.getMethod());
}
private void registerMappingForOperation(ExposableWebEndpoint endpoint, private void registerMappingForOperation(ExposableWebEndpoint endpoint,
WebOperation operation) { WebOperation operation) {
OperationInvoker invoker = operation::invoke;
if (operation.isBlocking()) {
invoker = new ElasticSchedulerInvoker(invoker);
}
ReactiveWebOperation reactiveWebOperation = wrapReactiveWebOperation(endpoint, ReactiveWebOperation reactiveWebOperation = wrapReactiveWebOperation(endpoint,
operation, new ReactiveWebOperationAdapter(invoker)); operation, new ReactiveWebOperationAdapter(operation));
if (operation.getType() == OperationType.WRITE) { if (operation.getType() == OperationType.WRITE) {
registerMapping(createRequestMappingInfo(operation), registerMapping(createRequestMappingInfo(operation),
new WriteOperationHandler((reactiveWebOperation)), new WriteOperationHandler((reactiveWebOperation)),
...@@ -183,7 +185,9 @@ public abstract class AbstractWebFluxEndpointHandlerMapping ...@@ -183,7 +185,9 @@ public abstract class AbstractWebFluxEndpointHandlerMapping
StringUtils.toStringArray(this.endpointMediaTypes.getProduced())); StringUtils.toStringArray(this.endpointMediaTypes.getProduced()));
RequestMappingInfo mapping = new RequestMappingInfo(patterns, methods, null, null, RequestMappingInfo mapping = new RequestMappingInfo(patterns, methods, null, null,
null, produces, null); null, produces, null);
registerMapping(mapping, this, this.linksMethod); LinksHandler linksHandler = getLinksHandler();
registerMapping(mapping, linksHandler, ReflectionUtils
.findMethod(linksHandler.getClass(), "links", ServerWebExchange.class));
} }
@Override @Override
...@@ -203,7 +207,11 @@ public abstract class AbstractWebFluxEndpointHandlerMapping ...@@ -203,7 +207,11 @@ public abstract class AbstractWebFluxEndpointHandlerMapping
return null; return null;
} }
protected abstract Object links(ServerWebExchange exchange); /**
* Return the Handler providing actuator links at the root endpoint.
* @return the links handler
*/
protected abstract LinksHandler getLinksHandler();
/** /**
* Return the web endpoints being mapped. * Return the web endpoints being mapped.
...@@ -243,6 +251,16 @@ public abstract class AbstractWebFluxEndpointHandlerMapping ...@@ -243,6 +251,16 @@ public abstract class AbstractWebFluxEndpointHandlerMapping
} }
/**
* Reactive handler providing actuator links at the root endpoint.
*/
@FunctionalInterface
protected interface LinksHandler {
Object links(ServerWebExchange exchange);
}
/** /**
* A reactive web operation that can be handled by WebFlux. * A reactive web operation that can be handled by WebFlux.
*/ */
...@@ -263,13 +281,24 @@ public abstract class AbstractWebFluxEndpointHandlerMapping ...@@ -263,13 +281,24 @@ public abstract class AbstractWebFluxEndpointHandlerMapping
private final OperationInvoker invoker; private final OperationInvoker invoker;
private final String operationId;
private final Supplier<Mono<? extends SecurityContext>> securityContextSupplier; private final Supplier<Mono<? extends SecurityContext>> securityContextSupplier;
private ReactiveWebOperationAdapter(OperationInvoker invoker) { private ReactiveWebOperationAdapter(WebOperation operation) {
this.invoker = invoker; this.invoker = getInvoker(operation);
this.operationId = operation.getId();
this.securityContextSupplier = getSecurityContextSupplier(); this.securityContextSupplier = getSecurityContextSupplier();
} }
private OperationInvoker getInvoker(WebOperation operation) {
OperationInvoker invoker = operation::invoke;
if (operation.isBlocking()) {
invoker = new ElasticSchedulerInvoker(invoker);
}
return invoker;
}
private Supplier<Mono<? extends SecurityContext>> getSecurityContextSupplier() { private Supplier<Mono<? extends SecurityContext>> getSecurityContextSupplier() {
if (ClassUtils.isPresent( if (ClassUtils.isPresent(
"org.springframework.security.core.context.ReactiveSecurityContextHolder", "org.springframework.security.core.context.ReactiveSecurityContextHolder",
...@@ -337,6 +366,11 @@ public abstract class AbstractWebFluxEndpointHandlerMapping ...@@ -337,6 +366,11 @@ public abstract class AbstractWebFluxEndpointHandlerMapping
HttpStatus.valueOf(webEndpointResponse.getStatus())); HttpStatus.valueOf(webEndpointResponse.getStatus()));
} }
@Override
public String toString() {
return "Actuator web endpoint '" + this.operationId + "'";
}
} }
/** /**
...@@ -376,6 +410,26 @@ public abstract class AbstractWebFluxEndpointHandlerMapping ...@@ -376,6 +410,26 @@ public abstract class AbstractWebFluxEndpointHandlerMapping
} }
private static class WebFluxEndpointHandlerMethod extends HandlerMethod {
WebFluxEndpointHandlerMethod(Object bean, Method method) {
super(bean, method);
}
@Override
public String toString() {
return getBean().toString();
}
@Override
public HandlerMethod createWithResolvedBean() {
HandlerMethod handlerMethod = super.createWithResolvedBean();
return new WebFluxEndpointHandlerMethod(handlerMethod.getBean(),
handlerMethod.getMethod());
}
}
private static final class ReactiveSecurityContext implements SecurityContext { private static final class ReactiveSecurityContext implements SecurityContext {
private final RoleVoter roleVoter = new RoleVoter(); private final RoleVoter roleVoter = new RoleVoter();
......
...@@ -38,6 +38,7 @@ import org.springframework.web.util.UriComponentsBuilder; ...@@ -38,6 +38,7 @@ import org.springframework.web.util.UriComponentsBuilder;
* *
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Phillip Webb * @author Phillip Webb
* @author Brian Clozel
* @since 2.0.0 * @since 2.0.0
*/ */
public class WebFluxEndpointHandlerMapping extends AbstractWebFluxEndpointHandlerMapping public class WebFluxEndpointHandlerMapping extends AbstractWebFluxEndpointHandlerMapping
...@@ -64,12 +65,31 @@ public class WebFluxEndpointHandlerMapping extends AbstractWebFluxEndpointHandle ...@@ -64,12 +65,31 @@ public class WebFluxEndpointHandlerMapping extends AbstractWebFluxEndpointHandle
} }
@Override @Override
@ResponseBody protected LinksHandler getLinksHandler() {
protected Map<String, Map<String, Link>> links(ServerWebExchange exchange) { return new WebFluxLinksHandler();
String requestUri = UriComponentsBuilder.fromUri(exchange.getRequest().getURI()) }
.replaceQuery(null).toUriString();
return Collections.singletonMap("_links", /**
this.linksResolver.resolveLinks(requestUri)); * Handler for root endpoint providing links.
*/
class WebFluxLinksHandler implements LinksHandler {
@Override
@ResponseBody
public Map<String, Map<String, Link>> links(ServerWebExchange exchange) {
String requestUri = UriComponentsBuilder
.fromUri(exchange.getRequest().getURI()).replaceQuery(null)
.toUriString();
return Collections.singletonMap("_links",
WebFluxEndpointHandlerMapping.this.linksResolver
.resolveLinks(requestUri));
}
@Override
public String toString() {
return "Actuator root web endpoint";
}
} }
} }
...@@ -49,6 +49,7 @@ import org.springframework.web.bind.annotation.RequestMethod; ...@@ -49,6 +49,7 @@ import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerMapping; import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.handler.MatchableHandlerMapping; import org.springframework.web.servlet.handler.MatchableHandlerMapping;
import org.springframework.web.servlet.handler.RequestMatchResult; import org.springframework.web.servlet.handler.RequestMatchResult;
...@@ -66,6 +67,7 @@ import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMappi ...@@ -66,6 +67,7 @@ import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMappi
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Madhura Bhave * @author Madhura Bhave
* @author Phillip Webb * @author Phillip Webb
* @author Brian Clozel
* @since 2.0.0 * @since 2.0.0
*/ */
public abstract class AbstractWebMvcEndpointHandlerMapping public abstract class AbstractWebMvcEndpointHandlerMapping
...@@ -80,9 +82,6 @@ public abstract class AbstractWebMvcEndpointHandlerMapping ...@@ -80,9 +82,6 @@ public abstract class AbstractWebMvcEndpointHandlerMapping
private final CorsConfiguration corsConfiguration; private final CorsConfiguration corsConfiguration;
private final Method linksMethod = ReflectionUtils.findMethod(getClass(), "links",
HttpServletRequest.class, HttpServletResponse.class);
private final Method handleMethod = ReflectionUtils.findMethod(OperationHandler.class, private final Method handleMethod = ReflectionUtils.findMethod(OperationHandler.class,
"handle", HttpServletRequest.class, Map.class); "handle", HttpServletRequest.class, Map.class);
...@@ -131,6 +130,13 @@ public abstract class AbstractWebMvcEndpointHandlerMapping ...@@ -131,6 +130,13 @@ public abstract class AbstractWebMvcEndpointHandlerMapping
} }
} }
@Override
protected HandlerMethod createHandlerMethod(Object handler, Method method) {
HandlerMethod handlerMethod = super.createHandlerMethod(handler, method);
return new WebMvcEndpointHandlerMethod(handlerMethod.getBean(),
handlerMethod.getMethod());
}
@Override @Override
public RequestMatchResult match(HttpServletRequest request, String pattern) { public RequestMatchResult match(HttpServletRequest request, String pattern) {
RequestMappingInfo info = RequestMappingInfo.paths(pattern).options(builderConfig) RequestMappingInfo info = RequestMappingInfo.paths(pattern).options(builderConfig)
...@@ -156,9 +162,8 @@ public abstract class AbstractWebMvcEndpointHandlerMapping ...@@ -156,9 +162,8 @@ public abstract class AbstractWebMvcEndpointHandlerMapping
private void registerMappingForOperation(ExposableWebEndpoint endpoint, private void registerMappingForOperation(ExposableWebEndpoint endpoint,
WebOperation operation) { WebOperation operation) {
OperationInvoker invoker = operation::invoke;
ServletWebOperation servletWebOperation = wrapServletWebOperation(endpoint, ServletWebOperation servletWebOperation = wrapServletWebOperation(endpoint,
operation, new ServletWebOperationAdapter(invoker)); operation, new ServletWebOperationAdapter(operation));
registerMapping(createRequestMappingInfo(operation), registerMapping(createRequestMappingInfo(operation),
new OperationHandler(servletWebOperation), this.handleMethod); new OperationHandler(servletWebOperation), this.handleMethod);
} }
...@@ -171,7 +176,6 @@ public abstract class AbstractWebMvcEndpointHandlerMapping ...@@ -171,7 +176,6 @@ public abstract class AbstractWebMvcEndpointHandlerMapping
* @param servletWebOperation the servlet web operation to wrap * @param servletWebOperation the servlet web operation to wrap
* @return a wrapped servlet web operation * @return a wrapped servlet web operation
*/ */
protected ServletWebOperation wrapServletWebOperation(ExposableWebEndpoint endpoint, protected ServletWebOperation wrapServletWebOperation(ExposableWebEndpoint endpoint,
WebOperation operation, ServletWebOperation servletWebOperation) { WebOperation operation, ServletWebOperation servletWebOperation) {
return servletWebOperation; return servletWebOperation;
...@@ -200,7 +204,10 @@ public abstract class AbstractWebMvcEndpointHandlerMapping ...@@ -200,7 +204,10 @@ public abstract class AbstractWebMvcEndpointHandlerMapping
.toStringArray(this.endpointMediaTypes.getProduced()))); .toStringArray(this.endpointMediaTypes.getProduced())));
RequestMappingInfo mapping = new RequestMappingInfo(patterns, methods, null, null, RequestMappingInfo mapping = new RequestMappingInfo(patterns, methods, null, null,
null, produces, null); null, produces, null);
registerMapping(mapping, this, this.linksMethod); LinksHandler linksHandler = getLinksHandler();
registerMapping(mapping, linksHandler,
ReflectionUtils.findMethod(linksHandler.getClass(), "links",
HttpServletRequest.class, HttpServletResponse.class));
} }
private PatternsRequestCondition patternsRequestConditionForPattern(String path) { private PatternsRequestCondition patternsRequestConditionForPattern(String path) {
...@@ -232,8 +239,11 @@ public abstract class AbstractWebMvcEndpointHandlerMapping ...@@ -232,8 +239,11 @@ public abstract class AbstractWebMvcEndpointHandlerMapping
interceptors.add(new SkipPathExtensionContentNegotiation()); interceptors.add(new SkipPathExtensionContentNegotiation());
} }
protected abstract Object links(HttpServletRequest request, /**
HttpServletResponse response); * Return the Handler providing actuator links at the root endpoint.
* @return the links handler
*/
protected abstract LinksHandler getLinksHandler();
/** /**
* Return the web endpoints being mapped. * Return the web endpoints being mapped.
...@@ -243,6 +253,16 @@ public abstract class AbstractWebMvcEndpointHandlerMapping ...@@ -243,6 +253,16 @@ public abstract class AbstractWebMvcEndpointHandlerMapping
return this.endpoints; return this.endpoints;
} }
/**
* Handler providing actuator links at the root endpoint.
*/
@FunctionalInterface
protected interface LinksHandler {
Object links(HttpServletRequest request, HttpServletResponse response);
}
/** /**
* A servlet web operation that can be handled by Spring MVC. * A servlet web operation that can be handled by Spring MVC.
*/ */
...@@ -259,10 +279,10 @@ public abstract class AbstractWebMvcEndpointHandlerMapping ...@@ -259,10 +279,10 @@ public abstract class AbstractWebMvcEndpointHandlerMapping
*/ */
private class ServletWebOperationAdapter implements ServletWebOperation { private class ServletWebOperationAdapter implements ServletWebOperation {
private final OperationInvoker invoker; private final WebOperation operation;
ServletWebOperationAdapter(OperationInvoker invoker) { ServletWebOperationAdapter(WebOperation operation) {
this.invoker = invoker; this.operation = operation;
} }
@Override @Override
...@@ -271,7 +291,7 @@ public abstract class AbstractWebMvcEndpointHandlerMapping ...@@ -271,7 +291,7 @@ public abstract class AbstractWebMvcEndpointHandlerMapping
Map<String, Object> arguments = getArguments(request, body); Map<String, Object> arguments = getArguments(request, body);
try { try {
return handleResult( return handleResult(
this.invoker.invoke(new InvocationContext( this.operation.invoke(new InvocationContext(
new ServletSecurityContext(request), arguments)), new ServletSecurityContext(request), arguments)),
HttpMethod.valueOf(request.getMethod())); HttpMethod.valueOf(request.getMethod()));
} }
...@@ -280,6 +300,11 @@ public abstract class AbstractWebMvcEndpointHandlerMapping ...@@ -280,6 +300,11 @@ public abstract class AbstractWebMvcEndpointHandlerMapping
} }
} }
@Override
public String toString() {
return "Actuator web endpoint '" + this.operation.getId() + "'";
}
private Map<String, Object> getArguments(HttpServletRequest request, private Map<String, Object> getArguments(HttpServletRequest request,
Map<String, String> body) { Map<String, String> body) {
Map<String, Object> arguments = new LinkedHashMap<>(); Map<String, Object> arguments = new LinkedHashMap<>();
...@@ -330,6 +355,32 @@ public abstract class AbstractWebMvcEndpointHandlerMapping ...@@ -330,6 +355,32 @@ public abstract class AbstractWebMvcEndpointHandlerMapping
return this.operation.handle(request, body); return this.operation.handle(request, body);
} }
@Override
public String toString() {
return this.operation.toString();
}
}
/**
* {@link HandlerMethod} subclass for endpoint information logging.
*/
private static class WebMvcEndpointHandlerMethod extends HandlerMethod {
WebMvcEndpointHandlerMethod(Object bean, Method method) {
super(bean, method);
}
@Override
public String toString() {
return getBean().toString();
}
@Override
public HandlerMethod createWithResolvedBean() {
return this;
}
} }
@ResponseStatus(code = HttpStatus.BAD_REQUEST) @ResponseStatus(code = HttpStatus.BAD_REQUEST)
......
...@@ -63,11 +63,29 @@ public class WebMvcEndpointHandlerMapping extends AbstractWebMvcEndpointHandlerM ...@@ -63,11 +63,29 @@ public class WebMvcEndpointHandlerMapping extends AbstractWebMvcEndpointHandlerM
} }
@Override @Override
@ResponseBody protected LinksHandler getLinksHandler() {
protected Map<String, Map<String, Link>> links(HttpServletRequest request, return new WebMvcLinksHandler();
HttpServletResponse response) { }
return Collections.singletonMap("_links",
this.linksResolver.resolveLinks(request.getRequestURL().toString())); /**
* Handler for root endpoint providing links.
*/
class WebMvcLinksHandler implements LinksHandler {
@Override
@ResponseBody
public Map<String, Map<String, Link>> links(HttpServletRequest request,
HttpServletResponse response) {
return Collections.singletonMap("_links",
WebMvcEndpointHandlerMapping.this.linksResolver
.resolveLinks(request.getRequestURL().toString()));
}
@Override
public String toString() {
return "Actuator root web endpoint";
}
} }
} }
...@@ -130,6 +130,7 @@ public class LoggingApplicationListener implements GenericApplicationListener { ...@@ -130,6 +130,7 @@ public class LoggingApplicationListener implements GenericApplicationListener {
loggers.add("web", "org.springframework.core.codec"); loggers.add("web", "org.springframework.core.codec");
loggers.add("web", "org.springframework.http"); loggers.add("web", "org.springframework.http");
loggers.add("web", "org.springframework.web"); loggers.add("web", "org.springframework.web");
loggers.add("web", "org.springframework.boot.actuate.endpoint.web");
loggers.add("sql", "org.springframework.jdbc.core"); loggers.add("sql", "org.springframework.jdbc.core");
loggers.add("sql", "org.hibernate.SQL"); loggers.add("sql", "org.hibernate.SQL");
DEFAULT_GROUP_LOGGERS = Collections.unmodifiableMap(loggers); DEFAULT_GROUP_LOGGERS = Collections.unmodifiableMap(loggers);
......
...@@ -569,6 +569,7 @@ public class LoggingApplicationListenerTests { ...@@ -569,6 +569,7 @@ public class LoggingApplicationListenerTests {
assertTraceEnabled("org.springframework.core.codec", true); assertTraceEnabled("org.springframework.core.codec", true);
assertTraceEnabled("org.springframework.http", true); assertTraceEnabled("org.springframework.http", true);
assertTraceEnabled("org.springframework.web", true); assertTraceEnabled("org.springframework.web", true);
assertTraceEnabled("org.springframework.boot.actuate.endpoint.web", true);
} }
@Test @Test
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment