Skip to content

Commit 811962d

Browse files
author
Fredrich Ombico
committed
Support query parameters in RedirectTo filter
Adds an optional boolean parameter to the RedirectTo filter to indicate whether the request query parameters should be included on the `url`. The parameter is treated as false when not set to preserve backwards compatibility.
1 parent 8a7a00c commit 811962d

File tree

5 files changed

+155
-10
lines changed

5 files changed

+155
-10
lines changed

docs/src/main/asciidoc/spring-cloud-gateway.adoc

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1242,10 +1242,12 @@ spring:
12421242

12431243
=== The `RedirectTo` `GatewayFilter` Factory
12441244

1245-
The `RedirectTo` `GatewayFilter` factory takes two parameters, `status` and `url`.
1245+
The `RedirectTo` `GatewayFilter` factory takes three parameters, `status`, `url`, and optionally `includeRequestParams`.
12461246
The `status` parameter should be a 300 series redirect HTTP code, such as 301.
12471247
The `url` parameter should be a valid URL.
12481248
This is the value of the `Location` header.
1249+
The `includeRequestParams` parameter indicates whether request query parameters should be included on the `url`.
1250+
When not set, it will be treated as `false`.
12491251
For relative redirects, you should use `uri: no://op` as the uri of your route definition.
12501252
The following listing configures a `RedirectTo` `GatewayFilter`:
12511253

@@ -1266,6 +1268,24 @@ spring:
12661268

12671269
This will send a status 302 with a `Location:https://acme.org` header to perform a redirect.
12681270

1271+
The following example configures a `RedirectTo` `GatewayFilter` with `includeRequestParams` set to `true`.
1272+
1273+
.application.yml
1274+
[source,yaml]
1275+
----
1276+
spring:
1277+
cloud:
1278+
gateway:
1279+
routes:
1280+
- id: prefixpath_route
1281+
uri: https://example.org
1282+
filters:
1283+
- RedirectTo=302, https://acme.org, true
1284+
----
1285+
1286+
When a request with query `?skip=10` is made to the gateway, the gateway will send a status 302 with a
1287+
`Location:https://acme.org?skip=10` header to perform a redirect.
1288+
12691289

12701290
=== `RemoveJsonAttributesResponseBody` `GatewayFilter` Factory
12711291

spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RedirectToGatewayFilterFactory.java

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.springframework.http.server.reactive.ServerHttpResponse;
3131
import org.springframework.util.Assert;
3232
import org.springframework.web.server.ServerWebExchange;
33+
import org.springframework.web.util.UriComponentsBuilder;
3334

3435
import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator;
3536
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.setResponseStatus;
@@ -50,40 +51,66 @@ public class RedirectToGatewayFilterFactory
5051
*/
5152
public static final String URL_KEY = "url";
5253

54+
/**
55+
* IncludeRequestParams key.
56+
*/
57+
public static final String INCLUDE_REQUEST_PARAMS_KEY = "includeRequestParams";
58+
5359
public RedirectToGatewayFilterFactory() {
5460
super(Config.class);
5561
}
5662

5763
@Override
5864
public List<String> shortcutFieldOrder() {
59-
return Arrays.asList(STATUS_KEY, URL_KEY);
65+
return Arrays.asList(STATUS_KEY, URL_KEY, INCLUDE_REQUEST_PARAMS_KEY);
6066
}
6167

6268
@Override
6369
public GatewayFilter apply(Config config) {
64-
return apply(config.status, config.url);
70+
return apply(config.status, config.url, config.includeRequestParams);
6571
}
6672

6773
public GatewayFilter apply(String statusString, String urlString) {
74+
return apply(statusString, urlString, false);
75+
}
76+
77+
public GatewayFilter apply(String statusString, String urlString, boolean includeRequestParams) {
6878
HttpStatusHolder httpStatus = HttpStatusHolder.parse(statusString);
6979
Assert.isTrue(httpStatus.is3xxRedirection(), "status must be a 3xx code, but was " + statusString);
7080
final URI url = URI.create(urlString);
71-
return apply(httpStatus, url);
81+
return apply(httpStatus, url, includeRequestParams);
7282
}
7383

7484
public GatewayFilter apply(HttpStatus httpStatus, URI uri) {
75-
return apply(new HttpStatusHolder(httpStatus, null), uri);
85+
return apply(new HttpStatusHolder(httpStatus, null), uri, false);
86+
}
87+
88+
public GatewayFilter apply(HttpStatus httpStatus, URI uri, boolean includeRequestParams) {
89+
return apply(new HttpStatusHolder(httpStatus, null), uri, includeRequestParams);
7690
}
7791

