Skip to content

Commit 18c5990

Browse files
committed
Conforms to updates in Framework/Boot ApiVersioning
1 parent cbff986 commit 18c5990

File tree

7 files changed

+137
-84
lines changed

7 files changed

+137
-84
lines changed

spring-cloud-gateway-server-webflux/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java

Lines changed: 33 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
import org.springframework.boot.webflux.autoconfigure.HttpHandlerAutoConfiguration;
6969
import org.springframework.boot.webflux.autoconfigure.WebFluxAutoConfiguration;
7070
import org.springframework.boot.webflux.autoconfigure.WebFluxProperties;
71+
import org.springframework.boot.webflux.autoconfigure.WebFluxProperties.Apiversion;
7172
import org.springframework.cloud.gateway.actuate.GatewayControllerEndpoint;
7273
import org.springframework.cloud.gateway.actuate.GatewayLegacyControllerEndpoint;
7374
import org.springframework.cloud.gateway.config.conditional.ConditionalOnEnabledFilter;
@@ -188,6 +189,7 @@
188189
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientManager;
189190
import org.springframework.security.web.server.SecurityWebFilterChain;
190191
import org.springframework.util.ClassUtils;
192+
import org.springframework.util.CollectionUtils;
191193
import org.springframework.util.StringUtils;
192194
import org.springframework.validation.Validator;
193195
import org.springframework.web.accept.ApiVersionParser;
@@ -198,15 +200,12 @@
198200
import org.springframework.web.reactive.accept.ApiVersionStrategy;
199201
import org.springframework.web.reactive.accept.MediaTypeParamApiVersionResolver;
200202
import org.springframework.web.reactive.accept.PathApiVersionResolver;
201-
import org.springframework.web.reactive.config.ApiVersionConfigurer;
202-
import org.springframework.web.reactive.config.WebFluxConfigurer;
203203
import org.springframework.web.reactive.socket.client.ReactorNettyWebSocketClient;
204204
import org.springframework.web.reactive.socket.client.WebSocketClient;
205205
import org.springframework.web.reactive.socket.server.RequestUpgradeStrategy;
206206
import org.springframework.web.reactive.socket.server.WebSocketService;
207207
import org.springframework.web.reactive.socket.server.support.HandshakeWebSocketService;
208208
import org.springframework.web.reactive.socket.server.upgrade.ReactorNettyRequestUpgradeStrategy;
209-
import org.springframework.web.server.ServerWebExchange;
210209

