Allow health groups to be configured at an additional path
Closes gh-25471 Co-authored-by: Phillip Webb <pwebb@vmware.com>
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2012-2021 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
|
||||
*
|
||||
* https://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.web;
|
||||
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Enumeration of server namespaces.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Madhura Bhave
|
||||
* @since 2.6.0
|
||||
*/
|
||||
public final class WebServerNamespace {
|
||||
|
||||
/**
|
||||
* {@link WebServerNamespace} that represents the main server.
|
||||
*/
|
||||
public static final WebServerNamespace SERVER = new WebServerNamespace("server");
|
||||
|
||||
/**
|
||||
* {@link WebServerNamespace} that represents the management server.
|
||||
*/
|
||||
public static final WebServerNamespace MANAGEMENT = new WebServerNamespace("management");
|
||||
|
||||
private final String value;
|
||||
|
||||
private WebServerNamespace(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
public static WebServerNamespace from(String value) {
|
||||
if (StringUtils.hasText(value)) {
|
||||
return new WebServerNamespace(value);
|
||||
}
|
||||
return SERVER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null || getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
WebServerNamespace other = (WebServerNamespace) obj;
|
||||
return this.value.equals(other.value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.value.hashCode();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -42,6 +42,7 @@ import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.InvalidEndpointRequestException;
|
||||
import org.springframework.boot.actuate.endpoint.InvocationContext;
|
||||
import org.springframework.boot.actuate.endpoint.OperationArgumentResolver;
|
||||
import org.springframework.boot.actuate.endpoint.ProducibleOperationArgumentResolver;
|
||||
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointLinksResolver;
|
||||
@@ -52,6 +53,7 @@ import org.springframework.boot.actuate.endpoint.web.Link;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebOperation;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebOperationRequestPredicate;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebServerNamespace;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
@@ -91,7 +93,7 @@ public class JerseyEndpointResourceFactory {
|
||||
return resources;
|
||||
}
|
||||
|
||||
private Resource createResource(EndpointMapping endpointMapping, WebOperation operation) {
|
||||
protected Resource createResource(EndpointMapping endpointMapping, WebOperation operation) {
|
||||
WebOperationRequestPredicate requestPredicate = operation.getRequestPredicate();
|
||||
String path = requestPredicate.getPath();
|
||||
String matchAllRemainingPathSegmentsVariable = requestPredicate.getMatchAllRemainingPathSegmentsVariable();
|
||||
@@ -99,11 +101,19 @@ public class JerseyEndpointResourceFactory {
|
||||
path = path.replace("{*" + matchAllRemainingPathSegmentsVariable + "}",
|
||||
"{" + matchAllRemainingPathSegmentsVariable + ": .*}");
|
||||
}
|
||||
Builder resourceBuilder = Resource.builder().path(endpointMapping.createSubPath(path));
|
||||
return getResource(endpointMapping, operation, requestPredicate, path, null, null);
|
||||
}
|
||||
|
||||
protected Resource getResource(EndpointMapping endpointMapping, WebOperation operation,
|
||||
WebOperationRequestPredicate requestPredicate, String path, WebServerNamespace serverNamespace,
|
||||
JerseyRemainingPathSegmentProvider remainingPathSegmentProvider) {
|
||||
Builder resourceBuilder = Resource.builder().path(endpointMapping.getPath())
|
||||
.path(endpointMapping.createSubPath(path));
|
||||
resourceBuilder.addMethod(requestPredicate.getHttpMethod().name())
|
||||
.consumes(StringUtils.toStringArray(requestPredicate.getConsumes()))
|
||||
.produces(StringUtils.toStringArray(requestPredicate.getProduces()))
|
||||
.handledBy(new OperationInflector(operation, !requestPredicate.getConsumes().isEmpty()));
|
||||
.handledBy(new OperationInflector(operation, !requestPredicate.getConsumes().isEmpty(), serverNamespace,
|
||||
remainingPathSegmentProvider));
|
||||
return resourceBuilder.build();
|
||||
}
|
||||
|
||||
@@ -137,9 +147,16 @@ public class JerseyEndpointResourceFactory {
|
||||
|
||||
private final boolean readBody;
|
||||
|
||||
private OperationInflector(WebOperation operation, boolean readBody) {
|
||||
private final WebServerNamespace serverNamespace;
|
||||
|
||||
private final JerseyRemainingPathSegmentProvider remainingPathSegmentProvider;
|
||||
|
||||
private OperationInflector(WebOperation operation, boolean readBody, WebServerNamespace serverNamespace,
|
||||
JerseyRemainingPathSegmentProvider remainingPathSegments) {
|
||||
this.operation = operation;
|
||||
this.readBody = readBody;
|
||||
this.serverNamespace = serverNamespace;
|
||||
this.remainingPathSegmentProvider = remainingPathSegments;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -152,7 +169,10 @@ public class JerseyEndpointResourceFactory {
|
||||
arguments.putAll(extractQueryParameters(data));
|
||||
try {
|
||||
JerseySecurityContext securityContext = new JerseySecurityContext(data.getSecurityContext());
|
||||
OperationArgumentResolver serverNamespaceArgumentResolver = OperationArgumentResolver
|
||||
.of(WebServerNamespace.class, () -> this.serverNamespace);
|
||||
InvocationContext invocationContext = new InvocationContext(securityContext, arguments,
|
||||
serverNamespaceArgumentResolver,
|
||||
new ProducibleOperationArgumentResolver(() -> data.getHeaders().get("Accept")));
|
||||
Object response = this.operation.invoke(invocationContext);
|
||||
return convertToJaxRsResponse(response, data.getRequest().getMethod());
|
||||
@@ -173,12 +193,21 @@ public class JerseyEndpointResourceFactory {
|
||||
String matchAllRemainingPathSegmentsVariable = this.operation.getRequestPredicate()
|
||||
.getMatchAllRemainingPathSegmentsVariable();
|
||||
if (matchAllRemainingPathSegmentsVariable != null) {
|
||||
String remainingPathSegments = (String) pathParameters.get(matchAllRemainingPathSegmentsVariable);
|
||||
String remainingPathSegments = getRemainingPathSegments(requestContext, pathParameters,
|
||||
matchAllRemainingPathSegmentsVariable);
|
||||
pathParameters.put(matchAllRemainingPathSegmentsVariable, tokenizePathSegments(remainingPathSegments));
|
||||
}
|
||||
return pathParameters;
|
||||
}
|
||||
|
||||
private String getRemainingPathSegments(ContainerRequestContext requestContext,
|
||||
Map<String, Object> pathParameters, String matchAllRemainingPathSegmentsVariable) {
|
||||
if (this.remainingPathSegmentProvider != null) {
|
||||
return this.remainingPathSegmentProvider.get(requestContext, matchAllRemainingPathSegmentsVariable);
|
||||
}
|
||||
return (String) pathParameters.get(matchAllRemainingPathSegmentsVariable);
|
||||
}
|
||||
|
||||
private String[] tokenizePathSegments(String path) {
|
||||
String[] segments = StringUtils.tokenizeToStringArray(path, PATH_SEPARATOR, false, true);
|
||||
for (int i = 0; i < segments.length; i++) {
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2012-2021 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
|
||||
*
|
||||
* https://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.web.jersey;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.glassfish.jersey.server.model.Resource;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMapping;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebOperation;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebOperationRequestPredicate;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebServerNamespace;
|
||||
import org.springframework.boot.actuate.health.AdditionalHealthEndpointPath;
|
||||
import org.springframework.boot.actuate.health.HealthEndpointGroup;
|
||||
import org.springframework.boot.actuate.health.HealthEndpointGroups;
|
||||
|
||||
/**
|
||||
* A factory for creating Jersey {@link Resource Resources} for health groups with
|
||||
* additional path.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
* @since 2.6.0
|
||||
*/
|
||||
public class JerseyHealthEndpointAdditionalPathResourceFactory extends JerseyEndpointResourceFactory {
|
||||
|
||||
private final Set<HealthEndpointGroup> groups;
|
||||
|
||||
private final WebServerNamespace serverNamespace;
|
||||
|
||||
public JerseyHealthEndpointAdditionalPathResourceFactory(WebServerNamespace serverNamespace,
|
||||
HealthEndpointGroups groups) {
|
||||
this.serverNamespace = serverNamespace;
|
||||
this.groups = groups.getAllWithAdditionalPath(serverNamespace);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Resource createResource(EndpointMapping endpointMapping, WebOperation operation) {
|
||||
WebOperationRequestPredicate requestPredicate = operation.getRequestPredicate();
|
||||
String matchAllRemainingPathSegmentsVariable = requestPredicate.getMatchAllRemainingPathSegmentsVariable();
|
||||
if (matchAllRemainingPathSegmentsVariable != null) {
|
||||
for (HealthEndpointGroup group : this.groups) {
|
||||
AdditionalHealthEndpointPath additionalPath = group.getAdditionalPath();
|
||||
if (additionalPath != null) {
|
||||
return getResource(endpointMapping, operation, requestPredicate, additionalPath.getValue(),
|
||||
this.serverNamespace, (data, pathSegmentsVariable) -> data.getUriInfo().getPath());
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2012-2021 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
|
||||
*
|
||||
* https://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.web.jersey;
|
||||
|
||||
import javax.ws.rs.container.ContainerRequestContext;
|
||||
|
||||
/**
|
||||
* Strategy interface used to provide the remaining path segments for a Jersey actuator
|
||||
* endpoint.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
interface JerseyRemainingPathSegmentProvider {
|
||||
|
||||
String get(ContainerRequestContext requestContext, String matchAllRemainingPathSegmentsVariable);
|
||||
|
||||
}
|
||||
@@ -31,6 +31,7 @@ import reactor.core.scheduler.Schedulers;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.InvalidEndpointRequestException;
|
||||
import org.springframework.boot.actuate.endpoint.InvocationContext;
|
||||
import org.springframework.boot.actuate.endpoint.OperationArgumentResolver;
|
||||
import org.springframework.boot.actuate.endpoint.OperationType;
|
||||
import org.springframework.boot.actuate.endpoint.ProducibleOperationArgumentResolver;
|
||||
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||
@@ -41,6 +42,8 @@ import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebOperation;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebOperationRequestPredicate;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebServerNamespace;
|
||||
import org.springframework.boot.web.context.WebServerApplicationContext;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
@@ -64,6 +67,7 @@ import org.springframework.web.reactive.result.method.RequestMappingInfo;
|
||||
import org.springframework.web.reactive.result.method.RequestMappingInfoHandlerMapping;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.util.pattern.PathPattern;
|
||||
|
||||
/**
|
||||
* A custom {@link HandlerMapping} that makes web endpoints available over HTTP using
|
||||
@@ -132,18 +136,25 @@ public abstract class AbstractWebFluxEndpointHandlerMapping extends RequestMappi
|
||||
}
|
||||
|
||||
private void registerMappingForOperation(ExposableWebEndpoint endpoint, WebOperation operation) {
|
||||
ReactiveWebOperation reactiveWebOperation = wrapReactiveWebOperation(endpoint, operation,
|
||||
new ReactiveWebOperationAdapter(operation));
|
||||
RequestMappingInfo requestMappingInfo = createRequestMappingInfo(operation);
|
||||
if (operation.getType() == OperationType.WRITE) {
|
||||
registerMapping(createRequestMappingInfo(operation), new WriteOperationHandler((reactiveWebOperation)),
|
||||
ReactiveWebOperation reactiveWebOperation = wrapReactiveWebOperation(endpoint, operation,
|
||||
new ReactiveWebOperationAdapter(operation));
|
||||
registerMapping(requestMappingInfo, new WriteOperationHandler((reactiveWebOperation)),
|
||||
this.handleWriteMethod);
|
||||
}
|
||||
else {
|
||||
registerMapping(createRequestMappingInfo(operation), new ReadOperationHandler((reactiveWebOperation)),
|
||||
this.handleReadMethod);
|
||||
registerReadMapping(requestMappingInfo, endpoint, operation);
|
||||
}
|
||||
}
|
||||
|
||||
protected void registerReadMapping(RequestMappingInfo requestMappingInfo, ExposableWebEndpoint endpoint,
|
||||
WebOperation operation) {
|
||||
ReactiveWebOperation reactiveWebOperation = wrapReactiveWebOperation(endpoint, operation,
|
||||
new ReactiveWebOperationAdapter(operation));
|
||||
registerMapping(requestMappingInfo, new ReadOperationHandler((reactiveWebOperation)), this.handleReadMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook point that allows subclasses to wrap the {@link ReactiveWebOperation} before
|
||||
* it's called. Allows additional features, such as security, to be added.
|
||||
@@ -299,32 +310,25 @@ public abstract class AbstractWebFluxEndpointHandlerMapping extends RequestMappi
|
||||
@Override
|
||||
public Mono<ResponseEntity<Object>> handle(ServerWebExchange exchange, Map<String, String> body) {
|
||||
Map<String, Object> arguments = getArguments(exchange, body);
|
||||
String matchAllRemainingPathSegmentsVariable = this.operation.getRequestPredicate()
|
||||
.getMatchAllRemainingPathSegmentsVariable();
|
||||
if (matchAllRemainingPathSegmentsVariable != null) {
|
||||
arguments.put(matchAllRemainingPathSegmentsVariable,
|
||||
tokenizePathSegments((String) arguments.get(matchAllRemainingPathSegmentsVariable)));
|
||||
}
|
||||
OperationArgumentResolver serverNamespaceArgumentResolver = OperationArgumentResolver
|
||||
.of(WebServerNamespace.class, () -> WebServerNamespace
|
||||
.from(WebServerApplicationContext.getServerNamepace(exchange.getApplicationContext())));
|
||||
return this.securityContextSupplier.get()
|
||||
.map((securityContext) -> new InvocationContext(securityContext, arguments,
|
||||
serverNamespaceArgumentResolver,
|
||||
new ProducibleOperationArgumentResolver(
|
||||
() -> exchange.getRequest().getHeaders().get("Accept"))))
|
||||
.flatMap((invocationContext) -> handleResult((Publisher<?>) this.invoker.invoke(invocationContext),
|
||||
exchange.getRequest().getMethod()));
|
||||
}
|
||||
|
||||
private String[] tokenizePathSegments(String path) {
|
||||
String[] segments = StringUtils.tokenizeToStringArray(path, PATH_SEPARATOR, false, true);
|
||||
for (int i = 0; i < segments.length; i++) {
|
||||
if (segments[i].contains("%")) {
|
||||
segments[i] = StringUtils.uriDecode(segments[i], StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
return segments;
|
||||
}
|
||||
|
||||
private Map<String, Object> getArguments(ServerWebExchange exchange, Map<String, String> body) {
|
||||
Map<String, Object> arguments = new LinkedHashMap<>(getTemplateVariables(exchange));
|
||||
String matchAllRemainingPathSegmentsVariable = this.operation.getRequestPredicate()
|
||||
.getMatchAllRemainingPathSegmentsVariable();
|
||||
if (matchAllRemainingPathSegmentsVariable != null) {
|
||||
arguments.put(matchAllRemainingPathSegmentsVariable, getRemainingPathSegments(exchange));
|
||||
}
|
||||
if (body != null) {
|
||||
arguments.putAll(body);
|
||||
}
|
||||
@@ -333,6 +337,26 @@ public abstract class AbstractWebFluxEndpointHandlerMapping extends RequestMappi
|
||||
return arguments;
|
||||
}
|
||||
|
||||
private Object getRemainingPathSegments(ServerWebExchange exchange) {
|
||||
PathPattern pathPattern = exchange.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
|
||||
if (pathPattern.hasPatternSyntax()) {
|
||||
String remainingSegments = pathPattern
|
||||
.extractPathWithinPattern(exchange.getRequest().getPath().pathWithinApplication()).value();
|
||||
return tokenizePathSegments(remainingSegments);
|
||||
}
|
||||
return tokenizePathSegments(pathPattern.toString());
|
||||
}
|
||||
|
||||
private String[] tokenizePathSegments(String value) {
|
||||
String[] segments = StringUtils.tokenizeToStringArray(value, PATH_SEPARATOR, false, true);
|
||||
for (int i = 0; i < segments.length; i++) {
|
||||
if (segments[i].contains("%")) {
|
||||
segments[i] = StringUtils.uriDecode(segments[i], StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
return segments;
|
||||
}
|
||||
|
||||
private Map<String, String> getTemplateVariables(ServerWebExchange exchange) {
|
||||
return exchange.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright 2012-2021 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
|
||||
*
|
||||
* https://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.web.reactive;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMapping;
|
||||
import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebOperation;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebOperationRequestPredicate;
|
||||
import org.springframework.boot.actuate.health.AdditionalHealthEndpointPath;
|
||||
import org.springframework.boot.actuate.health.HealthEndpointGroup;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.reactive.HandlerMapping;
|
||||
import org.springframework.web.reactive.result.method.RequestMappingInfo;
|
||||
|
||||
/**
|
||||
* A custom {@link HandlerMapping} that allows health groups to be mapped to an additional
|
||||
* path.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
* @since 2.6.0
|
||||
*/
|
||||
public class AdditionalHealthEndpointPathsWebFluxHandlerMapping extends AbstractWebFluxEndpointHandlerMapping {
|
||||
|
||||
private final EndpointMapping endpointMapping;
|
||||
|
||||
private final ExposableWebEndpoint endpoint;
|
||||
|
||||
private final Set<HealthEndpointGroup> groups;
|
||||
|
||||
public AdditionalHealthEndpointPathsWebFluxHandlerMapping(EndpointMapping endpointMapping,
|
||||
ExposableWebEndpoint endpoint, Set<HealthEndpointGroup> groups) {
|
||||
super(endpointMapping, Collections.singletonList(endpoint), null, null, false);
|
||||
this.endpointMapping = endpointMapping;
|
||||
this.groups = groups;
|
||||
this.endpoint = endpoint;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initHandlerMethods() {
|
||||
for (WebOperation operation : this.endpoint.getOperations()) {
|
||||
WebOperationRequestPredicate predicate = operation.getRequestPredicate();
|
||||
String matchAllRemainingPathSegmentsVariable = predicate.getMatchAllRemainingPathSegmentsVariable();
|
||||
if (matchAllRemainingPathSegmentsVariable != null) {
|
||||
for (HealthEndpointGroup group : this.groups) {
|
||||
AdditionalHealthEndpointPath additionalPath = group.getAdditionalPath();
|
||||
if (additionalPath != null) {
|
||||
RequestMappingInfo requestMappingInfo = getRequestMappingInfo(operation,
|
||||
additionalPath.getValue());
|
||||
registerReadMapping(requestMappingInfo, this.endpoint, operation);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private RequestMappingInfo getRequestMappingInfo(WebOperation operation, String additionalPath) {
|
||||
WebOperationRequestPredicate predicate = operation.getRequestPredicate();
|
||||
String path = this.endpointMapping.createSubPath(additionalPath);
|
||||
RequestMethod method = RequestMethod.valueOf(predicate.getHttpMethod().name());
|
||||
String[] consumes = StringUtils.toStringArray(predicate.getConsumes());
|
||||
String[] produces = StringUtils.toStringArray(predicate.getProduces());
|
||||
return RequestMappingInfo.paths(path).methods(method).consumes(consumes).produces(produces).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LinksHandler getLinksHandler() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -32,6 +32,7 @@ import javax.servlet.http.HttpServletResponse;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.boot.actuate.endpoint.InvalidEndpointRequestException;
|
||||
import org.springframework.boot.actuate.endpoint.InvocationContext;
|
||||
import org.springframework.boot.actuate.endpoint.OperationArgumentResolver;
|
||||
import org.springframework.boot.actuate.endpoint.ProducibleOperationArgumentResolver;
|
||||
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||
import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker;
|
||||
@@ -41,6 +42,8 @@ import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebOperation;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebOperationRequestPredicate;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebServerNamespace;
|
||||
import org.springframework.boot.web.context.WebServerApplicationContext;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
@@ -54,6 +57,8 @@ import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
import org.springframework.web.context.support.WebApplicationContextUtils;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
@@ -172,6 +177,11 @@ public abstract class AbstractWebMvcEndpointHandlerMapping extends RequestMappin
|
||||
if (matchAllRemainingPathSegmentsVariable != null) {
|
||||
path = path.replace("{*" + matchAllRemainingPathSegmentsVariable + "}", "**");
|
||||
}
|
||||
registerMapping(endpoint, predicate, operation, path);
|
||||
}
|
||||
|
||||
protected void registerMapping(ExposableWebEndpoint endpoint, WebOperationRequestPredicate predicate,
|
||||
WebOperation operation, String path) {
|
||||
ServletWebOperation servletWebOperation = wrapServletWebOperation(endpoint, operation,
|
||||
new ServletWebOperationAdapter(operation));
|
||||
registerMapping(createRequestMappingInfo(predicate, path), new OperationHandler(servletWebOperation),
|
||||
@@ -286,8 +296,17 @@ public abstract class AbstractWebMvcEndpointHandlerMapping extends RequestMappin
|
||||
Map<String, Object> arguments = getArguments(request, body);
|
||||
try {
|
||||
ServletSecurityContext securityContext = new ServletSecurityContext(request);
|
||||
ProducibleOperationArgumentResolver producibleOperationArgumentResolver = new ProducibleOperationArgumentResolver(
|
||||
() -> headers.get("Accept"));
|
||||
OperationArgumentResolver serverNamespaceArgumentResolver = OperationArgumentResolver
|
||||
.of(WebServerNamespace.class, () -> {
|
||||
WebApplicationContext applicationContext = WebApplicationContextUtils
|
||||
.getRequiredWebApplicationContext(request.getServletContext());
|
||||
return WebServerNamespace
|
||||
.from(WebServerApplicationContext.getServerNamepace(applicationContext));
|
||||
});
|
||||
InvocationContext invocationContext = new InvocationContext(securityContext, arguments,
|
||||
new ProducibleOperationArgumentResolver(() -> headers.get("Accept")));
|
||||
serverNamespaceArgumentResolver, producibleOperationArgumentResolver);
|
||||
return handleResult(this.operation.invoke(invocationContext), HttpMethod.resolve(request.getMethod()));
|
||||
}
|
||||
catch (InvalidEndpointRequestException ex) {
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2012-2021 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
|
||||
*
|
||||
* https://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.web.servlet;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMapping;
|
||||
import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebOperation;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebOperationRequestPredicate;
|
||||
import org.springframework.boot.actuate.health.AdditionalHealthEndpointPath;
|
||||
import org.springframework.boot.actuate.health.HealthEndpointGroup;
|
||||
import org.springframework.web.servlet.HandlerMapping;
|
||||
|
||||
/**
|
||||
* A custom {@link HandlerMapping} that allows health groups to be mapped to an additional
|
||||
* path.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
* @since 2.6.0
|
||||
*/
|
||||
public class AdditionalHealthEndpointPathsWebMvcHandlerMapping extends AbstractWebMvcEndpointHandlerMapping {
|
||||
|
||||
private final ExposableWebEndpoint endpoint;
|
||||
|
||||
private final Set<HealthEndpointGroup> groups;
|
||||
|
||||
public AdditionalHealthEndpointPathsWebMvcHandlerMapping(ExposableWebEndpoint endpoint,
|
||||
Set<HealthEndpointGroup> groups) {
|
||||
super(new EndpointMapping(""), Collections.singletonList(endpoint), null, false);
|
||||
this.endpoint = endpoint;
|
||||
this.groups = groups;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initHandlerMethods() {
|
||||
for (WebOperation operation : this.endpoint.getOperations()) {
|
||||
WebOperationRequestPredicate predicate = operation.getRequestPredicate();
|
||||
String matchAllRemainingPathSegmentsVariable = predicate.getMatchAllRemainingPathSegmentsVariable();
|
||||
if (matchAllRemainingPathSegmentsVariable != null) {
|
||||
for (HealthEndpointGroup group : this.groups) {
|
||||
AdditionalHealthEndpointPath additionalPath = group.getAdditionalPath();
|
||||
if (additionalPath != null) {
|
||||
registerMapping(this.endpoint, predicate, operation, additionalPath.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LinksHandler getLinksHandler() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* Copyright 2012-2021 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
|
||||
*
|
||||
* https://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.health;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.web.WebServerNamespace;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Value object that represents an additional path for a {@link HealthEndpointGroup}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Madhura Bhave
|
||||
* @since 2.6.0
|
||||
*/
|
||||
public final class AdditionalHealthEndpointPath {
|
||||
|
||||
private final WebServerNamespace namespace;
|
||||
|
||||
private final String value;
|
||||
|
||||
private final String canonicalValue;
|
||||
|
||||
private AdditionalHealthEndpointPath(WebServerNamespace namespace, String value) {
|
||||
this.namespace = namespace;
|
||||
this.value = value;
|
||||
this.canonicalValue = (!value.startsWith("/")) ? "/" + value : value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link WebServerNamespace} associated with this path.
|
||||
* @return the server namespace
|
||||
*/
|
||||
public WebServerNamespace getNamespace() {
|
||||
return this.namespace;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value corresponding to this path.
|
||||
* @return the path
|
||||
*/
|
||||
public String getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if this path has the given {@link WebServerNamespace}.
|
||||
* @param webServerNamespace the server namespace
|
||||
* @return the new instance
|
||||
*/
|
||||
public boolean hasNamespace(WebServerNamespace webServerNamespace) {
|
||||
return this.namespace.equals(webServerNamespace);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null || getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
AdditionalHealthEndpointPath other = (AdditionalHealthEndpointPath) obj;
|
||||
boolean result = true;
|
||||
result = result && this.namespace.equals(other.namespace);
|
||||
result = result && this.canonicalValue.equals(other.canonicalValue);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + this.namespace.hashCode();
|
||||
result = prime * result + this.canonicalValue.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.namespace.getValue() + ":" + this.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an {@link AdditionalHealthEndpointPath} from the given input. The input
|
||||
* must contain a prefix and value separated by a `:`. The value must be limited to
|
||||
* one path segment. For example, `server:/healthz`.
|
||||
* @param value the value to parse
|
||||
* @return the new instance
|
||||
*/
|
||||
public static AdditionalHealthEndpointPath from(String value) {
|
||||
Assert.hasText(value, "Value must not be null");
|
||||
String[] values = value.split(":");
|
||||
Assert.isTrue(values.length == 2, "Value must contain a valid namespace and value separated by ':'.");
|
||||
Assert.isTrue(StringUtils.hasText(values[0]), "Value must contain a valid namespace.");
|
||||
WebServerNamespace namespace = WebServerNamespace.from(values[0]);
|
||||
validateValue(values[1]);
|
||||
return new AdditionalHealthEndpointPath(namespace, values[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an {@link AdditionalHealthEndpointPath} from the given
|
||||
* {@link WebServerNamespace} and value.
|
||||
* @param webServerNamespace the server namespace
|
||||
* @param value the value
|
||||
* @return the new instance
|
||||
*/
|
||||
public static AdditionalHealthEndpointPath of(WebServerNamespace webServerNamespace, String value) {
|
||||
Assert.notNull(webServerNamespace, "The server namespace must not be null.");
|
||||
Assert.notNull(value, "The value must not be null.");
|
||||
validateValue(value);
|
||||
return new AdditionalHealthEndpointPath(webServerNamespace, value);
|
||||
}
|
||||
|
||||
private static void validateValue(String value) {
|
||||
Assert.isTrue(StringUtils.countOccurrencesOf(value, "/") <= 1 && value.indexOf("/") <= 0,
|
||||
"Value must contain only one segment.");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.ApiVersion;
|
||||
import org.springframework.boot.actuate.endpoint.EndpointId;
|
||||
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
||||
@@ -39,6 +40,11 @@ import org.springframework.boot.actuate.endpoint.annotation.Selector.Match;
|
||||
@Endpoint(id = "health")
|
||||
public class HealthEndpoint extends HealthEndpointSupport<HealthContributor, HealthComponent> {
|
||||
|
||||
/**
|
||||
* Health endpoint id.
|
||||
*/
|
||||
public static final EndpointId ID = EndpointId.of("health");
|
||||
|
||||
private static final String[] EMPTY_PATH = {};
|
||||
|
||||
/**
|
||||
@@ -62,7 +68,7 @@ public class HealthEndpoint extends HealthEndpointSupport<HealthContributor, Hea
|
||||
}
|
||||
|
||||
private HealthComponent health(ApiVersion apiVersion, String... path) {
|
||||
HealthResult<HealthComponent> result = getHealth(apiVersion, SecurityContext.NONE, true, path);
|
||||
HealthResult<HealthComponent> result = getHealth(apiVersion, null, SecurityContext.NONE, true, path);
|
||||
return (result != null) ? result.getHealth() : null;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
* Copyright 2012-2021 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.
|
||||
@@ -23,6 +23,7 @@ import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||
* by the {@link HealthEndpoint}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Madhura Bhave
|
||||
* @since 2.2.0
|
||||
*/
|
||||
public interface HealthEndpointGroup {
|
||||
@@ -62,4 +63,12 @@ public interface HealthEndpointGroup {
|
||||
*/
|
||||
HttpCodeStatusMapper getHttpCodeStatusMapper();
|
||||
|
||||
/**
|
||||
* Return an additional path that can be used to map the health group to an
|
||||
* alternative location.
|
||||
* @return the additional health path or {@code null}
|
||||
* @since 2.6.0
|
||||
*/
|
||||
AdditionalHealthEndpointPath getAdditionalPath();
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
* Copyright 2012-2021 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.
|
||||
@@ -16,9 +16,11 @@
|
||||
|
||||
package org.springframework.boot.actuate.health;
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.web.WebServerNamespace;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -48,6 +50,40 @@ public interface HealthEndpointGroups {
|
||||
*/
|
||||
HealthEndpointGroup get(String name);
|
||||
|
||||
/**
|
||||
* Return the group with the specified additional path or {@code null} if no group
|
||||
* with that path is found.
|
||||
* @param path the additional path
|
||||
* @return the matching {@link HealthEndpointGroup} or {@code null}
|
||||
* @since 2.6.0
|
||||
*/
|
||||
default HealthEndpointGroup get(AdditionalHealthEndpointPath path) {
|
||||
Assert.notNull(path, "Path must not be null");
|
||||
for (String name : getNames()) {
|
||||
HealthEndpointGroup group = get(name);
|
||||
if (path.equals(group.getAdditionalPath())) {
|
||||
return group;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all the groups with an additional path on the specified
|
||||
* {@link WebServerNamespace}.
|
||||
* @param namespace the {@link WebServerNamespace}
|
||||
* @return the matching groups
|
||||
* @since 2.6.0
|
||||
*/
|
||||
default Set<HealthEndpointGroup> getAllWithAdditionalPath(WebServerNamespace namespace) {
|
||||
Assert.notNull(namespace, "Namespace must not be null");
|
||||
Set<HealthEndpointGroup> filteredGroups = new LinkedHashSet<>();
|
||||
getNames().stream().map(this::get).filter(
|
||||
(group) -> group.getAdditionalPath() != null && group.getAdditionalPath().hasNamespace(namespace))
|
||||
.forEach(filteredGroups::add);
|
||||
return filteredGroups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to create a {@link HealthEndpointGroups} instance.
|
||||
* @param primary the primary group
|
||||
|
||||
@@ -23,6 +23,7 @@ import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.ApiVersion;
|
||||
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebServerNamespace;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -53,14 +54,28 @@ abstract class HealthEndpointSupport<C, T> {
|
||||
this.groups = groups;
|
||||
}
|
||||
|
||||
HealthResult<T> getHealth(ApiVersion apiVersion, SecurityContext securityContext, boolean showAll, String... path) {
|
||||
HealthEndpointGroup group = (path.length > 0) ? this.groups.get(path[0]) : null;
|
||||
if (group != null) {
|
||||
return getHealth(apiVersion, group, securityContext, showAll, path, 1);
|
||||
HealthResult<T> getHealth(ApiVersion apiVersion, WebServerNamespace serverNamespace,
|
||||
SecurityContext securityContext, boolean showAll, String... path) {
|
||||
if (path.length > 0) {
|
||||
HealthEndpointGroup group = getHealthGroup(serverNamespace, path);
|
||||
if (group != null) {
|
||||
return getHealth(apiVersion, group, securityContext, showAll, path, 1);
|
||||
}
|
||||
}
|
||||
return getHealth(apiVersion, this.groups.getPrimary(), securityContext, showAll, path, 0);
|
||||
}
|
||||
|
||||
private HealthEndpointGroup getHealthGroup(WebServerNamespace serverNamespace, String... path) {
|
||||
if (this.groups.get(path[0]) != null) {
|
||||
return this.groups.get(path[0]);
|
||||
}
|
||||
if (serverNamespace != null) {
|
||||
AdditionalHealthEndpointPath additionalPath = AdditionalHealthEndpointPath.of(serverNamespace, path[0]);
|
||||
return this.groups.get(additionalPath);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private HealthResult<T> getHealth(ApiVersion apiVersion, HealthEndpointGroup group, SecurityContext securityContext,
|
||||
boolean showAll, String[] path, int pathOffset) {
|
||||
boolean showComponents = showAll || group.showComponents(securityContext);
|
||||
@@ -71,8 +86,8 @@ abstract class HealthEndpointSupport<C, T> {
|
||||
return null;
|
||||
}
|
||||
Object contributor = getContributor(path, pathOffset);
|
||||
T health = getContribution(apiVersion, group, contributor, showComponents, showDetails,
|
||||
isSystemHealth ? this.groups.getNames() : null, false);
|
||||
Set<String> groupNames = isSystemHealth ? this.groups.getNames() : null;
|
||||
T health = getContribution(apiVersion, group, contributor, showComponents, showDetails, groupNames, false);
|
||||
return (health != null) ? new HealthResult<>(health, group) : null;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Selector;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Selector.Match;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebServerNamespace;
|
||||
import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExtension;
|
||||
|
||||
/**
|
||||
@@ -56,19 +57,20 @@ public class HealthEndpointWebExtension extends HealthEndpointSupport<HealthCont
|
||||
}
|
||||
|
||||
@ReadOperation
|
||||
public WebEndpointResponse<HealthComponent> health(ApiVersion apiVersion, SecurityContext securityContext) {
|
||||
return health(apiVersion, securityContext, false, NO_PATH);
|
||||
public WebEndpointResponse<HealthComponent> health(ApiVersion apiVersion, WebServerNamespace serverNamespace,
|
||||
SecurityContext securityContext) {
|
||||
return health(apiVersion, serverNamespace, securityContext, false, NO_PATH);
|
||||
}
|
||||
|
||||
@ReadOperation
|
||||
public WebEndpointResponse<HealthComponent> health(ApiVersion apiVersion, SecurityContext securityContext,
|
||||
@Selector(match = Match.ALL_REMAINING) String... path) {
|
||||
return health(apiVersion, securityContext, false, path);
|
||||
public WebEndpointResponse<HealthComponent> health(ApiVersion apiVersion, WebServerNamespace serverNamespace,
|
||||
SecurityContext securityContext, @Selector(match = Match.ALL_REMAINING) String... path) {
|
||||
return health(apiVersion, serverNamespace, securityContext, false, path);
|
||||
}
|
||||
|
||||
public WebEndpointResponse<HealthComponent> health(ApiVersion apiVersion, SecurityContext securityContext,
|
||||
boolean showAll, String... path) {
|
||||
HealthResult<HealthComponent> result = getHealth(apiVersion, securityContext, showAll, path);
|
||||
public WebEndpointResponse<HealthComponent> health(ApiVersion apiVersion, WebServerNamespace serverNamespace,
|
||||
SecurityContext securityContext, boolean showAll, String... path) {
|
||||
HealthResult<HealthComponent> result = getHealth(apiVersion, serverNamespace, securityContext, showAll, path);
|
||||
if (result == null) {
|
||||
return (Arrays.equals(path, NO_PATH))
|
||||
? new WebEndpointResponse<>(DEFAULT_HEALTH, WebEndpointResponse.STATUS_OK)
|
||||
|
||||
@@ -29,6 +29,7 @@ import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Selector;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Selector.Match;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebServerNamespace;
|
||||
import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExtension;
|
||||
|
||||
/**
|
||||
@@ -57,19 +58,21 @@ public class ReactiveHealthEndpointWebExtension
|
||||
|
||||
@ReadOperation
|
||||
public Mono<WebEndpointResponse<? extends HealthComponent>> health(ApiVersion apiVersion,
|
||||
SecurityContext securityContext) {
|
||||
return health(apiVersion, securityContext, false, NO_PATH);
|
||||
WebServerNamespace serverNamespace, SecurityContext securityContext) {
|
||||
return health(apiVersion, serverNamespace, securityContext, false, NO_PATH);
|
||||
}
|
||||
|
||||
@ReadOperation
|
||||
public Mono<WebEndpointResponse<? extends HealthComponent>> health(ApiVersion apiVersion,
|
||||
SecurityContext securityContext, @Selector(match = Match.ALL_REMAINING) String... path) {
|
||||
return health(apiVersion, securityContext, false, path);
|
||||
WebServerNamespace serverNamespace, SecurityContext securityContext,
|
||||
@Selector(match = Match.ALL_REMAINING) String... path) {
|
||||
return health(apiVersion, serverNamespace, securityContext, false, path);
|
||||
}
|
||||
|
||||
public Mono<WebEndpointResponse<? extends HealthComponent>> health(ApiVersion apiVersion,
|
||||
SecurityContext securityContext, boolean showAll, String... path) {
|
||||
HealthResult<Mono<? extends HealthComponent>> result = getHealth(apiVersion, securityContext, showAll, path);
|
||||
WebServerNamespace serverNamespace, SecurityContext securityContext, boolean showAll, String... path) {
|
||||
HealthResult<Mono<? extends HealthComponent>> result = getHealth(apiVersion, serverNamespace, securityContext,
|
||||
showAll, path);
|
||||
if (result == null) {
|
||||
return (Arrays.equals(path, NO_PATH))
|
||||
? Mono.just(new WebEndpointResponse<>(DEFAULT_HEALTH, WebEndpointResponse.STATUS_OK))
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2012-2021 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
|
||||
*
|
||||
* https://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.web;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link WebServerNamespace}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
class WebServerNamespaceTests {
|
||||
|
||||
@Test
|
||||
void fromWhenValueHasText() {
|
||||
assertThat(WebServerNamespace.from("management")).isEqualTo(WebServerNamespace.MANAGEMENT);
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromWhenValueIsNull() {
|
||||
assertThat(WebServerNamespace.from(null)).isEqualTo(WebServerNamespace.SERVER);
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromWhenValueIsEmpty() {
|
||||
assertThat(WebServerNamespace.from("")).isEqualTo(WebServerNamespace.SERVER);
|
||||
}
|
||||
|
||||
@Test
|
||||
void namespaceWithSameValueAreEqual() {
|
||||
assertThat(WebServerNamespace.from("value")).isEqualTo(WebServerNamespace.from("value"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void namespaceWithDifferentValuesAreNotEqual() {
|
||||
assertThat(WebServerNamespace.from("value")).isNotEqualTo(WebServerNamespace.from("other"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -137,7 +137,7 @@ public abstract class AbstractWebEndpointIntegrationTests<T extends Configurable
|
||||
@Test
|
||||
void matchAllRemainingPathsSelectorShouldDecodePath() {
|
||||
load(MatchAllRemainingEndpointConfiguration.class,
|
||||
(client) -> client.get().uri("/matchallremaining/one/two%20three/").exchange().expectStatus().isOk()
|
||||
(client) -> client.get().uri("/matchallremaining/one/two three/").exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("selection").isEqualTo("one|two three"));
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Copyright 2012-2021 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
|
||||
*
|
||||
* https://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.health;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.web.WebServerNamespace;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Tests for {@link AdditionalHealthEndpointPath}.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
class AdditionalHealthEndpointPathTests {
|
||||
|
||||
@Test
|
||||
void fromValidPathShouldCreatePath() {
|
||||
AdditionalHealthEndpointPath path = AdditionalHealthEndpointPath.from("server:/my-path");
|
||||
assertThat(path.getValue()).isEqualTo("/my-path");
|
||||
assertThat(path.getNamespace()).isEqualTo(WebServerNamespace.SERVER);
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromValidPathWithoutSlashShouldCreatePath() {
|
||||
AdditionalHealthEndpointPath path = AdditionalHealthEndpointPath.from("server:my-path");
|
||||
assertThat(path.getValue()).isEqualTo("my-path");
|
||||
assertThat(path.getNamespace()).isEqualTo(WebServerNamespace.SERVER);
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromNullPathShouldThrowException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> AdditionalHealthEndpointPath.from(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromEmptyPathShouldThrowException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> AdditionalHealthEndpointPath.from(""));
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromPathWithNoNamespaceShouldThrowException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> AdditionalHealthEndpointPath.from("my-path"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromPathWithEmptyNamespaceShouldThrowException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> AdditionalHealthEndpointPath.from(":my-path"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromPathWithMultipleSegmentsShouldThrowException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> AdditionalHealthEndpointPath.from("server:/my-path/my-sub-path"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromPathWithMultipleSegmentsNotStartingWithSlashShouldThrowException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> AdditionalHealthEndpointPath.from("server:my-path/my-sub-path"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void pathsWithTheSameNamespaceAndValueAreEqual() {
|
||||
assertThat(AdditionalHealthEndpointPath.from("server:/my-path"))
|
||||
.isEqualTo(AdditionalHealthEndpointPath.from("server:/my-path"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void pathsWithTheDifferentNamespaceAndSameValueAreNotEqual() {
|
||||
assertThat(AdditionalHealthEndpointPath.from("server:/my-path"))
|
||||
.isNotEqualTo((AdditionalHealthEndpointPath.from("management:/my-path")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void pathsWithTheSameNamespaceAndValuesWithNoSlashAreEqual() {
|
||||
assertThat(AdditionalHealthEndpointPath.from("server:/my-path"))
|
||||
.isEqualTo((AdditionalHealthEndpointPath.from("server:my-path")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void ofWithNullNamespaceShouldThrowException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> AdditionalHealthEndpointPath.of(null, "my-sub-path"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void ofWithNullPathShouldThrowException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> AdditionalHealthEndpointPath.of(WebServerNamespace.SERVER, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void ofWithMultipleSegmentValueShouldThrowException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> AdditionalHealthEndpointPath.of(WebServerNamespace.SERVER, "/my-path/my-subpath"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void ofShouldCreatePath() {
|
||||
AdditionalHealthEndpointPath additionalPath = AdditionalHealthEndpointPath.of(WebServerNamespace.SERVER,
|
||||
"my-path");
|
||||
assertThat(additionalPath.getValue()).isEqualTo("my-path");
|
||||
assertThat(additionalPath.getNamespace()).isEqualTo(WebServerNamespace.SERVER);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -24,6 +24,7 @@ import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.ApiVersion;
|
||||
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebServerNamespace;
|
||||
import org.springframework.boot.actuate.health.HealthEndpointSupport.HealthResult;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -72,7 +73,7 @@ abstract class HealthEndpointSupportTests<R extends ContributorRegistry<C>, C, T
|
||||
@Test
|
||||
void getHealthWhenPathIsEmptyUsesPrimaryGroup() {
|
||||
this.registry.registerContributor("test", createContributor(this.up));
|
||||
HealthResult<T> result = create(this.registry, this.groups).getHealth(ApiVersion.V3, SecurityContext.NONE,
|
||||
HealthResult<T> result = create(this.registry, this.groups).getHealth(ApiVersion.V3, null, SecurityContext.NONE,
|
||||
false);
|
||||
assertThat(result.getGroup()).isEqualTo(this.primaryGroup);
|
||||
assertThat(getHealth(result)).isNotSameAs(this.up);
|
||||
@@ -82,7 +83,7 @@ abstract class HealthEndpointSupportTests<R extends ContributorRegistry<C>, C, T
|
||||
@Test
|
||||
void getHealthWhenPathIsNotGroupReturnsResultFromPrimaryGroup() {
|
||||
this.registry.registerContributor("test", createContributor(this.up));
|
||||
HealthResult<T> result = create(this.registry, this.groups).getHealth(ApiVersion.V3, SecurityContext.NONE,
|
||||
HealthResult<T> result = create(this.registry, this.groups).getHealth(ApiVersion.V3, null, SecurityContext.NONE,
|
||||
false, "test");
|
||||
assertThat(result.getGroup()).isEqualTo(this.primaryGroup);
|
||||
assertThat(getHealth(result)).isEqualTo(this.up);
|
||||
@@ -92,7 +93,7 @@ abstract class HealthEndpointSupportTests<R extends ContributorRegistry<C>, C, T
|
||||
@Test
|
||||
void getHealthWhenPathIsGroupReturnsResultFromGroup() {
|
||||
this.registry.registerContributor("atest", createContributor(this.up));
|
||||
HealthResult<T> result = create(this.registry, this.groups).getHealth(ApiVersion.V3, SecurityContext.NONE,
|
||||
HealthResult<T> result = create(this.registry, this.groups).getHealth(ApiVersion.V3, null, SecurityContext.NONE,
|
||||
false, "alltheas", "atest");
|
||||
assertThat(result.getGroup()).isEqualTo(this.allTheAs);
|
||||
assertThat(getHealth(result)).isEqualTo(this.up);
|
||||
@@ -103,7 +104,7 @@ abstract class HealthEndpointSupportTests<R extends ContributorRegistry<C>, C, T
|
||||
C contributor = createContributor(this.up);
|
||||
C compositeContributor = createCompositeContributor(Collections.singletonMap("spring", contributor));
|
||||
this.registry.registerContributor("test", compositeContributor);
|
||||
HealthResult<T> result = create(this.registry, this.groups).getHealth(ApiVersion.V3, SecurityContext.NONE,
|
||||
HealthResult<T> result = create(this.registry, this.groups).getHealth(ApiVersion.V3, null, SecurityContext.NONE,
|
||||
false, "test");
|
||||
CompositeHealth health = (CompositeHealth) getHealth(result);
|
||||
assertThat(health.getComponents()).containsKey("spring");
|
||||
@@ -116,9 +117,9 @@ abstract class HealthEndpointSupportTests<R extends ContributorRegistry<C>, C, T
|
||||
C compositeContributor = createCompositeContributor(Collections.singletonMap("spring", contributor));
|
||||
this.registry.registerContributor("test", compositeContributor);
|
||||
HealthEndpointSupport<C, T> endpoint = create(this.registry, this.groups);
|
||||
HealthResult<T> rootResult = endpoint.getHealth(ApiVersion.V3, SecurityContext.NONE, false);
|
||||
HealthResult<T> rootResult = endpoint.getHealth(ApiVersion.V3, null, SecurityContext.NONE, false);
|
||||
assertThat(((CompositeHealth) getHealth(rootResult)).getComponents()).isNullOrEmpty();
|
||||
HealthResult<T> componentResult = endpoint.getHealth(ApiVersion.V3, SecurityContext.NONE, false, "test");
|
||||
HealthResult<T> componentResult = endpoint.getHealth(ApiVersion.V3, null, SecurityContext.NONE, false, "test");
|
||||
assertThat(componentResult).isNull();
|
||||
}
|
||||
|
||||
@@ -129,16 +130,16 @@ abstract class HealthEndpointSupportTests<R extends ContributorRegistry<C>, C, T
|
||||
C compositeContributor = createCompositeContributor(Collections.singletonMap("spring", contributor));
|
||||
this.registry.registerContributor("test", compositeContributor);
|
||||
HealthEndpointSupport<C, T> endpoint = create(this.registry, this.groups);
|
||||
HealthResult<T> rootResult = endpoint.getHealth(ApiVersion.V3, SecurityContext.NONE, false);
|
||||
HealthResult<T> rootResult = endpoint.getHealth(ApiVersion.V3, null, SecurityContext.NONE, false);
|
||||
assertThat(((CompositeHealth) getHealth(rootResult)).getComponents()).containsKey("test");
|
||||
HealthResult<T> componentResult = endpoint.getHealth(ApiVersion.V3, SecurityContext.NONE, false, "test");
|
||||
HealthResult<T> componentResult = endpoint.getHealth(ApiVersion.V3, null, SecurityContext.NONE, false, "test");
|
||||
assertThat(((CompositeHealth) getHealth(componentResult)).getComponents()).containsKey("spring");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getHealthWhenAlwaysShowIsFalseAndGroupIsTrueShowsDetails() {
|
||||
this.registry.registerContributor("test", createContributor(this.up));
|
||||
HealthResult<T> result = create(this.registry, this.groups).getHealth(ApiVersion.V3, SecurityContext.NONE,
|
||||
HealthResult<T> result = create(this.registry, this.groups).getHealth(ApiVersion.V3, null, SecurityContext.NONE,
|
||||
false, "test");
|
||||
assertThat(((Health) getHealth(result)).getDetails()).containsEntry("spring", "boot");
|
||||
}
|
||||
@@ -148,8 +149,8 @@ abstract class HealthEndpointSupportTests<R extends ContributorRegistry<C>, C, T
|
||||
this.primaryGroup.setShowDetails(false);
|
||||
this.registry.registerContributor("test", createContributor(this.up));
|
||||
HealthEndpointSupport<C, T> endpoint = create(this.registry, this.groups);
|
||||
HealthResult<T> rootResult = endpoint.getHealth(ApiVersion.V3, SecurityContext.NONE, false);
|
||||
HealthResult<T> componentResult = endpoint.getHealth(ApiVersion.V3, SecurityContext.NONE, false, "test");
|
||||
HealthResult<T> rootResult = endpoint.getHealth(ApiVersion.V3, null, SecurityContext.NONE, false);
|
||||
HealthResult<T> componentResult = endpoint.getHealth(ApiVersion.V3, null, SecurityContext.NONE, false, "test");
|
||||
assertThat(((CompositeHealth) getHealth(rootResult)).getStatus()).isEqualTo(Status.UP);
|
||||
assertThat(componentResult).isNull();
|
||||
}
|
||||
@@ -158,8 +159,8 @@ abstract class HealthEndpointSupportTests<R extends ContributorRegistry<C>, C, T
|
||||
void getHealthWhenAlwaysShowIsTrueShowsDetails() {
|
||||
this.primaryGroup.setShowDetails(false);
|
||||
this.registry.registerContributor("test", createContributor(this.up));
|
||||
HealthResult<T> result = create(this.registry, this.groups).getHealth(ApiVersion.V3, SecurityContext.NONE, true,
|
||||
"test");
|
||||
HealthResult<T> result = create(this.registry, this.groups).getHealth(ApiVersion.V3, null, SecurityContext.NONE,
|
||||
true, "test");
|
||||
assertThat(((Health) getHealth(result)).getDetails()).containsEntry("spring", "boot");
|
||||
}
|
||||
|
||||
@@ -169,7 +170,7 @@ abstract class HealthEndpointSupportTests<R extends ContributorRegistry<C>, C, T
|
||||
contributors.put("a", createContributor(this.up));
|
||||
contributors.put("b", createContributor(this.down));
|
||||
this.registry.registerContributor("test", createCompositeContributor(contributors));
|
||||
HealthResult<T> result = create(this.registry, this.groups).getHealth(ApiVersion.V3, SecurityContext.NONE,
|
||||
HealthResult<T> result = create(this.registry, this.groups).getHealth(ApiVersion.V3, null, SecurityContext.NONE,
|
||||
false);
|
||||
CompositeHealth root = (CompositeHealth) getHealth(result);
|
||||
CompositeHealth component = (CompositeHealth) root.getComponents().get("test");
|
||||
@@ -180,7 +181,7 @@ abstract class HealthEndpointSupportTests<R extends ContributorRegistry<C>, C, T
|
||||
|
||||
@Test
|
||||
void getHealthWhenPathDoesNotExistReturnsNull() {
|
||||
HealthResult<T> result = create(this.registry, this.groups).getHealth(ApiVersion.V3, SecurityContext.NONE,
|
||||
HealthResult<T> result = create(this.registry, this.groups).getHealth(ApiVersion.V3, null, SecurityContext.NONE,
|
||||
false, "missing");
|
||||
assertThat(result).isNull();
|
||||
}
|
||||
@@ -188,7 +189,7 @@ abstract class HealthEndpointSupportTests<R extends ContributorRegistry<C>, C, T
|
||||
@Test
|
||||
void getHealthWhenPathIsEmptyIncludesGroups() {
|
||||
this.registry.registerContributor("test", createContributor(this.up));
|
||||
HealthResult<T> result = create(this.registry, this.groups).getHealth(ApiVersion.V3, SecurityContext.NONE,
|
||||
HealthResult<T> result = create(this.registry, this.groups).getHealth(ApiVersion.V3, null, SecurityContext.NONE,
|
||||
false);
|
||||
assertThat(((SystemHealth) getHealth(result)).getGroups()).containsOnly("alltheas");
|
||||
}
|
||||
@@ -196,7 +197,7 @@ abstract class HealthEndpointSupportTests<R extends ContributorRegistry<C>, C, T
|
||||
@Test
|
||||
void getHealthWhenPathIsGroupDoesNotIncludesGroups() {
|
||||
this.registry.registerContributor("atest", createContributor(this.up));
|
||||
HealthResult<T> result = create(this.registry, this.groups).getHealth(ApiVersion.V3, SecurityContext.NONE,
|
||||
HealthResult<T> result = create(this.registry, this.groups).getHealth(ApiVersion.V3, null, SecurityContext.NONE,
|
||||
false, "alltheas");
|
||||
assertThat(getHealth(result)).isNotInstanceOf(SystemHealth.class);
|
||||
}
|
||||
@@ -204,7 +205,7 @@ abstract class HealthEndpointSupportTests<R extends ContributorRegistry<C>, C, T
|
||||
@Test
|
||||
void getHealthWithEmptyCompositeReturnsNullResult() { // gh-18687
|
||||
this.registry.registerContributor("test", createCompositeContributor(Collections.emptyMap()));
|
||||
HealthResult<T> result = create(this.registry, this.groups).getHealth(ApiVersion.V3, SecurityContext.NONE,
|
||||
HealthResult<T> result = create(this.registry, this.groups).getHealth(ApiVersion.V3, null, SecurityContext.NONE,
|
||||
false);
|
||||
assertThat(result).isNull();
|
||||
}
|
||||
@@ -217,12 +218,53 @@ abstract class HealthEndpointSupportTests<R extends ContributorRegistry<C>, C, T
|
||||
TestHealthEndpointGroup testGroup = new TestHealthEndpointGroup((name) -> name.startsWith("test"));
|
||||
HealthEndpointGroups groups = HealthEndpointGroups.of(this.primaryGroup,
|
||||
Collections.singletonMap("testGroup", testGroup));
|
||||
HealthResult<T> result = create(this.registry, groups).getHealth(ApiVersion.V3, SecurityContext.NONE, false,
|
||||
"testGroup");
|
||||
HealthResult<T> result = create(this.registry, groups).getHealth(ApiVersion.V3, null, SecurityContext.NONE,
|
||||
false, "testGroup");
|
||||
CompositeHealth health = (CompositeHealth) getHealth(result);
|
||||
assertThat(health.getComponents()).containsKey("test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getHealthWhenGroupHasAdditionalPath() {
|
||||
this.registry.registerContributor("test", createContributor(this.up));
|
||||
TestHealthEndpointGroup testGroup = new TestHealthEndpointGroup((name) -> name.startsWith("test"));
|
||||
testGroup.setAdditionalPath(AdditionalHealthEndpointPath.from("server:/healthz"));
|
||||
HealthEndpointGroups groups = HealthEndpointGroups.of(this.primaryGroup,
|
||||
Collections.singletonMap("testGroup", testGroup));
|
||||
HealthResult<T> result = create(this.registry, groups).getHealth(ApiVersion.V3, WebServerNamespace.SERVER,
|
||||
SecurityContext.NONE, false, "healthz");
|
||||
CompositeHealth health = (CompositeHealth) getHealth(result);
|
||||
assertThat(health.getComponents()).containsKey("test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getHealthWhenGroupHasAdditionalPathAndShowComponentsFalse() {
|
||||
this.registry.registerContributor("test", createContributor(this.up));
|
||||
TestHealthEndpointGroup testGroup = new TestHealthEndpointGroup((name) -> name.startsWith("test"));
|
||||
testGroup.setAdditionalPath(AdditionalHealthEndpointPath.from("server:/healthz"));
|
||||
testGroup.setShowComponents(false);
|
||||
HealthEndpointGroups groups = HealthEndpointGroups.of(this.primaryGroup,
|
||||
Collections.singletonMap("testGroup", testGroup));
|
||||
HealthResult<T> result = create(this.registry, groups).getHealth(ApiVersion.V3, WebServerNamespace.SERVER,
|
||||
SecurityContext.NONE, false, "healthz");
|
||||
CompositeHealth health = (CompositeHealth) getHealth(result);
|
||||
assertThat(health.getStatus().getCode()).isEqualTo("UP");
|
||||
assertThat(health.getComponents()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getComponentHealthWhenGroupHasAdditionalPathAndShowComponentsFalse() {
|
||||
this.registry.registerContributor("test", createContributor(this.up));
|
||||
TestHealthEndpointGroup testGroup = new TestHealthEndpointGroup((name) -> name.startsWith("test"));
|
||||
testGroup.setAdditionalPath(AdditionalHealthEndpointPath.from("server:/healthz"));
|
||||
testGroup.setShowComponents(false);
|
||||
HealthEndpointGroups groups = HealthEndpointGroups.of(this.primaryGroup,
|
||||
Collections.singletonMap("testGroup", testGroup));
|
||||
HealthResult<T> result = create(this.registry, groups).getHealth(ApiVersion.V3, WebServerNamespace.SERVER,
|
||||
SecurityContext.NONE, false, "healthz", "test");
|
||||
assertThat(result).isEqualTo(null);
|
||||
}
|
||||
|
||||
protected abstract HealthEndpointSupport<C, T> create(R registry, HealthEndpointGroups groups);
|
||||
|
||||
protected abstract R createRegistry();
|
||||
|
||||
@@ -24,6 +24,7 @@ import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.actuate.endpoint.ApiVersion;
|
||||
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebServerNamespace;
|
||||
import org.springframework.boot.actuate.health.HealthEndpointSupport.HealthResult;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -42,7 +43,7 @@ class HealthEndpointWebExtensionTests
|
||||
void healthReturnsSystemHealth() {
|
||||
this.registry.registerContributor("test", createContributor(this.up));
|
||||
WebEndpointResponse<HealthComponent> response = create(this.registry, this.groups).health(ApiVersion.LATEST,
|
||||
SecurityContext.NONE);
|
||||
WebServerNamespace.SERVER, SecurityContext.NONE);
|
||||
HealthComponent health = response.getBody();
|
||||
assertThat(health.getStatus()).isEqualTo(Status.UP);
|
||||
assertThat(health).isInstanceOf(SystemHealth.class);
|
||||
@@ -54,7 +55,7 @@ class HealthEndpointWebExtensionTests
|
||||
assertThat(this.registry).isEmpty();
|
||||
WebEndpointResponse<HealthComponent> response = create(this.registry,
|
||||
HealthEndpointGroups.of(mock(HealthEndpointGroup.class), Collections.emptyMap()))
|
||||
.health(ApiVersion.LATEST, SecurityContext.NONE);
|
||||
.health(ApiVersion.LATEST, WebServerNamespace.SERVER, SecurityContext.NONE);
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
HealthComponent health = response.getBody();
|
||||
assertThat(health.getStatus()).isEqualTo(Status.UP);
|
||||
@@ -65,7 +66,7 @@ class HealthEndpointWebExtensionTests
|
||||
void healthWhenPathDoesNotExistReturnsHttp404() {
|
||||
this.registry.registerContributor("test", createContributor(this.up));
|
||||
WebEndpointResponse<HealthComponent> response = create(this.registry, this.groups).health(ApiVersion.LATEST,
|
||||
SecurityContext.NONE, "missing");
|
||||
WebServerNamespace.SERVER, SecurityContext.NONE, "missing");
|
||||
assertThat(response.getBody()).isNull();
|
||||
assertThat(response.getStatus()).isEqualTo(404);
|
||||
}
|
||||
@@ -74,7 +75,7 @@ class HealthEndpointWebExtensionTests
|
||||
void healthWhenPathExistsReturnsHealth() {
|
||||
this.registry.registerContributor("test", createContributor(this.up));
|
||||
WebEndpointResponse<HealthComponent> response = create(this.registry, this.groups).health(ApiVersion.LATEST,
|
||||
SecurityContext.NONE, "test");
|
||||
WebServerNamespace.SERVER, SecurityContext.NONE, "test");
|
||||
assertThat(response.getBody()).isEqualTo(this.up);
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ class ReactiveHealthEndpointWebExtensionTests extends
|
||||
void healthReturnsSystemHealth() {
|
||||
this.registry.registerContributor("test", createContributor(this.up));
|
||||
WebEndpointResponse<? extends HealthComponent> response = create(this.registry, this.groups)
|
||||
.health(ApiVersion.LATEST, SecurityContext.NONE).block();
|
||||
.health(ApiVersion.LATEST, null, SecurityContext.NONE).block();
|
||||
HealthComponent health = response.getBody();
|
||||
assertThat(health.getStatus()).isEqualTo(Status.UP);
|
||||
assertThat(health).isInstanceOf(SystemHealth.class);
|
||||
@@ -55,7 +55,7 @@ class ReactiveHealthEndpointWebExtensionTests extends
|
||||
assertThat(this.registry).isEmpty();
|
||||
WebEndpointResponse<? extends HealthComponent> response = create(this.registry,
|
||||
HealthEndpointGroups.of(mock(HealthEndpointGroup.class), Collections.emptyMap()))
|
||||
.health(ApiVersion.LATEST, SecurityContext.NONE).block();
|
||||
.health(ApiVersion.LATEST, null, SecurityContext.NONE).block();
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
HealthComponent health = response.getBody();
|
||||
assertThat(health.getStatus()).isEqualTo(Status.UP);
|
||||
@@ -66,7 +66,7 @@ class ReactiveHealthEndpointWebExtensionTests extends
|
||||
void healthWhenPathDoesNotExistReturnsHttp404() {
|
||||
this.registry.registerContributor("test", createContributor(this.up));
|
||||
WebEndpointResponse<? extends HealthComponent> response = create(this.registry, this.groups)
|
||||
.health(ApiVersion.LATEST, SecurityContext.NONE, "missing").block();
|
||||
.health(ApiVersion.LATEST, null, SecurityContext.NONE, "missing").block();
|
||||
assertThat(response.getBody()).isNull();
|
||||
assertThat(response.getStatus()).isEqualTo(404);
|
||||
}
|
||||
@@ -75,7 +75,7 @@ class ReactiveHealthEndpointWebExtensionTests extends
|
||||
void healthWhenPathExistsReturnsHealth() {
|
||||
this.registry.registerContributor("test", createContributor(this.up));
|
||||
WebEndpointResponse<? extends HealthComponent> response = create(this.registry, this.groups)
|
||||
.health(ApiVersion.LATEST, SecurityContext.NONE, "test").block();
|
||||
.health(ApiVersion.LATEST, null, SecurityContext.NONE, "test").block();
|
||||
assertThat(response.getBody()).isEqualTo(this.up);
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
* Copyright 2012-2021 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.
|
||||
@@ -37,6 +37,8 @@ class TestHealthEndpointGroup implements HealthEndpointGroup {
|
||||
|
||||
private boolean showDetails = true;
|
||||
|
||||
private AdditionalHealthEndpointPath additionalPath;
|
||||
|
||||
TestHealthEndpointGroup() {
|
||||
this((name) -> true);
|
||||
}
|
||||
@@ -78,4 +80,13 @@ class TestHealthEndpointGroup implements HealthEndpointGroup {
|
||||
return this.httpCodeStatusMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AdditionalHealthEndpointPath getAdditionalPath() {
|
||||
return this.additionalPath;
|
||||
}
|
||||
|
||||
void setAdditionalPath(AdditionalHealthEndpointPath additionalPath) {
|
||||
this.additionalPath = additionalPath;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user