|
22 | 22 | import java.security.KeyStoreException; |
23 | 23 | import java.security.NoSuchAlgorithmException; |
24 | 24 | import java.security.cert.CertificateException; |
| 25 | +import java.util.ArrayList; |
25 | 26 | import java.util.List; |
26 | 27 | import java.util.Set; |
27 | 28 | import java.util.function.Supplier; |
|
31 | 32 | import io.github.bucket4j.distributed.proxy.AsyncProxyManager; |
32 | 33 | import org.apache.commons.logging.Log; |
33 | 34 | import org.apache.commons.logging.LogFactory; |
| 35 | +import org.jspecify.annotations.Nullable; |
34 | 36 | import reactor.core.publisher.Flux; |
35 | 37 | import reactor.netty.http.client.HttpClient; |
36 | 38 | import reactor.netty.http.client.WebsocketClientSpec; |
|
40 | 42 | import org.springframework.aot.hint.RuntimeHints; |
41 | 43 | import org.springframework.aot.hint.RuntimeHintsRegistrar; |
42 | 44 | import org.springframework.aot.hint.TypeReference; |
| 45 | +import org.springframework.beans.BeansException; |
43 | 46 | import org.springframework.beans.factory.BeanFactory; |
44 | 47 | import org.springframework.beans.factory.ObjectProvider; |
45 | 48 | import org.springframework.beans.factory.annotation.Qualifier; |
| 49 | +import org.springframework.beans.factory.config.BeanPostProcessor; |
46 | 50 | import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint; |
47 | 51 | import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties; |
48 | 52 | import org.springframework.boot.autoconfigure.AutoConfigureAfter; |
|
151 | 155 | import org.springframework.cloud.gateway.handler.predicate.ReadBodyRoutePredicateFactory; |
152 | 156 | import org.springframework.cloud.gateway.handler.predicate.RemoteAddrRoutePredicateFactory; |
153 | 157 | import org.springframework.cloud.gateway.handler.predicate.RoutePredicateFactory; |
| 158 | +import org.springframework.cloud.gateway.handler.predicate.VersionRoutePredicateFactory; |
154 | 159 | import org.springframework.cloud.gateway.handler.predicate.WeightRoutePredicateFactory; |
155 | 160 | import org.springframework.cloud.gateway.handler.predicate.XForwardedRemoteAddrRoutePredicateFactory; |
156 | 161 | import org.springframework.cloud.gateway.route.CachingRouteLocator; |
|
165 | 170 | import org.springframework.cloud.gateway.route.RouteRefreshListener; |
166 | 171 | import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; |
167 | 172 | import org.springframework.cloud.gateway.support.ConfigurationService; |
| 173 | +import org.springframework.cloud.gateway.support.GatewayApiVersionStrategy; |
168 | 174 | import org.springframework.cloud.gateway.support.StringToZonedDateTimeConverter; |
169 | 175 | import org.springframework.cloud.gateway.support.config.KeyValueConverter; |
170 | 176 | import org.springframework.context.ApplicationEventPublisher; |
|
182 | 188 | import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientManager; |
183 | 189 | import org.springframework.security.web.server.SecurityWebFilterChain; |
184 | 190 | import org.springframework.util.ClassUtils; |
| 191 | +import org.springframework.util.StringUtils; |
185 | 192 | import org.springframework.validation.Validator; |
| 193 | +import org.springframework.web.accept.ApiVersionParser; |
| 194 | +import org.springframework.web.accept.SemanticApiVersionParser; |
186 | 195 | import org.springframework.web.reactive.DispatcherHandler; |
| 196 | +import org.springframework.web.reactive.accept.ApiVersionDeprecationHandler; |
| 197 | +import org.springframework.web.reactive.accept.ApiVersionResolver; |
| 198 | +import org.springframework.web.reactive.accept.ApiVersionStrategy; |
| 199 | +import org.springframework.web.reactive.accept.MediaTypeParamApiVersionResolver; |
| 200 | +import org.springframework.web.reactive.accept.PathApiVersionResolver; |
| 201 | +import org.springframework.web.reactive.config.ApiVersionConfigurer; |
| 202 | +import org.springframework.web.reactive.config.WebFluxConfigurer; |
187 | 203 | import org.springframework.web.reactive.socket.client.ReactorNettyWebSocketClient; |
188 | 204 | import org.springframework.web.reactive.socket.client.WebSocketClient; |
189 | 205 | import org.springframework.web.reactive.socket.server.RequestUpgradeStrategy; |
190 | 206 | import org.springframework.web.reactive.socket.server.WebSocketService; |
191 | 207 | import org.springframework.web.reactive.socket.server.support.HandshakeWebSocketService; |
192 | 208 | import org.springframework.web.reactive.socket.server.upgrade.ReactorNettyRequestUpgradeStrategy; |
| 209 | +import org.springframework.web.server.ServerWebExchange; |
193 | 210 |
|
194 | 211 | /** |
195 | 212 | * @author Spencer Gibb |
@@ -299,18 +316,23 @@ public RoutePredicateHandlerMapping routePredicateHandlerMapping(FilteringWebHan |
299 | 316 | return new RoutePredicateHandlerMapping(webHandler, routeLocator, globalCorsProperties, environment); |
300 | 317 | } |
301 | 318 |
|
| 319 | + // ConfigurationProperty beans |
| 320 | + |
302 | 321 | @Bean |
303 | 322 | public GatewayProperties gatewayProperties() { |
304 | 323 | return new GatewayProperties(); |
305 | 324 | } |
306 | 325 |
|
307 | | - // ConfigurationProperty beans |
308 | | - |
309 | 326 | @Bean |
310 | 327 | public SecureHeadersProperties secureHeadersProperties() { |
311 | 328 | return new SecureHeadersProperties(); |
312 | 329 | } |
313 | 330 |
|
| 331 | + @Bean |
| 332 | + public VersionProperties versionProperties() { |
| 333 | + return new VersionProperties(); |
| 334 | + } |
| 335 | + |
314 | 336 | @Bean |
315 | 337 | @Conditional(TrustedProxies.ForwardedTrustedProxiesCondition.class) |
316 | 338 | public ForwardedHeadersFilter forwardedHeadersFilter(Environment env, ServerProperties serverProperties, |
@@ -499,6 +521,13 @@ public RemoteAddrRoutePredicateFactory remoteAddrRoutePredicateFactory() { |
499 | 521 | return new RemoteAddrRoutePredicateFactory(); |
500 | 522 | } |
501 | 523 |
|
| 524 | + @Bean |
| 525 | + @ConditionalOnEnabledPredicate |
| 526 | + public VersionRoutePredicateFactory versionRoutePredicateFactory( |
| 527 | + @Qualifier("mvcApiVersionStrategy") @Nullable ApiVersionStrategy apiVersionStrategy) { |
| 528 | + return new VersionRoutePredicateFactory(apiVersionStrategy); |
| 529 | + } |
| 530 | + |
502 | 531 | @Bean |
503 | 532 | @ConditionalOnEnabledPredicate |
504 | 533 | public XForwardedRemoteAddrRoutePredicateFactory xForwardedRemoteAddrRoutePredicateFactory() { |
@@ -746,6 +775,16 @@ public GzipMessageBodyResolver gzipMessageBodyResolver() { |
746 | 775 | return new GzipMessageBodyResolver(); |
747 | 776 | } |
748 | 777 |
|
| 778 | + @Bean |
| 779 | + public GatewayServerWebfluxBeanPostProcessor gatewayServerWebfluxBeanPostProcessor( |
| 780 | + VersionProperties versionProperties, |
| 781 | + ObjectProvider<ApiVersionDeprecationHandler> deprecationHandlerProvider, |
| 782 | + ObjectProvider<ApiVersionParser<?>> versionParserProvider, |
| 783 | + ObjectProvider<ApiVersionResolver> versionResolvers) { |
| 784 | + return new GatewayServerWebfluxBeanPostProcessor(versionProperties, deprecationHandlerProvider.getIfAvailable(), |
| 785 | + versionParserProvider.getIfAvailable(), versionResolvers.orderedStream().toList()); |
| 786 | + } |
| 787 | + |
749 | 788 | @Bean |
750 | 789 | static ConfigurableHintsRegistrationProcessor configurableHintsRegistrationProcessor() { |
751 | 790 | return new ConfigurableHintsRegistrationProcessor(); |
@@ -924,6 +963,100 @@ public TokenRelayGatewayFilterFactory tokenRelayGatewayFilterFactory( |
924 | 963 |
|
925 | 964 | } |
926 | 965 |
|
| 966 | + // FIXME: without adding a version resolver, things fail until I can replace |
| 967 | + // ApiVersionStrategy in a bean post processor |
| 968 | + @Configuration(proxyBeanMethods = false) |
| 969 | + protected static class ApiVersionConfiguration implements WebFluxConfigurer { |
| 970 | + |
| 971 | + private final VersionProperties versionProperties; |
| 972 | + |
| 973 | + protected ApiVersionConfiguration(VersionProperties versionProperties) { |
| 974 | + this.versionProperties = versionProperties; |
| 975 | + } |
| 976 | + |
| 977 | + @Override |
| 978 | + public void configureApiVersioning(ApiVersionConfigurer configurer) { |
| 979 | + if (StringUtils.hasText(versionProperties.getHeaderName()) |
| 980 | + || (versionProperties.getMediaType() != null |
| 981 | + && StringUtils.hasText(versionProperties.getMediaTypeParamName())) |
| 982 | + || versionProperties.getPathSegment() != null |
| 983 | + || StringUtils.hasText(versionProperties.getRequestParamName())) { |
| 984 | + // only add if version resolver configured |
| 985 | + configurer.useVersionResolver(new ApiVersionResolver() { |
| 986 | + @Override |
| 987 | + public @Nullable String resolveVersion(ServerWebExchange exchange) { |
| 988 | + return null; |
| 989 | + } |
| 990 | + }); |
| 991 | + } |
| 992 | + } |
| 993 | + |
| 994 | + } |
| 995 | + |
| 996 | + protected static class GatewayServerWebfluxBeanPostProcessor implements BeanPostProcessor { |
| 997 | + |
| 998 | + private final VersionProperties versionProperties; |
| 999 | + |
| 1000 | + private final ApiVersionDeprecationHandler deprecationHandler; |
| 1001 | + |
| 1002 | + private final ApiVersionParser<?> versionParser; |
| 1003 | + |
| 1004 | + private final List<ApiVersionResolver> apiVersionResolvers; |
| 1005 | + |
| 1006 | + public GatewayServerWebfluxBeanPostProcessor(VersionProperties versionProperties, |
| 1007 | + ApiVersionDeprecationHandler deprecationHandler, ApiVersionParser<?> versionParser, |
| 1008 | + List<ApiVersionResolver> apiVersionResolvers) { |
| 1009 | + this.versionProperties = versionProperties; |
| 1010 | + this.deprecationHandler = deprecationHandler; |
| 1011 | + this.versionParser = (versionParser != null) ? versionParser : new SemanticApiVersionParser(); |
| 1012 | + this.apiVersionResolvers = apiVersionResolvers; |
| 1013 | + } |
| 1014 | + |
| 1015 | + @Override |
| 1016 | + public @Nullable Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { |
| 1017 | + |
| 1018 | + // TODO: Use custom ApiVersionConfigurer when able to |
| 1019 | + if (bean instanceof ApiVersionStrategy && beanName.equals("mvcApiVersionStrategy")) { |
| 1020 | + List<ApiVersionResolver> versionResolvers = new ArrayList<>(); |
| 1021 | + if (StringUtils.hasText(versionProperties.getHeaderName())) { |
| 1022 | + versionResolvers.add( |
| 1023 | + exchange -> exchange.getRequest().getHeaders().getFirst(versionProperties.getHeaderName())); |
| 1024 | + } |
| 1025 | + if (versionProperties.getMediaType() != null |
| 1026 | + && StringUtils.hasText(versionProperties.getMediaTypeParamName())) { |
| 1027 | + versionResolvers.add(new MediaTypeParamApiVersionResolver(versionProperties.getMediaType(), |
| 1028 | + versionProperties.getMediaTypeParamName())); |
| 1029 | + } |
| 1030 | + if (versionProperties.getPathSegment() != null) { |
| 1031 | + versionResolvers.add(new PathApiVersionResolver(versionProperties.getPathSegment())); |
| 1032 | + } |
| 1033 | + if (StringUtils.hasText(versionProperties.getRequestParamName())) { |
| 1034 | + versionResolvers.add(exchange -> exchange.getRequest() |
| 1035 | + .getQueryParams() |
| 1036 | + .getFirst(versionProperties.getRequestParamName())); |
| 1037 | + } |
| 1038 | + |
| 1039 | + if (apiVersionResolvers != null && !apiVersionResolvers.isEmpty()) { |
| 1040 | + versionResolvers.addAll(apiVersionResolvers); |
| 1041 | + } |
| 1042 | + |
| 1043 | + if (versionResolvers.isEmpty()) { |
| 1044 | + return bean; |
| 1045 | + } |
| 1046 | + |
| 1047 | + GatewayApiVersionStrategy strategy = new GatewayApiVersionStrategy(versionResolvers, versionParser, |
| 1048 | + versionProperties.isRequired(), versionProperties.getDefaultVersion(), |
| 1049 | + versionProperties.isDetectSupportedVersions(), deprecationHandler); |
| 1050 | + if (!versionProperties.getSupportedVersions().isEmpty()) { |
| 1051 | + strategy.addSupportedVersion(versionProperties.getSupportedVersions().toArray(new String[0])); |
| 1052 | + } |
| 1053 | + return strategy; |
| 1054 | + } |
| 1055 | + return bean; |
| 1056 | + } |
| 1057 | + |
| 1058 | + } |
| 1059 | + |
927 | 1060 | } |
928 | 1061 |
|
929 | 1062 | class GatewayHints implements RuntimeHintsRegistrar { |
|
0 commit comments