Improve actuator endpoint mapping debug logging
Closes gh-14292
This commit is contained in:
parent
8cdfb6c70b
commit
8e86bcafc1
|
|
@ -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;
|
||||||
*
|
*
|
||||||
* @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
|
||||||
}
|
}
|
||||||
|
|
||||||
@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);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return links.entrySet().stream()
|
|
||||||
.filter((entry) -> entry.getKey().equals("self")
|
private Map<String, Link> getAccessibleLinks(AccessLevel accessLevel,
|
||||||
|| accessLevel.isAccessAllowed(entry.getKey()))
|
Map<String, Link> links) {
|
||||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Actuator root web endpoint";
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
@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);
|
|
||||||
if (!securityResponse.getStatus().equals(HttpStatus.OK)) {
|
|
||||||
sendFailureResponse(response, securityResponse);
|
|
||||||
}
|
|
||||||
AccessLevel accessLevel = (AccessLevel) request
|
|
||||||
.getAttribute(AccessLevel.REQUEST_ATTRIBUTE);
|
|
||||||
Map<String, Link> links = 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendFailureResponse(HttpServletResponse response,
|
class CloudFoundryLinksHandler implements LinksHandler {
|
||||||
SecurityResponse securityResponse) {
|
|
||||||
try {
|
@Override
|
||||||
response.sendError(securityResponse.getStatus().value(),
|
@ResponseBody
|
||||||
securityResponse.getMessage());
|
public Map<String, Map<String, Link>> links(HttpServletRequest request,
|
||||||
|
HttpServletResponse response) {
|
||||||
|
SecurityResponse securityResponse = CloudFoundryWebEndpointServletHandlerMapping.this.securityInterceptor
|
||||||
|
.preHandle(request, null);
|
||||||
|
if (!securityResponse.getStatus().equals(HttpStatus.OK)) {
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
catch (Exception ex) {
|
|
||||||
this.logger.debug("Failed to send error response", ex);
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Actuator root web endpoint";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
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;
|
||||||
* @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
|
||||||
|
|
||||||
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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
|
||||||
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
|
||||||
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
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
|
||||||
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
|
||||||
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
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
*
|
*
|
||||||
* @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
|
||||||
}
|
}
|
||||||
|
|
||||||
@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;
|
||||||
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
|
||||||
* @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
|
||||||
|
|
||||||
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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
|
||||||
|
|
||||||
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
|
||||||
* @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
|
||||||
.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
|
||||||
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
|
||||||
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
|
||||||
*/
|
*/
|
||||||
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
|
||||||
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
@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 {
|
||||||
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 {
|
||||||
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
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue