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
This commit is contained in:
Andy Wilkinson 2017-10-17 12:09:19 +01:00
parent 4c5c5105b3
commit 0af4536316
24 changed files with 370 additions and 133 deletions

View File

@ -20,6 +20,7 @@ import java.util.Arrays;
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointProvider;
import org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
import org.springframework.boot.actuate.endpoint.web.WebEndpointOperation;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
@ -67,11 +68,12 @@ public class CloudFoundryActuatorAutoConfiguration {
@Bean
public CloudFoundryWebEndpointServletHandlerMapping cloudFoundryWebEndpointServletHandlerMapping(
EndpointProvider<WebEndpointOperation> provider, Environment environment,
EndpointProvider<WebEndpointOperation> provider,
EndpointMediaTypes endpointMediaTypes, Environment environment,
RestTemplateBuilder builder) {
return new CloudFoundryWebEndpointServletHandlerMapping(
new EndpointMapping("/cloudfoundryapplication"),
provider.getEndpoints(), getCorsConfiguration(),
provider.getEndpoints(), endpointMediaTypes, getCorsConfiguration(),
getSecurityInterceptor(builder, environment));
}

View File

@ -36,6 +36,7 @@ import org.springframework.boot.actuate.endpoint.OperationInvoker;
import org.springframework.boot.actuate.endpoint.ParameterMappingException;
import org.springframework.boot.actuate.endpoint.ParametersMissingException;
import org.springframework.boot.actuate.endpoint.web.EndpointLinksResolver;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
import org.springframework.boot.actuate.endpoint.web.Link;
import org.springframework.boot.actuate.endpoint.web.WebEndpointOperation;
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
@ -76,9 +77,9 @@ class CloudFoundryWebEndpointServletHandlerMapping
CloudFoundryWebEndpointServletHandlerMapping(EndpointMapping endpointMapping,
Collection<EndpointInfo<WebEndpointOperation>> webEndpoints,
CorsConfiguration corsConfiguration,
EndpointMediaTypes endpointMediaTypes, CorsConfiguration corsConfiguration,
CloudFoundrySecurityInterceptor securityInterceptor) {
super(endpointMapping, webEndpoints, corsConfiguration);
super(endpointMapping, webEndpoints, endpointMediaTypes, corsConfiguration);
this.securityInterceptor = securityInterceptor;
}

View File

@ -25,6 +25,7 @@ import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.cache.CachingConfigurationFactory;
import org.springframework.boot.actuate.endpoint.convert.ConversionServiceOperationParameterMapper;
import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
import org.springframework.boot.actuate.endpoint.web.WebEndpointOperation;
import org.springframework.boot.actuate.endpoint.web.annotation.WebAnnotationEndpointDiscoverer;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
@ -71,6 +72,11 @@ public class EndpointAutoConfiguration {
this.applicationContext = applicationContext;
}
@Bean
public EndpointMediaTypes endpointMediaTypes() {
return new EndpointMediaTypes(MEDIA_TYPES, MEDIA_TYPES);
}
@Bean
public EndpointProvider<WebEndpointOperation> webEndpointProvider(
OperationParameterMapper parameterMapper,
@ -78,7 +84,7 @@ public class EndpointAutoConfiguration {
Environment environment = this.applicationContext.getEnvironment();
WebAnnotationEndpointDiscoverer endpointDiscoverer = new WebAnnotationEndpointDiscoverer(
this.applicationContext, parameterMapper, cachingConfigurationFactory,
MEDIA_TYPES, MEDIA_TYPES);
endpointMediaTypes());
return new EndpointProvider<>(environment, endpointDiscoverer,
EndpointExposure.WEB);
}

View File

@ -26,6 +26,7 @@ import org.springframework.boot.actuate.autoconfigure.endpoint.web.EndpointPathP
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
import org.springframework.boot.actuate.endpoint.web.WebEndpointOperation;
import org.springframework.boot.actuate.endpoint.web.jersey.JerseyEndpointResourceFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
@ -55,11 +56,12 @@ class JerseyWebEndpointManagementContextConfiguration {
@Bean
public ResourceConfigCustomizer webEndpointRegistrar(
EndpointProvider<WebEndpointOperation> provider,
EndpointMediaTypes endpointMediaTypes,
WebEndpointProperties webEndpointProperties) {
return (resourceConfig) -> resourceConfig.registerResources(
new HashSet<>(new JerseyEndpointResourceFactory().createEndpointResources(
new EndpointMapping(webEndpointProperties.getBasePath()),
provider.getEndpoints())));
provider.getEndpoints(), endpointMediaTypes)));
}
@Bean

View File

@ -22,6 +22,7 @@ import org.springframework.boot.actuate.autoconfigure.endpoint.web.EndpointPathP
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
import org.springframework.boot.actuate.endpoint.web.WebEndpointOperation;
import org.springframework.boot.actuate.endpoint.web.reactive.WebFluxEndpointHandlerMapping;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@ -45,10 +46,11 @@ public class WebFluxEndpointManagementContextConfiguration {
@ConditionalOnMissingBean
public WebFluxEndpointHandlerMapping webEndpointReactiveHandlerMapping(
EndpointProvider<WebEndpointOperation> provider,
EndpointMediaTypes endpointMediaTypes,
WebEndpointProperties webEndpointProperties) {
return new WebFluxEndpointHandlerMapping(
new EndpointMapping(webEndpointProperties.getBasePath()),
provider.getEndpoints());
provider.getEndpoints(), endpointMediaTypes);
}
@Bean

View File

@ -23,6 +23,7 @@ import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointPr
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration;
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementServerProperties;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
import org.springframework.boot.actuate.endpoint.web.WebEndpointOperation;
import org.springframework.boot.actuate.endpoint.web.servlet.WebMvcEndpointHandlerMapping;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
@ -56,11 +57,12 @@ public class WebMvcEndpointManagementContextConfiguration {
@ConditionalOnMissingBean
public WebMvcEndpointHandlerMapping webEndpointServletHandlerMapping(
EndpointProvider<WebEndpointOperation> provider,
EndpointMediaTypes endpointMediaTypes,
CorsEndpointProperties corsProperties,
WebEndpointProperties webEndpointProperties) {
WebMvcEndpointHandlerMapping handlerMapping = new WebMvcEndpointHandlerMapping(
new EndpointMapping(webEndpointProperties.getBasePath()),
provider.getEndpoints(), getCorsConfiguration(corsProperties));
provider.getEndpoints(), endpointMediaTypes, getCorsConfiguration(corsProperties));
return handlerMapping;
}