7892
public GatewayFilter apply(HttpStatusHolder httpStatus, URI uri) {
93+
return apply(httpStatus, uri, false);
94+
}
95+
96+
public GatewayFilter apply(HttpStatusHolder httpStatus, URI uri, boolean includeRequestParams) {
7997
return new GatewayFilter() {
8098
@Override
8199
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
82100
if (!exchange.getResponse().isCommitted()) {
83101
setResponseStatus(exchange, httpStatus);
84102

103+
String location;
104+
if (includeRequestParams) {
105+
location = UriComponentsBuilder.fromUri(uri).queryParams(exchange.getRequest().getQueryParams())
106+
.build().toUri().toString();
107+
}
108+
else {
109+
location = uri.toString();
110+
}
111+
85112
final ServerHttpResponse response = exchange.getResponse();
86-
response.getHeaders().set(HttpHeaders.LOCATION, uri.toString());
113+
response.getHeaders().set(HttpHeaders.LOCATION, location);
87114
return response.setComplete();
88115
}
89116
return Mono.empty();
@@ -98,7 +125,8 @@ public String toString() {
98125
else {
99126
status = httpStatus.getStatus().toString();
100127
}
101-
return filterToStringCreator(RedirectToGatewayFilterFactory.this).append(status, uri).toString();
128+
return filterToStringCreator(RedirectToGatewayFilterFactory.this).append(status, uri)
129+
.append(INCLUDE_REQUEST_PARAMS_KEY, includeRequestParams).toString();
102130
}
103131
};
104132
}
@@ -109,6 +137,8 @@ public static class Config {
109137

110138
String url;
111139

140+
boolean includeRequestParams;
141+
112142
public String getStatus() {
113143
return status;
114144
}
@@ -125,6 +155,14 @@ public void setUrl(String url) {
125155
this.url = url;
126156
}
127157

158+
public boolean isIncludeRequestParams() {
159+
return includeRequestParams;
160+
}
161+
162+
public void setIncludeRequestParams(boolean includeRequestParams) {
163+
this.includeRequestParams = includeRequestParams;
164+
}
165+
128166
}
129167

130168
}

spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/builder/GatewayFilterSpec.java

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,18 @@ public GatewayFilterSpec redirect(int status, URI url) {
435435
return redirect(String.valueOf(status), url.toString());
436436
}
437437

438+
/**
439+
* A filter that will return a redirect response back to the client.
440+
* @param status an HTTP status code, should be a {@code 300} series redirect
441+
* @param url the URL to redirect to. This URL will be set in the {@code location}
442+
* header
443+
* @param includeRequestParams if true, query params will be passed to the url
444+
* @return a {@link GatewayFilterSpec} that can be used to apply additional filters
445+
*/
446+
public GatewayFilterSpec redirect(int status, URI url, boolean includeRequestParams) {
447+
return redirect(String.valueOf(status), url.toString(), includeRequestParams);
448+
}
449+
438450
/**
439451
* A filter that will return a redirect response back to the client.
440452
* @param status an HTTP status code, should be a {@code 300} series redirect
@@ -446,6 +458,18 @@ public GatewayFilterSpec redirect(int status, String url) {
446458
return redirect(String.valueOf(status), url);
447459
}
448460

461+
/**
462+
* A filter that will return a redirect response back to the client.
463+
* @param status an HTTP status code, should be a {@code 300} series redirect
464+
* @param url the URL to redirect to. This URL will be set in the {@code location}
465+
* header
466+
* @param includeRequestParams if true, query params will be passed to the url
467+
* @return a {@link GatewayFilterSpec} that can be used to apply additional filters
468+
*/
469+
public GatewayFilterSpec redirect(int status, String url, boolean includeRequestParams) {
470+
return redirect(String.valueOf(status), url, includeRequestParams);
471+
}
472+
449473
/**
450474
* A filter that will return a redirect response back to the client.
451475
* @param status an HTTP status code, should be a {@code 300} series redirect
@@ -454,7 +478,19 @@ public GatewayFilterSpec redirect(int status, String url) {
454478
* @return a {@link GatewayFilterSpec} that can be used to apply additional filters
455479
*/
456480
public GatewayFilterSpec redirect(String status, URI url) {
457-
return redirect(status, url.toString());
481+
return redirect(status, url.toString(), false);
482+
}
483+
484+
/**
485+
* A filter that will return a redirect response back to the client.
486+
* @param status an HTTP status code, should be a {@code 300} series redirect
487+
* @param url the URL to redirect to. This URL will be set in the {@code location}
488+
* header
489+
* @param includeRequestParams if true, query params will be passed to the url
490+
* @return a {@link GatewayFilterSpec} that can be used to apply additional filters
491+
*/
492+
public GatewayFilterSpec redirect(String status, String url, boolean includeRequestParams) {
493+
return filter(getBean(RedirectToGatewayFilterFactory.class).apply(status, url, includeRequestParams));
458494
}
459495

