Skip to content

Commit f9c1565

Browse files
committed
Remove Content-* response headers for error handling
Prior to this commit, when WebFlux handlers added `"Content-*"` response headers and an error happened while handling the request, all those headers would not be cleared from the response before error handling. This commit clears those headers from the response in two places: * when invoking the handler and adapting the response * when writing the response body Not removing those headers might break HTTP clients since they're given wrong information about how to interpret the HTTP response body: the error response body might be very different from the original one. Fixes gh-24238
1 parent 59ade91 commit f9c1565

File tree

4 files changed

+53
-21
lines changed

4 files changed

+53
-21
lines changed

spring-web/src/main/java/org/springframework/http/HttpHeaders.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -1827,6 +1827,22 @@ public static String encodeBasicAuth(String username, String password, @Nullable
18271827
return new String(encodedBytes, charset);
18281828
}
18291829

1830+
/**
1831+
* Remove the well-known {@code "Content-*"} HTTP headers from the given instance.
1832+
* <p>Such headers should be cleared, if possible, from the response if the intended
1833+
* body can't be written due to errors.
1834+
* @since 5.2.3
1835+
*/
1836+
public static void clearContentHeaders(HttpHeaders headers) {
1837+
headers.remove(HttpHeaders.CONTENT_DISPOSITION);
1838+
headers.remove(HttpHeaders.CONTENT_ENCODING);
1839+
headers.remove(HttpHeaders.CONTENT_LANGUAGE);
1840+
headers.remove(HttpHeaders.CONTENT_LENGTH);
1841+
headers.remove(HttpHeaders.CONTENT_LOCATION);
1842+
headers.remove(HttpHeaders.CONTENT_RANGE);
1843+
headers.remove(HttpHeaders.CONTENT_TYPE);
1844+
}
1845+
18301846
// Package-private: used in ResponseCookie
18311847
static String formatDate(long date) {
18321848
Instant instant = Instant.ofEpochMilli(date);

spring-web/src/main/java/org/springframework/http/server/reactive/AbstractServerHttpResponse.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -181,21 +181,22 @@ public final Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
181181
if (body instanceof Mono) {
182182
return ((Mono<? extends DataBuffer>) body).flatMap(buffer ->
183183
doCommit(() -> writeWithInternal(Mono.just(buffer)))
184-
.doOnDiscard(PooledDataBuffer.class, DataBufferUtils::release));
184+
.doOnDiscard(PooledDataBuffer.class, DataBufferUtils::release))
185+
.doOnError(t -> clearContentHeaders());
185186
}
186187
return new ChannelSendOperator<>(body, inner -> doCommit(() -> writeWithInternal(inner)))
187-
.doOnError(t -> removeContentLength());
188+
.doOnError(t -> clearContentHeaders());
188189
}
189190

190191
@Override
191192
public final Mono<Void> writeAndFlushWith(Publisher<? extends Publisher<? extends DataBuffer>> body) {
192193
return new ChannelSendOperator<>(body, inner -> doCommit(() -> writeAndFlushWithInternal(inner)))
193-
.doOnError(t -> removeContentLength());
194+
.doOnError(t -> clearContentHeaders());
194195
}
195196

