Skip to content

Commit 10f887a

Browse files
committed
Introduce management base-path property for servlet and reactive actuator
Previously, the base path of a servlet-based management server could be configured using management.server.servlet.context-path but there was no equivalent property for WebFlux. This commit introduces a new property, management.server.base-path, that can be used with both servlet and reactive management servers. The existing servlet-specific property has been deprecated in favour of the new general property. When using the servlet stack, if both the general property and the servlet-specific property are set, the new general property takes precedence. When using the reactive stack, only the new general property is considered. Closes gh-22906
1 parent 653e64c commit 10f887a

File tree

11 files changed

+258
-21
lines changed

11 files changed

+258
-21
lines changed

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointProperties.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -38,8 +38,11 @@ public class WebEndpointProperties {
3838
private final Exposure exposure = new Exposure();
3939

4040
/**
41-
* Base path for Web endpoints. Relative to server.servlet.context-path or
42-
* management.server.servlet.context-path if management.server.port is configured.
41+
* Base path for Web endpoints. Relative to the servlet context path
42+
* (server.servlet.context-path) or WebFlux base path (spring.webflux.base-path) when
43+
* the management server is sharing the main server port. Relative to the management
44+
* server base path (management.server.base-path) when a separate management server
45+
* port (management.server.port) is configured.
4346
*/
4447
private String basePath = "/actuator";
4548

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/reactive/ReactiveManagementChildContextConfiguration.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,13 @@
1616

1717
package org.springframework.boot.actuate.autoconfigure.web.reactive;
1818

19+
import java.util.Collections;
20+
import java.util.Map;
21+
1922
import org.springframework.beans.factory.ListableBeanFactory;
2023
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration;
2124
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextType;
25+
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementServerProperties;
2226
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementWebServerFactoryCustomizer;
2327
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
2428
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
@@ -31,7 +35,9 @@
3135
import org.springframework.boot.web.reactive.server.ConfigurableReactiveWebServerFactory;
3236
import org.springframework.context.ApplicationContext;
3337
import org.springframework.context.annotation.Bean;
38+
import org.springframework.http.server.reactive.ContextPathCompositeHandler;
3439
import org.springframework.http.server.reactive.HttpHandler;
40+
import org.springframework.util.StringUtils;
3541
import org.springframework.web.reactive.config.EnableWebFlux;
3642
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
3743

@@ -56,8 +62,13 @@ public ReactiveManagementWebServerFactoryCustomizer reactiveManagementWebServerF
5662
}
5763

