Skip to content

AddResponseHeaderGatewayFilterFactory support override option #3803

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -31,6 +36,23 @@
*/
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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible to leave this apply method signature and call the new one? Trying to avoid breaking changes

return new GatewayFilter() {
Expand All @@ -41,6 +63,13 @@ public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

@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();
Expand All @@ -49,12 +78,51 @@ public String toString() {
}

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

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,22 @@ public GatewayFilterSpec addResponseHeader(String headerName, String headerValue
.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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -51,15 +53,64 @@ void testResponseHeaderFilter() {
.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)
.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";
testClient.get().uri(uri).header("Host", host).exchange().expectHeader().valueEquals("example", expectedValue);

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
Expand All @@ -81,11 +132,11 @@ static class TestConfig {
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();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
Loading