View File

@ -25,6 +25,7 @@ import org.junit.Test;
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration;
import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType;
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
@ -40,11 +41,15 @@ import org.springframework.mock.web.MockServletContext;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.cors.CorsConfiguration;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
/**
* Tests for {@link CloudFoundryActuatorAutoConfiguration}.
@ -92,6 +97,18 @@ public class CloudFoundryActuatorAutoConfigurationTests {
Arrays.asList("Authorization", "X-Cf-App-Instance", "Content-Type"));
}
@Test
public void cloudfoundryapplicationProducesActuatorMediaType() throws Exception {
TestPropertyValues
.of("VCAP_APPLICATION:---", "vcap.application.application_id:my-app-id",
"vcap.application.cf_api:http://my-cloud-controller.com")
.applyTo(this.context);
this.context.refresh();
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build();
mockMvc.perform(get("/cloudfoundryapplication")).andExpect(header()
.string("Content-Type", ActuatorMediaType.V2_JSON + ";charset=UTF-8"));
}
@Test
public void cloudFoundryPlatformActiveSetsApplicationId() throws Exception {
CloudFoundryWebEndpointServletHandlerMapping handlerMapping = getHandlerMapping();

View File

@ -31,6 +31,7 @@ import org.springframework.boot.actuate.endpoint.annotation.Selector;
import org.springframework.boot.actuate.endpoint.annotation.WriteOperation;
import org.springframework.boot.actuate.endpoint.cache.CachingConfiguration;
import org.springframework.boot.actuate.endpoint.convert.ConversionServiceOperationParameterMapper;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
import org.springframework.boot.actuate.endpoint.web.annotation.WebAnnotationEndpointDiscoverer;
import org.springframework.boot.endpoint.web.EndpointMapping;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
@ -190,28 +191,35 @@ public class CloudFoundryMvcWebEndpointIntegrationTests {
"app-id");
}
@Bean
public EndpointMediaTypes EndpointMediaTypes() {
return new EndpointMediaTypes(Collections.singletonList("application/json"),
Collections.singletonList("application/json"));
}
@Bean
public CloudFoundryWebEndpointServletHandlerMapping cloudFoundryWebEndpointServletHandlerMapping(
WebAnnotationEndpointDiscoverer webEndpointDiscoverer,
EndpointMediaTypes endpointMediaTypes,
CloudFoundrySecurityInterceptor interceptor) {
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.setAllowedOrigins(Arrays.asList("http://example.com"));
corsConfiguration.setAllowedMethods(Arrays.asList("GET", "POST"));
return new CloudFoundryWebEndpointServletHandlerMapping(
new EndpointMapping("/cfApplication"),
webEndpointDiscoverer.discoverEndpoints(), corsConfiguration,
interceptor);
webEndpointDiscoverer.discoverEndpoints(), endpointMediaTypes,
corsConfiguration, interceptor);
}
@Bean
public WebAnnotationEndpointDiscoverer webEndpointDiscoverer(
ApplicationContext applicationContext) {
ApplicationContext applicationContext,
EndpointMediaTypes endpointMediaTypes) {
OperationParameterMapper parameterMapper = new ConversionServiceOperationParameterMapper(
DefaultConversionService.getSharedInstance());
return new WebAnnotationEndpointDiscoverer(applicationContext,
parameterMapper, (id) -> new CachingConfiguration(0),
Collections.singletonList("application/json"),
Collections.singletonList("application/json"));
endpointMediaTypes);
}
@Bean

View File

@ -0,0 +1,47 @@
/*
* Copyright 2012-2017 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
*
* http://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.endpoint;
import org.junit.Test;
import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link EndpointAutoConfiguration}.
*
* @author Andy Wilkinson
*/
public class EndpointAutoConfigurationTests {
@Test
public void webApplicationConfiguresEndpointMediaTypes() {
new WebApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(EndpointAutoConfiguration.class))
.run((context) -> {
EndpointMediaTypes endpointMediaTypes = context
.getBean(EndpointMediaTypes.class);
assertThat(endpointMediaTypes.getConsumed()).containsExactly(
ActuatorMediaType.V2_JSON, "application/json");
});
}
}

View File

@ -16,6 +16,7 @@
package org.springframework.boot.actuate.autoconfigure.web.servlet;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
@ -24,6 +25,7 @@ import org.junit.Test;
import org.springframework.boot.actuate.endpoint.DefaultEnablement;
import org.springframework.boot.actuate.endpoint.EndpointInfo;
import org.springframework.boot.actuate.endpoint.OperationType;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
import org.springframework.boot.actuate.endpoint.web.OperationRequestPredicate;
import org.springframework.boot.actuate.endpoint.web.WebEndpointHttpMethod;
import org.springframework.boot.actuate.endpoint.web.WebEndpointOperation;
@ -142,7 +144,9 @@ public class RequestMappingEndpointTests {
WebMvcEndpointHandlerMapping mapping = new WebMvcEndpointHandlerMapping(
new EndpointMapping("application"),
Collections.singleton(new EndpointInfo<>("test",
DefaultEnablement.ENABLED, Collections.singleton(operation))));
DefaultEnablement.ENABLED, Collections.singleton(operation))),
new EndpointMediaTypes(Arrays.asList("application/vnd.test+json"),
Arrays.asList("application/vnd.test+json")));
mapping.setApplicationContext(new StaticApplicationContext());
mapping.afterPropertiesSet();
return mapping;

View File