460496
/**
@@ -476,8 +512,21 @@ public GatewayFilterSpec redirect(String status, String url) {
476512
* @return a {@link GatewayFilterSpec} that can be used to apply additional filters
477513
*/
478514
public GatewayFilterSpec redirect(HttpStatus status, URL url) {
515+
return redirect(status, url, false);
516+
}
517+
518+
/**
519+
* A filter that will return a redirect response back to the client.
520+
* @param status an HTTP status code, should be a {@code 300} series redirect
521+
* @param url the URL to redirect to. This URL will be set in the {@code location}
522+
* header
523+
* @param includeRequestParams if true, query params will be passed to the url
524+
* @return a {@link GatewayFilterSpec} that can be used to apply additional filters
525+
*/
526+
public GatewayFilterSpec redirect(HttpStatus status, URL url, boolean includeRequestParams) {
479527
try {
480-
return filter(getBean(RedirectToGatewayFilterFactory.class).apply(status, url.toURI()));
528+
return filter(
529+
getBean(RedirectToGatewayFilterFactory.class).apply(status, url.toURI(), includeRequestParams));
481530
}
482531
catch (URISyntaxException e) {
483532
throw new IllegalArgumentException("Invalid URL", e);

spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RedirectToGatewayFilterFactoryTests.java

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,19 @@ public void redirectToFilterWorks() {
4747
.isEqualTo(HttpStatus.FOUND).expectHeader().valueEquals(HttpHeaders.LOCATION, "https://example.org");
4848
}
4949

50+
@Test
51+
public void redirectToUrlDoesNotPassQueryParametersByDefault() {
52+
testClient.get().uri("/?membership=gold").header("Host", "www.redirectto.org").exchange().expectStatus()
53+
.isEqualTo(HttpStatus.FOUND).expectHeader().valueEquals(HttpHeaders.LOCATION, "https://example.org");
54+
}
55+
56+
@Test
57+
public void redirectToUrlAddsQueryParametersWhenEnabledOnFilter() {
58+
testClient.get().uri("/?membership=gold").header("Host", "queryparams.redirectto.org").exchange().expectStatus()
59+
.isEqualTo(HttpStatus.FOUND).expectHeader()
60+
.valueEquals(HttpHeaders.LOCATION, "https://example.org?membership=gold");
61+
}
62+
5063
@Test
5164
public void redirectToRelativeUrlFilterWorks() {
5265
testClient.get().uri("/").header("Host", "www.relativeredirect.org").exchange().expectStatus()
@@ -59,13 +72,27 @@ public void redirectToRelativeUrlFilterWorksWithStrStatusCode() {
5972
.isEqualTo(HttpStatus.FOUND).expectHeader().valueEquals(HttpHeaders.LOCATION, "/index.html#/customers");
6073
}
6174

75+
@Test
76+
public void redirectToRelativeUrlDoesNotPassQueryParametersByDefault() {
77+
testClient.get().uri("/?membership=gold").header("Host", "www.relativeredirect.org").exchange().expectStatus()
78+
.isEqualTo(HttpStatus.FOUND).expectHeader().valueEquals(HttpHeaders.LOCATION, "/index.html#/customers");
79+
}
80+
81+
@Test
82+
public void redirectToRelativeUrlAddsQueryParametersWhenEnabledOnFilter() {
83+
testClient.get().uri("/?membership=gold").header("Host", "queryparams.relativeredirect.org").exchange()
84+
.expectStatus().isEqualTo(HttpStatus.FOUND).expectHeader()
85+
.valueEquals(HttpHeaders.LOCATION, "/index.html?membership=gold#/customers");
86+
}
87+
6288
@Test
6389
public void toStringFormat() {
6490
Config config = new Config();
6591
config.setStatus("301");
6692
config.setUrl("http://newurl");
93+
config.setIncludeRequestParams(true);
6794
GatewayFilter filter = new RedirectToGatewayFilterFactory().apply(config);
68-
assertThat(filter.toString()).contains("301").contains("http://newurl");
95+
assertThat(filter.toString()).contains("301").contains("http://newurl").contains("true");
6996
}
7097

7198
@EnableAutoConfiguration
@@ -78,6 +105,9 @@ public RouteLocator testRouteLocator(RouteLocatorBuilder builder) {
78105
return builder.routes()
79106
.route("relative_redirect_uri_object", r -> r.host("strcode.relativeredirect.org")
80107
.filters(f -> f.redirect("302", URI.create("/index.html#/customers"))).uri("no://op"))
108+
.route("relative_redirect_with_query_params",
109+
r -> r.host("queryparams.relativeredirect.org")
110+
.filters(f -> f.redirect(302, "/index.html#/customers", true)).uri("no://op"))
81111
.route("relative_redirect", r -> r.host("**.relativeredirect.org")
82112
.filters(f -> f.redirect(302, "/index.html#/customers")).uri("no://op"))
83113
.build();

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,14 @@ spring:
296296
filters:
297297
- SetPath=/anything/{digits}
298298

299+
# =====================================
300+
- id: redirect_to_include_query_params_test
301+
uri: ${test.uri}
302+
predicates:
303+
- Host=queryparams.redirectto.org
304+
filters:
305+
- RedirectTo=302, https://example.org, true
306+
299307
# =====================================
300308
- id: redirect_to_test
301309
uri: ${test.uri}

0 commit comments

Comments
 (0)