mirror of
https://github.com/spring-projects/spring-boot.git
synced 2024-08-29 03:06:45 +08:00
Fix request mapping of endpoint path-mapped to /
Closes gh-35426
This commit is contained in:
parent
31936f036b
commit
3df77c67ec
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2012-2023 the original author or authors.
|
* Copyright 2012-2024 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -235,6 +235,24 @@ class EndpointRequestTests {
|
|||||||
assertThat(matcher).hasToString("EndpointRequestMatcher includes=[*], excludes=[bar], includeLinks=false");
|
assertThat(matcher).hasToString("EndpointRequestMatcher includes=[*], excludes=[bar], includeLinks=false");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void toAnyEndpointWhenEndpointPathMappedToRootIsExcludedShouldNotMatchRoot() {
|
||||||
|
ServerWebExchangeMatcher matcher = EndpointRequest.toAnyEndpoint().excluding("root");
|
||||||
|
RequestMatcherAssert assertMatcher = assertMatcher(matcher, new PathMappedEndpoints("/", () -> List
|
||||||
|
.of(mockEndpoint(EndpointId.of("root"), "/"), mockEndpoint(EndpointId.of("alpha"), "alpha"))));
|
||||||
|
assertMatcher.doesNotMatch("/");
|
||||||
|
assertMatcher.matches("/alpha");
|
||||||
|
assertMatcher.matches("/alpha/sub");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void toEndpointWhenEndpointPathMappedToRootShouldMatchRoot() {
|
||||||
|
ServerWebExchangeMatcher matcher = EndpointRequest.to("root");
|
||||||
|
RequestMatcherAssert assertMatcher = assertMatcher(matcher,
|
||||||
|
new PathMappedEndpoints("/", () -> List.of(mockEndpoint(EndpointId.of("root"), "/"))));
|
||||||
|
assertMatcher.matches("/");
|
||||||
|
}
|
||||||
|
|
||||||
private RequestMatcherAssert assertMatcher(ServerWebExchangeMatcher matcher) {
|
private RequestMatcherAssert assertMatcher(ServerWebExchangeMatcher matcher) {
|
||||||
return assertMatcher(matcher, mockPathMappedEndpoints("/actuator"));
|
return assertMatcher(matcher, mockPathMappedEndpoints("/actuator"));
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2012-2023 the original author or authors.
|
* Copyright 2012-2024 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -24,6 +24,7 @@ import org.assertj.core.api.AssertDelegateTarget;
|
|||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
|
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
|
||||||
|
import org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest.EndpointRequestMatcher;
|
||||||
import org.springframework.boot.actuate.endpoint.EndpointId;
|
import org.springframework.boot.actuate.endpoint.EndpointId;
|
||||||
import org.springframework.boot.actuate.endpoint.ExposableEndpoint;
|
import org.springframework.boot.actuate.endpoint.ExposableEndpoint;
|
||||||
import org.springframework.boot.actuate.endpoint.Operation;
|
import org.springframework.boot.actuate.endpoint.Operation;
|
||||||
@ -239,6 +240,24 @@ class EndpointRequestTests {
|
|||||||
assertThat(matcher).hasToString("EndpointRequestMatcher includes=[*], excludes=[bar], includeLinks=false");
|
assertThat(matcher).hasToString("EndpointRequestMatcher includes=[*], excludes=[bar], includeLinks=false");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void toAnyEndpointWhenEndpointPathMappedToRootIsExcludedShouldNotMatchRoot() {
|
||||||
|
EndpointRequestMatcher matcher = EndpointRequest.toAnyEndpoint().excluding("root");
|
||||||
|
RequestMatcherAssert assertMatcher = assertMatcher(matcher, new PathMappedEndpoints("", () -> List
|
||||||
|
.of(mockEndpoint(EndpointId.of("root"), "/"), mockEndpoint(EndpointId.of("alpha"), "alpha"))));
|
||||||
|
assertMatcher.doesNotMatch("/");
|
||||||
|
assertMatcher.matches("/alpha");
|
||||||
|
assertMatcher.matches("/alpha/sub");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void toEndpointWhenEndpointPathMappedToRootShouldMatchRoot() {
|
||||||
|
EndpointRequestMatcher matcher = EndpointRequest.to("root");
|
||||||
|
RequestMatcherAssert assertMatcher = assertMatcher(matcher,
|
||||||
|
new PathMappedEndpoints("", () -> List.of(mockEndpoint(EndpointId.of("root"), "/"))));
|
||||||
|
assertMatcher.matches("/");
|
||||||
|
}
|
||||||
|
|
||||||
private RequestMatcherAssert assertMatcher(RequestMatcher matcher) {
|
private RequestMatcherAssert assertMatcher(RequestMatcher matcher) {
|
||||||
return assertMatcher(matcher, mockPathMappedEndpoints("/actuator"));
|
return assertMatcher(matcher, mockPathMappedEndpoints("/actuator"));
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2012-2022 the original author or authors.
|
* Copyright 2012-2024 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -140,7 +140,17 @@ public class PathMappedEndpoints implements Iterable<PathMappedEndpoint> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String getPath(PathMappedEndpoint endpoint) {
|
private String getPath(PathMappedEndpoint endpoint) {
|
||||||
return (endpoint != null) ? this.basePath + "/" + endpoint.getRootPath() : null;
|
if (endpoint == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
StringBuilder path = new StringBuilder(this.basePath);
|
||||||
|
if (!this.basePath.equals("/")) {
|
||||||
|
path.append("/");
|
||||||
|
}
|
||||||
|
if (!endpoint.getRootPath().equals("/")) {
|
||||||
|
path.append(endpoint.getRootPath());
|
||||||
|
}
|
||||||
|
return path.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2012-2023 the original author or authors.
|
* Copyright 2012-2024 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -86,7 +86,7 @@ class RequestPredicateFactory {
|
|||||||
boolean matchRemainingPathSegments) {
|
boolean matchRemainingPathSegments) {
|
||||||
StringBuilder path = new StringBuilder(rootPath);
|
StringBuilder path = new StringBuilder(rootPath);
|
||||||
for (int i = 0; i < selectorParameters.length; i++) {
|
for (int i = 0; i < selectorParameters.length; i++) {
|
||||||
path.append("/{");
|
path.append((i != 0 || !rootPath.endsWith("/")) ? "/{" : "{");
|
||||||
if (i == selectorParameters.length - 1 && matchRemainingPathSegments) {
|
if (i == selectorParameters.length - 1 && matchRemainingPathSegments) {
|
||||||
path.append("*");
|
path.append("*");
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2012-2023 the original author or authors.
|
* Copyright 2012-2024 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -19,8 +19,10 @@ package org.springframework.boot.actuate.endpoint.web.reactive;
|
|||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.security.Principal;
|
import java.security.Principal;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
@ -176,10 +178,19 @@ public abstract class AbstractWebFluxEndpointHandlerMapping extends RequestMappi
|
|||||||
private RequestMappingInfo createRequestMappingInfo(WebOperation operation) {
|
private RequestMappingInfo createRequestMappingInfo(WebOperation operation) {
|
||||||
WebOperationRequestPredicate predicate = operation.getRequestPredicate();
|
WebOperationRequestPredicate predicate = operation.getRequestPredicate();
|
||||||
String path = this.endpointMapping.createSubPath(predicate.getPath());
|
String path = this.endpointMapping.createSubPath(predicate.getPath());
|
||||||
|
List<String> paths = new ArrayList<>();
|
||||||
|
paths.add(path);
|
||||||
|
if (!StringUtils.hasText(path)) {
|
||||||
|
paths.add("/");
|
||||||
|
}
|
||||||
RequestMethod method = RequestMethod.valueOf(predicate.getHttpMethod().name());
|
RequestMethod method = RequestMethod.valueOf(predicate.getHttpMethod().name());
|
||||||
String[] consumes = StringUtils.toStringArray(predicate.getConsumes());
|
String[] consumes = StringUtils.toStringArray(predicate.getConsumes());
|
||||||
String[] produces = StringUtils.toStringArray(predicate.getProduces());
|
String[] produces = StringUtils.toStringArray(predicate.getProduces());
|
||||||
return RequestMappingInfo.paths(path).methods(method).consumes(consumes).produces(produces).build();
|
return RequestMappingInfo.paths(paths.toArray(new String[0]))
|
||||||
|
.methods(method)
|
||||||
|
.consumes(consumes)
|
||||||
|
.produces(produces)
|
||||||
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void registerLinksMapping() {
|
private void registerLinksMapping() {
|
||||||
|
@ -196,7 +196,13 @@ public abstract class AbstractWebMvcEndpointHandlerMapping extends RequestMappin
|
|||||||
}
|
}
|
||||||
|
|
||||||
private RequestMappingInfo createRequestMappingInfo(WebOperationRequestPredicate predicate, String path) {
|
private RequestMappingInfo createRequestMappingInfo(WebOperationRequestPredicate predicate, String path) {
|
||||||
return RequestMappingInfo.paths(this.endpointMapping.createSubPath(path))
|
String subPath = this.endpointMapping.createSubPath(path);
|
||||||
|
List<String> paths = new ArrayList<>();
|
||||||
|
paths.add(subPath);
|
||||||
|
if (!StringUtils.hasLength(subPath)) {
|
||||||
|
paths.add("/");
|
||||||
|
}
|
||||||
|
return RequestMappingInfo.paths(paths.toArray(new String[0]))
|
||||||
.options(this.builderConfig)
|
.options(this.builderConfig)
|
||||||
.methods(RequestMethod.valueOf(predicate.getHttpMethod().name()))
|
.methods(RequestMethod.valueOf(predicate.getHttpMethod().name()))
|
||||||
.consumes(predicate.getConsumes().toArray(new String[0]))
|
.consumes(predicate.getConsumes().toArray(new String[0]))
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2012-2023 the original author or authors.
|
* Copyright 2012-2024 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -91,6 +91,20 @@ class PathMappedEndpointsTests {
|
|||||||
assertThat(mapped.getPath(EndpointId.of("xx"))).isNull();
|
assertThat(mapped.getPath(EndpointId.of("xx"))).isNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getPathWhenBasePathIsRootAndEndpointIsPathMappedToRootShouldReturnSingleSlash() {
|
||||||
|
PathMappedEndpoints mapped = new PathMappedEndpoints("/",
|
||||||
|
() -> List.of(mockEndpoint(EndpointId.of("root"), "/")));
|
||||||
|
assertThat(mapped.getPath(EndpointId.of("root"))).isEqualTo("/");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getPathWhenBasePathIsRootAndEndpointIsPathMapped() {
|
||||||
|
PathMappedEndpoints mapped = new PathMappedEndpoints("/",
|
||||||
|
() -> List.of(mockEndpoint(EndpointId.of("a"), "alpha")));
|
||||||
|
assertThat(mapped.getPath(EndpointId.of("a"))).isEqualTo("/alpha");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void getAllRootPathsShouldReturnAllPaths() {
|
void getAllRootPathsShouldReturnAllPaths() {
|
||||||
PathMappedEndpoints mapped = createTestMapped(null);
|
PathMappedEndpoints mapped = createTestMapped(null);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2012-2023 the original author or authors.
|
* Copyright 2012-2024 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -38,6 +38,7 @@ import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
|||||||
import org.springframework.boot.actuate.endpoint.annotation.Selector;
|
import org.springframework.boot.actuate.endpoint.annotation.Selector;
|
||||||
import org.springframework.boot.actuate.endpoint.annotation.Selector.Match;
|
import org.springframework.boot.actuate.endpoint.annotation.Selector.Match;
|
||||||
import org.springframework.boot.actuate.endpoint.annotation.WriteOperation;
|
import org.springframework.boot.actuate.endpoint.annotation.WriteOperation;
|
||||||
|
import org.springframework.boot.actuate.endpoint.web.PathMapper;
|
||||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
|
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.context.ConfigurableApplicationContext;
|
import org.springframework.context.ConfigurableApplicationContext;
|
||||||
@ -108,6 +109,21 @@ public abstract class AbstractWebEndpointIntegrationTests<T extends Configurable
|
|||||||
.isEqualTo(true));
|
.isEqualTo(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void readOperationWithEndpointPathMappedToTheRoot() {
|
||||||
|
load(EndpointPathMappedToRootConfiguration.class, "", (client) -> {
|
||||||
|
client.get().uri("/").exchange().expectStatus().isOk().expectBody().jsonPath("All").isEqualTo(true);
|
||||||
|
client.get()
|
||||||
|
.uri("/some-part")
|
||||||
|
.exchange()
|
||||||
|
.expectStatus()
|
||||||
|
.isOk()
|
||||||
|
.expectBody()
|
||||||
|
.jsonPath("part")
|
||||||
|
.isEqualTo("some-part");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void readOperationWithSelector() {
|
void readOperationWithSelector() {
|
||||||
load(TestEndpointConfiguration.class,
|
load(TestEndpointConfiguration.class,
|
||||||
@ -672,6 +688,17 @@ public abstract class AbstractWebEndpointIntegrationTests<T extends Configurable
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
@Import(TestEndpointConfiguration.class)
|
||||||
|
protected static class EndpointPathMappedToRootConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
PathMapper pathMapper() {
|
||||||
|
return (endpointId) -> "/";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@Configuration(proxyBeanMethods = false)
|
@Configuration(proxyBeanMethods = false)
|
||||||
@Import(BaseConfiguration.class)
|
@Import(BaseConfiguration.class)
|
||||||
static class MatchAllRemainingEndpointConfiguration {
|
static class MatchAllRemainingEndpointConfiguration {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2012-2023 the original author or authors.
|
* Copyright 2012-2024 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -20,9 +20,11 @@ import java.util.Arrays;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.ObjectProvider;
|
||||||
import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper;
|
import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper;
|
||||||
import org.springframework.boot.actuate.endpoint.invoke.convert.ConversionServiceParameterValueMapper;
|
import org.springframework.boot.actuate.endpoint.invoke.convert.ConversionServiceParameterValueMapper;
|
||||||
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
|
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
|
||||||
|
import org.springframework.boot.actuate.endpoint.web.PathMapper;
|
||||||
import org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader;
|
import org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
@ -62,11 +64,11 @@ class BaseConfiguration {
|
|||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
WebEndpointDiscoverer webEndpointDiscoverer(EndpointMediaTypes endpointMediaTypes,
|
WebEndpointDiscoverer webEndpointDiscoverer(EndpointMediaTypes endpointMediaTypes,
|
||||||
ApplicationContext applicationContext) {
|
ApplicationContext applicationContext, ObjectProvider<PathMapper> pathMappers) {
|
||||||
ParameterValueMapper parameterMapper = new ConversionServiceParameterValueMapper(
|
ParameterValueMapper parameterMapper = new ConversionServiceParameterValueMapper(
|
||||||
DefaultConversionService.getSharedInstance());
|
DefaultConversionService.getSharedInstance());
|
||||||
return new WebEndpointDiscoverer(applicationContext, parameterMapper, endpointMediaTypes, null,
|
return new WebEndpointDiscoverer(applicationContext, parameterMapper, endpointMediaTypes,
|
||||||
Collections.emptyList(), Collections.emptyList());
|
pathMappers.orderedStream().toList(), Collections.emptyList(), Collections.emptyList());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2012-2023 the original author or authors.
|
* Copyright 2012-2024 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -68,6 +68,13 @@ class RequestPredicateFactoryTests {
|
|||||||
assertThat(requestPredicate.getPath()).isEqualTo("/root/{one}/{*two}");
|
assertThat(requestPredicate.getPath()).isEqualTo("/root/{one}/{*two}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getRequestPredicateWithSlashRootReturnsPredicateWithPathWithoutDoubleSlash() {
|
||||||
|
DiscoveredOperationMethod operationMethod = getDiscoveredOperationMethod(ValidSelectors.class);
|
||||||
|
WebOperationRequestPredicate requestPredicate = this.factory.getRequestPredicate("/", operationMethod);
|
||||||
|
assertThat(requestPredicate.getPath()).isEqualTo("/{one}/{*two}");
|
||||||
|
}
|
||||||
|
|
||||||
private DiscoveredOperationMethod getDiscoveredOperationMethod(Class<?> source) {
|
private DiscoveredOperationMethod getDiscoveredOperationMethod(Class<?> source) {
|
||||||
Method method = source.getDeclaredMethods()[0];
|
Method method = source.getDeclaredMethods()[0];
|
||||||
AnnotationAttributes attributes = new AnnotationAttributes();
|
AnnotationAttributes attributes = new AnnotationAttributes();
|
||||||
|
Loading…
Reference in New Issue
Block a user