@ -0,0 +1,68 @@
/*
* Copyright 2012-2017 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
*
* http://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 java.util.Collections;
import java.util.List;
import org.springframework.util.Assert;
/**
* Media types that are, by default, produced and consumed by an endpoint.
*
* @author Andy Wilkinson
* @since 2.0.0
*/
public class EndpointMediaTypes {
private final List<String> produced;
private final List<String> consumed;
/**
* Creates a new {@link EndpointMediaTypes} with the given {@code produced} and
* {@code consumed} media types.
* @param produced the default media types that are produced by an endpoint. Must not
* be {@code null}.
* @param consumed the default media types that are consumed by an endpoint. Must not
*/
public EndpointMediaTypes(List<String> produced, List<String> consumed) {
Assert.notNull(produced, () -> "Produced must not be null");
Assert.notNull(consumed, () -> "Consumed must not be null");
this.produced = Collections.unmodifiableList(produced);
this.consumed = Collections.unmodifiableList(consumed);
}
/**
* Returns the media types produced by an endpoint.
*
* @return the produced media types
*/
public List<String> getProduced() {
return this.produced;
}
/**
* Returns the media types consumed by an endpoint.
*
* @return the consumed media types
*/
public List<String> getConsumed() {
return this.consumed;
}
}

View File

@ -39,6 +39,7 @@ import org.springframework.boot.actuate.endpoint.annotation.Selector;
import org.springframework.boot.actuate.endpoint.cache.CachingConfiguration;
import org.springframework.boot.actuate.endpoint.cache.CachingConfigurationFactory;
import org.springframework.boot.actuate.endpoint.cache.CachingOperationInvoker;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
import org.springframework.boot.actuate.endpoint.web.OperationRequestPredicate;
import org.springframework.boot.actuate.endpoint.web.WebEndpointHttpMethod;
import org.springframework.boot.actuate.endpoint.web.WebEndpointOperation;
@ -68,17 +69,16 @@ public class WebAnnotationEndpointDiscoverer extends
* @param operationParameterMapper the {@link OperationParameterMapper} used to
* convert arguments when an operation is invoked
* @param cachingConfigurationFactory the {@link CachingConfiguration} factory to use
* @param consumedMediaTypes the media types consumed by web endpoint operations
* @param producedMediaTypes the media types produced by web endpoint operations
* @param endpointMediaTypes the media types produced and consumed by web endpoint
* operations
*/
public WebAnnotationEndpointDiscoverer(ApplicationContext applicationContext,
OperationParameterMapper operationParameterMapper,
CachingConfigurationFactory cachingConfigurationFactory,
Collection<String> consumedMediaTypes,
Collection<String> producedMediaTypes) {
EndpointMediaTypes endpointMediaTypes) {
super(applicationContext,
new WebEndpointOperationFactory(operationParameterMapper,
consumedMediaTypes, producedMediaTypes),
endpointMediaTypes),
WebEndpointOperation::getRequestPredicate, cachingConfigurationFactory);
}
@ -119,16 +119,12 @@ public class WebAnnotationEndpointDiscoverer extends
private final OperationParameterMapper parameterMapper;
private final Collection<String> consumedMediaTypes;
private final Collection<String> producedMediaTypes;
private final EndpointMediaTypes endpointMediaTypes;
private WebEndpointOperationFactory(OperationParameterMapper parameterMapper,
Collection<String> consumedMediaTypes,
Collection<String> producedMediaTypes) {
EndpointMediaTypes endpointMediaTypes) {
this.parameterMapper = parameterMapper;
this.consumedMediaTypes = consumedMediaTypes;
this.producedMediaTypes = producedMediaTypes;
this.endpointMediaTypes = endpointMediaTypes;
}
@Override
@ -172,7 +168,7 @@ public class WebAnnotationEndpointDiscoverer extends
private Collection<String> determineConsumedMediaTypes(
WebEndpointHttpMethod httpMethod, Method method) {
if (WebEndpointHttpMethod.POST == httpMethod && consumesRequestBody(method)) {
return this.consumedMediaTypes;
return this.endpointMediaTypes.getConsumed();
}
return Collections.emptyList();
}
@ -189,7 +185,7 @@ public class WebAnnotationEndpointDiscoverer extends
if (producesResourceResponseBody(method)) {
return Collections.singletonList("application/octet-stream");
}
return this.producedMediaTypes;
return this.endpointMediaTypes.getProduced();
}
private boolean producesResourceResponseBody(Method method) {

View File

@ -27,7 +27,6 @@ import java.util.function.Function;
import javax.ws.rs.HttpMethod;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
@ -43,6 +42,7 @@ import org.springframework.boot.actuate.endpoint.OperationInvoker;
import org.springframework.boot.actuate.endpoint.ParameterMappingException;
import org.springframework.boot.actuate.endpoint.ParametersMissingException;
import org.springframework.boot.actuate.endpoint.web.EndpointLinksResolver;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
import org.springframework.boot.actuate.endpoint.web.Link;
import org.springframework.boot.actuate.endpoint.web.OperationRequestPredicate;
import org.springframework.boot.actuate.endpoint.web.WebEndpointOperation;
@ -68,18 +68,20 @@ public class JerseyEndpointResourceFactory {
* {@code webEndpoints}.
* @param endpointMapping the base mapping for all endpoints
* @param webEndpoints the web endpoints
* @param endpointMediaTypes media types consumed and produced by the endpoints
* @return the resources for the operations
*/
public Collection<Resource> createEndpointResources(EndpointMapping endpointMapping,
Collection<EndpointInfo<WebEndpointOperation>> webEndpoints) {
Collection<EndpointInfo<WebEndpointOperation>> webEndpoints,
EndpointMediaTypes endpointMediaTypes) {
List<Resource> resources = new ArrayList<>();
webEndpoints.stream()
.flatMap((endpointInfo) -> endpointInfo.getOperations().stream())
.map((operation) -> createResource(endpointMapping, operation))
.forEach(resources::add);
if (StringUtils.hasText(endpointMapping.getPath())) {
resources.add(
createEndpointLinksResource(endpointMapping.getPath(), webEndpoints));
resources.add(createEndpointLinksResource(endpointMapping.getPath(),
webEndpoints, endpointMediaTypes));
}
return resources;
}
@ -102,10 +104,14 @@ public class JerseyEndpointResourceFactory {
}
private Resource createEndpointLinksResource(String endpointPath,
Collection<EndpointInfo<WebEndpointOperation>> webEndpoints) {
Collection<EndpointInfo<WebEndpointOperation>> webEndpoints,
EndpointMediaTypes endpointMediaTypes) {
Builder resourceBuilder = Resource.builder().path(endpointPath);
resourceBuilder.addMethod("GET").produces(MediaType.APPLICATION_JSON).handledBy(
new EndpointLinksInflector(webEndpoints, this.endpointLinksResolver));
resourceBuilder.addMethod("GET")
.produces(endpointMediaTypes.getProduced()
.toArray(new String[endpointMediaTypes.getProduced().size()]))
.handledBy(new EndpointLinksInflector(webEndpoints,
this.endpointLinksResolver));
return resourceBuilder.build();
}

View File

@ -34,6 +34,7 @@ import org.springframework.boot.actuate.endpoint.OperationType;
import org.springframework.boot.actuate.endpoint.ParameterMappingException;
import org.springframework.boot.actuate.endpoint.ParametersMissingException;
import org.springframework.boot.actuate.endpoint.web.EndpointLinksResolver;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
import org.springframework.boot.actuate.endpoint.web.Link;
import org.springframework.boot.actuate.endpoint.web.OperationRequestPredicate;
import org.springframework.boot.actuate.endpoint.web.WebEndpointOperation;
@ -87,6 +88,8 @@ public class WebFluxEndpointHandlerMapping extends RequestMappingInfoHandlerMapp
private final Collection<EndpointInfo<WebEndpointOperation>> webEndpoints;
private final EndpointMediaTypes endpointMediaTypes;
private final CorsConfiguration corsConfiguration;
/**
@ -94,10 +97,12 @@ public class WebFluxEndpointHandlerMapping extends RequestMappingInfoHandlerMapp
* operations of the given {@code webEndpoints}.
* @param endpointMapping the base mapping for all endpoints
* @param collection the web endpoints
* @param endpointMediaTypes media types consumed and produced by the endpoints
*/
public WebFluxEndpointHandlerMapping(EndpointMapping endpointMapping,
Collection<EndpointInfo<WebEndpointOperation>> collection) {
this(endpointMapping, collection, null);
Collection<EndpointInfo<WebEndpointOperation>> collection,
EndpointMediaTypes endpointMediaTypes) {
this(endpointMapping, collection, endpointMediaTypes, null);
}
/**
@ -105,13 +110,15 @@ public class WebFluxEndpointHandlerMapping extends RequestMappingInfoHandlerMapp
* operations of the given {@code webEndpoints}.
* @param endpointMapping the path beneath which all endpoints should be mapped
* @param webEndpoints the web endpoints
* @param endpointMediaTypes media types consumed and produced by the endpoints
* @param corsConfiguration the CORS configuration for the endpoints
*/
public WebFluxEndpointHandlerMapping(EndpointMapping endpointMapping,
Collection<EndpointInfo<WebEndpointOperation>> webEndpoints,
CorsConfiguration corsConfiguration) {
EndpointMediaTypes endpointMediaTypes, CorsConfiguration corsConfiguration) {
this.endpointMapping = endpointMapping;
this.webEndpoints = webEndpoints;
this.endpointMediaTypes = endpointMediaTypes;
this.corsConfiguration = corsConfiguration;
setOrder(-100);
}
@ -127,11 +134,18 @@ public class WebFluxEndpointHandlerMapping extends RequestMappingInfoHandlerMapp
}
private void registerLinksMapping() {
registerMapping(new RequestMappingInfo(
new PatternsRequestCondition(
pathPatternParser.parse(this.endpointMapping.getPath())),
new RequestMethodsRequestCondition(RequestMethod.GET), null, null, null,
null, null), this, this.links);
registerMapping(
new RequestMappingInfo(
new PatternsRequestCondition(
pathPatternParser.parse(this.endpointMapping.getPath())),
new RequestMethodsRequestCondition(RequestMethod.GET), null, null,
null,
new ProducesRequestCondition(
this.endpointMediaTypes.getProduced()
.toArray(new String[this.endpointMediaTypes
.getProduced().size()])),
null),
this, this.links);
}
@Override

View File

@ -25,6 +25,7 @@ import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.actuate.endpoint.EndpointInfo;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
import org.springframework.boot.actuate.endpoint.web.OperationRequestPredicate;
import org.springframework.boot.actuate.endpoint.web.WebEndpointOperation;
import org.springframework.boot.endpoint.web.EndpointMapping;
@ -56,6 +57,8 @@ public abstract class AbstractWebMvcEndpointHandlerMapping
private final Collection<EndpointInfo<WebEndpointOperation>> webEndpoints;
private final EndpointMediaTypes endpointMediaTypes;
private final CorsConfiguration corsConfiguration;
/**
@ -63,10 +66,12 @@ public abstract class AbstractWebMvcEndpointHandlerMapping
* operations of the given {@code webEndpoints}.
* @param endpointMapping the base mapping for all endpoints
* @param collection the web endpoints operations
* @param endpointMediaTypes media types consumed and produced by the endpoints
*/
public AbstractWebMvcEndpointHandlerMapping(EndpointMapping endpointMapping,
Collection<EndpointInfo<WebEndpointOperation>> collection) {
this(endpointMapping, collection, null);
Collection<EndpointInfo<WebEndpointOperation>> collection,
EndpointMediaTypes endpointMediaTypes) {
this(endpointMapping, collection, endpointMediaTypes, null);
}
/**
@ -74,13 +79,15 @@ public abstract class AbstractWebMvcEndpointHandlerMapping
* operations of the given {@code webEndpoints}.
* @param endpointMapping the base mapping for all endpoints
* @param webEndpoints the web endpoints
* @param endpointMediaTypes media types consumed and produced by the endpoints
* @param corsConfiguration the CORS configuration for the endpoints
*/
public AbstractWebMvcEndpointHandlerMapping(EndpointMapping endpointMapping,
Collection<EndpointInfo<WebEndpointOperation>> webEndpoints,
CorsConfiguration corsConfiguration) {
EndpointMediaTypes endpointMediaTypes, CorsConfiguration corsConfiguration) {
this.endpointMapping = endpointMapping;
this.webEndpoints = webEndpoints;
this.endpointMediaTypes = endpointMediaTypes;
this.corsConfiguration = corsConfiguration;
setOrder(-100);
}
@ -107,8 +114,11 @@ public abstract class AbstractWebMvcEndpointHandlerMapping
PatternsRequestCondition patterns = patternsRequestConditionForPattern("");
RequestMethodsRequestCondition methods = new RequestMethodsRequestCondition(
RequestMethod.GET);
ProducesRequestCondition produces = new ProducesRequestCondition(
this.endpointMediaTypes.getProduced().toArray(
new String[this.endpointMediaTypes.getProduced().size()]));
RequestMappingInfo mapping = new RequestMappingInfo(patterns, methods, null, null,
null, null, null);
null, produces, null);
registerMapping(mapping, this, getLinks());
}

