Allow endpoints to be mapped to /
This commit removes the restriction that was added in 4a61e45 to
prevent / from being used as the management context path when the
management context was not using a different port
The management context path can now be set to / irrespective of the
configuration of the management port. To avoid a possible clash
with the application's welcome page or similar, the links "endpoint"
that is mapping to the management context path is disabled when
the management context path is /.
As part of allowing / to be used as the management context path again,
the handling of endpoint mappings and the creation of paths for
individual operations has been consolidated into a new EndpointMapping
class that is used across the three (MVC, WebFlux, and Jersey)
implementations.
See gh-9898
This commit is contained in:
parent
4c0a70ced6
commit
c06de245d9
|
|
@ -82,7 +82,6 @@ public class ManagementContextAutoConfiguration {
|
|||
@Override
|
||||
public void afterSingletonsInstantiated() {
|
||||
verifySslConfiguration();
|
||||
verifyContextPathConfiguration();
|
||||
if (this.environment instanceof ConfigurableEnvironment) {
|
||||
addLocalManagementPortPropertyAlias(
|
||||
(ConfigurableEnvironment) this.environment);
|
||||
|
|
@ -97,15 +96,6 @@ public class ManagementContextAutoConfiguration {
|
|||
+ "server is not listening on a separate port");
|
||||
}
|
||||
|
||||
private void verifyContextPathConfiguration() {
|
||||
String contextPath = this.environment.getProperty("management.context-path");
|
||||
if ("".equals(contextPath) || "/".equals(contextPath)) {
|
||||
throw new IllegalStateException("A management context path of '"
|
||||
+ contextPath + "' requires the management server to be "
|
||||
+ "listening on a separate port");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an alias for 'local.management.port' that actually resolves using
|
||||
* 'local.server.port'.
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplicat
|
|||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
|
||||
import org.springframework.boot.autoconfigure.jersey.ResourceConfigCustomizer;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.endpoint.web.EndpointMapping;
|
||||
import org.springframework.boot.endpoint.web.WebEndpointOperation;
|
||||
import org.springframework.boot.endpoint.web.jersey.JerseyEndpointResourceFactory;
|
||||
import org.springframework.boot.endpoint.web.mvc.WebEndpointServletHandlerMapping;
|
||||
|
|
@ -66,7 +67,8 @@ class WebEndpointInfrastructureManagementContextConfiguration {
|
|||
ManagementServerProperties managementServerProperties) {
|
||||
return (resourceConfig) -> resourceConfig.registerResources(new HashSet<>(
|
||||
new JerseyEndpointResourceFactory().createEndpointResources(
|
||||
managementServerProperties.getContextPath(),
|
||||
new EndpointMapping(
|
||||
managementServerProperties.getContextPath()),
|
||||
provider.getEndpoints())));
|
||||
}
|
||||
|
||||
|
|
@ -93,8 +95,8 @@ class WebEndpointInfrastructureManagementContextConfiguration {
|
|||
CorsEndpointProperties corsProperties,
|
||||
ManagementServerProperties managementServerProperties) {
|
||||
WebEndpointServletHandlerMapping handlerMapping = new WebEndpointServletHandlerMapping(
|
||||
managementServerProperties.getContextPath(), provider.getEndpoints(),
|
||||
getCorsConfiguration(corsProperties));
|
||||
new EndpointMapping(managementServerProperties.getContextPath()),
|
||||
provider.getEndpoints(), getCorsConfiguration(corsProperties));
|
||||
for (WebEndpointHandlerMappingCustomizer customizer : this.mappingCustomizers) {
|
||||
customizer.customize(handlerMapping);
|
||||
}
|
||||
|
|
@ -137,7 +139,8 @@ class WebEndpointInfrastructureManagementContextConfiguration {
|
|||
EndpointProvider<WebEndpointOperation> provider,
|
||||
ManagementServerProperties managementServerProperties) {
|
||||
return new WebEndpointReactiveHandlerMapping(
|
||||
managementServerProperties.getContextPath(), provider.getEndpoints());
|
||||
new EndpointMapping(managementServerProperties.getContextPath()),
|
||||
provider.getEndpoints());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
|||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||
import org.springframework.boot.autoconfigure.security.SecurityProperties;
|
||||
import org.springframework.boot.cloud.CloudPlatform;
|
||||
import org.springframework.boot.endpoint.web.EndpointMapping;
|
||||
import org.springframework.boot.endpoint.web.WebEndpointOperation;
|
||||
import org.springframework.boot.web.client.RestTemplateBuilder;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
|
@ -69,8 +70,9 @@ public class CloudFoundryActuatorAutoConfiguration {
|
|||
EndpointProvider<WebEndpointOperation> provider, Environment environment,
|
||||
RestTemplateBuilder builder) {
|
||||
return new CloudFoundryWebEndpointServletHandlerMapping(
|
||||
"/cloudfoundryapplication", provider.getEndpoints(),
|
||||
getCorsConfiguration(), getSecurityInterceptor(builder, environment));
|
||||
new EndpointMapping("/cloudfoundryapplication"),
|
||||
provider.getEndpoints(), getCorsConfiguration(),
|
||||
getSecurityInterceptor(builder, environment));
|
||||
}
|
||||
|
||||
private CloudFoundrySecurityInterceptor getSecurityInterceptor(
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ import org.springframework.boot.endpoint.EndpointInfo;
|
|||
import org.springframework.boot.endpoint.OperationInvoker;
|
||||
import org.springframework.boot.endpoint.ParameterMappingException;
|
||||
import org.springframework.boot.endpoint.web.EndpointLinksResolver;
|
||||
import org.springframework.boot.endpoint.web.EndpointMapping;
|
||||
import org.springframework.boot.endpoint.web.Link;
|
||||
import org.springframework.boot.endpoint.web.WebEndpointOperation;
|
||||
import org.springframework.boot.endpoint.web.WebEndpointResponse;
|
||||
|
|
@ -72,11 +73,11 @@ class CloudFoundryWebEndpointServletHandlerMapping
|
|||
|
||||
private final EndpointLinksResolver endpointLinksResolver = new EndpointLinksResolver();
|
||||
|
||||
CloudFoundryWebEndpointServletHandlerMapping(String endpointPath,
|
||||
CloudFoundryWebEndpointServletHandlerMapping(EndpointMapping endpointMapping,
|
||||
Collection<EndpointInfo<WebEndpointOperation>> webEndpoints,
|
||||
CorsConfiguration corsConfiguration,
|
||||
CloudFoundrySecurityInterceptor securityInterceptor) {
|
||||
super(endpointPath, webEndpoints, corsConfiguration);
|
||||
super(endpointMapping, webEndpoints, corsConfiguration);
|
||||
this.securityInterceptor = securityInterceptor;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ public class CloudFoundryActuatorAutoConfigurationTests {
|
|||
@Test
|
||||
public void cloudFoundryPlatformActive() throws Exception {
|
||||
CloudFoundryWebEndpointServletHandlerMapping handlerMapping = getHandlerMapping();
|
||||
assertThat(handlerMapping.getEndpointPath())
|
||||
assertThat(handlerMapping.getEndpointMapping().getPath())
|
||||
.isEqualTo("/cloudfoundryapplication");
|
||||
CorsConfiguration corsConfiguration = (CorsConfiguration) ReflectionTestUtils
|
||||
.getField(handlerMapping, "corsConfiguration");
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ import org.springframework.boot.endpoint.OperationParameterMapper;
|
|||
import org.springframework.boot.endpoint.ReadOperation;
|
||||
import org.springframework.boot.endpoint.Selector;
|
||||
import org.springframework.boot.endpoint.WriteOperation;
|
||||
import org.springframework.boot.endpoint.web.EndpointMapping;
|
||||
import org.springframework.boot.endpoint.web.WebAnnotationEndpointDiscoverer;
|
||||
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
|
||||
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
|
||||
|
|
@ -196,7 +197,8 @@ public class CloudFoundryMvcWebEndpointIntegrationTests {
|
|||
CorsConfiguration corsConfiguration = new CorsConfiguration();
|
||||
corsConfiguration.setAllowedOrigins(Arrays.asList("http://example.com"));
|
||||
corsConfiguration.setAllowedMethods(Arrays.asList("GET", "POST"));
|
||||
return new CloudFoundryWebEndpointServletHandlerMapping("/cfApplication",
|
||||
return new CloudFoundryWebEndpointServletHandlerMapping(
|
||||
new EndpointMapping("/cfApplication"),
|
||||
webEndpointDiscoverer.discoverEndpoints(), corsConfiguration,
|
||||
interceptor);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import org.junit.Test;
|
|||
|
||||
import org.springframework.boot.endpoint.EndpointInfo;
|
||||
import org.springframework.boot.endpoint.OperationType;
|
||||
import org.springframework.boot.endpoint.web.EndpointMapping;
|
||||
import org.springframework.boot.endpoint.web.OperationRequestPredicate;
|
||||
import org.springframework.boot.endpoint.web.WebEndpointHttpMethod;
|
||||
import org.springframework.boot.endpoint.web.WebEndpointOperation;
|
||||
|
|
@ -138,7 +139,8 @@ public class RequestMappingEndpointTests {
|
|||
WebEndpointOperation operation = new WebEndpointOperation(OperationType.READ,
|
||||
(arguments) -> "Invoked", true, requestPredicate, "test");
|
||||
WebEndpointServletHandlerMapping mapping = new WebEndpointServletHandlerMapping(
|
||||
"application", Collections.singleton(new EndpointInfo<>("test", true,
|
||||
new EndpointMapping("application"),
|
||||
Collections.singleton(new EndpointInfo<>("test", true,
|
||||
Collections.singleton(operation))));
|
||||
mapping.setApplicationContext(new StaticApplicationContext());
|
||||
mapping.afterPropertiesSet();
|
||||
|
|
|
|||
|
|
@ -223,14 +223,15 @@ property. For example, the following will disable _all_ endpoints except for `in
|
|||
|
||||
|
||||
[[production-ready-endpoint-hypermedia]]
|
||||
=== Hypermedia for actuator MVC endpoints
|
||||
=== Hypermedia for actuator web endpoints
|
||||
A "`discovery page`" is added with links to all the endpoints. The "`discovery page`" is
|
||||
available on `/application` by default.
|
||||
|
||||
When a custom management context path is configured, the "`discovery page`" will
|
||||
automatically move from `/application` to the root of the management context. For example,
|
||||
if the management context path is `/management` then the discovery page will be available
|
||||
from `/management`.
|
||||
from `/management`. When the management context path is set to `/` the discovery page
|
||||
is disabled to prevent the possiblility of a clash with other mappings.
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* 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.endpoint.web;
|
||||
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* A value object for the base mapping for endpoints.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class EndpointMapping {
|
||||
|
||||
private final String path;
|
||||
|
||||
/**
|
||||
* Creates a new {@code EndpointMapping} using the given {@code path}.
|
||||
*
|
||||
* @param path the path
|
||||
*/
|
||||
public EndpointMapping(String path) {
|
||||
this.path = normalizePath(path);
|
||||
}
|
||||
|
||||
private static String normalizePath(String path) {
|
||||
if (!StringUtils.hasText(path)) {
|
||||
return path;
|
||||
}
|
||||
String normalizedPath = path;
|
||||
if (!normalizedPath.startsWith("/")) {
|
||||
normalizedPath = "/" + normalizedPath;
|
||||
}
|
||||
if (normalizedPath.endsWith("/")) {
|
||||
normalizedPath = normalizedPath.substring(0, normalizedPath.length() - 1);
|
||||
}
|
||||
return normalizedPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path to which endpoints should be mapped.
|
||||
* @return the path
|
||||
*/
|
||||
public String getPath() {
|
||||
return this.path;
|
||||
}
|
||||
|
||||
public String createSubPath(String path) {
|
||||
return this.path + normalizePath(path);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -27,6 +27,7 @@ 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;
|
||||
|
|
@ -41,12 +42,14 @@ import org.springframework.boot.endpoint.EndpointInfo;
|
|||
import org.springframework.boot.endpoint.OperationInvoker;
|
||||
import org.springframework.boot.endpoint.ParameterMappingException;
|
||||
import org.springframework.boot.endpoint.web.EndpointLinksResolver;
|
||||
import org.springframework.boot.endpoint.web.EndpointMapping;
|
||||
import org.springframework.boot.endpoint.web.Link;
|
||||
import org.springframework.boot.endpoint.web.OperationRequestPredicate;
|
||||
import org.springframework.boot.endpoint.web.WebEndpointOperation;
|
||||
import org.springframework.boot.endpoint.web.WebEndpointResponse;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* A factory for creating Jersey {@link Resource Resources} for web endpoint operations.
|
||||
|
|
@ -61,25 +64,29 @@ public class JerseyEndpointResourceFactory {
|
|||
/**
|
||||
* Creates {@link Resource Resources} for the operations of the given
|
||||
* {@code webEndpoints}.
|
||||
* @param endpointPath the path beneath which all endpoints should be mapped
|
||||
* @param endpointMapping the base mapping for all endpoints
|
||||
* @param webEndpoints the web endpoints
|
||||
* @return the resources for the operations
|
||||
*/
|
||||
public Collection<Resource> createEndpointResources(String endpointPath,
|
||||
public Collection<Resource> createEndpointResources(EndpointMapping endpointMapping,
|
||||
Collection<EndpointInfo<WebEndpointOperation>> webEndpoints) {
|
||||
List<Resource> resources = new ArrayList<>();
|
||||
webEndpoints.stream()
|
||||
.flatMap((endpointInfo) -> endpointInfo.getOperations().stream())
|
||||
.map((operation) -> createResource(endpointPath, operation))
|
||||
.map((operation) -> createResource(endpointMapping, operation))
|
||||
.forEach(resources::add);
|
||||
resources.add(createEndpointLinksResource(endpointPath, webEndpoints));
|
||||
if (StringUtils.hasText(endpointMapping.getPath())) {
|
||||
resources.add(
|
||||
createEndpointLinksResource(endpointMapping.getPath(), webEndpoints));
|
||||
}
|
||||
return resources;
|
||||
}
|
||||
|
||||
private Resource createResource(String endpointPath, WebEndpointOperation operation) {
|
||||
private Resource createResource(EndpointMapping endpointMapping,
|
||||
WebEndpointOperation operation) {
|
||||
OperationRequestPredicate requestPredicate = operation.getRequestPredicate();
|
||||
Builder resourceBuilder = Resource.builder()
|
||||
.path(endpointPath + "/" + requestPredicate.getPath());
|
||||
.path(endpointMapping.createSubPath(requestPredicate.getPath()));
|
||||
resourceBuilder.addMethod(requestPredicate.getHttpMethod().name())
|
||||
.consumes(toStringArray(requestPredicate.getConsumes()))
|
||||
.produces(toStringArray(requestPredicate.getProduces()))
|
||||
|
|
@ -95,7 +102,7 @@ public class JerseyEndpointResourceFactory {
|
|||
private Resource createEndpointLinksResource(String endpointPath,
|
||||
Collection<EndpointInfo<WebEndpointOperation>> webEndpoints) {
|
||||
Builder resourceBuilder = Resource.builder().path(endpointPath);
|
||||
resourceBuilder.addMethod("GET").handledBy(
|
||||
resourceBuilder.addMethod("GET").produces(MediaType.APPLICATION_JSON).handledBy(
|
||||
new EndpointLinksInflector(webEndpoints, this.endpointLinksResolver));
|
||||
return resourceBuilder.build();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import javax.servlet.http.HttpServletResponse;
|
|||
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.boot.endpoint.EndpointInfo;
|
||||
import org.springframework.boot.endpoint.web.EndpointMapping;
|
||||
import org.springframework.boot.endpoint.web.OperationRequestPredicate;
|
||||
import org.springframework.boot.endpoint.web.WebEndpointOperation;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
|
@ -50,7 +51,7 @@ import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMappi
|
|||
public abstract class AbstractWebEndpointServletHandlerMapping
|
||||
extends RequestMappingInfoHandlerMapping implements InitializingBean {
|
||||
|
||||
private final String endpointPath;
|
||||
private final EndpointMapping endpointMapping;
|
||||
|
||||
private final Collection<EndpointInfo<WebEndpointOperation>> webEndpoints;
|
||||
|
||||
|
|
@ -59,25 +60,25 @@ public abstract class AbstractWebEndpointServletHandlerMapping
|
|||
/**
|
||||
* Creates a new {@code WebEndpointHandlerMapping} that provides mappings for the
|
||||
* operations of the given {@code webEndpoints}.
|
||||
* @param endpointPath the path beneath which all endpoints should be mapped
|
||||
* @param endpointMapping the base mapping for all endpoints
|
||||
* @param collection the web endpoints operations
|
||||
*/
|
||||
public AbstractWebEndpointServletHandlerMapping(String endpointPath,
|
||||
public AbstractWebEndpointServletHandlerMapping(EndpointMapping endpointMapping,
|
||||
Collection<EndpointInfo<WebEndpointOperation>> collection) {
|
||||
this(endpointPath, collection, null);
|
||||
this(endpointMapping, collection, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@code WebEndpointHandlerMapping} that provides mappings for the
|
||||
* operations of the given {@code webEndpoints}.
|
||||
* @param endpointPath the path beneath which all endpoints should be mapped
|
||||
* @param endpointMapping the base mapping for all endpoints
|
||||
* @param webEndpoints the web endpoints
|
||||
* @param corsConfiguration the CORS configuration for the endpoints
|
||||
*/
|
||||
public AbstractWebEndpointServletHandlerMapping(String endpointPath,
|
||||
public AbstractWebEndpointServletHandlerMapping(EndpointMapping endpointMapping,
|
||||
Collection<EndpointInfo<WebEndpointOperation>> webEndpoints,
|
||||
CorsConfiguration corsConfiguration) {
|
||||
this.endpointPath = (endpointPath.startsWith("/") ? "" : "/") + endpointPath;
|
||||
this.endpointMapping = endpointMapping;
|
||||
this.webEndpoints = webEndpoints;
|
||||
this.corsConfiguration = corsConfiguration;
|
||||
setOrder(-100);
|
||||
|
|
@ -87,8 +88,8 @@ public abstract class AbstractWebEndpointServletHandlerMapping
|
|||
return this.webEndpoints;
|
||||
}
|
||||
|
||||
public String getEndpointPath() {
|
||||
return this.endpointPath;
|
||||
public EndpointMapping getEndpointMapping() {
|
||||
return this.endpointMapping;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -96,6 +97,12 @@ public abstract class AbstractWebEndpointServletHandlerMapping
|
|||
this.webEndpoints.stream()
|
||||
.flatMap((webEndpoint) -> webEndpoint.getOperations().stream())
|
||||
.forEach(this::registerMappingForOperation);
|
||||
if (StringUtils.hasText(this.endpointMapping.getPath())) {
|
||||
registerLinksRequestMapping();
|
||||
}
|
||||
}
|
||||
|
||||
private void registerLinksRequestMapping() {
|
||||
PatternsRequestCondition patterns = patternsRequestConditionForPattern("");
|
||||
RequestMethodsRequestCondition methods = new RequestMethodsRequestCondition(
|
||||
RequestMethod.GET);
|
||||
|
|
@ -130,8 +137,7 @@ public abstract class AbstractWebEndpointServletHandlerMapping
|
|||
}
|
||||
|
||||
private PatternsRequestCondition patternsRequestConditionForPattern(String path) {
|
||||
String[] patterns = new String[] {
|
||||
this.endpointPath + (StringUtils.hasText(path) ? "/" + path : "") };
|
||||
String[] patterns = new String[] { this.endpointMapping.createSubPath(path) };
|
||||
return new PatternsRequestCondition(patterns, null, null, false, false);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import org.springframework.boot.endpoint.EndpointInfo;
|
|||
import org.springframework.boot.endpoint.OperationInvoker;
|
||||
import org.springframework.boot.endpoint.ParameterMappingException;
|
||||
import org.springframework.boot.endpoint.web.EndpointLinksResolver;
|
||||
import org.springframework.boot.endpoint.web.EndpointMapping;
|
||||
import org.springframework.boot.endpoint.web.Link;
|
||||
import org.springframework.boot.endpoint.web.WebEndpointOperation;
|
||||
import org.springframework.boot.endpoint.web.WebEndpointResponse;
|
||||
|
|
@ -63,25 +64,25 @@ public class WebEndpointServletHandlerMapping
|
|||
/**
|
||||
* Creates a new {@code WebEndpointHandlerMapping} that provides mappings for the
|
||||
* operations of the given {@code webEndpoints}.
|
||||
* @param endpointPath the path beneath which all endpoints should be mapped
|
||||
* @param endpointMapping the base mapping for all endpoints
|
||||
* @param collection the web endpoints operations
|
||||
*/
|
||||
public WebEndpointServletHandlerMapping(String endpointPath,
|
||||
public WebEndpointServletHandlerMapping(EndpointMapping endpointMapping,
|
||||
Collection<EndpointInfo<WebEndpointOperation>> collection) {
|
||||
this(endpointPath, collection, null);
|
||||
this(endpointMapping, collection, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@code WebEndpointHandlerMapping} that provides mappings for the
|
||||
* operations of the given {@code webEndpoints}.
|
||||
* @param endpointPath the path beneath which all endpoints should be mapped
|
||||
* @param endpointMapping the base mapping for all endpoints
|
||||
* @param webEndpoints the web endpoints
|
||||
* @param corsConfiguration the CORS configuration for the endpoints
|
||||
*/
|
||||
public WebEndpointServletHandlerMapping(String endpointPath,
|
||||
public WebEndpointServletHandlerMapping(EndpointMapping endpointMapping,
|
||||
Collection<EndpointInfo<WebEndpointOperation>> webEndpoints,
|
||||
CorsConfiguration corsConfiguration) {
|
||||
super(endpointPath, webEndpoints, corsConfiguration);
|
||||
super(endpointMapping, webEndpoints, corsConfiguration);
|
||||
setOrder(-100);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import org.springframework.boot.endpoint.OperationInvoker;
|
|||
import org.springframework.boot.endpoint.OperationType;
|
||||
import org.springframework.boot.endpoint.ParameterMappingException;
|
||||
import org.springframework.boot.endpoint.web.EndpointLinksResolver;
|
||||
import org.springframework.boot.endpoint.web.EndpointMapping;
|
||||
import org.springframework.boot.endpoint.web.Link;
|
||||
import org.springframework.boot.endpoint.web.OperationRequestPredicate;
|
||||
import org.springframework.boot.endpoint.web.WebEndpointOperation;
|
||||
|
|
@ -42,6 +43,7 @@ import org.springframework.http.HttpStatus;
|
|||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
|
@ -80,7 +82,7 @@ public class WebEndpointReactiveHandlerMapping extends RequestMappingInfoHandler
|
|||
|
||||
private final EndpointLinksResolver endpointLinksResolver = new EndpointLinksResolver();
|
||||
|
||||
private final String endpointPath;
|
||||
private final EndpointMapping endpointMapping;
|
||||
|
||||
private final Collection<EndpointInfo<WebEndpointOperation>> webEndpoints;
|
||||
|
||||
|
|
@ -89,25 +91,25 @@ public class WebEndpointReactiveHandlerMapping extends RequestMappingInfoHandler
|
|||
/**
|
||||
* Creates a new {@code WebEndpointHandlerMapping} that provides mappings for the
|
||||
* operations of the given {@code webEndpoints}.
|
||||
* @param endpointPath the path beneath which all endpoints should be mapped
|
||||
* @param endpointMapping the base mapping for all endpoints
|
||||
* @param collection the web endpoints
|
||||
*/
|
||||
public WebEndpointReactiveHandlerMapping(String endpointPath,
|
||||
public WebEndpointReactiveHandlerMapping(EndpointMapping endpointMapping,
|
||||
Collection<EndpointInfo<WebEndpointOperation>> collection) {
|
||||
this(endpointPath, collection, null);
|
||||
this(endpointMapping, collection, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@code WebEndpointHandlerMapping} that provides mappings for the
|
||||
* operations of the given {@code webEndpoints}.
|
||||
* @param endpointPath the path beneath which all endpoints should be mapped
|
||||
* @param endpointMapping the path beneath which all endpoints should be mapped
|
||||
* @param webEndpoints the web endpoints
|
||||
* @param corsConfiguration the CORS configuration for the endpoints
|
||||
*/
|
||||
public WebEndpointReactiveHandlerMapping(String endpointPath,
|
||||
public WebEndpointReactiveHandlerMapping(EndpointMapping endpointMapping,
|
||||
Collection<EndpointInfo<WebEndpointOperation>> webEndpoints,
|
||||
CorsConfiguration corsConfiguration) {
|
||||
this.endpointPath = (endpointPath.startsWith("/") ? "" : "/") + endpointPath;
|
||||
this.endpointMapping = endpointMapping;
|
||||
this.webEndpoints = webEndpoints;
|
||||
this.corsConfiguration = corsConfiguration;
|
||||
setOrder(-100);
|
||||
|
|
@ -118,8 +120,15 @@ public class WebEndpointReactiveHandlerMapping extends RequestMappingInfoHandler
|
|||
this.webEndpoints.stream()
|
||||
.flatMap((webEndpoint) -> webEndpoint.getOperations().stream())
|
||||
.forEach(this::registerMappingForOperation);
|
||||
if (StringUtils.hasText(this.endpointMapping.getPath())) {
|
||||
registerLinksMapping();
|
||||
}
|
||||
}
|
||||
|
||||
private void registerLinksMapping() {
|
||||
registerMapping(new RequestMappingInfo(
|
||||
new PatternsRequestCondition(pathPatternParser.parse(this.endpointPath)),
|
||||
new PatternsRequestCondition(
|
||||
pathPatternParser.parse(this.endpointMapping.getPath())),
|
||||
new RequestMethodsRequestCondition(RequestMethod.GET), null, null, null,
|
||||
null, null), this, this.links);
|
||||
}
|
||||
|
|
@ -148,7 +157,7 @@ public class WebEndpointReactiveHandlerMapping extends RequestMappingInfoHandler
|
|||
WebEndpointOperation operationInfo) {
|
||||
OperationRequestPredicate requestPredicate = operationInfo.getRequestPredicate();
|
||||
PatternsRequestCondition patterns = new PatternsRequestCondition(pathPatternParser
|
||||
.parse(this.endpointPath + "/" + requestPredicate.getPath()));
|
||||
.parse(this.endpointMapping.createSubPath(requestPredicate.getPath())));
|
||||
RequestMethodsRequestCondition methods = new RequestMethodsRequestCondition(
|
||||
RequestMethod.valueOf(requestPredicate.getHttpMethod().name()));
|
||||
ConsumesRequestCondition consumes = new ConsumesRequestCondition(
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import java.util.function.Consumer;
|
|||
import org.junit.Test;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
|
||||
import org.springframework.boot.endpoint.CachingConfiguration;
|
||||
import org.springframework.boot.endpoint.ConversionServiceOperationParameterMapper;
|
||||
import org.springframework.boot.endpoint.DeleteOperation;
|
||||
|
|
@ -40,6 +41,7 @@ import org.springframework.context.annotation.Bean;
|
|||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.core.env.MapPropertySource;
|
||||
import org.springframework.core.io.ByteArrayResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.MediaType;
|
||||
|
|
@ -71,6 +73,14 @@ public abstract class AbstractWebEndpointIntegrationTests<T extends Configurable
|
|||
.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));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readOperationWithSelector() {
|
||||
load(TestEndpointConfiguration.class,
|
||||
|
|
@ -101,6 +111,13 @@ public abstract class AbstractWebEndpointIntegrationTests<T extends Configurable
|
|||
.jsonPath("_links.test-part.templated").isEqualTo(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void linksMappingIsDisabledWhenEndpointPathIsEmpty() {
|
||||
load(TestEndpointConfiguration.class, "",
|
||||
(client) -> client.get().uri("").accept(MediaType.APPLICATION_JSON)
|
||||
.exchange().expectStatus().isNotFound());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readOperationWithSingleQueryParameters() {
|
||||
load(QueryEndpointConfiguration.class,
|
||||
|
|
@ -268,12 +285,20 @@ public abstract class AbstractWebEndpointIntegrationTests<T extends Configurable
|
|||
|
||||
private void load(Class<?> configuration,
|
||||
BiConsumer<ApplicationContext, WebTestClient> consumer) {
|
||||
load(configuration, "/endpoints", consumer);
|
||||
}
|
||||
|
||||
private void load(Class<?> configuration, String endpointPath,
|
||||
BiConsumer<ApplicationContext, WebTestClient> consumer) {
|
||||
T context = createApplicationContext(configuration, this.exporterConfiguration);
|
||||
context.getEnvironment().getPropertySources().addLast(new MapPropertySource(
|
||||
"test", Collections.singletonMap("endpointPath", endpointPath)));
|
||||
context.refresh();
|
||||
try {
|
||||
consumer.accept(context,
|
||||
WebTestClient.bindToServer()
|
||||
.baseUrl(
|
||||
"http://localhost:" + getPort(context) + "/endpoints")
|
||||
"http://localhost:" + getPort(context) + endpointPath)
|
||||
.build());
|
||||
}
|
||||
finally {
|
||||
|
|
@ -282,7 +307,14 @@ public abstract class AbstractWebEndpointIntegrationTests<T extends Configurable
|
|||
}
|
||||
|
||||
protected void load(Class<?> configuration, Consumer<WebTestClient> clientConsumer) {
|
||||
load(configuration, (context, client) -> clientConsumer.accept(client));
|
||||
load(configuration, "/endpoints",
|
||||
(context, client) -> clientConsumer.accept(client));
|
||||
}
|
||||
|
||||
protected void load(Class<?> configuration, String endpointPath,
|
||||
Consumer<WebTestClient> clientConsumer) {
|
||||
load(configuration, endpointPath,
|
||||
(context, client) -> clientConsumer.accept(client));
|
||||
}
|
||||
|
||||
@Configuration
|
||||
|
|
@ -304,6 +336,11 @@ public abstract class AbstractWebEndpointIntegrationTests<T extends Configurable
|
|||
Collections.singletonList("application/json"));
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PropertyPlaceholderConfigurer propertyPlaceholderConfigurer() {
|
||||
return new PropertyPlaceholderConfigurer();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
|
|
|
|||
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* 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.endpoint.web;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link EndpointMapping}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class EndpointMappingTests {
|
||||
|
||||
@Test
|
||||
public void normalizationTurnsASlashIntoAnEmptyString() {
|
||||
assertThat(new EndpointMapping("/").getPath()).isEqualTo("");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void normalizationLeavesAnEmptyStringAsIs() {
|
||||
assertThat(new EndpointMapping("").getPath()).isEqualTo("");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void normalizationRemovesATrailingSlash() {
|
||||
assertThat(new EndpointMapping("/test/").getPath()).isEqualTo("/test");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void normalizationAddsALeadingSlash() {
|
||||
assertThat(new EndpointMapping("test").getPath()).isEqualTo("/test");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void normalizationAddsALeadingSlashAndRemovesATrailingSlash() {
|
||||
assertThat(new EndpointMapping("test/").getPath()).isEqualTo("/test");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void normalizationLeavesAPathWithALeadingSlashAndNoTrailingSlashAsIs() {
|
||||
assertThat(new EndpointMapping("/test").getPath()).isEqualTo("/test");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void subPathForAnEmptyStringReturnsBasePath() {
|
||||
assertThat(new EndpointMapping("/test").createSubPath("")).isEqualTo("/test");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void subPathWithALeadingSlashIsSeparatedFromBasePathBySingleSlash() {
|
||||
assertThat(new EndpointMapping("/test").createSubPath("/one"))
|
||||
.isEqualTo("/test/one");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void subPathWithoutALeadingSlashIsSeparaedFromBasePathBySingleSlash() {
|
||||
assertThat(new EndpointMapping("/test").createSubPath("one")).isEqualTo("/test/one");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void trailingSlashIsRemovedFromASubPath() {
|
||||
assertThat(new EndpointMapping("/test").createSubPath("one/"))
|
||||
.isEqualTo("/test/one");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -28,12 +28,14 @@ import org.glassfish.jersey.server.model.Resource;
|
|||
import org.glassfish.jersey.servlet.ServletContainer;
|
||||
|
||||
import org.springframework.boot.endpoint.web.AbstractWebEndpointIntegrationTests;
|
||||
import org.springframework.boot.endpoint.web.EndpointMapping;
|
||||
import org.springframework.boot.endpoint.web.WebAnnotationEndpointDiscoverer;
|
||||
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
|
||||
import org.springframework.boot.web.servlet.ServletRegistrationBean;
|
||||
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.env.Environment;
|
||||
|
||||
/**
|
||||
* Integration tests for web endpoints exposed using Jersey.
|
||||
|
|
@ -50,7 +52,9 @@ public class JerseyWebEndpointIntegrationTests extends
|
|||
@Override
|
||||
protected AnnotationConfigServletWebServerApplicationContext createApplicationContext(
|
||||
Class<?>... config) {
|
||||
return new AnnotationConfigServletWebServerApplicationContext(config);
|
||||
AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext();
|
||||
context.register(config);
|
||||
return context;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -74,11 +78,12 @@ public class JerseyWebEndpointIntegrationTests extends
|
|||
}
|
||||
|
||||
@Bean
|
||||
public ResourceConfig resourceConfig(
|
||||
public ResourceConfig resourceConfig(Environment environment,
|
||||
WebAnnotationEndpointDiscoverer endpointDiscoverer) {
|
||||
ResourceConfig resourceConfig = new ResourceConfig();
|
||||
Collection<Resource> resources = new JerseyEndpointResourceFactory()
|
||||
.createEndpointResources("endpoints",
|
||||
.createEndpointResources(
|
||||
new EndpointMapping(environment.getProperty("endpointPath")),
|
||||
endpointDiscoverer.discoverEndpoints());
|
||||
resourceConfig.registerResources(new HashSet<>(resources));
|
||||
resourceConfig.register(JacksonFeature.class);
|
||||
|
|
|
|||
|
|
@ -21,11 +21,13 @@ import java.util.Arrays;
|
|||
import org.junit.Test;
|
||||
|
||||
import org.springframework.boot.endpoint.web.AbstractWebEndpointIntegrationTests;
|
||||
import org.springframework.boot.endpoint.web.EndpointMapping;
|
||||
import org.springframework.boot.endpoint.web.WebAnnotationEndpointDiscoverer;
|
||||
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
|
||||
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.servlet.DispatcherServlet;
|
||||
|
|
@ -59,7 +61,9 @@ public class MvcWebEndpointIntegrationTests extends
|
|||
@Override
|
||||
protected AnnotationConfigServletWebServerApplicationContext createApplicationContext(
|
||||
Class<?>... config) {
|
||||
return new AnnotationConfigServletWebServerApplicationContext(config);
|
||||
AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext();
|
||||
context.register(config);
|
||||
return context;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -83,11 +87,13 @@ public class MvcWebEndpointIntegrationTests extends
|
|||
|
||||
@Bean
|
||||
public WebEndpointServletHandlerMapping webEndpointHandlerMapping(
|
||||
Environment environment,
|
||||
WebAnnotationEndpointDiscoverer webEndpointDiscoverer) {
|
||||
CorsConfiguration corsConfiguration = new CorsConfiguration();
|
||||
corsConfiguration.setAllowedOrigins(Arrays.asList("http://example.com"));
|
||||
corsConfiguration.setAllowedMethods(Arrays.asList("GET", "POST"));
|
||||
return new WebEndpointServletHandlerMapping("/endpoints",
|
||||
return new WebEndpointServletHandlerMapping(
|
||||
new EndpointMapping(environment.getProperty("endpointPath")),
|
||||
webEndpointDiscoverer.discoverEndpoints(), corsConfiguration);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import java.util.Arrays;
|
|||
import org.junit.Test;
|
||||
|
||||
import org.springframework.boot.endpoint.web.AbstractWebEndpointIntegrationTests;
|
||||
import org.springframework.boot.endpoint.web.EndpointMapping;
|
||||
import org.springframework.boot.endpoint.web.WebAnnotationEndpointDiscoverer;
|
||||
import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory;
|
||||
import org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext;
|
||||
|
|
@ -29,6 +30,7 @@ import org.springframework.context.ApplicationContext;
|
|||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.server.reactive.HttpHandler;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
|
|
@ -63,7 +65,9 @@ public class ReactiveWebEndpointIntegrationTests
|
|||
@Override
|
||||
protected ReactiveWebServerApplicationContext createApplicationContext(
|
||||
Class<?>... config) {
|
||||
return new ReactiveWebServerApplicationContext(config);
|
||||
ReactiveWebServerApplicationContext context = new ReactiveWebServerApplicationContext();
|
||||
context.register(config);
|
||||
return context;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -89,11 +93,13 @@ public class ReactiveWebEndpointIntegrationTests
|
|||
|
||||
@Bean
|
||||
public WebEndpointReactiveHandlerMapping webEndpointHandlerMapping(
|
||||
Environment environment,
|
||||
WebAnnotationEndpointDiscoverer endpointDiscoverer) {
|
||||
CorsConfiguration corsConfiguration = new CorsConfiguration();
|
||||
corsConfiguration.setAllowedOrigins(Arrays.asList("http://example.com"));
|
||||
corsConfiguration.setAllowedMethods(Arrays.asList("GET", "POST"));
|
||||
return new WebEndpointReactiveHandlerMapping("endpoints",
|
||||
return new WebEndpointReactiveHandlerMapping(
|
||||
new EndpointMapping(environment.getProperty("endpointPath")),
|
||||
endpointDiscoverer.discoverEndpoints(), corsConfiguration);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue