Merge branch '2.1.x'
This commit is contained in:
@@ -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`.
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user