Rewrite response header filter factory.

This commit is contained in:
Vitaliy Pavlyuk
2018-10-04 20:49:16 -04:00
parent b27ea537ba
commit 6b52f76ac2
7 changed files with 188 additions and 58 deletions

View File

@@ -620,6 +620,24 @@ spring:
For a request path of `/foo/bar`, this will set the path to `/bar` before making the downstream request. Notice the `$\` which is replaced with `$` because of the YAML spec.
=== RewriteResponseHeader GatewayFilter Factory
The RewriteResponseHeader GatewayFilter Factory takes `name`, `regexp`, and `replacement` parameters. It uses Java regular expressions for a flexible way to rewrite the response header value.
.application.yml
[source,yaml]
----
spring:
cloud:
gateway:
routes:
- id: rewriteresponseheader_route
uri: http://example.org
filters:
- RewriteResponseHeader=X-Response-Foo, , password=[^&]+, password=***
----
For a header value of `/42?user=ford&password=omg!what&flag=true`, it will be set to `/42?user=ford&password=\***&flag=true` before making the downstream request. Please use `$\` to mean `$` because of the YAML spec.
=== SaveSession GatewayFilter Factory
The SaveSession GatewayFilter Factory forces a `WebSession::save` operation _before_ forwarding the call downstream. This is of particular use when
using something like http://projects.spring.io/spring-session/[Spring Session] with a lazy data store and need to ensure the session state has been saved before making the forwarded call.

View File

@@ -24,6 +24,7 @@ import java.util.function.Consumer;
import com.netflix.hystrix.HystrixObservableCommand;
import io.netty.channel.ChannelOption;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import org.springframework.cloud.gateway.filter.factory.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.ipc.netty.http.client.HttpClient;
@@ -55,28 +56,6 @@ import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter;
import org.springframework.cloud.gateway.filter.RouteToRequestUrlFilter;
import org.springframework.cloud.gateway.filter.WebsocketRoutingFilter;
import org.springframework.cloud.gateway.filter.WeightCalculatorWebFilter;
import org.springframework.cloud.gateway.filter.factory.AddRequestHeaderGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.AddRequestParameterGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.AddResponseHeaderGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.GatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.HystrixGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.PrefixPathGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.PreserveHostHeaderGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.RedirectToGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.RemoveRequestHeaderGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.RemoveResponseHeaderGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.RequestHeaderToRequestUriGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.RequestRateLimiterGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.RetryGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.RewritePathGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.SaveSessionGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.SecureHeadersGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.SecureHeadersProperties;
import org.springframework.cloud.gateway.filter.factory.SetPathGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.SetRequestHeaderGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.SetResponseHeaderGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.SetStatusGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.StripPrefixGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.rewrite.ModifyRequestBodyGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.rewrite.ModifyResponseBodyGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.headers.ForwardedHeadersFilter;
@@ -575,6 +554,11 @@ public class GatewayAutoConfiguration {
return new SetResponseHeaderGatewayFilterFactory();
}
@Bean
public RewriteResponseHeaderGatewayFilterFactory rewriteResponseHeaderGatewayFilterFactory() {
return new RewriteResponseHeaderGatewayFilterFactory();
}
@Bean
public SetStatusGatewayFilterFactory setStatusGatewayFilterFactory() {
return new SetStatusGatewayFilterFactory();

View File

@@ -0,0 +1,87 @@
/*
* Copyright 2013-2018 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
*
* http://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.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.Arrays;
import java.util.List;
/**
* @author Vitaliy Pavlyuk
*/
public class RewriteResponseHeaderGatewayFilterFactory extends AbstractGatewayFilterFactory<RewriteResponseHeaderGatewayFilterFactory.Config> {
public static final String REGEXP_KEY = "regexp";
public static final String REPLACEMENT_KEY = "replacement";
public RewriteResponseHeaderGatewayFilterFactory() {
super(Config.class);
}
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList(NAME_KEY, REGEXP_KEY, REPLACEMENT_KEY);
}
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> chain.filter(exchange).then(Mono.fromRunnable(() -> {
rewriteHeader(exchange, config);
}));
}
protected void rewriteHeader(ServerWebExchange exchange, Config config) {
final String name = config.getName();
final String value = exchange.getResponse().getHeaders().getFirst(name);
if (value == null) {
return;
}
final String newValue = rewrite(value, config.getRegexp(), config.getReplacement());
exchange.getResponse().getHeaders().set(name, newValue);
}
String rewrite(String value, String regexp, String replacement) {
return value.replaceAll(regexp, replacement.replace("$\\", "$"));
}
public static class Config extends AbstractGatewayFilterFactory.NameConfig {
private String regexp;
private String replacement;
public String getRegexp() {
return regexp;
}
public Config setRegexp(String regexp) {
this.regexp = regexp;
return this;
}
public String getReplacement() {
return replacement;
}
public Config setReplacement(String replacement) {
this.replacement = replacement;
return this;
}
}
}

View File

@@ -29,33 +29,13 @@ import java.util.stream.Stream;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.gateway.filter.factory.*;
import reactor.retry.Repeat;
import reactor.retry.Retry;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.OrderedGatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractChangeRequestUriGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.AddRequestHeaderGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.AddRequestParameterGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.AddResponseHeaderGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.HystrixGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.PrefixPathGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.PreserveHostHeaderGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.RedirectToGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.RemoveRequestHeaderGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.RemoveResponseHeaderGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.RequestHeaderToRequestUriGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.RequestRateLimiterGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.RetryGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.RewritePathGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.SaveSessionGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.SecureHeadersGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.SetPathGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.SetRequestHeaderGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.SetResponseHeaderGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.SetStatusGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.StripPrefixGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.rewrite.ModifyRequestBodyGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.rewrite.ModifyResponseBodyGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.rewrite.RewriteFunction;
@@ -475,6 +455,18 @@ public class GatewayFilterSpec extends UriSpec {
.apply(c -> c.setName(headerName).setValue(headerValue)));
}
/**
* A filter that rewrites a header value on the response before it is returned to the client by the Gateway.
* @param headerName the header name
* @param regex a Java regular expression to match the path against
* @param replacement the replacement for the path
* @return a {@link GatewayFilterSpec} that can be used to apply additional filters
*/
public GatewayFilterSpec rewriteResponseHeader(String headerName, String regex, String replacement) {
return filter(getBean(RewriteResponseHeaderGatewayFilterFactory.class)
.apply(c -> c.setReplacement(replacement).setRegexp(regex).setName(headerName)));
}
/**
* A filter that sets the status on the response before it is returned to the client by the Gateway.
* @param status the status to set on the response

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2013-2017 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
*
* http://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.Test;
import org.junit.runner.RunWith;
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 org.springframework.test.context.junit4.SpringRunner;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = RANDOM_PORT)
@DirtiesContext
public class RewriteResponseHeaderGatewayFilterFactoryTests extends BaseWebClientTests {
@Test
public void rewriteResponseHeaderFilterWorks() {
testClient.get()
.uri("/headers")
.header("Host", "www.rewriteresponseheader.org")
.exchange()
.expectStatus().isOk()
.expectHeader().valueEquals("X-Request-Foo", "/42?user=ford&password=***&flag=true");
}
@EnableAutoConfiguration
@SpringBootConfiguration
@Import(DefaultTestConfig.class)
public static class TestConfig { }
}

View File

@@ -25,21 +25,7 @@ import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
import org.junit.runners.model.Statement;
import org.springframework.cloud.gateway.filter.factory.AddRequestHeaderGatewayFilterFactoryTests;
import org.springframework.cloud.gateway.filter.factory.AddRequestParameterGatewayFilterFactoryTests;
import org.springframework.cloud.gateway.filter.factory.HystrixGatewayFilterFactoryTests;
import org.springframework.cloud.gateway.filter.factory.PreserveHostHeaderGatewayFilterFactoryTests;
import org.springframework.cloud.gateway.filter.factory.RedirectToGatewayFilterFactoryTests;
import org.springframework.cloud.gateway.filter.factory.RemoveRequestHeaderGatewayFilterFactoryTests;
import org.springframework.cloud.gateway.filter.factory.RemoveResponseHeaderGatewayFilterFactoryTests;
import org.springframework.cloud.gateway.filter.factory.RequestRateLimiterGatewayFilterFactoryTests;
import org.springframework.cloud.gateway.filter.factory.RewritePathGatewayFilterFactoryIntegrationTests;
import org.springframework.cloud.gateway.filter.factory.RewritePathGatewayFilterFactoryTests;
import org.springframework.cloud.gateway.filter.factory.SecureHeadersGatewayFilterFactoryTests;
import org.springframework.cloud.gateway.filter.factory.SetPathGatewayFilterFactoryIntegrationTests;
import org.springframework.cloud.gateway.filter.factory.SetPathGatewayFilterFactoryTests;
import org.springframework.cloud.gateway.filter.factory.SetResponseHeaderGatewayFilterFactoryTests;
import org.springframework.cloud.gateway.filter.factory.SetStatusGatewayFilterFactoryTests;
import org.springframework.cloud.gateway.filter.factory.*;
import org.springframework.cloud.gateway.filter.headers.ForwardedHeadersFilterTests;
import org.springframework.cloud.gateway.filter.headers.RemoveHopByHopHeadersFilterTests;
import org.springframework.cloud.gateway.filter.headers.XForwardedHeadersFilterTests;
@@ -89,6 +75,7 @@ import static org.junit.Assume.assumeThat;
SetPathGatewayFilterFactoryIntegrationTests.class,
AddRequestParameterGatewayFilterFactoryTests.class,
SetResponseHeaderGatewayFilterFactoryTests.class,
RewriteResponseHeaderGatewayFilterFactoryTests.class,
PrincipalNameKeyResolverIntegrationTests.class,
RedisRateLimiterTests.class,
RouteDefinitionRouteLocatorTests.class,

View File

@@ -65,6 +65,16 @@ spring:
filters:
- AddResponseHeader=X-Request-Foo, Bar
# =====================================
- id: rewrite_response_header_test
uri: ${test.uri}
predicates:
- Host=**.rewriteresponseheader.org
- Path=/headers
filters:
- AddResponseHeader=X-Request-Foo, /42?user=ford&password=omg!what&flag=true
- RewriteResponseHeader=X-Request-Foo, password=[^&]+, password=***
# =====================================
- id: forward_test
uri: forward:/localcontroller