Allow health groups to be configured at an additional path
Closes gh-25471 Co-authored-by: Phillip Webb <pwebb@vmware.com>pull/27659/head
parent
fdde40e4fb
commit
49c86e6e1b
@ -0,0 +1,95 @@
|
||||
/*
|
||||
* 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.autoconfigure.integrationtest;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener;
|
||||
import org.springframework.boot.test.context.assertj.ApplicationContextAssertProvider;
|
||||
import org.springframework.boot.test.context.runner.AbstractApplicationContextRunner;
|
||||
import org.springframework.boot.test.context.runner.ContextConsumer;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
|
||||
/**
|
||||
* Abstract base class for health groups with an additional path.
|
||||
*
|
||||
* @param <T> the runner
|
||||
* @param <C> the application context type
|
||||
* @param <A> the assertions
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
abstract class AbstractHealthEndpointAdditionalPathIntegrationTests<T extends AbstractApplicationContextRunner<T, C, A>, C extends ConfigurableApplicationContext, A extends ApplicationContextAssertProvider<C>> {
|
||||
|
||||
private final T runner;
|
||||
|
||||
AbstractHealthEndpointAdditionalPathIntegrationTests(T runner) {
|
||||
this.runner = runner;
|
||||
}
|
||||
|
||||
@Test
|
||||
void groupIsAvailableAtAdditionalPath() {
|
||||
this.runner
|
||||
.withPropertyValues("management.endpoint.health.group.live.include=diskSpace",
|
||||
"management.endpoint.health.group.live.additional-path=server:/healthz",
|
||||
"management.endpoint.health.group.live.show-components=always")
|
||||
.run(withWebTestClient(this::testResponse, "local.server.port"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void groupIsAvailableAtAdditionalPathWithoutSlash() {
|
||||
this.runner
|
||||
.withPropertyValues("management.endpoint.health.group.live.include=diskSpace",
|
||||
"management.endpoint.health.group.live.additional-path=server:healthz",
|
||||
"management.endpoint.health.group.live.show-components=always")
|
||||
.run(withWebTestClient(this::testResponse, "local.server.port"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void groupIsAvailableAtAdditionalPathOnManagementPort() {
|
||||
this.runner.withPropertyValues("management.endpoint.health.group.live.include=diskSpace",
|
||||
"management.server.port=0", "management.endpoint.health.group.live.additional-path=management:healthz",
|
||||
"management.endpoint.health.group.live.show-components=always")
|
||||
.run(withWebTestClient(this::testResponse, "local.management.port"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void groupIsAvailableAtAdditionalPathOnServerPortWithDifferentManagementPort() {
|
||||
this.runner.withPropertyValues("management.endpoint.health.group.live.include=diskSpace",
|
||||
"management.server.port=0", "management.endpoint.health.group.live.additional-path=server:healthz",
|
||||
"management.endpoint.health.group.live.show-components=always")
|
||||
.withInitializer(new ConditionEvaluationReportLoggingListener())
|
||||
.run(withWebTestClient(this::testResponse, "local.server.port"));
|
||||
}
|
||||
|
||||
private void testResponse(WebTestClient client) {
|
||||
client.get().uri("/healthz").accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("status").isEqualTo("UP").jsonPath("components.diskSpace").exists();
|
||||
}
|
||||
|
||||
private ContextConsumer<A> withWebTestClient(Consumer<WebTestClient> consumer, String property) {
|
||||
return (context) -> {
|
||||
String port = context.getEnvironment().getProperty(property);
|
||||
WebTestClient client = WebTestClient.bindToServer().baseUrl("http://localhost:" + port).build();
|
||||
consumer.accept(client);
|
||||
};
|
||||
}
|
||||
|
||||
}
|
@ -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.autoconfigure.integrationtest;
|
||||
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.system.DiskSpaceHealthContributorAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration;
|
||||
import org.springframework.boot.test.context.FilteredClassLoader;
|
||||
import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext;
|
||||
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
|
||||
import org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer;
|
||||
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
|
||||
import org.springframework.web.context.ConfigurableWebApplicationContext;
|
||||
import org.springframework.web.servlet.DispatcherServlet;
|
||||
|
||||
/**
|
||||
* Integration tests for health groups on an additional path on Jersey.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
class JerseyHealthEndpointAdditionalPathIntegrationTests extends
|
||||
AbstractHealthEndpointAdditionalPathIntegrationTests<WebApplicationContextRunner, ConfigurableWebApplicationContext, AssertableWebApplicationContext> {
|
||||
|
||||
JerseyHealthEndpointAdditionalPathIntegrationTests() {
|
||||
super(new WebApplicationContextRunner(AnnotationConfigServletWebServerApplicationContext::new)
|
||||
.withConfiguration(AutoConfigurations.of(JacksonAutoConfiguration.class, JerseyAutoConfiguration.class,
|
||||
EndpointAutoConfiguration.class, ServletWebServerFactoryAutoConfiguration.class,
|
||||
WebEndpointAutoConfiguration.class, JerseyAutoConfiguration.class,
|
||||
ManagementContextAutoConfiguration.class, ServletManagementContextAutoConfiguration.class,
|
||||
HealthEndpointAutoConfiguration.class, DiskSpaceHealthContributorAutoConfiguration.class))
|
||||
.withInitializer(new ServerPortInfoApplicationContextInitializer())
|
||||
.withClassLoader(new FilteredClassLoader(DispatcherServlet.class)).withPropertyValues("server.port=0"));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
/*
|
||||
* 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.autoconfigure.integrationtest;
|
||||
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.system.DiskSpaceHealthContributorAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
|
||||
import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext;
|
||||
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
|
||||
import org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer;
|
||||
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
|
||||
import org.springframework.web.context.ConfigurableWebApplicationContext;
|
||||
|
||||
/**
|
||||
* Integration tests for MVC health groups on an additional path.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
class WebMvcHealthEndpointAdditionalPathIntegrationTests extends
|
||||
AbstractHealthEndpointAdditionalPathIntegrationTests<WebApplicationContextRunner, ConfigurableWebApplicationContext, AssertableWebApplicationContext> {
|
||||
|
||||
WebMvcHealthEndpointAdditionalPathIntegrationTests() {
|
||||
super(new WebApplicationContextRunner(AnnotationConfigServletWebServerApplicationContext::new)
|
||||
.withConfiguration(AutoConfigurations.of(JacksonAutoConfiguration.class,
|
||||
HttpMessageConvertersAutoConfiguration.class, ManagementContextAutoConfiguration.class,
|
||||
ServletWebServerFactoryAutoConfiguration.class, WebMvcAutoConfiguration.class,
|
||||
ServletManagementContextAutoConfiguration.class, WebEndpointAutoConfiguration.class,
|
||||
EndpointAutoConfiguration.class, DispatcherServletAutoConfiguration.class,
|
||||
HealthEndpointAutoConfiguration.class, DiskSpaceHealthContributorAutoConfiguration.class))
|
||||
.withInitializer(new ServerPortInfoApplicationContextInitializer())
|
||||
.withPropertyValues("server.port=0"));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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.autoconfigure.integrationtest;
|
||||
|
||||
import org.springframework.boot.actuate.autoconfigure.beans.BeansEndpointAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.system.DiskSpaceHealthContributorAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.web.reactive.ReactiveManagementContextAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration;
|
||||
import org.springframework.boot.test.context.assertj.AssertableReactiveWebApplicationContext;
|
||||
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
|
||||
import org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer;
|
||||
import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext;
|
||||
import org.springframework.boot.web.reactive.context.ConfigurableReactiveWebApplicationContext;
|
||||
|
||||
/**
|
||||
* Integration tests for Webflux health groups on an additional path.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
class WebfluxHealthEndpointAdditionalPathIntegrationTests extends
|
||||
AbstractHealthEndpointAdditionalPathIntegrationTests<ReactiveWebApplicationContextRunner, ConfigurableReactiveWebApplicationContext, AssertableReactiveWebApplicationContext> {
|
||||
|
||||
WebfluxHealthEndpointAdditionalPathIntegrationTests() {
|
||||
super(new ReactiveWebApplicationContextRunner(AnnotationConfigReactiveWebServerApplicationContext::new)
|
||||
.withConfiguration(AutoConfigurations.of(JacksonAutoConfiguration.class, CodecsAutoConfiguration.class,
|
||||
WebFluxAutoConfiguration.class, HttpHandlerAutoConfiguration.class,
|
||||
EndpointAutoConfiguration.class, HealthEndpointAutoConfiguration.class,
|
||||
DiskSpaceHealthContributorAutoConfiguration.class, WebEndpointAutoConfiguration.class,
|
||||
ManagementContextAutoConfiguration.class, ReactiveWebServerFactoryAutoConfiguration.class,
|
||||
ReactiveManagementContextAutoConfiguration.class, BeansEndpointAutoConfiguration.class))
|
||||
.withInitializer(new ServerPortInfoApplicationContextInitializer())
|
||||
.withPropertyValues("server.port=0"));
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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.");
|
||||
}
|
||||
|
||||
}
|
@ -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"));
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue