Merge branch '4.2.x'

This commit is contained in:
Ryan Baxter
2025-05-23 09:05:14 -04:00
5 changed files with 149 additions and 10 deletions

View File

@@ -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.

View File

@@ -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<String> 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();
}
}
}

View File

@@ -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.
* <p>

View File

@@ -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<String, String> 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<String, String> 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();
}

View File

@@ -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}