Merge branch '2.1.x'

This commit is contained in:
Spencer Gibb
2019-07-24 17:27:34 -04:00
7 changed files with 323 additions and 1 deletions

View File

@@ -607,6 +607,24 @@ their default values:
You can find more information on how Hystrix works with Gateway in the <<hystrix, Hystrix GatewayFilter Factory section>>.
=== MapRequestHeader GatewayFilter Factory
The MapRequestHeader GatewayFilter Factory takes 'fromHeader' and 'toHeader' parameters. It creates a new named header (toHeader) and the value is extracted out of an existing named header (fromHeader) from the incoming http request. If the input header does not exist then the filter has no impact. If the new named header already exists then it's values will be augmented with the new values.
.application.yml
[source,yaml]
----
spring:
cloud:
gateway:
routes:
- id: map_request_header_route
uri: https://example.org
filters:
- MapRequestHeader=Bar, X-Request-Foo
----
This will add `X-Request-Foo:<values>` header to the downstream request's with updated values from the incoming http request `Bar` header.
=== PrefixPath GatewayFilter Factory
The PrefixPath GatewayFilter Factory takes a single `prefix` parameter.
@@ -854,7 +872,7 @@ spring:
- id: rewritelocationresponseheader_route
uri: http://example.org
filters:
- RewriteLocationResponseHeader=AS_IN_REQUEST, Location, ,
- RewriteLocationResponseHeader=AS_IN_REQUEST, Location, ,
----
For example, for a request `POST https://api.example.com/some/object/name`, `Location` response header value `https://object-service.prod.example.net/v2/some/object/id` will be rewritten as `https://api.example.com/some/object/id`.

View File

@@ -68,6 +68,7 @@ import org.springframework.cloud.gateway.filter.factory.DedupeResponseHeaderGate
import org.springframework.cloud.gateway.filter.factory.FallbackHeadersGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.GatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.HystrixGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.MapRequestHeaderGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.PrefixPathGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.PreserveHostHeaderGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.RedirectToGatewayFilterFactory;
@@ -398,6 +399,11 @@ public class GatewayAutoConfiguration {
return new AddRequestHeaderGatewayFilterFactory();
}
@Bean
public MapRequestHeaderGatewayFilterFactory mapRequestHeaderGatewayFilterFactory() {
return new MapRequestHeaderGatewayFilterFactory();
}
@Bean
public AddRequestParameterGatewayFilterFactory addRequestParameterGatewayFilterFactory() {
return new AddRequestParameterGatewayFilterFactory();

View File

@@ -0,0 +1,124 @@
/*
* Copyright 2013-2019 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.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.core.style.ToStringCreator;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.web.server.ServerWebExchange;
import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator;
/**
* @author Tony Clarke
*/
public class MapRequestHeaderGatewayFilterFactory extends
AbstractGatewayFilterFactory<MapRequestHeaderGatewayFilterFactory.Config> {
/**
* From Header key.
*/
public static final String FROM_HEADER_KEY = "fromHeader";
/**
* To Header key.
*/
public static final String TO_HEADER_KEY = "toHeader";
public MapRequestHeaderGatewayFilterFactory() {
super(Config.class);
}
public List<String> shortcutFieldOrder() {
return Arrays.asList(FROM_HEADER_KEY, TO_HEADER_KEY);
}
@Override
public GatewayFilter apply(MapRequestHeaderGatewayFilterFactory.Config config) {
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange,
GatewayFilterChain chain) {
if (!exchange.getRequest().getHeaders()
.containsKey(config.getFromHeader())) {
return chain.filter(exchange);
}
List<String> headerValues = exchange.getRequest().getHeaders()
.get(config.getFromHeader());
ServerHttpRequest request = exchange.getRequest().mutate()
.headers(i -> i.addAll(config.getToHeader(), headerValues))
.build();
return chain.filter(exchange.mutate().request(request).build());
}
@Override
public String toString() {
// @formatter:off
return filterToStringCreator(MapRequestHeaderGatewayFilterFactory.this)
.append(FROM_HEADER_KEY, config.getFromHeader())
.append(TO_HEADER_KEY, config.getToHeader())
.toString();
// @formatter:on
}
};
}
public static class Config {
private String fromHeader;
private String toHeader;
public String getFromHeader() {
return this.fromHeader;
}
public Config setFromHeader(String fromHeader) {
this.fromHeader = fromHeader;
return this;
}
public String getToHeader() {
return this.toHeader;
}
public Config setToHeader(String toHeader) {
this.toHeader = toHeader;
return this;
}
@Override
public String toString() {
// @formatter:off
return new ToStringCreator(this)
.append("fromHeader", fromHeader)
.append("toHeader", toHeader)
.toString();
// @formatter:on
}
}
}

View File

