diff --git a/docs/modules/ROOT/pages/spring-cloud-gateway-server-webflux/gatewayfilter-factories/addresponseheader-factory.adoc b/docs/modules/ROOT/pages/spring-cloud-gateway-server-webflux/gatewayfilter-factories/addresponseheader-factory.adoc index e15ea73c..f13de378 100644 --- a/docs/modules/ROOT/pages/spring-cloud-gateway-server-webflux/gatewayfilter-factories/addresponseheader-factory.adoc +++ b/docs/modules/ROOT/pages/spring-cloud-gateway-server-webflux/gatewayfilter-factories/addresponseheader-factory.adoc @@ -1,7 +1,7 @@ [[addresponseheader-gatewayfilter-factory]] = `AddResponseHeader` `GatewayFilter` Factory -The `AddResponseHeader` `GatewayFilter` Factory takes a `name` and `value` parameter. +The `AddResponseHeader` `GatewayFilter` Factory takes three parameters: `name`, `value` and `override`(default value is `true`) . The following example configures an `AddResponseHeader` `GatewayFilter`: .application.yml @@ -15,9 +15,12 @@ spring: uri: https://example.org filters: - AddResponseHeader=X-Response-Red, Blue + - AddResponseHeader=X-Response-Black, White, false ---- This adds `X-Response-Red:Blue` header to the downstream response's headers for all matching requests. +and if the response already contains the `X-Response-Black` header, this will not add the `X-Response-Black: White` +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 are expanded at runtime. diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/AddResponseHeaderGatewayFilterFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/AddResponseHeaderGatewayFilterFactory.java index 937aa196..4e1d04d5 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/AddResponseHeaderGatewayFilterFactory.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/AddResponseHeaderGatewayFilterFactory.java @@ -16,12 +16,17 @@ 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.cloud.gateway.support.ServerWebExchangeUtils; +import org.springframework.core.style.ToStringCreator; import org.springframework.http.HttpHeaders; +import org.springframework.util.StringUtils; import org.springframework.web.server.ServerWebExchange; import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator; @@ -31,6 +36,23 @@ import static org.springframework.cloud.gateway.support.GatewayToStringStyler.fi */ public class AddResponseHeaderGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory { + private static final String OVERRIDE_KEY = "override"; + + @Override + public Class getConfigClass() { + return Config.class; + } + + @Override + public NameValueConfig newConfig() { + return new Config(); + } + + @Override + public List shortcutFieldOrder() { + return Arrays.asList(GatewayFilter.NAME_KEY, GatewayFilter.VALUE_KEY, OVERRIDE_KEY); + } + @Override public GatewayFilter apply(NameValueConfig config) { return new GatewayFilter() { @@ -41,6 +63,13 @@ public class AddResponseHeaderGatewayFilterFactory extends AbstractNameValueGate @Override public String toString() { + if (config instanceof Config) { + return filterToStringCreator(AddResponseHeaderGatewayFilterFactory.this) + .append(GatewayFilter.NAME_KEY, config.getName()) + .append(GatewayFilter.VALUE_KEY, config.getValue()) + .append(OVERRIDE_KEY, ((Config) config).isOverride()) + .toString(); + } return filterToStringCreator(AddResponseHeaderGatewayFilterFactory.this) .append(config.getName(), config.getValue()) .toString(); @@ -49,12 +78,51 @@ public class AddResponseHeaderGatewayFilterFactory extends AbstractNameValueGate } void addHeader(ServerWebExchange exchange, NameValueConfig config) { - final String value = ServerWebExchangeUtils.expand(exchange, config.getValue()); - HttpHeaders headers = exchange.getResponse().getHeaders(); // if response has been commited, no more response headers will bee added. if (!exchange.getResponse().isCommitted()) { - headers.add(config.getName(), value); + final String value = ServerWebExchangeUtils.expand(exchange, config.getValue()); + HttpHeaders headers = exchange.getResponse().getHeaders(); + + boolean override = true; // default is true + if (config instanceof Config) { + override = ((Config) config).isOverride(); + } + + if (override) { + headers.add(config.getName(), value); + } + else { + boolean headerIsMissingOrBlank = headers.getOrEmpty(config.getName()) + .stream() + .allMatch(h -> !StringUtils.hasText(h)); + if (headerIsMissingOrBlank) { + headers.add(config.getName(), value); + } + } } } + public static class Config extends AbstractNameValueGatewayFilterFactory.NameValueConfig { + + private boolean override = true; + + public boolean isOverride() { + return override; + } + + public Config setOverride(boolean override) { + this.override = override; + return this; + } + + @Override + public String toString() { + return new ToStringCreator(this).append(NAME_KEY, name) + .append(VALUE_KEY, value) + .append(OVERRIDE_KEY, override) + .toString(); + } + + } + } diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/builder/GatewayFilterSpec.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/builder/GatewayFilterSpec.java index 8b87e45a..604f1018 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/builder/GatewayFilterSpec.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/builder/GatewayFilterSpec.java @@ -226,6 +226,22 @@ public class GatewayFilterSpec extends UriSpec { .apply(c -> c.setName(headerName).setValue(headerValue))); } + /** + * Adds a header to the response returned to the Gateway from the route. + * @param headerName the header name + * @param headerValue the header value + * @param override override or not + * @return a {@link GatewayFilterSpec} that can be used to apply additional filters + */ + public GatewayFilterSpec addResponseHeader(String headerName, String headerValue, boolean override) { + AddResponseHeaderGatewayFilterFactory.Config config = new AddResponseHeaderGatewayFilterFactory.Config(); + config.setName(headerName); + config.setValue(headerValue); + config.setOverride(override); + + return filter(getBean(AddResponseHeaderGatewayFilterFactory.class).apply(config)); + } + /** * A filter that adds a local cache for storing response body for repeated requests. *

diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/AddResponseHeaderGatewayFilterFactoryTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/AddResponseHeaderGatewayFilterFactoryTests.java index 4c3c002e..1c58a88f 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/AddResponseHeaderGatewayFilterFactoryTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/AddResponseHeaderGatewayFilterFactoryTests.java @@ -17,6 +17,8 @@ package org.springframework.cloud.gateway.filter.factory; import java.net.URI; +import java.util.HashMap; +import java.util.Map; import org.junit.jupiter.api.Test; @@ -51,15 +53,64 @@ class AddResponseHeaderGatewayFilterFactoryTests extends BaseWebClientTests { .header("Host", host) .exchange() .expectHeader() - .valueEquals("X-Request-Foo", expectedValue); + .valueEquals("X-Request-Foo", expectedValue) + .expectHeader() + .valueEquals("X-Request-Example", "ValueA"); + } + + @Test + void testResponseHeaderFilterHeaderPresent() { + URI uri = UriComponentsBuilder.fromUriString(this.baseUri + "/headers").build(true).toUri(); + String host = "www.addresponseheader.org"; + String expectedValue = "Bar"; + + Map body = new HashMap<>(); + body.put("X-Request-Example", "ValueB"); + + testClient.patch() + .uri(uri) + .header("Host", host) + .bodyValue(body) + .exchange() + .expectHeader() + .valueEquals("X-Request-Foo", expectedValue) + .expectHeader() + .valueEquals("X-Request-Example", "ValueB"); } @Test void testResponseHeaderFilterJavaDsl() { - URI uri = UriComponentsBuilder.fromUriString(this.baseUri + "/get").build(true).toUri(); + URI uri = UriComponentsBuilder.fromUriString(this.baseUri + "/headers").build(true).toUri(); String host = "www.addresponseheaderjava.org"; String expectedValue = "myresponsevalue-www"; - testClient.get().uri(uri).header("Host", host).exchange().expectHeader().valueEquals("example", expectedValue); + testClient.get() + .uri(uri) + .header("Host", host) + .exchange() + .expectHeader() + .valueEquals("example", expectedValue) + .expectHeader() + .valueEquals("example2", "myresponsevalue2-www"); + } + + @Test + void testResponseHeaderFilterHeaderPresentJavaDsl() { + URI uri = UriComponentsBuilder.fromUriString(this.baseUri + "/headers").build(true).toUri(); + String host = "www.addresponseheaderjava.org"; + String expectedValue = "myresponsevalue-www"; + + Map body = new HashMap<>(); + body.put("example2", "myresponsevalue2"); + + testClient.patch() + .uri(uri) + .header("Host", host) + .bodyValue(body) + .exchange() + .expectHeader() + .valueEquals("example", expectedValue) + .expectHeader() + .valueEquals("example2", "myresponsevalue2"); } @Test @@ -81,11 +132,11 @@ class AddResponseHeaderGatewayFilterFactoryTests extends BaseWebClientTests { public RouteLocator testRouteLocator(RouteLocatorBuilder builder) { return builder.routes() .route("add_response_header_java_test", - r -> r.path("/get") + r -> r.path("/headers") .and() .host("{sub}.addresponseheaderjava.org") - .filters( - f -> f.prefixPath("/httpbin").addResponseHeader("example", "myresponsevalue-{sub}")) + .filters(f -> f.addResponseHeader("example", "myresponsevalue-{sub}") + .addResponseHeader("example2", "myresponsevalue2-{sub}", false)) .uri(uri)) .build(); } diff --git a/spring-cloud-gateway-server/src/test/resources/application.yml b/spring-cloud-gateway-server/src/test/resources/application.yml index c8b6a23f..4e1a44aa 100644 --- a/spring-cloud-gateway-server/src/test/resources/application.yml +++ b/spring-cloud-gateway-server/src/test/resources/application.yml @@ -72,6 +72,7 @@ spring: - Path=/headers filters: - AddResponseHeader=X-Request-Foo, Bar + - AddResponseHeader=X-Request-Example, ValueA, false - id: cache_request_body_test uri: ${test.uri}