5864
@Bean
59-
public HttpHandler httpHandler(ApplicationContext applicationContext) {
60-
return WebHttpHandlerBuilder.applicationContext(applicationContext).build();
65+
public HttpHandler httpHandler(ApplicationContext applicationContext, ManagementServerProperties properties) {
66+
HttpHandler httpHandler = WebHttpHandlerBuilder.applicationContext(applicationContext).build();
67+
if (StringUtils.hasText(properties.getBasePath())) {
68+
Map<String, HttpHandler> handlersMap = Collections.singletonMap(properties.getBasePath(), httpHandler);
69+
return new ContextPathCompositeHandler(handlersMap);
70+
}
71+
return httpHandler;
6172
}
6273

6374
static class ReactiveManagementWebServerFactoryCustomizer

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/server/ManagementServerProperties.java

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@
2020

2121
import org.springframework.boot.autoconfigure.web.ServerProperties;
2222
import org.springframework.boot.context.properties.ConfigurationProperties;
23+
import org.springframework.boot.context.properties.DeprecatedConfigurationProperty;
2324
import org.springframework.boot.context.properties.NestedConfigurationProperty;
2425
import org.springframework.boot.web.server.Ssl;
2526
import org.springframework.util.Assert;
@@ -49,6 +50,12 @@ public class ManagementServerProperties {
4950
*/
5051
private InetAddress address;
5152

53+
/**
54+
* Management endpoint base path (for instance, `/management`). Requires a custom
55+
* management.server.port.
56+
*/
57+
private String basePath = "";
58+
5259
private final Servlet servlet = new Servlet();
5360

5461
@NestedConfigurationProperty
@@ -82,6 +89,14 @@ public void setAddress(InetAddress address) {
8289
this.address = address;
8390
}
8491

92+
public String getBasePath() {
93+
return this.basePath;
94+
}
95+
96+
public void setBasePath(String basePath) {
97+
this.basePath = cleanBasePath(basePath);
98+
}
99+
85100
public Ssl getSsl() {
86101
return this.ssl;
87102
}
@@ -94,6 +109,19 @@ public Servlet getServlet() {
94109
return this.servlet;
95110
}
96111

112+
private String cleanBasePath(String basePath) {
113+
String candidate = StringUtils.trimWhitespace(basePath);
114+
if (StringUtils.hasText(candidate)) {
115+
if (!candidate.startsWith("/")) {
116+
candidate = "/" + candidate;
117+
}
118+
if (candidate.endsWith("/")) {
119+
candidate = candidate.substring(0, candidate.length() - 1);
120+
}
121+
}
122+
return candidate;
123+
}
124+
97125
/**
98126
* Servlet properties.
99127
*/
@@ -109,11 +137,22 @@ public static class Servlet {
109137
* Return the context path with no trailing slash (i.e. the '/' root context is
110138
* represented as the empty string).
111139
* @return the context path (no trailing slash)
140+
* @deprecated since 2.4.0 in favor of
141+
* {@link ManagementServerProperties#getBasePath()}
112142
*/
143+
@Deprecated
144+
@DeprecatedConfigurationProperty(replacement = "management.server.base-path")
113145
public String getContextPath() {
114146
return this.contextPath;
115147
}
116148

149+
/**
150+
* Set the context path.
151+
* @param contextPath the context path
152+
* @deprecated since 2.4.0 in favor of
153+
* {@link ManagementServerProperties#setBasePath(String)}
154+
*/
155+
@Deprecated
117156
public void setContextPath(String contextPath) {
118157
Assert.notNull(contextPath, "ContextPath must not be null");
119158
this.contextPath = cleanContextPath(contextPath);

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/servlet/ServletManagementChildContextConfiguration.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,13 @@ static class ServletManagementWebServerFactoryCustomizer
123123
protected void customize(ConfigurableServletWebServerFactory webServerFactory,
124124
ManagementServerProperties managementServerProperties, ServerProperties serverProperties) {
125125
super.customize(webServerFactory, managementServerProperties, serverProperties);
126-
webServerFactory.setContextPath(managementServerProperties.getServlet().getContextPath());
126+
webServerFactory.setContextPath(getContextPath(managementServerProperties));
127+
}
128+
129+
@SuppressWarnings("deprecation")
130+
private String getContextPath(ManagementServerProperties managementServerProperties) {
131+
String basePath = managementServerProperties.getBasePath();
132+
return StringUtils.hasText(basePath) ? basePath : managementServerProperties.getServlet().getContextPath();
127133
}
128134

129135
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*
2+
* Copyright 2012-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.actuate.autoconfigure.web.reactive;
18+
19+
import java.util.function.Consumer;
20+
21+
import org.junit.jupiter.api.Test;
22+
23+
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;
24+
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration;
25+
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration;
26+
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
27+
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
28+
import org.springframework.boot.autoconfigure.AutoConfigurations;
29+
import org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration;
30+
import org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration;
31+
import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration;
32+
import org.springframework.boot.test.context.assertj.AssertableReactiveWebApplicationContext;
33+
import org.springframework.boot.test.context.runner.ContextConsumer;
34+
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
35+
import org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer;
36+
import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext;
37+
import org.springframework.http.MediaType;
38+
import org.springframework.web.reactive.function.client.WebClient;
39+
40+
import static org.assertj.core.api.Assertions.assertThat;
41+
42+
/**
43+
* Integration tests for {@link ReactiveManagementChildContextConfiguration}.
44+
*
45+
* @author Andy Wilkinson
46+
*/
47+
class ReactiveManagementChildContextConfigurationIntegrationTests {
48+
49+
private final ReactiveWebApplicationContextRunner runner = new ReactiveWebApplicationContextRunner(
50+
AnnotationConfigReactiveWebServerApplicationContext::new)
51+
.withConfiguration(AutoConfigurations.of(ManagementContextAutoConfiguration.class,
52+
ReactiveWebServerFactoryAutoConfiguration.class,
53+
ReactiveManagementContextAutoConfiguration.class, WebEndpointAutoConfiguration.class,
54+
EndpointAutoConfiguration.class, HttpHandlerAutoConfiguration.class,
55+
WebFluxAutoConfiguration.class))
56+
.withUserConfiguration(SucceedingEndpoint.class)
57+
.withInitializer(new ServerPortInfoApplicationContextInitializer()).withPropertyValues(
58+
"server.port=0", "management.server.port=0", "management.endpoints.web.exposure.include=*");
59+
60+
@Test
61+
void endpointsAreBeneathActuatorByDefault() {
62+
this.runner.withPropertyValues("management.server.port:0").run(withWebTestClient((client) -> {
63+
String body = client.get().uri("actuator/success").accept(MediaType.APPLICATION_JSON)
64+
.exchangeToMono((response) -> response.bodyToMono(String.class)).block();
65+
assertThat(body).isEqualTo("Success");
66+
}));
67+
}
68+
69+
@Test
70+
void whenManagementServerBasePathIsConfiguredThenEndpointsAreBeneathThatPath() {
71+
this.runner.withPropertyValues("management.server.port:0", "management.server.base-path:/manage")
72+
.run(withWebTestClient((client) -> {
73+
String body = client.get().uri("manage/actuator/success").accept(MediaType.APPLICATION_JSON)
74+
.exchangeToMono((response) -> response.bodyToMono(String.class)).block();
75+
assertThat(body).isEqualTo("Success");
76+
}));
77+
}
78+
79+
private ContextConsumer<AssertableReactiveWebApplicationContext> withWebTestClient(Consumer<WebClient> webClient) {
80+
return (context) -> {
81+
String port = context.getEnvironment().getProperty("local.management.port");
82+
WebClient client = WebClient.create("http://localhost:" + port);
83+
webClient.accept(client);
84+
};
85+
}
86+
87+
@Endpoint(id = "success")
88+
static class SucceedingEndpoint {
89+
90+
@ReadOperation
91+
String fail() {
92+
return "Success";
93+
}
94+
95+
}
96+
97+
}

spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/server/ManagementServerPropertiesTests.java

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -29,33 +29,74 @@
2929
class ManagementServerPropertiesTests {
3030

3131
@Test
32-
void defaultManagementServerProperties() {
32+
void defaultPortIsNull() {
3333
ManagementServerProperties properties = new ManagementServerProperties();
3434
assertThat(properties.getPort()).isNull();
35-
assertThat(properties.getServlet().getContextPath()).isEqualTo("");
3635
}
3736

3837
@Test
39-
void definedManagementServerProperties() {
38+
void definedPort() {
4039
ManagementServerProperties properties = new ManagementServerProperties();
4140
properties.setPort(123);
42-
properties.getServlet().setContextPath("/foo");
4341
assertThat(properties.getPort()).isEqualTo(123);
42+
}
43+
44+
@Test
45+
@Deprecated
46+
void defaultContextPathIsEmptyString() {
47+
ManagementServerProperties properties = new ManagementServerProperties();
48+
assertThat(properties.getServlet().getContextPath()).isEqualTo("");
49+
}
50+
51+
@Test
52+
@Deprecated
53+
void definedContextPath() {
54+
ManagementServerProperties properties = new ManagementServerProperties();
55+
properties.getServlet().setContextPath("/foo");
4456
assertThat(properties.getServlet().getContextPath()).isEqualTo("/foo");
4557
}
4658

4759
@Test
60+
void defaultBasePathIsEmptyString() {
61+
ManagementServerProperties properties = new ManagementServerProperties();
62+
assertThat(properties.getBasePath()).isEqualTo("");
63+
}
64+
65+
@Test
66+
void definedBasePath() {
67+
ManagementServerProperties properties = new ManagementServerProperties();
68+
properties.setBasePath("/foo");
69+
assertThat(properties.getBasePath()).isEqualTo("/foo");
70+
}
71+
72+
@Test
73+
@Deprecated
4874
void trailingSlashOfContextPathIsRemoved() {
4975
ManagementServerProperties properties = new ManagementServerProperties();
5076
properties.getServlet().setContextPath("/foo/");
5177
assertThat(properties.getServlet().getContextPath()).isEqualTo("/foo");
5278
}
5379

5480
@Test
81+
void trailingSlashOfBasePathIsRemoved() {
82+
ManagementServerProperties properties = new ManagementServerProperties();
83+
properties.setBasePath("/foo/");
84+
assertThat(properties.getBasePath()).isEqualTo("/foo");
85+
}
86+
87+
@Test
88+
@Deprecated
5589
void slashOfContextPathIsDefaultValue() {
5690
ManagementServerProperties properties = new ManagementServerProperties();
5791
properties.getServlet().setContextPath("/");
5892
assertThat(properties.getServlet().getContextPath()).isEqualTo("");
5993
}
6094

95+
@Test
96+
void slashOfBasePathIsDefaultValue() {
97+
ManagementServerProperties properties = new ManagementServerProperties();
98+
properties.setBasePath("/");
99+
assertThat(properties.getBasePath()).isEqualTo("");
100+
}
101+
61102
}

0 commit comments

Comments
 (0)