Adds ability to expand uri variables for other filters. (#711)
Currently only SetPath can use the uri variables from the path and host predicates. Now, other filters can use them thru the ServerWebExchangeUtils.expand() method. fixes gh-704
This commit is contained in:
@@ -341,6 +341,23 @@ spring:
|
||||
|
||||
This will add `X-Request-Foo:Bar` header to the downstream request's headers for all matching requests.
|
||||
|
||||
AddRequestHeader is aware of URI variables used to match a path or host. URI variables may be used in the value and will be expanded at runtime.
|
||||
|
||||
.application.yml
|
||||
[source,yaml]
|
||||
----
|
||||
spring:
|
||||
cloud:
|
||||
gateway:
|
||||
routes:
|
||||
- id: add_request_header_route
|
||||
uri: https://example.org
|
||||
predicates:
|
||||
- Path=/foo/{segment}
|
||||
filters:
|
||||
- AddRequestHeader=X-Request-Foo, Bar-{segment}
|
||||
----
|
||||
|
||||
=== AddRequestParameter GatewayFilter Factory
|
||||
The AddRequestParameter GatewayFilter Factory takes a name and value parameter.
|
||||
|
||||
@@ -359,6 +376,23 @@ spring:
|
||||
|
||||
This will add `foo=bar` to the downstream request's query string for all matching requests.
|
||||
|
||||
AddRequestParameter is aware of URI variables used to match a path or host. URI variables may be used in the value and will be expanded at runtime.
|
||||
|
||||
.application.yml
|
||||
[source,yaml]
|
||||
----
|
||||
spring:
|
||||
cloud:
|
||||
gateway:
|
||||
routes:
|
||||
- id: add_request_parameter_route
|
||||
uri: https://example.org
|
||||
predicates:
|
||||
- Host: {segment}.myhost.org
|
||||
filters:
|
||||
- AddRequestParameter=foo, bar-{segment}
|
||||
----
|
||||
|
||||
=== AddResponseHeader GatewayFilter Factory
|
||||
The AddResponseHeader GatewayFilter Factory takes a name and value parameter.
|
||||
|
||||
@@ -377,6 +411,23 @@ spring:
|
||||
|
||||
This will add `X-Response-Foo:Bar` header to the downstream response's headers for all matching requests.
|
||||
|
||||
AddResponseHeader is aware of URI variables used to match a path or host. URI variables may be used in the value and will be expanded at runtime.
|
||||
|
||||
.application.yml
|
||||
[source,yaml]
|
||||
----
|
||||
spring:
|
||||
cloud:
|
||||
gateway:
|
||||
routes:
|
||||
- id: add_response_header_route
|
||||
uri: https://example.org
|
||||
predicates:
|
||||
- Host: {segment}.myhost.org
|
||||
filters:
|
||||
- AddResponseHeader=foo, bar-{segment}
|
||||
----
|
||||
|
||||
=== DedupeResponseHeader GatewayFilter Factory
|
||||
The DedupeResponseHeader GatewayFilter Factory takes a `name` parameter and an optional `strategy` parameter. `name` can contain a list of header names, space separated.
|
||||
|
||||
@@ -839,6 +890,41 @@ spring:
|
||||
|
||||
For a request path of `/foo/bar`, this will set the path to `/bar` before making the downstream request.
|
||||
|
||||
=== SetRequestHeader GatewayFilter Factory
|
||||
The SetRequestHeader GatewayFilter Factory takes `name` and `value` parameters.
|
||||
|
||||
.application.yml
|
||||
[source,yaml]
|
||||
----
|
||||
spring:
|
||||
cloud:
|
||||
gateway:
|
||||
routes:
|
||||
- id: setrequestheader_route
|
||||
uri: https://example.org
|
||||
filters:
|
||||
- SetRequestHeader=X-Request-Foo, Bar
|
||||
----
|
||||
|
||||
This GatewayFilter replaces all headers with the given name, rather than adding. So if the downstream server responded with a `X-Request-Foo:1234`, this would be replaced with `X-Request-Foo:Bar`, which is what the downstream service would receive.
|
||||
|
||||
SetRequestHeader is aware of URI variables used to match a path or host. URI variables may be used in the value and will be expanded at runtime.
|
||||
|
||||
.application.yml
|
||||
[source,yaml]
|
||||
----
|
||||
spring:
|
||||
cloud:
|
||||
gateway:
|
||||
routes:
|
||||
- id: setrequestheader_route
|
||||
uri: https://example.org
|
||||
predicates:
|
||||
- Host: {segment}.myhost.org
|
||||
filters:
|
||||
- SetRequestHeader=foo, bar-{segment}
|
||||
----
|
||||
|
||||
=== SetResponseHeader GatewayFilter Factory
|
||||
The SetResponseHeader GatewayFilter Factory takes `name` and `value` parameters.
|
||||
|
||||
@@ -857,6 +943,23 @@ spring:
|
||||
|
||||
This GatewayFilter replaces all headers with the given name, rather than adding. So if the downstream server responded with a `X-Response-Foo:1234`, this would be replaced with `X-Response-Foo:Bar`, which is what the gateway client would receive.
|
||||
|
||||
SetResponseHeader is aware of URI variables used to match a path or host. URI variables may be used in the value and will be expanded at runtime.
|
||||
|
||||
.application.yml
|
||||
[source,yaml]
|
||||
----
|
||||
spring:
|
||||
cloud:
|
||||
gateway:
|
||||
routes:
|
||||
- id: setresponseheader_route
|
||||
uri: https://example.org
|
||||
predicates:
|
||||
- Host: {segment}.myhost.org
|
||||
filters:
|
||||
- SetResponseHeader=foo, bar-{segment}
|
||||
----
|
||||
|
||||
=== SetStatus GatewayFilter Factory
|
||||
The SetStatus GatewayFilter Factory takes a single `status` parameter. It must be a valid Spring `HttpStatus`. It may be the integer value `404` or the string representation of the enumeration `NOT_FOUND`.
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilter;
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
||||
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
@@ -37,8 +38,9 @@ public class AddRequestHeaderGatewayFilterFactory
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange,
|
||||
GatewayFilterChain chain) {
|
||||
String value = ServerWebExchangeUtils.expand(exchange, config.getValue());
|
||||
ServerHttpRequest request = exchange.getRequest().mutate()
|
||||
.header(config.getName(), config.getValue()).build();
|
||||
.header(config.getName(), value).build();
|
||||
|
||||
return chain.filter(exchange.mutate().request(request).build());
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilter;
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
||||
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
@@ -52,10 +53,11 @@ public class AddRequestParameterGatewayFilterFactory
|
||||
}
|
||||
}
|
||||
|
||||
String value = ServerWebExchangeUtils.expand(exchange, config.getValue());
|
||||
// TODO urlencode?
|
||||
query.append(config.getName());
|
||||
query.append('=');
|
||||
query.append(config.getValue());
|
||||
query.append(value);
|
||||
|
||||
try {
|
||||
URI newUri = UriComponentsBuilder.fromUri(uri)
|
||||
|
||||
@@ -20,6 +20,7 @@ import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilter;
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
||||
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator;
|
||||
@@ -36,8 +37,8 @@ public class AddResponseHeaderGatewayFilterFactory
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange,
|
||||
GatewayFilterChain chain) {
|
||||
exchange.getResponse().getHeaders().add(config.getName(),
|
||||
config.getValue());
|
||||
String value = ServerWebExchangeUtils.expand(exchange, config.getValue());
|
||||
exchange.getResponse().getHeaders().add(config.getName(), value);
|
||||
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilter;
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
||||
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
@@ -37,9 +38,9 @@ public class SetRequestHeaderGatewayFilterFactory
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange,
|
||||
GatewayFilterChain chain) {
|
||||
String value = ServerWebExchangeUtils.expand(exchange, config.getValue());
|
||||
ServerHttpRequest request = exchange.getRequest().mutate()
|
||||
.headers(
|
||||
httpHeaders -> httpHeaders.set(config.name, config.value))
|
||||
.headers(httpHeaders -> httpHeaders.set(config.name, value))
|
||||
.build();
|
||||
|
||||
return chain.filter(exchange.mutate().request(request).build());
|
||||
|
||||
@@ -20,6 +20,7 @@ import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilter;
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
||||
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator;
|
||||
@@ -36,8 +37,9 @@ public class SetResponseHeaderGatewayFilterFactory
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange,
|
||||
GatewayFilterChain chain) {
|
||||
String value = ServerWebExchangeUtils.expand(exchange, config.getValue());
|
||||
return chain.filter(exchange).then(Mono.fromRunnable(() -> exchange
|
||||
.getResponse().getHeaders().set(config.name, config.value)));
|
||||
.getResponse().getHeaders().set(config.name, value)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -249,6 +249,19 @@ public final class ServerWebExchangeUtils {
|
||||
return AsyncPredicate.from(predicate);
|
||||
}
|
||||
|
||||
public static String expand(ServerWebExchange exchange, String template) {
|
||||
Assert.notNull(exchange, "exchange may not be null");
|
||||
Assert.notNull(template, "template may not be null");
|
||||
|
||||
if (template.indexOf('{') == -1) { // short circuit
|
||||
return template;
|
||||
}
|
||||
|
||||
Map<String, String> variables = getUriTemplateVariables(exchange);
|
||||
return UriComponentsBuilder.fromPath(template).build().expand(variables)
|
||||
.getPath();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static void putUriTemplateVariables(ServerWebExchange exchange,
|
||||
Map<String, String> uriVariables) {
|
||||
|
||||
@@ -66,7 +66,7 @@ public class AddRequestHeaderGatewayFilterFactoryTests extends BaseWebClientTest
|
||||
.exchange().expectBody(Map.class).consumeWith(result -> {
|
||||
Map<String, Object> headers = getMap(result.getResponseBody(),
|
||||
"headers");
|
||||
assertThat(headers).containsEntry("X-Request-Acme", "ValueB");
|
||||
assertThat(headers).containsEntry("X-Request-Acme", "ValueB-www");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -89,9 +89,9 @@ public class AddRequestHeaderGatewayFilterFactoryTests extends BaseWebClientTest
|
||||
@Bean
|
||||
public RouteLocator testRouteLocator(RouteLocatorBuilder builder) {
|
||||
return builder.routes().route("add_request_header_java_test",
|
||||
r -> r.path("/headers").and().host("**.addrequestheaderjava.org")
|
||||
r -> r.path("/headers").and().host("{sub}.addrequestheaderjava.org")
|
||||
.filters(f -> f.prefixPath("/httpbin")
|
||||
.addRequestHeader("X-Request-Acme", "ValueB"))
|
||||
.addRequestHeader("X-Request-Acme", "ValueB-{sub}"))
|
||||
.uri(uri))
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ public class AddRequestParameterGatewayFilterFactoryTests extends BaseWebClientT
|
||||
|
||||
@Test
|
||||
public void addRequestParameterFilterWorksEncodedQueryJavaDsl() {
|
||||
testRequestParameterFilter("www.addreqparamjava.org", "ValueB", "javaname",
|
||||
testRequestParameterFilter("www.addreqparamjava.org", "ValueB-www", "javaname",
|
||||
"%E6%89%8E%E6%A0%B9");
|
||||
}
|
||||
|
||||
@@ -129,9 +129,9 @@ public class AddRequestParameterGatewayFilterFactoryTests extends BaseWebClientT
|
||||
@Bean
|
||||
public RouteLocator testRouteLocator(RouteLocatorBuilder builder) {
|
||||
return builder.routes().route("add_request_param_java_test",
|
||||
r -> r.path("/get").and().host("**.addreqparamjava.org")
|
||||
r -> r.path("/get").and().host("{sub}.addreqparamjava.org")
|
||||
.filters(f -> f.prefixPath("/httpbin")
|
||||
.addRequestParameter("example", "ValueB"))
|
||||
.addRequestParameter("example", "ValueB-{sub}"))
|
||||
.uri(uri))
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ public class AddResponseHeaderGatewayFilterFactoryTests extends BaseWebClientTes
|
||||
URI uri = UriComponentsBuilder.fromUriString(this.baseUri + "/get").build(true)
|
||||
.toUri();
|
||||
String host = "www.addresponseheaderjava.org";
|
||||
String expectedValue = "myresponsevalue";
|
||||
String expectedValue = "myresponsevalue-www";
|
||||
testClient.get().uri(uri).header("Host", host).exchange().expectHeader()
|
||||
.valueEquals("example", expectedValue);
|
||||
}
|
||||
@@ -83,9 +83,9 @@ public class AddResponseHeaderGatewayFilterFactoryTests extends BaseWebClientTes
|
||||
@Bean
|
||||
public RouteLocator testRouteLocator(RouteLocatorBuilder builder) {
|
||||
return builder.routes().route("add_response_header_java_test",
|
||||
r -> r.path("/get").and().host("**.addresponseheaderjava.org")
|
||||
.filters(f -> f.prefixPath("/httpbin")
|
||||
.addResponseHeader("example", "myresponsevalue"))
|
||||
r -> r.path("/get").and().host("{sub}.addresponseheaderjava.org")
|
||||
.filters(f -> f.prefixPath("/httpbin").addResponseHeader(
|
||||
"example", "myresponsevalue-{sub}"))
|
||||
.uri(uri))
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -55,7 +55,9 @@ public class SetRequestHeaderGatewayFilterFactoryTests extends BaseWebClientTest
|
||||
.consumeWith(result -> {
|
||||
Map<String, Object> headers = getMap(result.getResponseBody(),
|
||||
"headers");
|
||||
assertThat(headers).containsEntry("X-Req-Foo", "Second");
|
||||
// add was called first, so sets will overwrite
|
||||
assertThat(headers).doesNotContainEntry("X-Req-Foo", "First");
|
||||
assertThat(headers).containsEntry("X-Req-Foo", "Second-www");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -78,10 +80,10 @@ public class SetRequestHeaderGatewayFilterFactoryTests extends BaseWebClientTest
|
||||
@Bean
|
||||
public RouteLocator testRouteLocator(RouteLocatorBuilder builder) {
|
||||
return builder.routes().route("test_set_request_header",
|
||||
r -> r.order(-1).host("**.setrequestheader.org")
|
||||
r -> r.order(-1).host("{sub}.setrequestheader.org")
|
||||
.filters(f -> f.prefixPath("/httpbin")
|
||||
.addRequestHeader("X-Req-Foo", "First")
|
||||
.setRequestHeader("X-Req-Foo", "Second"))
|
||||
.setRequestHeader("X-Req-Foo", "Second-{sub}"))
|
||||
.uri(uri))
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -19,12 +19,16 @@ package org.springframework.cloud.gateway.filter.factory;
|
||||
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.AbstractNameValueGatewayFilterFactory.NameValueConfig;
|
||||
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.junit4.SpringRunner;
|
||||
@@ -41,7 +45,14 @@ public class SetResponseHeaderGatewayFilterFactoryTests extends BaseWebClientTes
|
||||
public void setResponseHeaderFilterWorks() {
|
||||
testClient.get().uri("/headers").header("Host", "www.setreresponseheader.org")
|
||||
.exchange().expectStatus().isOk().expectHeader()
|
||||
.valueEquals("X-Request-Foo", "Bar");
|
||||
.valueEquals("X-Response-Foo", "Bar");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setResponseHeaderFilterWorksJavaDsl() {
|
||||
testClient.get().uri("/headers").header("Host", "www.setresponseheaderdsl.org")
|
||||
.exchange().expectStatus().isOk().expectHeader()
|
||||
.valueEquals("X-Res-Foo", "Second-www");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -57,6 +68,20 @@ public class SetResponseHeaderGatewayFilterFactoryTests extends BaseWebClientTes
|
||||
@Import(DefaultTestConfig.class)
|
||||
public static class TestConfig {
|
||||
|
||||
@Value("${test.uri}")
|
||||
String uri;
|
||||
|
||||
@Bean
|
||||
public RouteLocator testRouteLocator(RouteLocatorBuilder builder) {
|
||||
return builder.routes().route("test_set_response_header_dsl",
|
||||
r -> r.order(-1).host("{sub}.setresponseheaderdsl.org")
|
||||
.filters(f -> f.prefixPath("/httpbin")
|
||||
.addResponseHeader("X-Res-Foo", "First")
|
||||
.setResponseHeader("X-Res-Foo", "Second-{sub}"))
|
||||
.uri(uri))
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* 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.support;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
|
||||
import org.springframework.mock.web.server.MockServerWebExchange;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.expand;
|
||||
|
||||
public class ServerWebExchangeUtilsTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Test
|
||||
public void expandWorks() {
|
||||
HashMap<String, String> vars = new HashMap<>();
|
||||
vars.put("foo", "bar");
|
||||
vars.put("baz", "bam");
|
||||
|
||||
MockServerWebExchange exchange = mockExchange(vars);
|
||||
|
||||
String expanded = expand(exchange, "my-{foo}-{baz}");
|
||||
assertThat(expanded).isEqualTo("my-bar-bam");
|
||||
|
||||
expanded = expand(exchange, "my-noop");
|
||||
assertThat(expanded).isEqualTo("my-noop");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void missingVarThrowsException() {
|
||||
MockServerWebExchange exchange = mockExchange(Collections.emptyMap());
|
||||
thrown.expect(IllegalArgumentException.class);
|
||||
expand(exchange, "my-{foo}-{baz}");
|
||||
}
|
||||
|
||||
private MockServerWebExchange mockExchange(Map<String, String> vars) {
|
||||
MockServerHttpRequest request = MockServerHttpRequest.get("/get").build();
|
||||
MockServerWebExchange exchange = MockServerWebExchange.from(request);
|
||||
ServerWebExchangeUtils.putUriTemplateVariables(exchange, vars);
|
||||
return exchange;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -303,9 +303,9 @@ spring:
|
||||
- Host=**.setreresponseheader.org
|
||||
- Path=/headers
|
||||
filters:
|
||||
- AddResponseHeader=X-Request-Foo, Bar1
|
||||
- AddResponseHeader=X-Request-Foo, Bar2
|
||||
- SetResponseHeader=X-Request-Foo, Bar
|
||||
- AddResponseHeader=X-Response-Foo, Bar1
|
||||
- AddResponseHeader=X-Response-Foo, Bar2
|
||||
- SetResponseHeader=X-Response-Foo, Bar
|
||||
|
||||
# =====================================
|
||||
- id: set_status_int_test
|
||||
|
||||
Reference in New Issue
Block a user