View File

@ -30,6 +30,7 @@ import org.springframework.boot.actuate.endpoint.OperationInvoker;
import org.springframework.boot.actuate.endpoint.ParameterMappingException;
import org.springframework.boot.actuate.endpoint.ParametersMissingException;
import org.springframework.boot.actuate.endpoint.web.EndpointLinksResolver;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
import org.springframework.boot.actuate.endpoint.web.Link;
import org.springframework.boot.actuate.endpoint.web.WebEndpointOperation;
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
@ -65,10 +66,12 @@ public class WebMvcEndpointHandlerMapping extends AbstractWebMvcEndpointHandlerM
* operations of the given {@code webEndpoints}.
* @param endpointMapping the base mapping for all endpoints
* @param collection the web endpoints operations
* @param endpointMediaTypes media types consumed and produced by the endpoints
*/
public WebMvcEndpointHandlerMapping(EndpointMapping endpointMapping,
Collection<EndpointInfo<WebEndpointOperation>> collection) {
this(endpointMapping, collection, null);
Collection<EndpointInfo<WebEndpointOperation>> collection,
EndpointMediaTypes endpointMediaTypes) {
this(endpointMapping, collection, endpointMediaTypes, null);
}
/**
@ -76,12 +79,13 @@ public class WebMvcEndpointHandlerMapping extends AbstractWebMvcEndpointHandlerM
* operations of the given {@code webEndpoints}.
* @param endpointMapping the base mapping for all endpoints
* @param webEndpoints the web endpoints
* @param endpointMediaTypes media types consumed and produced by the endpoints
* @param corsConfiguration the CORS configuration for the endpoints
*/
public WebMvcEndpointHandlerMapping(EndpointMapping endpointMapping,
Collection<EndpointInfo<WebEndpointOperation>> webEndpoints,
CorsConfiguration corsConfiguration) {
super(endpointMapping, webEndpoints, corsConfiguration);
EndpointMediaTypes endpointMediaTypes, CorsConfiguration corsConfiguration) {
super(endpointMapping, webEndpoints, endpointMediaTypes, corsConfiguration);
setOrder(-100);
}

View File

