Skip to content

Commit 0af4536

Browse files
committed
Ensure media types are used consistently across endpoint mappings
Previously, the media types that are consumed and produced by endpoints were configured in the web stack-specific configuration. Furthermore, these configured media types were not used for the discovery "endpoint" that links to all the available endpoints. This commit introduces EndpointMediaTypes that is configred in a single, central location and then used to configure the consumed and produced media types for endpoints exposed via WebFlux, Web MVC, and Jersey as well as the discovery "endpoint" provided by each. Closes gh-10659
1 parent 4c5c510 commit 0af4536

24 files changed

+370
-133
lines changed

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryActuatorAutoConfiguration.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointProvider;
2222
import org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration;
23+
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
2324
import org.springframework.boot.actuate.endpoint.web.WebEndpointOperation;
2425
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
2526
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
@@ -67,11 +68,12 @@ static class MvcWebEndpointConfiguration {
6768

6869
@Bean
6970
public CloudFoundryWebEndpointServletHandlerMapping cloudFoundryWebEndpointServletHandlerMapping(
70-
EndpointProvider<WebEndpointOperation> provider, Environment environment,
71+
EndpointProvider<WebEndpointOperation> provider,
72+
EndpointMediaTypes endpointMediaTypes, Environment environment,
7173
RestTemplateBuilder builder) {
7274
return new CloudFoundryWebEndpointServletHandlerMapping(
7375
new EndpointMapping("/cloudfoundryapplication"),
74-
provider.getEndpoints(), getCorsConfiguration(),
76+
provider.getEndpoints(), endpointMediaTypes, getCorsConfiguration(),
7577
getSecurityInterceptor(builder, environment));
7678
}
7779

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryWebEndpointServletHandlerMapping.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import org.springframework.boot.actuate.endpoint.ParameterMappingException;
3737
import org.springframework.boot.actuate.endpoint.ParametersMissingException;
3838
import org.springframework.boot.actuate.endpoint.web.EndpointLinksResolver;
39+
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
3940
import org.springframework.boot.actuate.endpoint.web.Link;
4041
import org.springframework.boot.actuate.endpoint.web.WebEndpointOperation;
4142
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
@@ -76,9 +77,9 @@ class CloudFoundryWebEndpointServletHandlerMapping
7677

7778
CloudFoundryWebEndpointServletHandlerMapping(EndpointMapping endpointMapping,
7879
Collection<EndpointInfo<WebEndpointOperation>> webEndpoints,
79-
CorsConfiguration corsConfiguration,
80+
EndpointMediaTypes endpointMediaTypes, CorsConfiguration corsConfiguration,
8081
CloudFoundrySecurityInterceptor securityInterceptor) {
81-
super(endpointMapping, webEndpoints, corsConfiguration);
82+
super(endpointMapping, webEndpoints, endpointMediaTypes, corsConfiguration);
8283
this.securityInterceptor = securityInterceptor;
8384
}
8485

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.springframework.boot.actuate.endpoint.cache.CachingConfigurationFactory;
2626
import org.springframework.boot.actuate.endpoint.convert.ConversionServiceOperationParameterMapper;
2727
import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType;
28+
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
2829
import org.springframework.boot.actuate.endpoint.web.WebEndpointOperation;
2930
import org.springframework.boot.actuate.endpoint.web.annotation.WebAnnotationEndpointDiscoverer;
3031
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
@@ -71,14 +72,19 @@ static class EndpointWebConfiguration {
7172
this.applicationContext = applicationContext;
7273
}
7374

75+
@Bean
76+
public EndpointMediaTypes endpointMediaTypes() {
77+
return new EndpointMediaTypes(MEDIA_TYPES, MEDIA_TYPES);
78+
}
79+
7480
@Bean
7581
public EndpointProvider<WebEndpointOperation> webEndpointProvider(
7682
OperationParameterMapper parameterMapper,
7783
DefaultCachingConfigurationFactory cachingConfigurationFactory) {
7884
Environment environment = this.applicationContext.getEnvironment();
7985
WebAnnotationEndpointDiscoverer endpointDiscoverer = new WebAnnotationEndpointDiscoverer(
8086
this.applicationContext, parameterMapper, cachingConfigurationFactory,
81-
MEDIA_TYPES, MEDIA_TYPES);
87+
endpointMediaTypes());
8288
return new EndpointProvider<>(environment, endpointDiscoverer,
8389
EndpointExposure.WEB);
8490
}

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
2727
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration;
2828
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
29+
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
2930
import org.springframework.boot.actuate.endpoint.web.WebEndpointOperation;
3031
import org.springframework.boot.actuate.endpoint.web.jersey.JerseyEndpointResourceFactory;
3132
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
@@ -55,11 +56,12 @@ class JerseyWebEndpointManagementContextConfiguration {
5556
@Bean
5657
public ResourceConfigCustomizer webEndpointRegistrar(
5758
EndpointProvider<WebEndpointOperation> provider,
59+
EndpointMediaTypes endpointMediaTypes,
5860
WebEndpointProperties webEndpointProperties) {
5961
return (resourceConfig) -> resourceConfig.registerResources(
6062
new HashSet<>(new JerseyEndpointResourceFactory().createEndpointResources(
6163
new EndpointMapping(webEndpointProperties.getBasePath()),
62-
provider.getEndpoints())));
64+
provider.getEndpoints(), endpointMediaTypes)));
6365
}
6466

