From ca861ff4c837af19b0555fad795db8f4c888605f Mon Sep 17 00:00:00 2001 From: jiangyuan Date: Mon, 19 May 2025 17:23:20 +0800 Subject: [PATCH] add new config for AddResponseHeaderGatewayFilterFactory Signed-off-by: jiangyuan --- ...AddResponseHeaderGatewayFilterFactory.java | 88 +++++++++++++++++-- .../route/builder/GatewayFilterSpec.java | 12 +++ ...sponseHeaderGatewayFilterFactoryTests.java | 68 ++++++++++++-- .../src/test/resources/application.yml | 1 + 4 files changed, 154 insertions(+), 15 deletions(-) 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..745c5807 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; @@ -29,10 +34,22 @@ import static org.springframework.cloud.gateway.support.GatewayToStringStyler.fi /** * @author Spencer Gibb */ -public class AddResponseHeaderGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory { +public class AddResponseHeaderGatewayFilterFactory + extends AbstractGatewayFilterFactory { + + private static final String OVERRIDE_KEY = "override"; + + public AddResponseHeaderGatewayFilterFactory() { + super(Config.class); + } @Override - public GatewayFilter apply(NameValueConfig config) { + public List shortcutFieldOrder() { + return Arrays.asList(GatewayFilter.NAME_KEY, GatewayFilter.VALUE_KEY, OVERRIDE_KEY); + } + + @Override + public GatewayFilter apply(Config config) { return new GatewayFilter() { @Override public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { @@ -42,19 +59,76 @@ public class AddResponseHeaderGatewayFilterFactory extends AbstractNameValueGate @Override public String toString() { return filterToStringCreator(AddResponseHeaderGatewayFilterFactory.this) - .append(config.getName(), config.getValue()) + .append(GatewayFilter.NAME_KEY, config.getName()) + .append(GatewayFilter.VALUE_KEY, config.getValue()) + .append(OVERRIDE_KEY, config.isOverride()) .toString(); } }; } - void addHeader(ServerWebExchange exchange, NameValueConfig config) { - final String value = ServerWebExchangeUtils.expand(exchange, config.getValue()); - HttpHeaders headers = exchange.getResponse().getHeaders(); + void addHeader(ServerWebExchange exchange, Config config) { // 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(); + if (config.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 { + + private String name; + + private String value; + + private boolean override = true; + + public String getName() { + return name; + } + + public Config setName(String name) { + this.name = name; + return this; + } + + public String getValue() { + return value; + } + + public Config setValue(String value) { + this.value = value; + return this; + } + + 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 47723b74..fdfec010 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 @@ -225,6 +225,18 @@ 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) { + return filter(getBean(AddResponseHeaderGatewayFilterFactory.class) + .apply(c -> c.setName(headerName).setValue(headerValue).setOverride(override))); + } + /** * 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..d42472bb 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; @@ -25,7 +27,6 @@ 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; @@ -51,20 +52,71 @@ 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 void toStringFormat() { - NameValueConfig config = new NameValueConfig().setName("myname").setValue("myvalue"); + AddResponseHeaderGatewayFilterFactory.Config config = new AddResponseHeaderGatewayFilterFactory.Config() + .setName("myname") + .setValue("myvalue"); GatewayFilter filter = new AddResponseHeaderGatewayFilterFactory().apply(config); assertThat(filter.toString()).contains("myname").contains("myvalue"); } @@ -81,11 +133,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 a08f82da..1d34803b 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}