@ -17,6 +17,7 @@
package org.springframework.boot.actuate.endpoint.web;
import java.time.Duration;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@ -65,6 +66,10 @@ public abstract class AbstractWebEndpointIntegrationTests<T extends Configurable
private static final Duration TIMEOUT = Duration.ofMinutes(6);
private static final String ACTUATOR_MEDIA_TYPE_PATTERN = "application/vnd.test\\+json(;charset=UTF-8)?";
private static final String JSON_MEDIA_TYPE_PATTERN = "application/json(;charset=UTF-8)?";
private final Class<?> exporterConfiguration;
protected AbstractWebEndpointIntegrationTests(Class<?> exporterConfiguration) {
@ -74,41 +79,36 @@ public abstract class AbstractWebEndpointIntegrationTests<T extends Configurable
@Test
public void readOperation() {
load(TestEndpointConfiguration.class,
(client) -> client.get().uri("/test").accept(MediaType.APPLICATION_JSON)
.exchange().expectStatus().isOk().expectBody().jsonPath("All")
.isEqualTo(true));
(client) -> client.get().uri("/test").exchange().expectStatus().isOk()
.expectBody().jsonPath("All").isEqualTo(true));
}
@Test
public void readOperationWithEndpointsMappedToTheRoot() {
load(TestEndpointConfiguration.class, "",
(client) -> client.get().uri("/test").accept(MediaType.APPLICATION_JSON)
.exchange().expectStatus().isOk().expectBody().jsonPath("All")
.isEqualTo(true));
(client) -> client.get().uri("/test").exchange().expectStatus().isOk()
.expectBody().jsonPath("All").isEqualTo(true));
}
@Test
public void readOperationWithSelector() {
load(TestEndpointConfiguration.class,
(client) -> client.get().uri("/test/one")
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus()
.isOk().expectBody().jsonPath("part").isEqualTo("one"));
(client) -> client.get().uri("/test/one").exchange().expectStatus().isOk()
.expectBody().jsonPath("part").isEqualTo("one"));
}
@Test
public void readOperationWithSelectorContainingADot() {
load(TestEndpointConfiguration.class,
(client) -> client.get().uri("/test/foo.bar")
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus()
(client) -> client.get().uri("/test/foo.bar").exchange().expectStatus()
.isOk().expectBody().jsonPath("part").isEqualTo("foo.bar"));
}
@Test
public void linksToOtherEndpointsAreProvided() {
load(TestEndpointConfiguration.class,
(client) -> client.get().uri("").accept(MediaType.APPLICATION_JSON)
.exchange().expectStatus().isOk().expectBody()
.jsonPath("_links.length()").isEqualTo(3)
(client) -> client.get().uri("").exchange().expectStatus().isOk()
.expectBody().jsonPath("_links.length()").isEqualTo(3)
.jsonPath("_links.self.href").isNotEmpty()
.jsonPath("_links.self.templated").isEqualTo(false)
.jsonPath("_links.test.href").isNotEmpty()
@ -120,48 +120,45 @@ public abstract class AbstractWebEndpointIntegrationTests<T extends Configurable
@Test
public void linksMappingIsDisabledWhenEndpointPathIsEmpty() {
load(TestEndpointConfiguration.class, "",
(client) -> client.get().uri("").accept(MediaType.APPLICATION_JSON)
.exchange().expectStatus().isNotFound());
(client) -> client.get().uri("").exchange().expectStatus().isNotFound());
}
@Test
public void readOperationWithSingleQueryParameters() {
load(QueryEndpointConfiguration.class,
(client) -> client.get().uri("/query?one=1&two=2")
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus()
.isOk().expectBody().jsonPath("query").isEqualTo("1 2"));
(client) -> client.get().uri("/query?one=1&two=2").exchange()
.expectStatus().isOk().expectBody().jsonPath("query")
.isEqualTo("1 2"));
}
@Test
public void readOperationWithSingleQueryParametersAndMultipleValues() {
load(QueryEndpointConfiguration.class,
(client) -> client.get().uri("/query?one=1&one=1&two=2")
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus()
.isOk().expectBody().jsonPath("query").isEqualTo("1,1 2"));
(client) -> client.get().uri("/query?one=1&one=1&two=2").exchange()
.expectStatus().isOk().expectBody().jsonPath("query")
.isEqualTo("1,1 2"));
}
@Test
public void readOperationWithListQueryParameterAndSingleValue() {
load(QueryWithListEndpointConfiguration.class,
(client) -> client.get().uri("/query?one=1&two=2")
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus()
.isOk().expectBody().jsonPath("query").isEqualTo("1 [2]"));
(client) -> client.get().uri("/query?one=1&two=2").exchange()
.expectStatus().isOk().expectBody().jsonPath("query")
.isEqualTo("1 [2]"));
}
@Test
public void readOperationWithListQueryParameterAndMultipleValues() {
load(QueryWithListEndpointConfiguration.class,
(client) -> client.get().uri("/query?one=1&two=2&two=2")
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus()
.isOk().expectBody().jsonPath("query").isEqualTo("1 [2, 2]"));
(client) -> client.get().uri("/query?one=1&two=2&two=2").exchange()
.expectStatus().isOk().expectBody().jsonPath("query")
.isEqualTo("1 [2, 2]"));
}
@Test
public void readOperationWithMappingFailureProducesBadRequestResponse() {
load(QueryEndpointConfiguration.class,
(client) -> client.get().uri("/query?two=two")
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus()
.isBadRequest());
load(QueryEndpointConfiguration.class, (client) -> client.get()
.uri("/query?two=two").exchange().expectStatus().isBadRequest());
}
@Test
@ -170,16 +167,16 @@ public abstract class AbstractWebEndpointIntegrationTests<T extends Configurable
Map<String, Object> body = new HashMap<>();
body.put("foo", "one");
body.put("bar", "two");
client.post().uri("/test").syncBody(body).accept(MediaType.APPLICATION_JSON)
.exchange().expectStatus().isNoContent().expectBody().isEmpty();
client.post().uri("/test").syncBody(body).exchange().expectStatus()
.isNoContent().expectBody().isEmpty();
});
}
@Test
public void writeOperationWithVoidResponse() {
load(VoidWriteResponseEndpointConfiguration.class, (context, client) -> {
client.post().uri("/voidwrite").accept(MediaType.APPLICATION_JSON).exchange()
.expectStatus().isNoContent().expectBody().isEmpty();
client.post().uri("/voidwrite").exchange().expectStatus().isNoContent()
.expectBody().isEmpty();
verify(context.getBean(EndpointDelegate.class)).write();
});
}
@ -187,16 +184,15 @@ public abstract class AbstractWebEndpointIntegrationTests<T extends Configurable
@Test
public void deleteOperation() {
load(TestEndpointConfiguration.class,
(client) -> client.delete().uri("/test/one")
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus()
(client) -> client.delete().uri("/test/one").exchange().expectStatus()
.isOk().expectBody().jsonPath("part").isEqualTo("one"));
}
@Test
public void deleteOperationWithVoidResponse() {
load(VoidDeleteResponseEndpointConfiguration.class, (context, client) -> {
client.delete().uri("/voiddelete").accept(MediaType.APPLICATION_JSON)
.exchange().expectStatus().isNoContent().expectBody().isEmpty();
client.delete().uri("/voiddelete").exchange().expectStatus().isNoContent()
.expectBody().isEmpty();
verify(context.getBean(EndpointDelegate.class)).delete();
});
}
@ -206,8 +202,8 @@ public abstract class AbstractWebEndpointIntegrationTests<T extends Configurable
load(TestEndpointConfiguration.class, (context, client) -> {
Map<String, Object> body = new HashMap<>();
body.put("foo", "one");
client.post().uri("/test").syncBody(body).accept(MediaType.APPLICATION_JSON)
.exchange().expectStatus().isNoContent().expectBody().isEmpty();
client.post().uri("/test").syncBody(body).exchange().expectStatus()
.isNoContent().expectBody().isEmpty();
verify(context.getBean(EndpointDelegate.class)).write("one", null);
});
}
@ -215,35 +211,28 @@ public abstract class AbstractWebEndpointIntegrationTests<T extends Configurable
@Test
public void nullsArePassedToTheOperationWhenPostRequestHasNoBody() {
load(TestEndpointConfiguration.class, (context, client) -> {
client.post().uri("/test").contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus()
.isNoContent().expectBody().isEmpty();
client.post().uri("/test").contentType(MediaType.APPLICATION_JSON).exchange()
.expectStatus().isNoContent().expectBody().isEmpty();
verify(context.getBean(EndpointDelegate.class)).write(null, null);
});
}
@Test
public void nullResponseFromReadOperationResultsInNotFoundResponseStatus() {
load(NullReadResponseEndpointConfiguration.class,
(context, client) -> client.get().uri("/nullread")
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus()
.isNotFound());
load(NullReadResponseEndpointConfiguration.class, (context, client) -> client
.get().uri("/nullread").exchange().expectStatus().isNotFound());
}
@Test
public void nullResponseFromDeleteOperationResultsInNoContentResponseStatus() {
load(NullDeleteResponseEndpointConfiguration.class,
(context, client) -> client.delete().uri("/nulldelete")
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus()
.isNoContent());
load(NullDeleteResponseEndpointConfiguration.class, (context, client) -> client
.delete().uri("/nulldelete").exchange().expectStatus().isNoContent());
}
@Test
public void nullResponseFromWriteOperationResultsInNoContentResponseStatus() {
load(NullWriteResponseEndpointConfiguration.class,
(context, client) -> client.post().uri("/nullwrite")
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus()
.isNoContent());
load(NullWriteResponseEndpointConfiguration.class, (context, client) -> client
.post().uri("/nullwrite").exchange().expectStatus().isNoContent());
}
@Test
@ -272,9 +261,8 @@ public abstract class AbstractWebEndpointIntegrationTests<T extends Configurable
@Test
public void readOperationWithMonoResponse() {
load(MonoResponseEndpointConfiguration.class,
(client) -> client.get().uri("/mono").accept(MediaType.APPLICATION_JSON)
.exchange().expectStatus().isOk().expectBody().jsonPath("a")
.isEqualTo("alpha"));
(client) -> client.get().uri("/mono").exchange().expectStatus().isOk()
.expectBody().jsonPath("a").isEqualTo("alpha"));
}
@Test
@ -298,6 +286,38 @@ public abstract class AbstractWebEndpointIntegrationTests<T extends Configurable
.uri("/requiredparameters?foo=hello").exchange().expectStatus().isOk());
}
@Test
public void endpointsProducePrimaryMediaTypeByDefault() {
load(TestEndpointConfiguration.class,
(client) -> client.get().uri("/test").exchange().expectStatus().isOk()
.expectHeader()
.valueMatches("Content-Type", ACTUATOR_MEDIA_TYPE_PATTERN));
}
@Test
public void endpointsProduceSecondaryMediaTypeWhenRequested() {
load(TestEndpointConfiguration.class,
(client) -> client.get().uri("/test").accept(MediaType.APPLICATION_JSON)
.exchange().expectStatus().isOk().expectHeader()
.valueMatches("Content-Type", JSON_MEDIA_TYPE_PATTERN));
}
@Test
public void linksProducesPrimaryMediaTypeByDefault() {
load(TestEndpointConfiguration.class,
(client) -> client.get().uri("").exchange().expectStatus().isOk()
.expectHeader()
.valueMatches("Content-Type", ACTUATOR_MEDIA_TYPE_PATTERN));
}
@Test
public void linksProducesSecondaryMediaTypeWhenRequested() {
load(TestEndpointConfiguration.class,
(client) -> client.get().uri("").accept(MediaType.APPLICATION_JSON)
.exchange().expectStatus().isOk().expectHeader()
.valueMatches("Content-Type", JSON_MEDIA_TYPE_PATTERN));
}
protected abstract T createApplicationContext(Class<?>... config);
protected abstract int getPort(T context);
@ -351,6 +371,13 @@ public abstract class AbstractWebEndpointIntegrationTests<T extends Configurable
}
}
@Bean
public EndpointMediaTypes endpointMediaTypes() {
List<String> mediaTypes = Arrays.asList("application/vnd.test+json",
"application/json");
return new EndpointMediaTypes(mediaTypes, mediaTypes);
}
@Bean
public WebAnnotationEndpointDiscoverer webEndpointDiscoverer(
ApplicationContext applicationContext) {
@ -358,8 +385,7 @@ public abstract class AbstractWebEndpointIntegrationTests<T extends Configurable
DefaultConversionService.getSharedInstance());
return new WebAnnotationEndpointDiscoverer(applicationContext,
parameterMapper, (id) -> new CachingConfiguration(0),
Collections.singletonList("application/json"),
Collections.singletonList("application/json"));
endpointMediaTypes());
}
@Bean