@@ -43,6 +43,7 @@ import org.springframework.cloud.gateway.filter.factory.DedupeResponseHeaderGate
import org.springframework.cloud.gateway.filter.factory.DedupeResponseHeaderGatewayFilterFactory.Strategy;
import org.springframework.cloud.gateway.filter.factory.FallbackHeadersGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.HystrixGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.MapRequestHeaderGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.PrefixPathGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.PreserveHostHeaderGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.RedirectToGatewayFilterFactory;
@@ -217,6 +218,17 @@ public class GatewayFilterSpec extends UriSpec {
return filter(factory.apply(this.routeBuilder.getId(), configConsumer));
}
/**
* Maps headers from one name to another.
* @param fromHeader the header name of the original header.
* @param toHeader the header name of the new header.
* @return a {@link GatewayFilterSpec} that can be used to apply additional filters
*/
public GatewayFilterSpec mapRequestHeader(String fromHeader, String toHeader) {
return filter(getBean(MapRequestHeaderGatewayFilterFactory.class)
.apply(c -> c.setFromHeader(fromHeader).setToHeader(toHeader)));
}
/**
* A filter that can be used to modify the request body. This filter is BETA and may
* be subject to change in a future release.

View File

@@ -0,0 +1,138 @@
/*
* Copyright 2013-2019 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.util.List;
import java.util.Map;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.MapRequestHeaderGatewayFilterFactory.Config;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.cloud.gateway.test.BaseWebClientTests;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
import static org.springframework.cloud.gateway.test.TestUtils.getMap;
/**
* @author Tony Clarke
*/
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = RANDOM_PORT)
@DirtiesContext
@ActiveProfiles(profiles = "request-map-header-web-filter")
public class MapRequestHeaderGatewayFilterFactoryTests extends BaseWebClientTests {
@Test
public void mapRequestHeaderFilterWorks() {
testClient.get().uri("/headers").header("Host", "www.maprequestheader.org")
.header("a", "tome").exchange().expectBody(Map.class)
.consumeWith(result -> {
Map<String, Object> headers = getMap(result.getResponseBody(),
"headers");
assertThat(headers).containsEntry("X-Request-Example", "tome");
});
}
@Test
public void mapRequestHeaderFilterWorksJavaDsl() {
testClient.get().uri("/headers").header("Host", "www.maprequestheaderjava.org")
.header("b", "tome").exchange().expectBody(Map.class)
.consumeWith(result -> {
Map<String, Object> headers = getMap(result.getResponseBody(),
"headers");
assertThat(headers).containsEntry("X-Request-Example-Java", "tome");
});
}
@SuppressWarnings("unchecked")
@Test
public void mapRequestHeaderWithMultiValueFilterWorks() {
testClient.get().uri("/multivalueheaders")
.header("Host", "www.maprequestheader.org").header("a", "tome", "toyou")
.exchange().expectBody(Map.class).consumeWith(result -> {
Map<String, Object> headers = getMap(result.getResponseBody(),
"headers");
assertThat(headers).containsKey("X-Request-Example");
List<String> values = (List<String>) headers.get("X-Request-Example");
assertThat(values).contains("tome", "toyou");
});
}
@Test
public void mapRequestHeaderWithNullValueFilterWorks() {
testClient.get().uri("/headers").header("Host", "www.maprequestheader.org")
.header("a", (String) null).exchange().expectBody(Map.class)
.consumeWith(result -> {
Map<String, Object> headers = getMap(result.getResponseBody(),
"headers");
assertThat(headers).doesNotContainKey("X-Request-Example");
});
}
@Test
public void mapRequestHeaderWhenInputHeaderDoesNotExist() {
testClient.get().uri("/headers").header("Host", "www.maprequestheader.org")
.exchange().expectBody(Map.class).consumeWith(result -> {
Map<String, Object> headers = getMap(result.getResponseBody(),
"headers");
assertThat(headers).doesNotContainKey("X-Request-Example");
});
}
@Test
public void toStringFormat() {
Config config = new Config().setFromHeader("myfromheader")
.setToHeader("mytoheader");
GatewayFilter filter = new MapRequestHeaderGatewayFilterFactory().apply(config);
assertThat(filter.toString()).contains("myfromheader").contains("mytoheader");
}
@EnableAutoConfiguration
@SpringBootConfiguration
@Import(DefaultTestConfig.class)
public static class TestConfig {
@Value("${test.uri}")
String uri;
@Bean
public RouteLocator testRouteLocator(RouteLocatorBuilder builder) {
return builder.routes().route("map_request_header_java_test",
r -> r.path("/headers").and().host("**.maprequestheaderjava.org")
.filters(f -> f.prefixPath("/httpbin").mapRequestHeader("b",
"X-Request-Example-Java"))
.uri(uri))
.build();
}
}
}

View File

@@ -59,6 +59,14 @@ public class HttpBinCompatibleController {
return result;
}
@RequestMapping(path = "/multivalueheaders", method = { RequestMethod.GET,
RequestMethod.POST }, produces = MediaType.APPLICATION_JSON_VALUE)
public Map<String, Object> multiValueHeaders(ServerWebExchange exchange) {
Map<String, Object> result = new HashMap<>();
result.put("headers", exchange.getRequest().getHeaders());
return result;
}
@RequestMapping(path = "/delay/{sec}", produces = MediaType.APPLICATION_JSON_VALUE)
public Mono<Map<String, Object>> get(ServerWebExchange exchange,
@PathVariable int sec) throws InterruptedException {

View File

@@ -0,0 +1,16 @@
spring:
cloud:
gateway:
routes:
- id: map_request_header_test_singleresponse
uri: ${test.uri}
predicates:
- Path=/headers
filters:
- MapRequestHeader=a, X-Request-Example
- id: map_request_header_test_multiresponse
uri: ${test.uri}
predicates:
- Path=/multivalueheaders
filters:
- MapRequestHeader=a, X-Request-Example