6567
@Bean

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
2323
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration;
2424
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
25+
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
2526
import org.springframework.boot.actuate.endpoint.web.WebEndpointOperation;
2627
import org.springframework.boot.actuate.endpoint.web.reactive.WebFluxEndpointHandlerMapping;
2728
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@@ -45,10 +46,11 @@ public class WebFluxEndpointManagementContextConfiguration {
4546
@ConditionalOnMissingBean
4647
public WebFluxEndpointHandlerMapping webEndpointReactiveHandlerMapping(
4748
EndpointProvider<WebEndpointOperation> provider,
49+
EndpointMediaTypes endpointMediaTypes,
4850
WebEndpointProperties webEndpointProperties) {
4951
return new WebFluxEndpointHandlerMapping(
5052
new EndpointMapping(webEndpointProperties.getBasePath()),
51-
provider.getEndpoints());
53+
provider.getEndpoints(), endpointMediaTypes);
5254
}
5355

5456
@Bean

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration;
2424
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementServerProperties;
2525
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
26+
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
2627
import org.springframework.boot.actuate.endpoint.web.WebEndpointOperation;
2728
import org.springframework.boot.actuate.endpoint.web.servlet.WebMvcEndpointHandlerMapping;
2829
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
@@ -56,11 +57,12 @@ public class WebMvcEndpointManagementContextConfiguration {
5657
@ConditionalOnMissingBean
5758
public WebMvcEndpointHandlerMapping webEndpointServletHandlerMapping(
5859
EndpointProvider<WebEndpointOperation> provider,
60+
EndpointMediaTypes endpointMediaTypes,
5961
CorsEndpointProperties corsProperties,
6062
WebEndpointProperties webEndpointProperties) {
6163
WebMvcEndpointHandlerMapping handlerMapping = new WebMvcEndpointHandlerMapping(
6264
new EndpointMapping(webEndpointProperties.getBasePath()),
63-
provider.getEndpoints(), getCorsConfiguration(corsProperties));
65+
provider.getEndpoints(), endpointMediaTypes, getCorsConfiguration(corsProperties));
6466
return handlerMapping;
6567
}
6668

spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryActuatorAutoConfigurationTests.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;
2626
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration;
2727
import org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration;
28+
import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType;
2829
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
2930
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
3031
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
@@ -40,11 +41,15 @@
4041
import org.springframework.security.web.FilterChainProxy;
4142
import org.springframework.security.web.SecurityFilterChain;
4243
import org.springframework.test.util.ReflectionTestUtils;
44+
import org.springframework.test.web.servlet.MockMvc;
45+
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
4346
import org.springframework.web.client.RestTemplate;
4447
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
4548
import org.springframework.web.cors.CorsConfiguration;
4649

4750
import static org.assertj.core.api.Assertions.assertThat;
51+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
52+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
4853