View File

@ -247,12 +247,14 @@ public class WebAnnotationEndpointDiscovererTests {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
configuration);
try {
consumer.accept(new WebAnnotationEndpointDiscoverer(context,
new ConversionServiceOperationParameterMapper(
DefaultConversionService.getSharedInstance()),
cachingConfigurationFactory,
Collections.singletonList("application/json"),
Collections.singletonList("application/json")));
consumer.accept(
new WebAnnotationEndpointDiscoverer(context,
new ConversionServiceOperationParameterMapper(
DefaultConversionService.getSharedInstance()),
cachingConfigurationFactory,
new EndpointMediaTypes(
Collections.singletonList("application/json"),
Collections.singletonList("application/json"))));
}
finally {
context.close();

View File

@ -28,6 +28,7 @@ import org.glassfish.jersey.server.model.Resource;
import org.glassfish.jersey.servlet.ServletContainer;
import org.springframework.boot.actuate.endpoint.web.AbstractWebEndpointIntegrationTests;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
import org.springframework.boot.actuate.endpoint.web.annotation.WebAnnotationEndpointDiscoverer;
import org.springframework.boot.endpoint.web.EndpointMapping;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
@ -80,12 +81,13 @@ public class JerseyWebEndpointIntegrationTests extends
@Bean
public ResourceConfig resourceConfig(Environment environment,
WebAnnotationEndpointDiscoverer endpointDiscoverer) {
WebAnnotationEndpointDiscoverer endpointDiscoverer,
EndpointMediaTypes endpointMediaTypes) {
ResourceConfig resourceConfig = new ResourceConfig();
Collection<Resource> resources = new JerseyEndpointResourceFactory()
.createEndpointResources(
new EndpointMapping(environment.getProperty("endpointPath")),
endpointDiscoverer.discoverEndpoints());
endpointDiscoverer.discoverEndpoints(), endpointMediaTypes);
resourceConfig.registerResources(new HashSet<>(resources));
resourceConfig.register(JacksonFeature.class);
resourceConfig.register(new ObjectMapperContextResolver(new ObjectMapper()),

View File

@ -21,6 +21,7 @@ import java.util.Arrays;
import org.junit.Test;
import org.springframework.boot.actuate.endpoint.web.AbstractWebEndpointIntegrationTests;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
import org.springframework.boot.actuate.endpoint.web.annotation.WebAnnotationEndpointDiscoverer;
import org.springframework.boot.endpoint.web.EndpointMapping;
import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory;
@ -110,13 +111,15 @@ public class WebFluxEndpointIntegrationTests
@Bean
public WebFluxEndpointHandlerMapping webEndpointHandlerMapping(
Environment environment,
WebAnnotationEndpointDiscoverer endpointDiscoverer) {
WebAnnotationEndpointDiscoverer endpointDiscoverer,
EndpointMediaTypes endpointMediaTypes) {
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.setAllowedOrigins(Arrays.asList("http://example.com"));
corsConfiguration.setAllowedMethods(Arrays.asList("GET", "POST"));
return new WebFluxEndpointHandlerMapping(
new EndpointMapping(environment.getProperty("endpointPath")),
endpointDiscoverer.discoverEndpoints(), corsConfiguration);
endpointDiscoverer.discoverEndpoints(), endpointMediaTypes,
corsConfiguration);
}
@Bean

View File

@ -21,6 +21,7 @@ import java.util.Arrays;
import org.junit.Test;
import org.springframework.boot.actuate.endpoint.web.AbstractWebEndpointIntegrationTests;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
import org.springframework.boot.actuate.endpoint.web.annotation.WebAnnotationEndpointDiscoverer;
import org.springframework.boot.endpoint.web.EndpointMapping;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
@ -104,13 +105,15 @@ public class MvcWebEndpointIntegrationTests extends
@Bean
public WebMvcEndpointHandlerMapping webEndpointHandlerMapping(
Environment environment,
WebAnnotationEndpointDiscoverer webEndpointDiscoverer) {
WebAnnotationEndpointDiscoverer webEndpointDiscoverer,
EndpointMediaTypes endpointMediaTypes) {
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.setAllowedOrigins(Arrays.asList("http://example.com"));
corsConfiguration.setAllowedMethods(Arrays.asList("GET", "POST"));
return new WebMvcEndpointHandlerMapping(
new EndpointMapping(environment.getProperty("endpointPath")),
webEndpointDiscoverer.discoverEndpoints(), corsConfiguration);
webEndpointDiscoverer.discoverEndpoints(), endpointMediaTypes,
corsConfiguration);
}
}

