diff --git a/spring-web-reactive/src/main/java/org/springframework/http/ExtendedHttpHeaders.java b/spring-web-reactive/src/main/java/org/springframework/http/ExtendedHttpHeaders.java
deleted file mode 100644
index f5fa19922bf..00000000000
--- a/spring-web-reactive/src/main/java/org/springframework/http/ExtendedHttpHeaders.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright 2002-2015 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.springframework.http;
-
-import java.util.ArrayList;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Variant of HttpHeaders (to be merged into HttpHeaders) that supports the
- * registration of {@link HeaderChangeListener}s.
- *
- *
For use with HTTP server response implementations that wish to propagate
- * header header changes to the underlying runtime as they occur.
- *
- * @author Rossen Stoyanchev
- */
-public class ExtendedHttpHeaders extends HttpHeaders {
-
- private final List listeners = new ArrayList<>(1);
-
-
- public ExtendedHttpHeaders() {
- }
-
- public ExtendedHttpHeaders(HeaderChangeListener listener) {
- this.listeners.add(listener);
- }
-
-
- @Override
- public void add(String name, String value) {
- for (HeaderChangeListener listener : this.listeners) {
- listener.headerAdded(name, value);
- }
- super.add(name, value);
- }
-
- @Override
- public void set(String name, String value) {
- List values = new LinkedList<>();
- values.add(value);
- put(name, values);
- }
-
- @Override
- public List put(String key, List values) {
- for (HeaderChangeListener listener : this.listeners) {
- listener.headerPut(key, values);
- }
- return super.put(key, values);
- }
-
- @Override
- public List remove(Object key) {
- for (HeaderChangeListener listener : this.listeners) {
- listener.headerRemoved((String) key);
- }
- return super.remove(key);
- }
-
- @Override
- public void putAll(Map extends String, ? extends List> map) {
- for (Entry extends String, ? extends List> entry : map.entrySet()) {
- put(entry.getKey(), entry.getValue());
- }
- super.putAll(map);
- }
-
- @Override
- public void clear() {
- for (Entry extends String, ? extends List> entry : super.entrySet()) {
- remove(entry.getKey(), entry.getValue());
- }
- super.clear();
- }
-
-
- public interface HeaderChangeListener {
-
- void headerAdded(String name, String value);
-
- void headerPut(String key, List values);
-
- void headerRemoved(String key);
-
- }
-
-}
diff --git a/spring-web-reactive/src/main/java/org/springframework/http/server/reactive/ReactorServerHttpResponse.java b/spring-web-reactive/src/main/java/org/springframework/http/server/reactive/ReactorServerHttpResponse.java
index 58a8947440e..f1c7af00ef6 100644
--- a/spring-web-reactive/src/main/java/org/springframework/http/server/reactive/ReactorServerHttpResponse.java
+++ b/spring-web-reactive/src/main/java/org/springframework/http/server/reactive/ReactorServerHttpResponse.java
@@ -16,7 +16,6 @@
package org.springframework.http.server.reactive;
import java.nio.ByteBuffer;
-import java.util.List;
import org.reactivestreams.Publisher;
import reactor.Flux;
@@ -25,7 +24,6 @@ import reactor.io.buffer.Buffer;
import reactor.io.net.http.HttpChannel;
import reactor.io.net.http.model.Status;
-import org.springframework.http.ExtendedHttpHeaders;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.util.Assert;
@@ -42,11 +40,13 @@ public class ReactorServerHttpResponse implements ServerHttpResponse {
private final HttpHeaders headers;
+ private boolean headersWritten = false;
+
public ReactorServerHttpResponse(HttpChannel, Buffer> response) {
Assert.notNull("'response' must not be null.");
this.channel = response;
- this.headers = new ExtendedHttpHeaders(new ReactorHeaderChangeListener());
+ this.headers = new HttpHeaders();
}
@@ -61,7 +61,7 @@ public class ReactorServerHttpResponse implements ServerHttpResponse {
@Override
public HttpHeaders getHeaders() {
- return this.headers;
+ return (this.headersWritten ? HttpHeaders.readOnlyHttpHeaders(this.headers) : this.headers);
}
@Override
@@ -70,26 +70,19 @@ public class ReactorServerHttpResponse implements ServerHttpResponse {
}
protected Mono setBodyInternal(Publisher publisher) {
+ writeHeaders();
return Mono.from(getReactorChannel().writeWith(Flux.from(publisher).map(Buffer::new)));
}
-
- private class ReactorHeaderChangeListener implements ExtendedHttpHeaders.HeaderChangeListener {
-
- @Override
- public void headerAdded(String name, String value) {
- getReactorChannel().responseHeaders().add(name, value);
- }
-
- @Override
- public void headerPut(String key, List values) {
- getReactorChannel().responseHeaders().remove(key);
- getReactorChannel().responseHeaders().add(key, values);
- }
-
- @Override
- public void headerRemoved(String key) {
- getReactorChannel().responseHeaders().remove(key);
+ @Override
+ public void writeHeaders() {
+ if (!this.headersWritten) {
+ for (String name : this.headers.keySet()) {
+ for (String value : this.headers.get(name)) {
+ this.channel.responseHeaders().add(name, value);
+ }
+ }
+ this.headersWritten = true;
}
}
diff --git a/spring-web-reactive/src/main/java/org/springframework/http/server/reactive/RxNettyServerHttpResponse.java b/spring-web-reactive/src/main/java/org/springframework/http/server/reactive/RxNettyServerHttpResponse.java
index 05ed55e8c3d..c60eaaded8d 100644
--- a/spring-web-reactive/src/main/java/org/springframework/http/server/reactive/RxNettyServerHttpResponse.java
+++ b/spring-web-reactive/src/main/java/org/springframework/http/server/reactive/RxNettyServerHttpResponse.java
@@ -17,7 +17,6 @@
package org.springframework.http.server.reactive;
import java.nio.ByteBuffer;
-import java.util.List;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.reactivex.netty.protocol.http.server.HttpServerResponse;
@@ -27,7 +26,6 @@ import reactor.Mono;
import reactor.core.publisher.convert.RxJava1Converter;
import rx.Observable;
-import org.springframework.http.ExtendedHttpHeaders;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.util.Assert;
@@ -44,11 +42,13 @@ public class RxNettyServerHttpResponse implements ServerHttpResponse {
private final HttpHeaders headers;
+ private boolean headersWritten = false;
+
public RxNettyServerHttpResponse(HttpServerResponse> response) {
Assert.notNull("'response', response must not be null.");
this.response = response;
- this.headers = new ExtendedHttpHeaders(new RxNettyHeaderChangeListener());
+ this.headers = new HttpHeaders();
}
@@ -63,7 +63,7 @@ public class RxNettyServerHttpResponse implements ServerHttpResponse {
@Override
public HttpHeaders getHeaders() {
- return this.headers;
+ return (this.headersWritten ? HttpHeaders.readOnlyHttpHeaders(this.headers) : this.headers);
}
@Override
@@ -72,6 +72,7 @@ public class RxNettyServerHttpResponse implements ServerHttpResponse {
}
protected Mono setBodyInternal(Publisher publisher) {
+ writeHeaders();
Observable content = RxJava1Converter.from(publisher).map(this::toBytes);
Observable completion = getRxNettyResponse().writeBytes(content);
return RxJava1Converter.from(completion).after();
@@ -83,26 +84,15 @@ public class RxNettyServerHttpResponse implements ServerHttpResponse {
return bytes;
}
-
- private class RxNettyHeaderChangeListener implements ExtendedHttpHeaders.HeaderChangeListener {
-
- @Override
- public void headerAdded(String name, String value) {
- getRxNettyResponse().addHeader(name, value);
- }
-
- @Override
- public void headerPut(String key, List values) {
- getRxNettyResponse().removeHeader(key);
- for (String value : values) {
- getRxNettyResponse().addHeader(key, value);
+ @Override
+ public void writeHeaders() {
+ if (!this.headersWritten) {
+ for (String name : this.headers.keySet()) {
+ for (String value : this.headers.get(name))
+ this.response.addHeader(name, value);
}
- }
-
- @Override
- public void headerRemoved(String key) {
- getRxNettyResponse().removeHeader(key);
+ this.headersWritten = true;
}
}
-}
+}
\ No newline at end of file
diff --git a/spring-web-reactive/src/main/java/org/springframework/http/server/reactive/ServerHttpResponse.java b/spring-web-reactive/src/main/java/org/springframework/http/server/reactive/ServerHttpResponse.java
index 4b31cf19c39..82320e98584 100644
--- a/spring-web-reactive/src/main/java/org/springframework/http/server/reactive/ServerHttpResponse.java
+++ b/spring-web-reactive/src/main/java/org/springframework/http/server/reactive/ServerHttpResponse.java
@@ -16,6 +16,8 @@
package org.springframework.http.server.reactive;
+import org.reactivestreams.Publisher;
+
import org.springframework.http.HttpStatus;
import org.springframework.http.ReactiveHttpOutputMessage;
@@ -31,5 +33,17 @@ public interface ServerHttpResponse extends ReactiveHttpOutputMessage {
* @param status the HTTP status as an {@link HttpStatus} enum value
*/
void setStatusCode(HttpStatus status);
-
+
+ /**
+ * Use this method to apply header changes made via {@link #getHeaders()} to
+ * the underlying server response. By default changes made via
+ * {@link #getHeaders()} are cached until a call to {@link #setBody}
+ * implicitly applies header changes or until this method is called.
+ *
+ * Note: After this method is called,
+ * {@link #getHeaders() headers} become read-only and any additional calls
+ * to this method are ignored.
+ */
+ void writeHeaders();
+
}
diff --git a/spring-web-reactive/src/main/java/org/springframework/http/server/reactive/ServletServerHttpResponse.java b/spring-web-reactive/src/main/java/org/springframework/http/server/reactive/ServletServerHttpResponse.java
index 0fc838af418..27b67989554 100644
--- a/spring-web-reactive/src/main/java/org/springframework/http/server/reactive/ServletServerHttpResponse.java
+++ b/spring-web-reactive/src/main/java/org/springframework/http/server/reactive/ServletServerHttpResponse.java
@@ -17,7 +17,9 @@
package org.springframework.http.server.reactive;
import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
import java.util.List;
+import java.util.Map;
import java.util.function.Function;
import javax.servlet.http.HttpServletResponse;
@@ -25,9 +27,9 @@ import org.reactivestreams.Publisher;
import reactor.Flux;
import reactor.Mono;
-import org.springframework.http.ExtendedHttpHeaders;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
import org.springframework.util.Assert;
/**
@@ -43,6 +45,8 @@ public class ServletServerHttpResponse implements ServerHttpResponse {
private final HttpHeaders headers;
+ private boolean headersWritten = false;
+
public ServletServerHttpResponse(HttpServletResponse response,
Function, Mono> responseBodyWriter) {
@@ -51,7 +55,7 @@ public class ServletServerHttpResponse implements ServerHttpResponse {
Assert.notNull(responseBodyWriter, "'responseBodyWriter' must not be null");
this.response = response;
this.responseBodyWriter = responseBodyWriter;
- this.headers = new ExtendedHttpHeaders(new ServletHeaderChangeListener());
+ this.headers = new HttpHeaders();
}
@@ -66,7 +70,7 @@ public class ServletServerHttpResponse implements ServerHttpResponse {
@Override
public HttpHeaders getHeaders() {
- return this.headers;
+ return (this.headersWritten ? HttpHeaders.readOnlyHttpHeaders(this.headers) : this.headers);
}
@Override
@@ -75,29 +79,29 @@ public class ServletServerHttpResponse implements ServerHttpResponse {
}
protected Mono setBodyInternal(Publisher publisher) {
+ writeHeaders();
return this.responseBodyWriter.apply(publisher);
}
-
- private class ServletHeaderChangeListener implements ExtendedHttpHeaders.HeaderChangeListener {
-
- @Override
- public void headerAdded(String name, String value) {
- getServletResponse().addHeader(name, value);
- }
-
- @Override
- public void headerPut(String key, List values) {
- // We can only add but not remove
- for (String value : values) {
- getServletResponse().addHeader(key, value);
+ @Override
+ public void writeHeaders() {
+ if (!this.headersWritten) {
+ for (Map.Entry> entry : this.headers.entrySet()) {
+ String headerName = entry.getKey();
+ for (String headerValue : entry.getValue()) {
+ this.response.addHeader(headerName, headerValue);
+ }
}
- }
-
- @Override
- public void headerRemoved(String key) {
- // No Servlet support for removing headers
+ MediaType contentType = this.headers.getContentType();
+ if (this.response.getContentType() == null && contentType != null) {
+ this.response.setContentType(contentType.toString());
+ }
+ Charset charset = (contentType != null ? contentType.getCharSet() : null);
+ if (this.response.getCharacterEncoding() == null && charset != null) {
+ this.response.setCharacterEncoding(charset.name());
+ }
+ this.headersWritten = true;
}
}
-}
+}
\ No newline at end of file
diff --git a/spring-web-reactive/src/main/java/org/springframework/http/server/reactive/UndertowServerHttpResponse.java b/spring-web-reactive/src/main/java/org/springframework/http/server/reactive/UndertowServerHttpResponse.java
index 2d303c4cd02..08032cf1e23 100644
--- a/spring-web-reactive/src/main/java/org/springframework/http/server/reactive/UndertowServerHttpResponse.java
+++ b/spring-web-reactive/src/main/java/org/springframework/http/server/reactive/UndertowServerHttpResponse.java
@@ -18,6 +18,7 @@ package org.springframework.http.server.reactive;
import java.nio.ByteBuffer;
import java.util.List;
+import java.util.Map;
import java.util.function.Function;
import io.undertow.server.HttpServerExchange;
@@ -26,7 +27,6 @@ import org.reactivestreams.Publisher;
import reactor.Flux;
import reactor.Mono;
-import org.springframework.http.ExtendedHttpHeaders;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.util.Assert;
@@ -45,6 +45,8 @@ public class UndertowServerHttpResponse implements ServerHttpResponse {
private final HttpHeaders headers;
+ private boolean headersWritten = false;
+
public UndertowServerHttpResponse(HttpServerExchange exchange,
Function, Mono> responseBodyWriter) {
@@ -53,7 +55,7 @@ public class UndertowServerHttpResponse implements ServerHttpResponse {
Assert.notNull(responseBodyWriter, "'responseBodyWriter' must not be null");
this.exchange = exchange;
this.responseBodyWriter = responseBodyWriter;
- this.headers = new ExtendedHttpHeaders(new UndertowHeaderChangeListener());
+ this.headers = new HttpHeaders();
}
@@ -69,7 +71,7 @@ public class UndertowServerHttpResponse implements ServerHttpResponse {
@Override
public HttpHeaders getHeaders() {
- return this.headers;
+ return (this.headersWritten ? HttpHeaders.readOnlyHttpHeaders(this.headers) : this.headers);
}
@Override
@@ -78,27 +80,18 @@ public class UndertowServerHttpResponse implements ServerHttpResponse {
}
protected Mono setBodyInternal(Publisher publisher) {
+ writeHeaders();
return this.responseBodyWriter.apply(publisher);
}
-
- private class UndertowHeaderChangeListener implements ExtendedHttpHeaders.HeaderChangeListener {
-
- @Override
- public void headerAdded(String name, String value) {
- HttpString headerName = HttpString.tryFromString(name);
- getUndertowExchange().getResponseHeaders().add(headerName, value);
- }
-
- @Override
- public void headerPut(String key, List values) {
- HttpString headerName = HttpString.tryFromString(key);
- getUndertowExchange().getResponseHeaders().putAll(headerName, values);
- }
-
- @Override
- public void headerRemoved(String key) {
- getUndertowExchange().getResponseHeaders().remove(key);
+ @Override
+ public void writeHeaders() {
+ if (!this.headersWritten) {
+ for (Map.Entry> entry : this.headers.entrySet()) {
+ HttpString headerName = HttpString.tryFromString(entry.getKey());
+ this.exchange.getResponseHeaders().addAll(headerName, entry.getValue());
+ }
+ this.headersWritten = true;
}
}
diff --git a/spring-web-reactive/src/main/java/org/springframework/web/server/WebToHttpHandlerAdapter.java b/spring-web-reactive/src/main/java/org/springframework/web/server/WebToHttpHandlerAdapter.java
index b2d45e7b6f4..aace5d7885d 100644
--- a/spring-web-reactive/src/main/java/org/springframework/web/server/WebToHttpHandlerAdapter.java
+++ b/spring-web-reactive/src/main/java/org/springframework/web/server/WebToHttpHandlerAdapter.java
@@ -37,7 +37,7 @@ public class WebToHttpHandlerAdapter extends WebHandlerDecorator implements Http
@Override
public Mono handle(ServerHttpRequest request, ServerHttpResponse response) {
WebServerExchange exchange = createWebServerExchange(request, response);
- return getDelegate().handle(exchange);
+ return getDelegate().handle(exchange).doOnTerminate((aVoid, ex) -> response.writeHeaders());
}
protected WebServerExchange createWebServerExchange(ServerHttpRequest request, ServerHttpResponse response) {
diff --git a/spring-web-reactive/src/test/java/org/springframework/http/server/reactive/MockServerHttpResponse.java b/spring-web-reactive/src/test/java/org/springframework/http/server/reactive/MockServerHttpResponse.java
index 0a282a95b34..0aab9caaf3a 100644
--- a/spring-web-reactive/src/test/java/org/springframework/http/server/reactive/MockServerHttpResponse.java
+++ b/spring-web-reactive/src/test/java/org/springframework/http/server/reactive/MockServerHttpResponse.java
@@ -60,4 +60,8 @@ public class MockServerHttpResponse implements ServerHttpResponse {
return this.body;
}
+ @Override
+ public void writeHeaders() {
+ }
+
}
diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/handler/SimpleUrlHandlerMappingIntegrationTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/handler/SimpleUrlHandlerMappingIntegrationTests.java
index 8bd43ba8309..dfc0fb1a563 100644
--- a/spring-web-reactive/src/test/java/org/springframework/web/reactive/handler/SimpleUrlHandlerMappingIntegrationTests.java
+++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/handler/SimpleUrlHandlerMappingIntegrationTests.java
@@ -32,8 +32,6 @@ import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.http.server.reactive.AbstractHttpHandlerIntegrationTests;
import org.springframework.http.server.reactive.HttpHandler;
-import org.springframework.http.server.reactive.ServerHttpRequest;
-import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.reactive.DispatcherHandler;