196-
private void removeContentLength() {
197+
private void clearContentHeaders() {
197198
if (!this.isCommitted()) {
198-
this.getHeaders().remove(HttpHeaders.CONTENT_LENGTH);
199+
HttpHeaders.clearContentHeaders(this.getHeaders());
199200
}
200201
}
201202

spring-web/src/test/java/org/springframework/http/server/reactive/ServerHttpResponseTests.java

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -30,18 +30,20 @@
3030
import org.springframework.core.io.buffer.DefaultDataBuffer;
3131
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
3232
import org.springframework.http.HttpHeaders;
33+
import org.springframework.http.MediaType;
3334
import org.springframework.http.ResponseCookie;
3435

3536
import static org.assertj.core.api.Assertions.assertThat;
3637

3738
/**
3839
* @author Rossen Stoyanchev
3940
* @author Sebastien Deleuze
41+
* @author Brian Clozel
4042
*/
4143
public class ServerHttpResponseTests {
4244

4345
@Test
44-
public void writeWith() throws Exception {
46+
void writeWith() throws Exception {
4547
TestServerHttpResponse response = new TestServerHttpResponse();
4648
response.writeWith(Flux.just(wrap("a"), wrap("b"), wrap("c"))).block();
4749

@@ -56,7 +58,7 @@ public void writeWith() throws Exception {
5658
}
5759

5860
@Test // SPR-14952
59-
public void writeAndFlushWithFluxOfDefaultDataBuffer() throws Exception {
61+
void writeAndFlushWithFluxOfDefaultDataBuffer() throws Exception {
6062
TestServerHttpResponse response = new TestServerHttpResponse();
6163
Flux<Flux<DefaultDataBuffer>> flux = Flux.just(Flux.just(wrap("foo")));
6264
response.writeAndFlushWith(flux).block();
@@ -70,21 +72,35 @@ public void writeAndFlushWithFluxOfDefaultDataBuffer() throws Exception {
7072
}
7173

7274
@Test
73-
public void writeWithError() throws Exception {
74-
TestServerHttpResponse response = new TestServerHttpResponse();
75-
response.getHeaders().setContentLength(12);
75+
void writeWithFluxError() throws Exception {
7676
IllegalStateException error = new IllegalStateException("boo");
77-
response.writeWith(Flux.error(error)).onErrorResume(ex -> Mono.empty()).block();
77+
writeWithError(Flux.error(error));
78+
}
79+
80+
@Test
81+
void writeWithMonoError() throws Exception {
82+
IllegalStateException error = new IllegalStateException("boo");
83+
writeWithError(Mono.error(error));
84+
}
85+
86+
void writeWithError(Publisher<DataBuffer> body) throws Exception {
87+
TestServerHttpResponse response = new TestServerHttpResponse();
88+
HttpHeaders headers = response.getHeaders();
89+
headers.setContentType(MediaType.APPLICATION_JSON);
90+
headers.set(HttpHeaders.CONTENT_ENCODING, "gzip");
91+
headers.setContentLength(12);
92+
response.writeWith(body).onErrorResume(ex -> Mono.empty()).block();
7893

7994
assertThat(response.statusCodeWritten).isFalse();
8095
assertThat(response.headersWritten).isFalse();
8196
assertThat(response.cookiesWritten).isFalse();
82-
assertThat(response.getHeaders().containsKey(HttpHeaders.CONTENT_LENGTH)).isFalse();
97+
assertThat(headers).doesNotContainKeys(HttpHeaders.CONTENT_TYPE, HttpHeaders.CONTENT_LENGTH,
98+
HttpHeaders.CONTENT_ENCODING);
8399
assertThat(response.body.isEmpty()).isTrue();
84100
}
85101

86102
@Test
87-
public void setComplete() throws Exception {
103+
void setComplete() throws Exception {
88104
TestServerHttpResponse response = new TestServerHttpResponse();
89105
response.setComplete().block();
90106

@@ -95,7 +111,7 @@ public void setComplete() throws Exception {
95111
}
96112

97113
@Test
98-
public void beforeCommitWithComplete() throws Exception {
114+
void beforeCommitWithComplete() throws Exception {
99115
ResponseCookie cookie = ResponseCookie.from("ID", "123").build();
100116
TestServerHttpResponse response = new TestServerHttpResponse();
101117
response.beforeCommit(() -> Mono.fromRunnable(() -> response.getCookies().add(cookie.getName(), cookie)));
@@ -113,7 +129,7 @@ public void beforeCommitWithComplete() throws Exception {
113129
}
114130

115131
@Test
116-
public void beforeCommitActionWithSetComplete() throws Exception {
132+
void beforeCommitActionWithSetComplete() throws Exception {
117133
ResponseCookie cookie = ResponseCookie.from("ID", "123").build();
118134
TestServerHttpResponse response = new TestServerHttpResponse();
119135
response.beforeCommit(() -> {
@@ -130,7 +146,6 @@ public void beforeCommitActionWithSetComplete() throws Exception {
130146
}
131147

132148

133-
134149
private DefaultDataBuffer wrap(String a) {
135150
return new DefaultDataBufferFactory().wrap(ByteBuffer.wrap(a.getBytes(StandardCharsets.UTF_8)));
136151
}

spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerAdapter.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -211,7 +211,7 @@ private Mono<HandlerResult> handleException(Throwable exception, HandlerMethod h
211211
// Success and error responses may use different content types
212212
exchange.getAttributes().remove(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
213213
if (!exchange.getResponse().isCommitted()) {
214-
exchange.getResponse().getHeaders().remove(HttpHeaders.CONTENT_TYPE);
214+
HttpHeaders.clearContentHeaders(exchange.getResponse().getHeaders());
215215
}
216216

217217
InvocableHandlerMethod invocable = this.methodResolver.getExceptionHandlerMethod(exception, handlerMethod);

0 commit comments

Comments
 (0)