Add RewriteRequestParameter GatewayFilter factory (#3081)
The `RewriteRequestParameter` `GatewayFilter` factory takes a `name` parameter and a `replacement` parameter. It will rewrite the value of the request parameter of the given `name`.
This commit is contained in:
@@ -43,6 +43,7 @@
|
||||
|spring.cloud.gateway.filter.rewrite-location-response-header.enabled | `+++true+++` | Enables the rewrite-location-response-header filter.
|
||||
|spring.cloud.gateway.filter.rewrite-location.enabled | `+++true+++` | Enables the rewrite-location filter.
|
||||
|spring.cloud.gateway.filter.rewrite-path.enabled | `+++true+++` | Enables the rewrite-path filter.
|
||||
|spring.cloud.gateway.filter.rewrite-request-parameter.enabled | `+++true+++` | Enables the rewrite-request-parameter filter.
|
||||
|spring.cloud.gateway.filter.rewrite-response-header.enabled | `+++true+++` | Enables the rewrite-response-header filter.
|
||||
|spring.cloud.gateway.filter.save-session.enabled | `+++true+++` | Enables the save-session filter.
|
||||
|spring.cloud.gateway.filter.secure-headers.content-security-policy | `+++default-src 'self' https:; font-src 'self' https: data:; img-src 'self' https: data:; object-src 'none'; script-src https:; style-src 'self' https: 'unsafe-inline'+++` |
|
||||
|
||||
@@ -1598,6 +1598,34 @@ spring:
|
||||
|
||||
For a request path of `/red/blue`, this sets the path to `/blue` before making the downstream request. Note that the `$` should be replaced with `$\` because of the YAML specification.
|
||||
|
||||
=== The `RewriteRequestParameter` `GatewayFilter` Factory
|
||||
|
||||
The `RewriteRequestParameter` `GatewayFilter` factory takes a `name` parameter and a `replacement` parameter.
|
||||
It will rewrite the value of the request parameter of the given `name`.
|
||||
If multiple request parameters with the same `name` are set, they will be replaced with a single value.
|
||||
If no request parameter is found, no changes will be made.
|
||||
The following listing configures a `RewriteRequestParameter` `GatewayFilter`:
|
||||
|
||||
.application.yml
|
||||
====
|
||||
[source,yaml]
|
||||
----
|
||||
spring:
|
||||
cloud:
|
||||
gateway:
|
||||
routes:
|
||||
- id: rewriterequestparameter_route
|
||||
uri: https://example.org
|
||||
predicates:
|
||||
- Path=/products
|
||||
filters:
|
||||
- RewriteRequestParameter=campaign,fall2023
|
||||
----
|
||||
====
|
||||
|
||||
For a request to `/products?campaign=old`, this sets the request parameter to `campaign=fall2023`.
|
||||
|
||||
|
||||
=== The `RewriteResponseHeader` `GatewayFilter` Factory
|
||||
|
||||
The `RewriteResponseHeader` `GatewayFilter` factory takes `name`, `regexp`, and `replacement` parameters.
|
||||
|
||||
@@ -97,6 +97,7 @@ import org.springframework.cloud.gateway.filter.factory.RequestSizeGatewayFilter
|
||||
import org.springframework.cloud.gateway.filter.factory.RetryGatewayFilterFactory;
|
||||
import org.springframework.cloud.gateway.filter.factory.RewriteLocationResponseHeaderGatewayFilterFactory;
|
||||
import org.springframework.cloud.gateway.filter.factory.RewritePathGatewayFilterFactory;
|
||||
import org.springframework.cloud.gateway.filter.factory.RewriteRequestParameterGatewayFilterFactory;
|
||||
import org.springframework.cloud.gateway.filter.factory.RewriteResponseHeaderGatewayFilterFactory;
|
||||
import org.springframework.cloud.gateway.filter.factory.SaveSessionGatewayFilterFactory;
|
||||
import org.springframework.cloud.gateway.filter.factory.SecureHeadersGatewayFilterFactory;
|
||||
@@ -703,6 +704,12 @@ public class GatewayAutoConfiguration {
|
||||
return new RequestHeaderSizeGatewayFilterFactory();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnEnabledFilter
|
||||
public RewriteRequestParameterGatewayFilterFactory rewriteRequestParameterGatewayFilterFactory() {
|
||||
return new RewriteRequestParameterGatewayFilterFactory();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public GzipMessageBodyResolver gzipMessageBodyResolver() {
|
||||
return new GzipMessageBodyResolver();
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright 2013-2023 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.filter.factory;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilter;
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator;
|
||||
|
||||
/**
|
||||
* @author Fredrich Ombico
|
||||
*/
|
||||
public class RewriteRequestParameterGatewayFilterFactory
|
||||
extends AbstractGatewayFilterFactory<RewriteRequestParameterGatewayFilterFactory.Config> {
|
||||
|
||||
/**
|
||||
* Replacement key.
|
||||
*/
|
||||
public static final String REPLACEMENT_KEY = "replacement";
|
||||
|
||||
public RewriteRequestParameterGatewayFilterFactory() {
|
||||
super(Config.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> shortcutFieldOrder() {
|
||||
return Arrays.asList(NAME_KEY, REPLACEMENT_KEY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GatewayFilter apply(Config config) {
|
||||
return new GatewayFilter() {
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
||||
ServerHttpRequest req = exchange.getRequest();
|
||||
|
||||
UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromUri(req.getURI());
|
||||
if (req.getQueryParams().containsKey(config.getName())) {
|
||||
uriComponentsBuilder.replaceQueryParam(config.getName(), config.getReplacement());
|
||||
}
|
||||
|
||||
URI uri = uriComponentsBuilder.build().toUri();
|
||||
ServerHttpRequest request = req.mutate().uri(uri).build();
|
||||
|
||||
return chain.filter(exchange.mutate().request(request).build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return filterToStringCreator(RewriteRequestParameterGatewayFilterFactory.this)
|
||||
.append(config.getName(), config.replacement).toString();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static class Config extends AbstractGatewayFilterFactory.NameConfig {
|
||||
|
||||
private String replacement;
|
||||
|
||||
public String getReplacement() {
|
||||
return replacement;
|
||||
}
|
||||
|
||||
public Config setReplacement(String replacement) {
|
||||
Assert.notNull(replacement, "replacement must not be null");
|
||||
this.replacement = replacement;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -64,6 +64,7 @@ import org.springframework.cloud.gateway.filter.factory.RetryGatewayFilterFactor
|
||||
import org.springframework.cloud.gateway.filter.factory.RewriteLocationResponseHeaderGatewayFilterFactory;
|
||||
import org.springframework.cloud.gateway.filter.factory.RewriteLocationResponseHeaderGatewayFilterFactory.StripVersion;
|
||||
import org.springframework.cloud.gateway.filter.factory.RewritePathGatewayFilterFactory;
|
||||
import org.springframework.cloud.gateway.filter.factory.RewriteRequestParameterGatewayFilterFactory;
|
||||
import org.springframework.cloud.gateway.filter.factory.RewriteResponseHeaderGatewayFilterFactory;
|
||||
import org.springframework.cloud.gateway.filter.factory.SaveSessionGatewayFilterFactory;
|
||||
import org.springframework.cloud.gateway.filter.factory.SecureHeadersGatewayFilterFactory;
|
||||
@@ -686,6 +687,17 @@ public class GatewayFilterSpec extends UriSpec {
|
||||
.setHostValue(hostValue).setProtocols(protocolsRegex)));
|
||||
}
|
||||
|
||||
/**
|
||||
* A filter that rewrites the value of a request parameter
|
||||
* @param name The name of the request parameter to replace
|
||||
* @param replacement The new value for the request parameter
|
||||
* @return a {@link GatewayFilterSpec} that can be used to apply additional filters
|
||||
*/
|
||||
public GatewayFilterSpec rewriteRequestParameter(String name, String replacement) {
|
||||
return filter(getBean(RewriteRequestParameterGatewayFilterFactory.class)
|
||||
.apply(c -> c.setReplacement(replacement).setName(name)));
|
||||
}
|
||||
|
||||
/**
|
||||
* A filter that sets the status on the response before it is returned to the client
|
||||
* by the Gateway.
|
||||
|
||||
@@ -167,6 +167,12 @@
|
||||
"description": "Enables the rewrite-location filter.",
|
||||
"defaultValue": "true"
|
||||
},
|
||||
{
|
||||
"name": "spring.cloud.gateway.filter.rewrite-request-parameter.enabled",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "Enables the rewrite-request-parameter filter.",
|
||||
"defaultValue": "true"
|
||||
},
|
||||
{
|
||||
"name": "spring.cloud.gateway.filter.set-status.enabled",
|
||||
"type": "java.lang.Boolean",
|
||||
|
||||
@@ -103,6 +103,7 @@ public class DisableBuiltInFiltersTests {
|
||||
"spring.cloud.gateway.filter.rewrite-response-header.enabled=false",
|
||||
"spring.cloud.gateway.filter.rewrite-location-response-header.enabled=false",
|
||||
"spring.cloud.gateway.filter.rewrite-location.enabled=false",
|
||||
"spring.cloud.gateway.filter.rewrite-request-parameter.enabled=false",
|
||||
"spring.cloud.gateway.filter.set-status.enabled=false",
|
||||
"spring.cloud.gateway.filter.save-session.enabled=false",
|
||||
"spring.cloud.gateway.filter.strip-prefix.enabled=false",
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2013-2023 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.filter.factory;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.SpringBootConfiguration;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.cloud.gateway.test.BaseWebClientTests;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
|
||||
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
|
||||
|
||||
/**
|
||||
* @author Fredrich Ombico
|
||||
*/
|
||||
@SpringBootTest(webEnvironment = RANDOM_PORT)
|
||||
@DirtiesContext
|
||||
class RewriteRequestParameterGatewayFilterFactoryIntegrationTests extends BaseWebClientTests {
|
||||
|
||||
@Test
|
||||
void rewriteRequestParameterFilterWorks() {
|
||||
testClient.get().uri("/get?campaign=old").header("Host", "www.rewriterequestparameter.org").exchange()
|
||||
.expectStatus().isOk().expectBody().jsonPath("$.args.size", "fall2023");
|
||||
}
|
||||
|
||||
@EnableAutoConfiguration
|
||||
@SpringBootConfiguration
|
||||
@Import(DefaultTestConfig.class)
|
||||
public static class TestConfig {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright 2013-2023 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.filter.factory;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilter;
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
|
||||
import org.springframework.mock.web.server.MockServerWebExchange;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.cloud.gateway.filter.factory.RewriteRequestParameterGatewayFilterFactory.Config;
|
||||
|
||||
/**
|
||||
* @author Fredrich Ombico
|
||||
*/
|
||||
class RewriteRequestParameterGatewayFilterFactoryTests {
|
||||
|
||||
@Test
|
||||
void toStringFormat() {
|
||||
Config config = new Config();
|
||||
config.setName("campaign");
|
||||
config.setReplacement("fall2023");
|
||||
GatewayFilter filter = new RewriteRequestParameterGatewayFilterFactory().apply(config);
|
||||
assertThat(filter.toString()).contains("campaign").contains("fall2023");
|
||||
}
|
||||
|
||||
@Test
|
||||
void rewriteRequestParameterFilterWorks() {
|
||||
testRewriteRequestParameterFilter("campaign", "fall2023", "size=small&campaign=old",
|
||||
Map.of("size", List.of("small"), "campaign", List.of("fall2023")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void rewriteRequestParameterFilterRewritesMultipleParamsWithSameName() {
|
||||
testRewriteRequestParameterFilter("campaign", "fall2023", "campaign=fall&size=small&campaign=old",
|
||||
Map.of("size", List.of("small"), "campaign", List.of("fall2023")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void rewriteRequestParameterFilterDoesNotAddParamIfNameNotFound() {
|
||||
testRewriteRequestParameterFilter("campaign", "winter2023", "color=green&sort=popular",
|
||||
Map.of("color", List.of("green"), "sort", List.of("popular")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void rewriteRequestParameterFilterWorksWithSpecialCharacters() {
|
||||
testRewriteRequestParameterFilter("campaign", "black friday~(1.A-B_C!)", "campaign=old&color=green",
|
||||
Map.of("campaign", List.of("black friday~(1.A-B_C!)"), "color", List.of("green")));
|
||||
}
|
||||
|
||||
private void testRewriteRequestParameterFilter(String name, String replacement, String query,
|
||||
Map<String, List<String>> expectedQueryParams) {
|
||||
GatewayFilter filter = new RewriteRequestParameterGatewayFilterFactory()
|
||||
.apply(config -> config.setReplacement(replacement).setName(name));
|
||||
|
||||
URI url = UriComponentsBuilder.fromUriString("http://localhost/get").query(query).build(true).toUri();
|
||||
MockServerHttpRequest request = MockServerHttpRequest.method(HttpMethod.GET, url).build();
|
||||
|
||||
ServerWebExchange exchange = MockServerWebExchange.from(request);
|
||||
|
||||
GatewayFilterChain filterChain = mock(GatewayFilterChain.class);
|
||||
|
||||
ArgumentCaptor<ServerWebExchange> captor = ArgumentCaptor.forClass(ServerWebExchange.class);
|
||||
when(filterChain.filter(captor.capture())).thenReturn(Mono.empty());
|
||||
|
||||
filter.filter(exchange, filterChain);
|
||||
|
||||
ServerWebExchange webExchange = captor.getValue();
|
||||
|
||||
MultiValueMap<String, String> actualQueryParams = webExchange.getRequest().getQueryParams();
|
||||
assertThat(actualQueryParams).containsExactlyInAnyOrderEntriesOf(expectedQueryParams);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -341,6 +341,14 @@ spring:
|
||||
- RewriteLocationResponseHeader
|
||||
- AddResponseHeader=Location, https://backend.org:443/v1/some/object/id
|
||||
|
||||
# =====================================
|
||||
- id: rewrite_request_parameter_test
|
||||
uri: ${test.uri}
|
||||
predicates:
|
||||
- Host=**.rewriterequestparameter.org
|
||||
filters:
|
||||
- RewriteRequestParameter=campaign,fall2023
|
||||
|
||||
# =====================================
|
||||
- id: rewrite_response_header_test
|
||||
uri: ${test.uri}
|
||||
|
||||
Reference in New Issue
Block a user