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 new file mode 100644 index 0000000000..ade3903d89 --- /dev/null +++ b/spring-web-reactive/src/main/java/org/springframework/http/ExtendedHttpHeaders.java @@ -0,0 +1,99 @@ +/* + * 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; + +/** + * Extension of HttpHeaders (to be merged into HttpHeaders) that allows the + * registration of {@link HeaderChangeListener}. For use with HTTP response + * implementations that can keep track of changes made headers and keep the + * underlying server headers always in sync. + * + * @author Rossen Stoyanchev + */ +public class ExtendedHttpHeaders extends HttpHeaders { + + private final List listeners = new ArrayList<>(1); + + + public void registerChangeListener(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> map) { + for (Entry> entry : map.entrySet()) { + put(entry.getKey(), entry.getValue()); + } + super.putAll(map); + } + + @Override + public void clear() { + for (Entry> 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/InternalServerErrorExceptionHandler.java b/spring-web-reactive/src/main/java/org/springframework/http/server/reactive/InternalServerErrorExceptionHandler.java index b9a69eac64..7c88c8febe 100644 --- a/spring-web-reactive/src/main/java/org/springframework/http/server/reactive/InternalServerErrorExceptionHandler.java +++ b/spring-web-reactive/src/main/java/org/springframework/http/server/reactive/InternalServerErrorExceptionHandler.java @@ -16,6 +16,7 @@ package org.springframework.http.server.reactive; import org.reactivestreams.Publisher; +import reactor.Publishers; import org.springframework.http.HttpStatus; @@ -30,7 +31,7 @@ public class InternalServerErrorExceptionHandler implements HttpExceptionHandler @Override public Publisher handle(ServerHttpRequest request, ServerHttpResponse response, Throwable ex) { response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR); - return response.writeHeaders(); + return Publishers.empty(); } } 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 3233d38daa..c774ead81f 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,6 +16,7 @@ package org.springframework.http.server.reactive; import java.nio.ByteBuffer; +import java.util.List; import org.reactivestreams.Publisher; import reactor.Publishers; @@ -23,12 +24,14 @@ 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; /** * @author Stephane Maldini + * @author Rossen Stoyanchev */ public class ReactorServerHttpResponse implements ServerHttpResponse { @@ -36,13 +39,17 @@ public class ReactorServerHttpResponse implements ServerHttpResponse { private final HttpHeaders headers; - private boolean headersWritten = false; - public ReactorServerHttpResponse(HttpChannel response) { Assert.notNull("'response', response must not be null."); this.channel = response; - this.headers = new HttpHeaders(); + this.headers = initHttpHeaders(); + } + + private HttpHeaders initHttpHeaders() { + ExtendedHttpHeaders headers = new ExtendedHttpHeaders(); + headers.registerChangeListener(new ReactorHeaderChangeListener()); + return headers; } @@ -53,34 +60,33 @@ public class ReactorServerHttpResponse implements ServerHttpResponse { @Override public HttpHeaders getHeaders() { - return (this.headersWritten ? HttpHeaders.readOnlyHttpHeaders(this.headers) : this.headers); - } - - @Override - public Publisher writeHeaders() { - if (this.headersWritten) { - return Publishers.empty(); - } - applyHeaders(); - return this.channel.writeHeaders(); + return this.headers; } @Override public Publisher setBody(Publisher publisher) { - return Publishers.lift(publisher, new WriteWithOperator<>(writePublisher -> { - applyHeaders(); - return this.channel.writeWith(Publishers.map(writePublisher, Buffer::new)); - })); + return Publishers.lift(publisher, new WriteWithOperator<>(writePublisher -> + this.channel.writeWith(Publishers.map(writePublisher, Buffer::new)))); } - private void applyHeaders() { - 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; + + private class ReactorHeaderChangeListener implements ExtendedHttpHeaders.HeaderChangeListener { + + @Override + public void headerAdded(String name, String value) { + channel.responseHeaders().add(name, value); + } + + @Override + public void headerPut(String key, List values) { + channel.responseHeaders().remove(key); + channel.responseHeaders().add(key, values); + } + + @Override + public void headerRemoved(String key) { + channel.responseHeaders().remove(key); } } + } diff --git a/spring-web-reactive/src/main/java/org/springframework/http/server/reactive/RxNettyServerHttpRequest.java b/spring-web-reactive/src/main/java/org/springframework/http/server/reactive/RxNettyServerHttpRequest.java index cf075d9f3f..2f4ae920c5 100644 --- a/spring-web-reactive/src/main/java/org/springframework/http/server/reactive/RxNettyServerHttpRequest.java +++ b/spring-web-reactive/src/main/java/org/springframework/http/server/reactive/RxNettyServerHttpRequest.java @@ -78,7 +78,9 @@ public class RxNettyServerHttpRequest implements ServerHttpRequest { @Override public Publisher getBody() { - Observable bytesContent = this.request.getContent().map(ByteBuf::nioBuffer); + Observable bytesContent = this.request.getContent() + .concatWith(Observable.empty()) + .map(ByteBuf::nioBuffer); return RxJava1Converter.from(bytesContent); } 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 78f7141589..2b316492b0 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,15 +17,16 @@ 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; import org.reactivestreams.Publisher; import reactor.Publishers; import reactor.core.publisher.convert.RxJava1Converter; -import reactor.io.buffer.Buffer; import rx.Observable; +import org.springframework.http.ExtendedHttpHeaders; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.util.Assert; @@ -40,13 +41,17 @@ 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 HttpHeaders(); + this.headers = initHttpHeaders(); + } + + private HttpHeaders initHttpHeaders() { + ExtendedHttpHeaders headers = new ExtendedHttpHeaders(); + headers.registerChangeListener(new RxNettyHeaderChangeListener()); + return headers; } @@ -57,36 +62,42 @@ public class RxNettyServerHttpResponse implements ServerHttpResponse { @Override public HttpHeaders getHeaders() { - return (this.headersWritten ? HttpHeaders.readOnlyHttpHeaders(this.headers) : this.headers); - } - - @Override - public Publisher writeHeaders() { - if (this.headersWritten) { - return Publishers.empty(); - } - applyHeaders(); - return RxJava1Converter.from(this.response.sendHeaders()); + return this.headers; } @Override public Publisher setBody(Publisher publisher) { return Publishers.lift(publisher, new WriteWithOperator<>(writePublisher -> { - applyHeaders(); Observable observable = RxJava1Converter.from(writePublisher) - .map(buffer -> new Buffer(buffer).asBytes()); + .map(buffer -> { + byte[] bytes = new byte[buffer.remaining()]; + buffer.get(bytes); + return bytes; + }); return RxJava1Converter.from(this.response.writeBytes(observable)); })); } - private void applyHeaders() { - if (!this.headersWritten) { - for (String name : this.headers.keySet()) { - for (String value : this.headers.get(name)) { - this.response.addHeader(name, value); - } + + private class RxNettyHeaderChangeListener implements ExtendedHttpHeaders.HeaderChangeListener { + + @Override + public void headerAdded(String name, String value) { + response.addHeader(name, value); + } + + @Override + public void headerPut(String key, List values) { + response.removeHeader(key); + for (String value : values) { + response.addHeader(key, value); } - this.headersWritten = true; + } + + @Override + public void headerRemoved(String key) { + response.removeHeader(key); } } + } 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 dabf620f1d..4b31cf19c3 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,8 +16,6 @@ package org.springframework.http.server.reactive; -import org.reactivestreams.Publisher; - import org.springframework.http.HttpStatus; import org.springframework.http.ReactiveHttpOutputMessage; @@ -34,11 +32,4 @@ public interface ServerHttpResponse extends ReactiveHttpOutputMessage { */ void setStatusCode(HttpStatus status); - /** - * Write the response headers. This method must be invoked to send responses without body. - * @return A {@code Publisher} used to signal the demand, and receive a notification - * when the handling is complete (success or error) including the flush of the data on the - * network. - */ - Publisher 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 984f9dd9ca..72ec01a344 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 @@ -18,9 +18,7 @@ package org.springframework.http.server.reactive; import java.io.IOException; import java.nio.ByteBuffer; -import java.nio.charset.Charset; import java.util.List; -import java.util.Map; import javax.servlet.ServletOutputStream; import javax.servlet.WriteListener; import javax.servlet.http.HttpServletResponse; @@ -32,9 +30,9 @@ import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; import reactor.Publishers; +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; /** @@ -51,16 +49,20 @@ public class ServletServerHttpResponse implements ServerHttpResponse { private final ResponseBodySubscriber subscriber; - private boolean headersWritten = false; - public ServletServerHttpResponse(HttpServletResponse response, ServletAsyncContextSynchronizer synchronizer) { Assert.notNull(response, "'response' must not be null"); this.response = response; - this.headers = new HttpHeaders(); + this.headers = initHttpHeaders(); this.subscriber = new ResponseBodySubscriber(synchronizer); } + private HttpHeaders initHttpHeaders() { + ExtendedHttpHeaders headers = new ExtendedHttpHeaders(); + headers.registerChangeListener(new ServletHeaderChangeListener()); + return headers; + } + @Override public void setStatusCode(HttpStatus status) { @@ -69,48 +71,41 @@ public class ServletServerHttpResponse implements ServerHttpResponse { @Override public HttpHeaders getHeaders() { - return (this.headersWritten ? HttpHeaders.readOnlyHttpHeaders(this.headers) : this.headers); + return this.headers; } WriteListener getWriteListener() { return this.subscriber; } - @Override - public Publisher writeHeaders() { - applyHeaders(); - return Publishers.empty(); - } - @Override public Publisher setBody(final Publisher publisher) { - return Publishers.lift(publisher, new WriteWithOperator<>(writePublisher -> { - applyHeaders(); - return (s -> writePublisher.subscribe(subscriber)); - })); + return Publishers.lift(publisher, new WriteWithOperator<>(writePublisher -> + (s -> writePublisher.subscribe(subscriber)))); } - private void applyHeaders() { - if (!this.headersWritten) { - for (Map.Entry> entry : this.headers.entrySet()) { - String headerName = entry.getKey(); - for (String headerValue : entry.getValue()) { - this.response.addHeader(headerName, headerValue); - } + + private class ServletHeaderChangeListener implements ExtendedHttpHeaders.HeaderChangeListener { + + @Override + public void headerAdded(String name, String value) { + response.addHeader(name, value); + } + + @Override + public void headerPut(String key, List values) { + // We can only add but not remove + for (String value : values) { + response.addHeader(key, value); } - 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; + } + + @Override + public void headerRemoved(String key) { + // No Servlet support for removing headers } } - private static class ResponseBodySubscriber implements WriteListener, Subscriber { private final ServletAsyncContextSynchronizer synchronizer; 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 01f1d9ee53..b7fa1edfb6 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 @@ -19,16 +19,11 @@ package org.springframework.http.server.reactive; import java.io.IOException; import java.nio.ByteBuffer; import java.util.List; -import java.util.Map; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.util.Assert; - import io.undertow.connector.PooledByteBuffer; import io.undertow.server.HttpServerExchange; import io.undertow.util.HttpString; @@ -41,6 +36,11 @@ import org.xnio.channels.StreamSinkChannel; import reactor.Publishers; import reactor.core.subscriber.BaseSubscriber; +import org.springframework.http.ExtendedHttpHeaders; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.util.Assert; + import static org.xnio.ChannelListeners.closingChannelExceptionHandler; import static org.xnio.ChannelListeners.flushingChannelListener; import static org.xnio.IoUtils.safeClose; @@ -58,14 +58,19 @@ public class UndertowServerHttpResponse implements ServerHttpResponse { private final ResponseBodySubscriber bodySubscriber = new ResponseBodySubscriber(); - private final HttpHeaders headers = new HttpHeaders(); - - private boolean headersWritten = false; + private final HttpHeaders headers; public UndertowServerHttpResponse(HttpServerExchange exchange) { Assert.notNull(exchange, "'exchange' is required."); this.exchange = exchange; + this.headers = initHttpHeaders(); + } + + private HttpHeaders initHttpHeaders() { + ExtendedHttpHeaders headers = new ExtendedHttpHeaders(); + headers.registerChangeListener(new UndertowHeaderChangeListener()); + return headers; } @@ -77,44 +82,34 @@ public class UndertowServerHttpResponse implements ServerHttpResponse { @Override public HttpHeaders getHeaders() { - return (this.headersWritten ? HttpHeaders.readOnlyHttpHeaders(this.headers) : this.headers); - } - - @Override - public Publisher writeHeaders() { - applyHeaders(); - return s -> s.onSubscribe(new Subscription() { - @Override - public void request(long n) { - s.onComplete(); - } - - @Override - public void cancel() { - } - }); - } - - private void applyHeaders() { - 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; - } + return this.headers; } @Override public Publisher setBody(Publisher publisher) { - return Publishers.lift(publisher, new WriteWithOperator<>(writePublisher -> { - applyHeaders(); - return (subscriber -> writePublisher.subscribe(bodySubscriber)); - })); + return Publishers.lift(publisher, new WriteWithOperator<>(writePublisher -> + (subscriber -> writePublisher.subscribe(bodySubscriber)))); } + private class UndertowHeaderChangeListener implements ExtendedHttpHeaders.HeaderChangeListener { + + @Override + public void headerAdded(String name, String value) { + exchange.getResponseHeaders().add(HttpString.tryFromString(name), value); + } + + @Override + public void headerPut(String key, List values) { + exchange.getResponseHeaders().putAll(HttpString.tryFromString(key), values); + } + + @Override + public void headerRemoved(String key) { + exchange.getResponseHeaders().remove(key); + } + } + private class ResponseBodySubscriber extends BaseSubscriber implements ChannelListener { @@ -266,4 +261,5 @@ public class UndertowServerHttpResponse implements ServerHttpResponse { } } } + } diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/ResponseStatusExceptionHandler.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/ResponseStatusExceptionHandler.java index 828c9a52c5..f9e3135532 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/ResponseStatusExceptionHandler.java +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/ResponseStatusExceptionHandler.java @@ -35,7 +35,7 @@ public class ResponseStatusExceptionHandler implements HttpExceptionHandler { public Publisher handle(ServerHttpRequest request, ServerHttpResponse response, Throwable ex) { if (ex instanceof ResponseStatusException) { response.setStatusCode(((ResponseStatusException) ex).getHttpStatus()); - return response.writeHeaders(); + return Publishers.empty(); } return Publishers.error(ex); } diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/handler/SimpleHandlerResultHandler.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/handler/SimpleHandlerResultHandler.java index 6c7bd02930..efa8d7e831 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/handler/SimpleHandlerResultHandler.java +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/handler/SimpleHandlerResultHandler.java @@ -16,8 +16,6 @@ package org.springframework.web.reactive.handler; -import java.util.Arrays; - import org.reactivestreams.Publisher; import reactor.Publishers; @@ -74,15 +72,19 @@ public class SimpleHandlerResultHandler implements Ordered, HandlerResultHandler ((this.conversionService != null) && this.conversionService.canConvert(type.getRawClass(), Publisher.class)); } + @SuppressWarnings("unchecked") @Override public Publisher handleResult(ServerHttpRequest request, ServerHttpResponse response, HandlerResult result) { Object value = result.getResult(); + if (Void.TYPE.equals(result.getResultType().getRawClass())) { - return response.writeHeaders(); + return Publishers.empty(); } - Publisher completion = (value instanceof Publisher ? (Publisher)value : this.conversionService.convert(value, Publisher.class)); - return Publishers.concat(Publishers.from(Arrays.asList(completion, response.writeHeaders()))); + + return (value instanceof Publisher ? (Publisher)value : + this.conversionService.convert(value, Publisher.class)); } + } 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 ba12b8d76d..d478d56702 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 @@ -49,11 +49,6 @@ public class MockServerHttpResponse implements ServerHttpResponse { return this.headers; } - @Override - public Publisher writeHeaders() { - return Publishers.empty(); - } - @Override public Publisher setBody(Publisher body) { this.body = body; 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 d0de9544d0..da6cf3fc28 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 @@ -23,6 +23,7 @@ import java.util.Map; import org.junit.Test; import org.reactivestreams.Publisher; +import reactor.Publishers; import reactor.io.buffer.Buffer; import reactor.rx.Streams; @@ -67,7 +68,7 @@ public class SimpleUrlHandlerMappingIntegrationTests extends AbstractHttpHandler } @Test - public void testFoo() throws Exception { + public void testFooHandler() throws Exception { RestTemplate restTemplate = new RestTemplate(); @@ -80,7 +81,7 @@ public class SimpleUrlHandlerMappingIntegrationTests extends AbstractHttpHandler } @Test - public void testBar() throws Exception { + public void testBarHandler() throws Exception { RestTemplate restTemplate = new RestTemplate(); @@ -92,6 +93,19 @@ public class SimpleUrlHandlerMappingIntegrationTests extends AbstractHttpHandler assertArrayEquals("bar".getBytes(UTF_8), response.getBody()); } + @Test + public void testHeaderSettingHandler() throws Exception { + + RestTemplate restTemplate = new RestTemplate(); + + URI url = new URI("http://localhost:" + port + "/header"); + RequestEntity request = RequestEntity.get(url).build(); + ResponseEntity response = restTemplate.exchange(request, byte[].class); + + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals("bar", response.getHeaders().getFirst("foo")); + } + @Test public void testNotFound() throws Exception { @@ -114,6 +128,7 @@ public class SimpleUrlHandlerMappingIntegrationTests extends AbstractHttpHandler Map map = new HashMap<>(); map.put("/foo", new FooHandler()); map.put("/bar", new BarHandler()); + map.put("/header", new HeaderSettingHandler()); setHandlers(map); } } @@ -134,4 +149,13 @@ public class SimpleUrlHandlerMappingIntegrationTests extends AbstractHttpHandler } } + private static class HeaderSettingHandler implements HttpHandler { + + @Override + public Publisher handle(ServerHttpRequest request, ServerHttpResponse response) { + response.getHeaders().add("foo", "bar"); + return Publishers.empty(); + } + } + }