211210
/**
212211
* @author Spencer Gibb
@@ -524,7 +523,7 @@ public RemoteAddrRoutePredicateFactory remoteAddrRoutePredicateFactory() {
524523
@Bean
525524
@ConditionalOnEnabledPredicate
526525
public VersionRoutePredicateFactory versionRoutePredicateFactory(
527-
@Qualifier("mvcApiVersionStrategy") @Nullable ApiVersionStrategy apiVersionStrategy) {
526+
@Qualifier("webFluxApiVersionStrategy") @Nullable ApiVersionStrategy apiVersionStrategy) {
528527
return new VersionRoutePredicateFactory(apiVersionStrategy);
529528
}
530529

@@ -776,13 +775,13 @@ public GzipMessageBodyResolver gzipMessageBodyResolver() {
776775
}
777776

778777
@Bean
779-
public GatewayServerWebfluxBeanPostProcessor gatewayServerWebfluxBeanPostProcessor(
780-
VersionProperties versionProperties,
778+
public GatewayServerWebfluxBeanPostProcessor gatewayServerWebfluxBeanPostProcessor(WebFluxProperties properties,
781779
ObjectProvider<ApiVersionDeprecationHandler> deprecationHandlerProvider,
782780
ObjectProvider<ApiVersionParser<?>> versionParserProvider,
783781
ObjectProvider<ApiVersionResolver> versionResolvers) {
784-
return new GatewayServerWebfluxBeanPostProcessor(versionProperties, deprecationHandlerProvider.getIfAvailable(),
785-
versionParserProvider.getIfAvailable(), versionResolvers.orderedStream().toList());
782+
return new GatewayServerWebfluxBeanPostProcessor(properties.getApiversion(),
783+
deprecationHandlerProvider.getIfAvailable(), versionParserProvider.getIfAvailable(),
784+
versionResolvers.orderedStream().toList());
786785
}
787786

788787
@Bean
@@ -963,47 +962,17 @@ public TokenRelayGatewayFilterFactory tokenRelayGatewayFilterFactory(
963962

964963
}
965964

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-
996965
protected static class GatewayServerWebfluxBeanPostProcessor implements BeanPostProcessor {
997966

998-
private final VersionProperties versionProperties;
967+
private final Apiversion versionProperties;
999968

1000969
private final ApiVersionDeprecationHandler deprecationHandler;
1001970

1002971
private final ApiVersionParser<?> versionParser;
1003972

1004973
private final List<ApiVersionResolver> apiVersionResolvers;
1005974

1006-
public GatewayServerWebfluxBeanPostProcessor(VersionProperties versionProperties,
975+
public GatewayServerWebfluxBeanPostProcessor(Apiversion versionProperties,
1007976
ApiVersionDeprecationHandler deprecationHandler, ApiVersionParser<?> versionParser,
1008977
List<ApiVersionResolver> apiVersionResolvers) {
1009978
this.versionProperties = versionProperties;
@@ -1016,24 +985,25 @@ public GatewayServerWebfluxBeanPostProcessor(VersionProperties versionProperties
1016985
public @Nullable Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
1017986

1018987
// TODO: Use custom ApiVersionConfigurer when able to
1019-
if (bean instanceof ApiVersionStrategy && beanName.equals("mvcApiVersionStrategy")) {
988+
if (bean instanceof ApiVersionStrategy && beanName.equals("webFluxApiVersionStrategy")) {
1020989
List<ApiVersionResolver> versionResolvers = new ArrayList<>();
1021-
if (StringUtils.hasText(versionProperties.getHeaderName())) {
1022-
versionResolvers.add(
1023-
exchange -> exchange.getRequest().getHeaders().getFirst(versionProperties.getHeaderName()));
990+
if (StringUtils.hasText(versionProperties.getUse().getHeader())) {
991+
versionResolvers.add(exchange -> exchange.getRequest()
992+
.getHeaders()
993+
.getFirst(versionProperties.getUse().getHeader()));
1024994
}
1025-
if (versionProperties.getMediaType() != null
1026-
&& StringUtils.hasText(versionProperties.getMediaTypeParamName())) {
1027-
versionResolvers.add(new MediaTypeParamApiVersionResolver(versionProperties.getMediaType(),
1028-
versionProperties.getMediaTypeParamName()));
995+
if (!CollectionUtils.isEmpty(versionProperties.getUse().getMediaTypeParameter())) {
996+
versionProperties.getUse().getMediaTypeParameter().forEach((mediaType, param) -> {
997+
versionResolvers.add(new MediaTypeParamApiVersionResolver(mediaType, param));
998+
});
1029999
}
1030-
if (versionProperties.getPathSegment() != null) {
1031-
versionResolvers.add(new PathApiVersionResolver(versionProperties.getPathSegment()));
1000+
if (versionProperties.getUse().getPathSegment() != null) {
1001+
versionResolvers.add(new PathApiVersionResolver(versionProperties.getUse().getPathSegment()));
10321002
}
1033-
if (StringUtils.hasText(versionProperties.getRequestParamName())) {
1003+
if (StringUtils.hasText(versionProperties.getUse().getQueryParameter())) {
10341004
versionResolvers.add(exchange -> exchange.getRequest()
10351005
.getQueryParams()
1036-
.getFirst(versionProperties.getRequestParamName()));
1006+
.getFirst(versionProperties.getUse().getQueryParameter()));
10371007
}
10381008

10391009
if (apiVersionResolvers != null && !apiVersionResolvers.isEmpty()) {
@@ -1044,11 +1014,18 @@ public GatewayServerWebfluxBeanPostProcessor(VersionProperties versionProperties
10441014
return bean;
10451015
}
10461016

1017+
Boolean required = versionProperties.getRequired();
1018+
if (required == null) {
1019+
required = false;
1020+
}
1021+
Boolean detectSupported = versionProperties.getDetectSupported();
1022+
if (detectSupported == null) {
1023+
detectSupported = true;
1024+
}
10471025
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]));
1026+
required, versionProperties.getDefaultVersion(), detectSupported, deprecationHandler);
1027+
if (!CollectionUtils.isEmpty(versionProperties.getSupported())) {
1028+
strategy.addSupportedVersion(versionProperties.getSupported().toArray(new String[0]));
10521029
}
10531030
return strategy;
10541031
}

spring-cloud-gateway-server-webflux/src/main/java/org/springframework/cloud/gateway/handler/predicate/VersionRoutePredicateFactory.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,8 @@ public boolean test(ServerWebExchange exchange) {
7979
.get(HandlerMapping.API_VERSION_ATTRIBUTE);
8080

8181
if (requestVersion == null) {
82-
traceMatch("Version", config.version, null, true);
83-
return true;
82+
traceMatch("Version", config.version, null, false);
83+
return false;
8484
}
8585

8686
int result = compareVersions(config.parsedVersion, requestVersion);

spring-cloud-gateway-server-webflux/src/main/java/org/springframework/cloud/gateway/support/GatewayApiVersionStrategy.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,17 @@ public GatewayApiVersionStrategy(List<ApiVersionResolver> versionResolvers, ApiV
5757
deprecationHandler);
5858
}
5959

60+
@Override
61+
public @Nullable Comparable<?> resolveParseAndValidateVersion(ServerWebExchange exchange) {
62+
try {
63+
return super.resolveParseAndValidateVersion(exchange);
64+
}
65+
catch (InvalidApiVersionException e) {
66+
// ignore, so gateway will 404, not 400
67+
return null;
68+
}
69+
}
70+
6071
@Override
6172
public void validateVersion(@Nullable Comparable<?> requestVersion, ServerWebExchange exchange)
6273
throws MissingApiVersionException, InvalidApiVersionException {

spring-cloud-gateway-server-webflux/src/test/java/org/springframework/cloud/gateway/handler/predicate/VersionRoutePredicateFactoryIntegrationTests.java

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -134,19 +134,6 @@ public void customVersionResolverBeanWorks() {
134134
.valueEquals("X-Matched-Version", "1.1+");
135135
}
136136

137-
@Test
138-
public void versionPathSegmentWorks() {
139-
testClient.mutate()
140-
.build()
141-
.get()
142-
.uri("/version14/1.4.0")
143-
.exchange()
144-
.expectStatus()
145-
.isOk()
146-
.expectHeader()
147-
.valueEquals("X-Matched-Version", "1.4");
148-
}
149-
150137
@Test
151138
public void invalidVersionNotFound() {
152139
testClient.mutate()
@@ -236,14 +223,8 @@ public RouteLocator testRouteLocator(RouteLocatorBuilder builder) {
236223
.version("2.0+")
237224
.filters(f -> f.prefixPath("/httpbin").setResponseHeader("X-Matched-Version", "2.0+"))
238225
.uri(uri))
239-
.route("version14_dsl",
240-
r -> r.path("/version14/{pathVersion}")
241-
.and()
242-
.version("1.4")
243-
.filters(f -> f.setPath("/httpbin/anything/version14{pathVersion}")
244-
.setResponseHeader("X-Matched-Version", "1.4"))
245-
.uri(uri))
246226
.build();
227+
247228
}
248229

249230
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Copyright 2013-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.cloud.gateway.handler.predicate;
18+
19+
import org.junit.jupiter.api.Test;
20+
21+
import org.springframework.beans.factory.annotation.Value;
22+
import org.springframework.boot.SpringBootConfiguration;
23+
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
24+
import org.springframework.boot.test.context.SpringBootTest;
25+
import org.springframework.cloud.gateway.route.RouteLocator;
26+
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
27+
import org.springframework.cloud.gateway.test.BaseWebClientTests;
28+
import org.springframework.context.annotation.Bean;
29+
import org.springframework.context.annotation.Import;
30+
import org.springframework.test.context.ActiveProfiles;
31+
32+
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
33+
34+
@SpringBootTest(webEnvironment = RANDOM_PORT)
35+
@ActiveProfiles("versionspathsegment")
36+
public class VersionRoutePredicateFactoryPathSegmentIntegrationTests extends BaseWebClientTests {
37+
38+
@Test
39+
public void versionPathSegmentWorks() {
40+
testClient.mutate()
41+
.build()
42+
.get()
43+
.uri("/version14/1.4.0")
44+
.exchange()
45+
.expectStatus()
46+
.isOk()
47+
.expectHeader()
48+
.valueEquals("X-Matched-Version", "1.4");
49+
}
50+
51+
@EnableAutoConfiguration
52+
@SpringBootConfiguration
53+
@Import(DefaultTestConfig.class)
54+
public static class TestConfig {
55+
56+
@Value("${test.uri}")
57+
String uri;
58+
59+
@Bean
60+
public RouteLocator testRouteLocator(RouteLocatorBuilder builder) {
61+
return builder.routes()
62+
.route("version14_dsl",
63+
r -> r.path("/version14/{pathVersion}")
64+
.and()
65+
.version("1.4")
66+
.filters(f -> f.setPath("/httpbin/anything/version14{pathVersion}")
67+
.setResponseHeader("X-Matched-Version", "1.4"))
68+
.uri(uri))
69+
.build();
70+
}
71+
72+
}
73+
74+
}

spring-cloud-gateway-server-webflux/src/test/resources/application-versions.yml

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,6 @@ spring:
33
gateway.server.webflux:
44
default-filters:
55
- PrefixPath=/httpbin
6-
version:
7-
header-name: X-API-Version
8-
request-param-name: apiVersion
9-
path-segment: 3
10-
media-type: application/json
11-
media-type-param-name: version
126
routes:
137
# =====================================
148
- id: version_13
@@ -18,6 +12,14 @@ spring:
1812
- Version=1.3
1913
filters:
2014
- AddResponseHeader=X-Matched-Version,1.3
15+
webflux:
16+
apiversion:
17+
use:
18+
header: X-API-Version
19+
media-type-parameter:
20+
"[application/json]": version
21+
query-parameter: apiVersion
22+
required: false
2123
logging:
2224
level:
2325
org.springframework.cloud.gateway: TRACE
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
spring:
2+
webflux:
3+
apiversion:
4+
use:
5+
path-segment: 1
6+
logging:
7+
level:
8+
org.springframework.cloud.gateway: TRACE

0 commit comments

Comments
 (0)