Merge origin/4.1.x
Signed-off-by: Olga Maciaszek-Sharma <olga.maciaszek-sharma@broadcom.com>
This commit is contained in:
@@ -31,9 +31,9 @@ import org.springframework.boot.env.EnvironmentPostProcessor;
|
||||
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings.Redirects;
|
||||
import org.springframework.boot.web.client.RestClientCustomizer;
|
||||
import org.springframework.cloud.gateway.server.mvc.common.ArgumentSupplierBeanPostProcessor;
|
||||
import org.springframework.cloud.gateway.server.mvc.config.GatewayMvcAotRuntimeHintsRegistrar;
|
||||
import org.springframework.cloud.gateway.server.mvc.config.GatewayMvcProperties;
|
||||
import org.springframework.cloud.gateway.server.mvc.config.GatewayMvcPropertiesBeanDefinitionRegistrar;
|
||||
import org.springframework.cloud.gateway.server.mvc.config.GatewayMvcRuntimeHintsProcessor;
|
||||
import org.springframework.cloud.gateway.server.mvc.config.RouterFunctionHolderFactory;
|
||||
import org.springframework.cloud.gateway.server.mvc.filter.FormFilter;
|
||||
import org.springframework.cloud.gateway.server.mvc.filter.ForwardedRequestHeadersFilter;
|
||||
@@ -54,7 +54,6 @@ import org.springframework.cloud.gateway.server.mvc.predicate.PredicateDiscovere
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.context.annotation.ImportRuntimeHints;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.env.MapPropertySource;
|
||||
@@ -73,7 +72,6 @@ import org.springframework.web.client.RestClient;
|
||||
RestClientAutoConfiguration.class })
|
||||
@ConditionalOnProperty(name = "spring.cloud.gateway.mvc.enabled", matchIfMissing = true)
|
||||
@Import(GatewayMvcPropertiesBeanDefinitionRegistrar.class)
|
||||
@ImportRuntimeHints(GatewayMvcAotRuntimeHintsRegistrar.class)
|
||||
public class GatewayServerMvcAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@@ -199,6 +197,11 @@ public class GatewayServerMvcAutoConfiguration {
|
||||
return new XForwardedRequestHeadersFilterProperties();
|
||||
}
|
||||
|
||||
@Bean
|
||||
static GatewayMvcRuntimeHintsProcessor gatewayMvcRuntimeHintsProcessor() {
|
||||
return new GatewayMvcRuntimeHintsProcessor();
|
||||
}
|
||||
|
||||
static class GatewayHttpClientEnvironmentPostProcessor implements EnvironmentPostProcessor {
|
||||
|
||||
static final boolean APACHE = ClassUtils.isPresent("org.apache.hc.client5.http.impl.classic.HttpClients", null);
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2024 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
|
||||
*
|
||||
* https://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.cloud.gateway.server.mvc.config;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.aot.hint.ExecutableMode;
|
||||
import org.springframework.aot.hint.MemberCategory;
|
||||
import org.springframework.aot.hint.ReflectionHints;
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
import org.springframework.aot.hint.RuntimeHintsRegistrar;
|
||||
import org.springframework.cloud.gateway.server.mvc.filter.AfterFilterFunctions;
|
||||
import org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions;
|
||||
import org.springframework.cloud.gateway.server.mvc.filter.BodyFilterFunctions;
|
||||
import org.springframework.cloud.gateway.server.mvc.filter.Bucket4jFilterFunctions;
|
||||
import org.springframework.cloud.gateway.server.mvc.filter.CircuitBreakerFilterFunctions;
|
||||
import org.springframework.cloud.gateway.server.mvc.filter.FilterFunctions;
|
||||
import org.springframework.cloud.gateway.server.mvc.filter.LoadBalancerFilterFunctions;
|
||||
import org.springframework.cloud.gateway.server.mvc.filter.LoadBalancerHandlerSupplier;
|
||||
import org.springframework.cloud.gateway.server.mvc.filter.TokenRelayFilterFunctions;
|
||||
import org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions;
|
||||
import org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions;
|
||||
import org.springframework.cloud.gateway.server.mvc.predicate.GatewayRequestPredicates;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* AOT runtime hints registrar on the gateway server mvc.
|
||||
*
|
||||
* @author Jürgen Wißkirchen
|
||||
*/
|
||||
public class GatewayMvcAotRuntimeHintsRegistrar implements RuntimeHintsRegistrar {
|
||||
|
||||
private static final Set<Class<?>> FUNCTION_PROVIDERS = Set.of(HandlerFunctions.class,
|
||||
LoadBalancerHandlerSupplier.class, FilterFunctions.class, BeforeFilterFunctions.class,
|
||||
AfterFilterFunctions.class, TokenRelayFilterFunctions.class, BodyFilterFunctions.class,
|
||||
CircuitBreakerFilterFunctions.class, GatewayRouterFunctions.class, LoadBalancerFilterFunctions.class,
|
||||
GatewayRequestPredicates.class, Bucket4jFilterFunctions.class);
|
||||
|
||||
private static final Set<Class<?>> PROPERTIES = Set.of(FilterProperties.class, PredicateProperties.class,
|
||||
RouteProperties.class);
|
||||
|
||||
@Override
|
||||
public void registerHints(@NonNull RuntimeHints hints, ClassLoader classLoader) {
|
||||
final ReflectionHints reflectionHints = hints.reflection();
|
||||
FUNCTION_PROVIDERS.forEach(clazz -> addHintsForClass(reflectionHints, clazz, classLoader));
|
||||
|
||||
PROPERTIES.forEach(clazz -> reflectionHints.registerType(clazz, MemberCategory.PUBLIC_FIELDS,
|
||||
MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add hints for the given class. Since we need to register mostly static methods, the
|
||||
* annotation way with @Reflective does not work here.
|
||||
* @param reflectionHints the reflection hints
|
||||
* @param clazz the class to add hints for
|
||||
* @param classLoader the class loader
|
||||
*/
|
||||
private void addHintsForClass(ReflectionHints reflectionHints, Class<?> clazz, ClassLoader classLoader) {
|
||||
if (!ClassUtils.isPresent(clazz.getName(), classLoader)) {
|
||||
return; // safety net
|
||||
}
|
||||
Arrays.stream(clazz.getMethods())
|
||||
.forEach(method -> reflectionHints.registerMethod(method, ExecutableMode.INVOKE));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Copyright 2013-2025 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
|
||||
*
|
||||
* https://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.cloud.gateway.server.mvc.config;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.aot.hint.MemberCategory;
|
||||
import org.springframework.aot.hint.ReflectionHints;
|
||||
import org.springframework.aot.hint.TypeReference;
|
||||
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution;
|
||||
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
|
||||
import org.springframework.core.type.filter.AssignableTypeFilter;
|
||||
|
||||
/**
|
||||
* A {@link BeanFactoryInitializationAotProcessor} responsible for registering reflection
|
||||
* hints for Gateway MVC beans.
|
||||
*
|
||||
* @author Jürgen Wißkirchen
|
||||
* @author Olga Maciaszek-Sharma
|
||||
* @since 4.3.0
|
||||
*/
|
||||
public class GatewayMvcRuntimeHintsProcessor implements BeanFactoryInitializationAotProcessor {
|
||||
|
||||
private static final Log LOG = LogFactory.getLog(GatewayMvcRuntimeHintsProcessor.class);
|
||||
|
||||
private static final String GATEWAY_MVC_FILTER_PACKAGE_NAME = "org.springframework.cloud.gateway.server.mvc.filter";
|
||||
|
||||
private static final String GATEWAY_MVC_PREDICATE_PACKAGE_NAME = "org.springframework.cloud.gateway.server.mvc.predicate";
|
||||
|
||||
private static final Map<String, Set<String>> beansConditionalOnClasses = Map.of(
|
||||
"io.github.bucket4j.BucketConfiguration",
|
||||
Set.of("org.springframework.cloud.gateway.server.mvc.filter.Bucket4jFilterFunctions"),
|
||||
"org.springframework.cloud.client.circuitbreaker.CircuitBreaker",
|
||||
Set.of("org.springframework.cloud.gateway.server.mvc.filter.CircuitBreakerFilterFunctions"),
|
||||
"org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient",
|
||||
Set.of("org.springframework.cloud.gateway.server.mvc.filter.LoadBalancerFilterFunctions"),
|
||||
"org.springframework.retry.support.RetryTemplate",
|
||||
Set.of("org.springframework.cloud.gateway.server.mvc.filter.RetryFilterFunctions"),
|
||||
"org.springframework.security.oauth2.client.OAuth2AuthorizedClient",
|
||||
Set.of("org.springframework.cloud.gateway.server.mvc.filter.TokenRelayFilterFunctions"));
|
||||
|
||||
private static final Set<Class<?>> PROPERTIES = Set.of(FilterProperties.class, PredicateProperties.class,
|
||||
RouteProperties.class);
|
||||
|
||||
@Override
|
||||
public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) {
|
||||
return (generationContext, beanFactoryInitializationCode) -> {
|
||||
ReflectionHints hints = generationContext.getRuntimeHints().reflection();
|
||||
Set<Class<?>> typesToRegister = Stream
|
||||
.of(getTypesToRegister(GATEWAY_MVC_FILTER_PACKAGE_NAME),
|
||||
getTypesToRegister(GATEWAY_MVC_PREDICATE_PACKAGE_NAME), PROPERTIES)
|
||||
.flatMap(Set::stream)
|
||||
.collect(Collectors.toSet());
|
||||
typesToRegister.forEach(clazz -> hints.registerType(TypeReference.of(clazz),
|
||||
hint -> hint.withMembers(MemberCategory.DECLARED_FIELDS, MemberCategory.INVOKE_DECLARED_METHODS,
|
||||
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)));
|
||||
};
|
||||
}
|
||||
|
||||
private static Set<Class<?>> getTypesToRegister(String packageName) {
|
||||
Set<Class<?>> classesToAdd = new HashSet<>();
|
||||
ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
|
||||
provider.addIncludeFilter(new AssignableTypeFilter(Object.class));
|
||||
Set<BeanDefinition> components = provider.findCandidateComponents(packageName);
|
||||
for (BeanDefinition component : components) {
|
||||
Class<?> clazz;
|
||||
try {
|
||||
clazz = Class.forName(component.getBeanClassName());
|
||||
if (shouldRegisterClass(clazz)) {
|
||||
classesToAdd.add(clazz);
|
||||
}
|
||||
}
|
||||
catch (NoClassDefFoundError | ClassNotFoundException exception) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug(exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
return classesToAdd;
|
||||
}
|
||||
|
||||
private static boolean shouldRegisterClass(Class<?> clazz) {
|
||||
Set<String> conditionClasses = beansConditionalOnClasses.getOrDefault(clazz.getName(), Collections.emptySet());
|
||||
for (String conditionClass : conditionClasses) {
|
||||
try {
|
||||
GatewayMvcRuntimeHintsProcessor.class.getClassLoader().loadClass(conditionClass);
|
||||
}
|
||||
catch (ClassNotFoundException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -60,58 +60,58 @@ class AfterFilterFunctionsTests {
|
||||
@Test
|
||||
void doesNotRemoveJsonAttributes() {
|
||||
restClient.get()
|
||||
.uri("/anything/does_not/remove_json_attributes")
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isOk()
|
||||
.expectBody(Map.class)
|
||||
.consumeWith(res -> {
|
||||
assertThat(res.getResponseBody()).containsEntry("foo", "bar");
|
||||
assertThat(res.getResponseBody()).containsEntry("baz", "qux");
|
||||
});
|
||||
.uri("/anything/does_not/remove_json_attributes")
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isOk()
|
||||
.expectBody(Map.class)
|
||||
.consumeWith(res -> {
|
||||
assertThat(res.getResponseBody()).containsEntry("foo", "bar");
|
||||
assertThat(res.getResponseBody()).containsEntry("baz", "qux");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void removeJsonAttributesToAvoidBeingRecursive() {
|
||||
restClient.get()
|
||||
.uri("/anything/remove_json_attributes_to_avoid_being_recursive")
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isOk()
|
||||
.expectBody(Map.class)
|
||||
.consumeWith(res -> {
|
||||
assertThat(res.getResponseBody()).doesNotContainKey("foo");
|
||||
assertThat(res.getResponseBody()).containsEntry("baz", "qux");
|
||||
});
|
||||
.uri("/anything/remove_json_attributes_to_avoid_being_recursive")
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isOk()
|
||||
.expectBody(Map.class)
|
||||
.consumeWith(res -> {
|
||||
assertThat(res.getResponseBody()).doesNotContainKey("foo");
|
||||
assertThat(res.getResponseBody()).containsEntry("baz", "qux");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void removeJsonAttributesRecursively() {
|
||||
restClient.get()
|
||||
.uri("/anything/remove_json_attributes_recursively")
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isOk()
|
||||
.expectBody(Map.class)
|
||||
.consumeWith(res -> {
|
||||
assertThat(res.getResponseBody()).containsKey("foo");
|
||||
assertThat((Map<String, String>) res.getResponseBody().get("foo")).containsEntry("bar", "A");
|
||||
assertThat(res.getResponseBody()).containsEntry("quux", "C");
|
||||
assertThat(res.getResponseBody()).doesNotContainKey("qux");
|
||||
});
|
||||
.uri("/anything/remove_json_attributes_recursively")
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isOk()
|
||||
.expectBody(Map.class)
|
||||
.consumeWith(res -> {
|
||||
assertThat(res.getResponseBody()).containsKey("foo");
|
||||
assertThat((Map<String, String>) res.getResponseBody().get("foo")).containsEntry("bar", "A");
|
||||
assertThat(res.getResponseBody()).containsEntry("quux", "C");
|
||||
assertThat(res.getResponseBody()).doesNotContainKey("qux");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void raisedErrorWhenRemoveJsonAttributes() {
|
||||
restClient.get()
|
||||
.uri("/anything/raised_error_when_remove_json_attributes")
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.is5xxServerError()
|
||||
.expectBody(String.class)
|
||||
.consumeWith(res -> {
|
||||
assertThat(res.getResponseBody()).isEqualTo("Failed to process JSON of response body.");
|
||||
});
|
||||
.uri("/anything/raised_error_when_remove_json_attributes")
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.is5xxServerError()
|
||||
.expectBody(String.class)
|
||||
.consumeWith(res -> {
|
||||
assertThat(res.getResponseBody()).isEqualTo("Failed to process JSON of response body.");
|
||||
});
|
||||
}
|
||||
|
||||
@SpringBootConfiguration
|
||||
|
||||
@@ -91,13 +91,13 @@ public class GatewayPredicateVisitorTests {
|
||||
|
||||
PathRoutePredicateFactory pathRoutePredicateFactory = new PathRoutePredicateFactory(webFluxProperties);
|
||||
PathRoutePredicateFactory.Config config = new PathRoutePredicateFactory.Config()
|
||||
.setPatterns(List.of("/temp/**"))
|
||||
.setMatchTrailingSlash(true);
|
||||
.setPatterns(List.of("/temp/**"))
|
||||
.setMatchTrailingSlash(true);
|
||||
|
||||
Predicate<ServerWebExchange> predicate = pathRoutePredicateFactory.apply(config);
|
||||
|
||||
ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("http://127.0.0.1:8080/gw/api/v1/temp/test")
|
||||
.build());
|
||||
ServerWebExchange exchange = MockServerWebExchange
|
||||
.from(MockServerHttpRequest.get("http://127.0.0.1:8080/gw/api/v1/temp/test").build());
|
||||
|
||||
assertThat(predicate.test(exchange)).isEqualTo(true);
|
||||
}
|
||||
@@ -109,13 +109,13 @@ public class GatewayPredicateVisitorTests {
|
||||
|
||||
PathRoutePredicateFactory pathRoutePredicateFactory = new PathRoutePredicateFactory(webFluxProperties);
|
||||
PathRoutePredicateFactory.Config config = new PathRoutePredicateFactory.Config()
|
||||
.setPatterns(List.of("/temp/**"))
|
||||
.setMatchTrailingSlash(true);
|
||||
.setPatterns(List.of("/temp/**"))
|
||||
.setMatchTrailingSlash(true);
|
||||
|
||||
Predicate<ServerWebExchange> predicate = pathRoutePredicateFactory.apply(config);
|
||||
|
||||
ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("http://127.0.0.1:8080/gw/api/v1/temp/test")
|
||||
.build());
|
||||
ServerWebExchange exchange = MockServerWebExchange
|
||||
.from(MockServerHttpRequest.get("http://127.0.0.1:8080/gw/api/v1/temp/test").build());
|
||||
|
||||
assertThat(predicate.test(exchange)).isEqualTo(true);
|
||||
|
||||
@@ -139,4 +139,5 @@ public class GatewayPredicateVisitorTests {
|
||||
LinkedHashSet<URI> uris = webExchange.getRequiredAttribute(GATEWAY_ORIGINAL_REQUEST_URL_ATTR);
|
||||
assertThat(uris).contains(exchange.getRequest().getURI());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -55,7 +55,8 @@ public class PathRoutePredicatePathContainerAttrBenchMarkTests {
|
||||
PathRoutePredicateFactory.Config config = new PathRoutePredicateFactory.Config()
|
||||
.setPatterns(Collections.singletonList(PATH_PATTERN_PREFIX + i))
|
||||
.setMatchTrailingSlash(true);
|
||||
Predicate<ServerWebExchange> predicate = new PathRoutePredicateFactory(new WebFluxProperties()).apply(config);
|
||||
Predicate<ServerWebExchange> predicate = new PathRoutePredicateFactory(new WebFluxProperties())
|
||||
.apply(config);
|
||||
predicates.add(predicate);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user