View File

@ -21,6 +21,8 @@ import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import javax.ws.rs.core.MediaType;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.model.Resource;
import org.junit.runners.BlockJUnit4ClassRunner;
@ -28,6 +30,7 @@ import org.junit.runners.model.InitializationError;
import org.springframework.boot.actuate.endpoint.convert.ConversionServiceOperationParameterMapper;
import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
import org.springframework.boot.actuate.endpoint.web.annotation.WebAnnotationEndpointDiscoverer;
import org.springframework.boot.actuate.endpoint.web.jersey.JerseyEndpointResourceFactory;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
@ -41,7 +44,6 @@ import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
/**
* {@link BlockJUnit4ClassRunner} for Jersey.
@ -90,15 +92,17 @@ class JerseyEndpointsRunner extends AbstractWebEndpointRunner {
}
private void customize(ResourceConfig config) {
List<String> mediaTypes = Arrays.asList(MediaType.APPLICATION_JSON_VALUE,
List<String> mediaTypes = Arrays.asList(MediaType.APPLICATION_JSON,
ActuatorMediaType.V2_JSON);
EndpointMediaTypes endpointMediaTypes = new EndpointMediaTypes(mediaTypes,
mediaTypes);
WebAnnotationEndpointDiscoverer discoverer = new WebAnnotationEndpointDiscoverer(
this.applicationContext,
new ConversionServiceOperationParameterMapper(), (id) -> null,
mediaTypes, mediaTypes);
endpointMediaTypes);
Collection<Resource> resources = new JerseyEndpointResourceFactory()
.createEndpointResources(new EndpointMapping("/application"),
discoverer.discoverEndpoints());
discoverer.discoverEndpoints(), endpointMediaTypes);
config.registerResources(new HashSet<>(resources));
}

View File

@ -24,6 +24,7 @@ import org.junit.runners.model.InitializationError;
import org.springframework.boot.actuate.endpoint.convert.ConversionServiceOperationParameterMapper;
import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
import org.springframework.boot.actuate.endpoint.web.annotation.WebAnnotationEndpointDiscoverer;
import org.springframework.boot.actuate.endpoint.web.reactive.WebFluxEndpointHandlerMapping;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
@ -99,12 +100,15 @@ class WebFluxEndpointsRunner extends AbstractWebEndpointRunner {
public WebFluxEndpointHandlerMapping webEndpointReactiveHandlerMapping() {
List<String> mediaTypes = Arrays.asList(MediaType.APPLICATION_JSON_VALUE,
ActuatorMediaType.V2_JSON);
EndpointMediaTypes endpointMediaTypes = new EndpointMediaTypes(mediaTypes,
mediaTypes);
WebAnnotationEndpointDiscoverer discoverer = new WebAnnotationEndpointDiscoverer(
this.applicationContext,
new ConversionServiceOperationParameterMapper(), (id) -> null,
mediaTypes, mediaTypes);
endpointMediaTypes);
return new WebFluxEndpointHandlerMapping(new EndpointMapping("/application"),
discoverer.discoverEndpoints(), new CorsConfiguration());
discoverer.discoverEndpoints(), endpointMediaTypes,
new CorsConfiguration());
}
}

View File

@ -24,6 +24,7 @@ import org.junit.runners.model.InitializationError;
import org.springframework.boot.actuate.endpoint.convert.ConversionServiceOperationParameterMapper;
import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
import org.springframework.boot.actuate.endpoint.web.annotation.WebAnnotationEndpointDiscoverer;
import org.springframework.boot.actuate.endpoint.web.servlet.WebMvcEndpointHandlerMapping;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
@ -82,12 +83,15 @@ class WebMvcEndpointRunner extends AbstractWebEndpointRunner {
public WebMvcEndpointHandlerMapping webEndpointServletHandlerMapping() {
List<String> mediaTypes = Arrays.asList(MediaType.APPLICATION_JSON_VALUE,
ActuatorMediaType.V2_JSON);
EndpointMediaTypes endpointMediaTypes = new EndpointMediaTypes(mediaTypes,
mediaTypes);
WebAnnotationEndpointDiscoverer discoverer = new WebAnnotationEndpointDiscoverer(
this.applicationContext,
new ConversionServiceOperationParameterMapper(), (id) -> null,
mediaTypes, mediaTypes);
endpointMediaTypes);
return new WebMvcEndpointHandlerMapping(new EndpointMapping("/application"),
discoverer.discoverEndpoints(), new CorsConfiguration());
discoverer.discoverEndpoints(), endpointMediaTypes,
new CorsConfiguration());
}
}