|
16 | 16 |
|
17 | 17 | package org.springframework.cloud.gateway.server.mvc; |
18 | 18 |
|
| 19 | +import java.util.ArrayList; |
| 20 | +import java.util.List; |
19 | 21 | import java.util.Map; |
20 | 22 |
|
| 23 | +import jakarta.servlet.http.HttpServletRequest; |
| 24 | +import org.jspecify.annotations.Nullable; |
| 25 | + |
| 26 | +import org.springframework.beans.BeansException; |
21 | 27 | import org.springframework.beans.factory.BeanFactory; |
22 | 28 | import org.springframework.beans.factory.ObjectProvider; |
| 29 | +import org.springframework.beans.factory.config.BeanPostProcessor; |
23 | 30 | import org.springframework.boot.SpringApplication; |
24 | 31 | import org.springframework.boot.autoconfigure.AutoConfiguration; |
25 | 32 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; |
|
31 | 38 | import org.springframework.boot.restclient.RestClientCustomizer; |
32 | 39 | import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration; |
33 | 40 | import org.springframework.boot.restclient.autoconfigure.RestTemplateAutoConfiguration; |
| 41 | +import org.springframework.boot.webmvc.autoconfigure.WebMvcProperties; |
| 42 | +import org.springframework.boot.webmvc.autoconfigure.WebMvcProperties.Apiversion; |
34 | 43 | import org.springframework.cloud.gateway.server.mvc.common.ArgumentSupplierBeanPostProcessor; |
35 | 44 | import org.springframework.cloud.gateway.server.mvc.config.GatewayMvcProperties; |
36 | 45 | import org.springframework.cloud.gateway.server.mvc.config.GatewayMvcPropertiesBeanDefinitionRegistrar; |
|
67 | 76 | import org.springframework.core.env.MapPropertySource; |
68 | 77 | import org.springframework.http.client.ClientHttpRequestFactory; |
69 | 78 | import org.springframework.util.ClassUtils; |
| 79 | +import org.springframework.util.CollectionUtils; |
70 | 80 | import org.springframework.util.StringUtils; |
| 81 | +import org.springframework.web.accept.ApiVersionDeprecationHandler; |
| 82 | +import org.springframework.web.accept.ApiVersionParser; |
| 83 | +import org.springframework.web.accept.ApiVersionResolver; |
| 84 | +import org.springframework.web.accept.ApiVersionStrategy; |
| 85 | +import org.springframework.web.accept.DefaultApiVersionStrategy; |
| 86 | +import org.springframework.web.accept.InvalidApiVersionException; |
| 87 | +import org.springframework.web.accept.MediaTypeParamApiVersionResolver; |
| 88 | +import org.springframework.web.accept.MissingApiVersionException; |
| 89 | +import org.springframework.web.accept.PathApiVersionResolver; |
| 90 | +import org.springframework.web.accept.QueryApiVersionResolver; |
| 91 | +import org.springframework.web.accept.SemanticApiVersionParser; |
71 | 92 | import org.springframework.web.client.RestClient; |
72 | 93 |
|
73 | 94 | /** |
@@ -214,6 +235,18 @@ static GatewayMvcRuntimeHintsProcessor gatewayMvcRuntimeHintsProcessor() { |
214 | 235 | return new GatewayMvcRuntimeHintsProcessor(); |
215 | 236 | } |
216 | 237 |
|
| 238 | + @Bean |
| 239 | + GatewayServerWebMvcBeanPostProcessor gatewayServerWebMvcBeanPostProcessor( |
| 240 | + ObjectProvider<WebMvcProperties> properties, |
| 241 | + ObjectProvider<ApiVersionDeprecationHandler> deprecationHandlerProvider, |
| 242 | + ObjectProvider<ApiVersionParser<?>> versionParserProvider, |
| 243 | + ObjectProvider<ApiVersionResolver> versionResolvers) { |
| 244 | + return new GatewayServerWebMvcBeanPostProcessor( |
| 245 | + properties.getIfAvailable(WebMvcProperties::new).getApiversion(), |
| 246 | + deprecationHandlerProvider.getIfAvailable(), versionParserProvider.getIfAvailable(), |
| 247 | + versionResolvers.orderedStream().toList()); |
| 248 | + } |
| 249 | + |
217 | 250 | static class GatewayHttpClientEnvironmentPostProcessor implements EnvironmentPostProcessor { |
218 | 251 |
|
219 | 252 | static final boolean APACHE = ClassUtils.isPresent("org.apache.hc.client5.http.impl.classic.HttpClients", null); |
@@ -258,4 +291,105 @@ else if (StringUtils.hasText(restrictedHeaders) && !restrictedHeaders.contains(" |
258 | 291 |
|
259 | 292 | } |
260 | 293 |
|
| 294 | + protected static class GatewayServerWebMvcBeanPostProcessor implements BeanPostProcessor { |
| 295 | + |
| 296 | + private final Apiversion versionProperties; |
| 297 | + |
| 298 | + private final ApiVersionDeprecationHandler deprecationHandler; |
| 299 | + |
| 300 | + private final ApiVersionParser<?> versionParser; |
| 301 | + |
| 302 | + private final List<ApiVersionResolver> apiVersionResolvers; |
| 303 | + |
| 304 | + public GatewayServerWebMvcBeanPostProcessor(Apiversion versionProperties, |
| 305 | + ApiVersionDeprecationHandler deprecationHandler, ApiVersionParser<?> versionParser, |
| 306 | + List<ApiVersionResolver> apiVersionResolvers) { |
| 307 | + this.versionProperties = versionProperties; |
| 308 | + this.deprecationHandler = deprecationHandler; |
| 309 | + this.versionParser = (versionParser != null) ? versionParser : new SemanticApiVersionParser(); |
| 310 | + this.apiVersionResolvers = apiVersionResolvers; |
| 311 | + } |
| 312 | + |
| 313 | + @Override |
| 314 | + public @Nullable Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { |
| 315 | + |
| 316 | + // TODO: Use custom ApiVersionConfigurer when able to |
| 317 | + if (bean instanceof ApiVersionStrategy && beanName.equals("mvcApiVersionStrategy")) { |
| 318 | + List<ApiVersionResolver> versionResolvers = new ArrayList<>(); |
| 319 | + if (StringUtils.hasText(versionProperties.getUse().getHeader())) { |
| 320 | + versionResolvers.add(request -> request.getHeader(versionProperties.getUse().getHeader())); |
| 321 | + } |
| 322 | + if (!CollectionUtils.isEmpty(versionProperties.getUse().getMediaTypeParameter())) { |
| 323 | + versionProperties.getUse().getMediaTypeParameter().forEach((mediaType, param) -> { |
| 324 | + versionResolvers.add(new MediaTypeParamApiVersionResolver(mediaType, param)); |
| 325 | + }); |
| 326 | + } |
| 327 | + if (versionProperties.getUse().getPathSegment() != null) { |
| 328 | + versionResolvers.add(new PathApiVersionResolver(versionProperties.getUse().getPathSegment())); |
| 329 | + } |
| 330 | + if (StringUtils.hasText(versionProperties.getUse().getQueryParameter())) { |
| 331 | + versionResolvers.add(new QueryApiVersionResolver(versionProperties.getUse().getQueryParameter())); |
| 332 | + } |
| 333 | + |
| 334 | + if (apiVersionResolvers != null && !apiVersionResolvers.isEmpty()) { |
| 335 | + versionResolvers.addAll(apiVersionResolvers); |
| 336 | + } |
| 337 | + |
| 338 | + if (versionResolvers.isEmpty()) { |
| 339 | + return bean; |
| 340 | + } |
| 341 | + |
| 342 | + Boolean required = versionProperties.getRequired(); |
| 343 | + if (required == null) { |
| 344 | + required = false; |
| 345 | + } |
| 346 | + Boolean detectSupported = versionProperties.getDetectSupported(); |
| 347 | + if (detectSupported == null) { |
| 348 | + detectSupported = true; |
| 349 | + } |
| 350 | + GatewayApiVersionStrategy strategy = new GatewayApiVersionStrategy(versionResolvers, versionParser, |
| 351 | + required, versionProperties.getDefaultVersion(), detectSupported, deprecationHandler); |
| 352 | + if (!CollectionUtils.isEmpty(versionProperties.getSupported())) { |
| 353 | + strategy.addSupportedVersion(versionProperties.getSupported().toArray(new String[0])); |
| 354 | + } |
| 355 | + return strategy; |
| 356 | + } |
| 357 | + return bean; |
| 358 | + } |
| 359 | + |
| 360 | + } |
| 361 | + |
| 362 | + protected static class GatewayApiVersionStrategy extends DefaultApiVersionStrategy { |
| 363 | + |
| 364 | + public GatewayApiVersionStrategy(List<ApiVersionResolver> versionResolvers, ApiVersionParser<?> versionParser, |
| 365 | + boolean versionRequired, @Nullable String defaultVersion, boolean detectSupportedVersions, |
| 366 | + @Nullable ApiVersionDeprecationHandler deprecationHandler) { |
| 367 | + super(versionResolvers, versionParser, versionRequired, defaultVersion, detectSupportedVersions, |
| 368 | + deprecationHandler); |
| 369 | + } |
| 370 | + |
| 371 | + @Override |
| 372 | + public @Nullable Comparable<?> resolveParseAndValidateVersion(HttpServletRequest request) { |
| 373 | + try { |
| 374 | + return super.resolveParseAndValidateVersion(request); |
| 375 | + } |
| 376 | + catch (InvalidApiVersionException e) { |
| 377 | + // ignore, so gateway will 404, not 400 |
| 378 | + return null; |
| 379 | + } |
| 380 | + } |
| 381 | + |
| 382 | + @Override |
| 383 | + public void validateVersion(@Nullable Comparable<?> requestVersion, HttpServletRequest request) |
| 384 | + throws MissingApiVersionException, InvalidApiVersionException { |
| 385 | + try { |
| 386 | + super.validateVersion(requestVersion, request); |
| 387 | + } |
| 388 | + catch (InvalidApiVersionException e) { |
| 389 | + // ignore, so gateway will 404, not 400 |
| 390 | + } |
| 391 | + } |
| 392 | + |
| 393 | + } |
| 394 | + |
261 | 395 | } |
0 commit comments