From 6b05d172487c3ab27bc947e6b9de950242e442a2 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Tue, 29 Dec 2015 17:35:19 -0500 Subject: [PATCH] Response headers always in sync with native response ServerHttpResponse implementations now immediately propagate HttpHeaders changes as they so there is no need to call applyHeaders(). The writeHeaders from ServerHttpResponse is also removed. RxNetty and Reactor Net both support implicitly completing if the handler completes without explicitly writing the headers or the response body. --- .../http/ExtendedHttpHeaders.java | 99 +++++++++++++++++++ .../InternalServerErrorExceptionHandler.java | 3 +- .../reactive/ReactorServerHttpResponse.java | 56 ++++++----- .../reactive/RxNettyServerHttpRequest.java | 4 +- .../reactive/RxNettyServerHttpResponse.java | 57 ++++++----- .../server/reactive/ServerHttpResponse.java | 9 -- .../reactive/ServletServerHttpResponse.java | 63 ++++++------ .../reactive/UndertowServerHttpResponse.java | 74 +++++++------- .../ResponseStatusExceptionHandler.java | 2 +- .../handler/SimpleHandlerResultHandler.java | 12 ++- .../reactive/MockServerHttpResponse.java | 5 - ...mpleUrlHandlerMappingIntegrationTests.java | 28 +++++- 12 files changed, 267 insertions(+), 145 deletions(-) create mode 100644 spring-web-reactive/src/main/java/org/springframework/http/ExtendedHttpHeaders.java 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 00000000000..ade3903d893 --- /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 b9a69eac648..7c88c8febe5 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 3233d38daae..c774ead81fc 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 cf075d9f3fc..2f4ae920c54 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 78f71415892..2b316492b0d 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 dabf620f1d2..4b31cf19c39 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 984f9dd9cad..72ec01a3445 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 01f1d9ee534..b7fa1edfb61 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 828c9a52c52..f9e3135532b 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 6c7bd02930e..efa8d7e8319 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 ba12b8d76d3..d478d567029 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 d0de9544d06..da6cf3fc28f 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(); + } + } + }