4954
/**
5055
* Tests for {@link CloudFoundryActuatorAutoConfiguration}.
@@ -92,6 +97,18 @@ public void cloudFoundryPlatformActive() throws Exception {
9297
Arrays.asList("Authorization", "X-Cf-App-Instance", "Content-Type"));
9398
}
9499

100+
@Test
101+
public void cloudfoundryapplicationProducesActuatorMediaType() throws Exception {
102+
TestPropertyValues
103+
.of("VCAP_APPLICATION:---", "vcap.application.application_id:my-app-id",
104+
"vcap.application.cf_api:http://my-cloud-controller.com")
105+
.applyTo(this.context);
106+
this.context.refresh();
107+
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build();
108+
mockMvc.perform(get("/cloudfoundryapplication")).andExpect(header()
109+
.string("Content-Type", ActuatorMediaType.V2_JSON + ";charset=UTF-8"));
110+
}
111+
95112
@Test
96113
public void cloudFoundryPlatformActiveSetsApplicationId() throws Exception {
97114
CloudFoundryWebEndpointServletHandlerMapping handlerMapping = getHandlerMapping();

spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryMvcWebEndpointIntegrationTests.java

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.springframework.boot.actuate.endpoint.annotation.WriteOperation;
3232
import org.springframework.boot.actuate.endpoint.cache.CachingConfiguration;
3333
import org.springframework.boot.actuate.endpoint.convert.ConversionServiceOperationParameterMapper;
34+
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
3435
import org.springframework.boot.actuate.endpoint.web.annotation.WebAnnotationEndpointDiscoverer;
3536
import org.springframework.boot.endpoint.web.EndpointMapping;
3637
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
@@ -190,28 +191,35 @@ public CloudFoundrySecurityInterceptor interceptor() {
190191
"app-id");
191192
}
192193

194+
@Bean
195+
public EndpointMediaTypes EndpointMediaTypes() {
196+
return new EndpointMediaTypes(Collections.singletonList("application/json"),
197+
Collections.singletonList("application/json"));
198+
}
199+
193200
@Bean
194201
public CloudFoundryWebEndpointServletHandlerMapping cloudFoundryWebEndpointServletHandlerMapping(
195202
WebAnnotationEndpointDiscoverer webEndpointDiscoverer,
203+
EndpointMediaTypes endpointMediaTypes,
196204
CloudFoundrySecurityInterceptor interceptor) {
197205
CorsConfiguration corsConfiguration = new CorsConfiguration();
198206
corsConfiguration.setAllowedOrigins(Arrays.asList("http://example.com"));
199207
corsConfiguration.setAllowedMethods(Arrays.asList("GET", "POST"));
200208
return new CloudFoundryWebEndpointServletHandlerMapping(
201209
new EndpointMapping("/cfApplication"),
202-
webEndpointDiscoverer.discoverEndpoints(), corsConfiguration,
203-
interceptor);
210+
webEndpointDiscoverer.discoverEndpoints(), endpointMediaTypes,
211+
corsConfiguration, interceptor);
204212
}
205213

206214
@Bean
207215
public WebAnnotationEndpointDiscoverer webEndpointDiscoverer(
208-
ApplicationContext applicationContext) {
216+
ApplicationContext applicationContext,
217+
EndpointMediaTypes endpointMediaTypes) {
209218
OperationParameterMapper parameterMapper = new ConversionServiceOperationParameterMapper(
210219
DefaultConversionService.getSharedInstance());
211220
return new WebAnnotationEndpointDiscoverer(applicationContext,
212221
parameterMapper, (id) -> new CachingConfiguration(0),
213-
Collections.singletonList("application/json"),
214-
Collections.singletonList("application/json"));
222+
endpointMediaTypes);
215223
}
216224

217225
@Bean
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright 2012-2017 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+
* http://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.endpoint;
18+
19+
import org.junit.Test;
20+
21+
import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType;
22+
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
23+
import org.springframework.boot.autoconfigure.AutoConfigurations;
24+
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
25+
26+
import static org.assertj.core.api.Assertions.assertThat;
27+
28+
/**
29+
* Tests for {@link EndpointAutoConfiguration}.
30+
*
31+
* @author Andy Wilkinson
32+
*/
33+
public class EndpointAutoConfigurationTests {
34+
35+
@Test
36+
public void webApplicationConfiguresEndpointMediaTypes() {
37+
new WebApplicationContextRunner()
38+
.withConfiguration(AutoConfigurations.of(EndpointAutoConfiguration.class))
39+
.run((context) -> {
40+
EndpointMediaTypes endpointMediaTypes = context
41+
.getBean(EndpointMediaTypes.class);
42+
assertThat(endpointMediaTypes.getConsumed()).containsExactly(
43+
ActuatorMediaType.V2_JSON, "application/json");
44+
});
45+
}
46+
47+
}

spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/servlet/RequestMappingEndpointTests.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

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

19+
import java.util.Arrays;
1920
import java.util.Collections;
2021
import java.util.Map;
2122

@@ -24,6 +25,7 @@
2425
import org.springframework.boot.actuate.endpoint.DefaultEnablement;
2526
import org.springframework.boot.actuate.endpoint.EndpointInfo;
2627
import org.springframework.boot.actuate.endpoint.OperationType;
28+
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
2729
import org.springframework.boot.actuate.endpoint.web.OperationRequestPredicate;
2830
import org.springframework.boot.actuate.endpoint.web.WebEndpointHttpMethod;
2931
import org.springframework.boot.actuate.endpoint.web.WebEndpointOperation;
@@ -142,7 +144,9 @@ private WebMvcEndpointHandlerMapping createHandlerMapping() {
142144
WebMvcEndpointHandlerMapping mapping = new WebMvcEndpointHandlerMapping(
143145
new EndpointMapping("application"),
144146
Collections.singleton(new EndpointInfo<>("test",
145-
DefaultEnablement.ENABLED, Collections.singleton(operation))));
147+
DefaultEnablement.ENABLED, Collections.singleton(operation))),
148+
new EndpointMediaTypes(Arrays.asList("application/vnd.test+json"),
149+
Arrays.asList("application/vnd.test+json")));
146150
mapping.setApplicationContext(new StaticApplicationContext());
147151
mapping.afterPropertiesSet();
148152
return mapping;

0 commit comments

Comments
 (0)