diff --git a/spring-core/src/main/java/org/springframework/core/io/buffer/FlushingDataBuffer.java b/spring-core/src/main/java/org/springframework/core/io/buffer/FlushingDataBuffer.java
deleted file mode 100644
index 74d7933542..0000000000
--- a/spring-core/src/main/java/org/springframework/core/io/buffer/FlushingDataBuffer.java
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright 2002-2016 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.core.io.buffer;
-
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.ByteBuffer;
-import java.util.function.IntPredicate;
-
-/**
- * Empty {@link DataBuffer} that indicates to the file or the socket writing it
- * that previously buffered data should be flushed.
- *
- * @author Sebastien Deleuze
- * @since 5.0
- * @see FlushingDataBuffer#INSTANCE
- */
-public class FlushingDataBuffer implements DataBuffer {
-
- /** Singleton instance of this class */
- public static final FlushingDataBuffer INSTANCE = new FlushingDataBuffer();
-
- private final DataBuffer buffer;
-
-
- private FlushingDataBuffer() {
- this.buffer = new DefaultDataBufferFactory().allocateBuffer(0);
- }
-
-
- @Override
- public DataBufferFactory factory() {
- return this.buffer.factory();
- }
-
- @Override
- public int indexOf(IntPredicate predicate, int fromIndex) {
- return this.buffer.indexOf(predicate, fromIndex);
- }
-
- @Override
- public int lastIndexOf(IntPredicate predicate, int fromIndex) {
- return this.buffer.lastIndexOf(predicate, fromIndex);
- }
-
- @Override
- public int readableByteCount() {
- return this.buffer.readableByteCount();
- }
-
- @Override
- public byte read() {
- return this.buffer.read();
- }
-
- @Override
- public DataBuffer read(byte[] destination) {
- return this.buffer.read(destination);
- }
-
- @Override
- public DataBuffer read(byte[] destination, int offset, int length) {
- return this.buffer.read(destination, offset, length);
- }
-
- @Override
- public DataBuffer write(byte b) {
- return this.buffer.write(b);
- }
-
- @Override
- public DataBuffer write(byte[] source) {
- return this.buffer.write(source);
- }
-
- @Override
- public DataBuffer write(byte[] source, int offset, int length) {
- return this.buffer.write(source, offset, length);
- }
-
- @Override
- public DataBuffer write(DataBuffer... buffers) {
- return this.buffer.write(buffers);
- }
-
- @Override
- public DataBuffer write(ByteBuffer... buffers) {
- return this.buffer.write(buffers);
- }
-
- @Override
- public DataBuffer slice(int index, int length) {
- return this.buffer.slice(index, length);
- }
-
- @Override
- public ByteBuffer asByteBuffer() {
- return this.buffer.asByteBuffer();
- }
-
- @Override
- public InputStream asInputStream() {
- return this.buffer.asInputStream();
- }
-
- @Override
- public OutputStream asOutputStream() {
- return this.buffer.asOutputStream();
- }
-
-}
diff --git a/spring-core/src/main/java/org/springframework/core/io/buffer/NettyDataBufferFactory.java b/spring-core/src/main/java/org/springframework/core/io/buffer/NettyDataBufferFactory.java
index 2a6a1d5886..e4bce79cb0 100644
--- a/spring-core/src/main/java/org/springframework/core/io/buffer/NettyDataBufferFactory.java
+++ b/spring-core/src/main/java/org/springframework/core/io/buffer/NettyDataBufferFactory.java
@@ -78,6 +78,24 @@ public class NettyDataBufferFactory implements DataBufferFactory {
return new NettyDataBuffer(byteBuf, this);
}
+ /**
+ * Returns the given Netty {@link DataBuffer} as a {@link ByteBuf}. Returns the
+ * {@linkplain NettyDataBuffer#getNativeBuffer() native buffer} if {@code buffer} is
+ * a {@link NettyDataBuffer}; returns {@link Unpooled#wrappedBuffer(ByteBuffer)}
+ * otherwise.
+ * @param buffer the {@code DataBuffer} to return a {@code ByteBuf} for.
+ * @return the netty {@code ByteBuf}
+ */
+ public static ByteBuf toByteBuf(DataBuffer buffer) {
+ if (buffer instanceof NettyDataBuffer) {
+ return ((NettyDataBuffer) buffer).getNativeBuffer();
+ }
+ else {
+ return Unpooled.wrappedBuffer(buffer.asByteBuffer());
+ }
+ }
+
+
@Override
public String toString() {
return "NettyDataBufferFactory (" + this.byteBufAllocator + ")";
diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/WebReactiveConfiguration.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/WebReactiveConfiguration.java
index b06b6637df..6244967dc4 100644
--- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/WebReactiveConfiguration.java
+++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/WebReactiveConfiguration.java
@@ -41,7 +41,6 @@ import org.springframework.format.FormatterRegistry;
import org.springframework.format.support.DefaultFormattingConversionService;
import org.springframework.format.support.FormattingConversionService;
import org.springframework.http.MediaType;
-import org.springframework.http.codec.SseEventEncoder;
import org.springframework.http.codec.json.JacksonJsonDecoder;
import org.springframework.http.codec.json.JacksonJsonEncoder;
import org.springframework.http.codec.xml.Jaxb2Decoder;
@@ -51,6 +50,7 @@ import org.springframework.http.converter.reactive.EncoderHttpMessageWriter;
import org.springframework.http.converter.reactive.HttpMessageReader;
import org.springframework.http.converter.reactive.HttpMessageWriter;
import org.springframework.http.converter.reactive.ResourceHttpMessageWriter;
+import org.springframework.http.converter.reactive.SseEventHttpMessageWriter;
import org.springframework.util.ClassUtils;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
@@ -391,7 +391,7 @@ public class WebReactiveConfiguration implements ApplicationContextAware {
writers.add(new EncoderHttpMessageWriter<>(jacksonEncoder));
sseDataEncoders.add(jacksonEncoder);
}
- writers.add(new EncoderHttpMessageWriter<>(new SseEventEncoder(sseDataEncoders)));
+ writers.add(new SseEventHttpMessageWriter(sseDataEncoders));
}
/**
* Override this to modify the list of message writers after it has been
diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/SseIntegrationTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/SseIntegrationTests.java
index dc0fd016c0..cc1bf2b27a 100644
--- a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/SseIntegrationTests.java
+++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/SseIntegrationTests.java
@@ -17,8 +17,6 @@
package org.springframework.web.reactive.result.method.annotation;
import java.time.Duration;
-import java.util.ArrayList;
-import java.util.List;
import org.junit.Before;
import org.junit.Test;
@@ -29,13 +27,9 @@ import reactor.test.TestSubscriber;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
-import org.springframework.core.codec.StringDecoder;
import org.springframework.http.MediaType;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.http.codec.SseEvent;
-import org.springframework.http.codec.json.JacksonJsonDecoder;
-import org.springframework.http.converter.reactive.DecoderHttpMessageReader;
-import org.springframework.http.converter.reactive.HttpMessageReader;
import org.springframework.http.server.reactive.AbstractHttpHandlerIntegrationTests;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.http.server.reactive.bootstrap.JettyHttpServer;
diff --git a/spring-web/src/main/java/org/springframework/http/ReactiveHttpOutputMessage.java b/spring-web/src/main/java/org/springframework/http/ReactiveHttpOutputMessage.java
index 41013728f3..8d12cb8297 100644
--- a/spring-web/src/main/java/org/springframework/http/ReactiveHttpOutputMessage.java
+++ b/spring-web/src/main/java/org/springframework/http/ReactiveHttpOutputMessage.java
@@ -23,7 +23,6 @@ import reactor.core.publisher.Mono;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
-import org.springframework.core.io.buffer.FlushingDataBuffer;
/**
* A "reactive" HTTP output message that accepts output as a {@link Publisher}.
@@ -45,17 +44,23 @@ public interface ReactiveHttpOutputMessage extends HttpMessage {
/**
* Use the given {@link Publisher} to write the body of the message to the underlying
- * HTTP layer, and flush the data when the complete signal is received (data could be
- * flushed before depending on the configuration, the HTTP engine and the amount of
- * data sent).
- *
- *
Each {@link FlushingDataBuffer} element will trigger a flush.
+ * HTTP layer.
*
* @param body the body content publisher
* @return a publisher that indicates completion or error.
*/
Mono writeWith(Publisher body);
+ /**
+ * Use the given {@link Publisher} of {@code Publishers} to write the body of the
+ * message to the underlying HTTP layer, flushing after each
+ * {@code Publisher}.
+ *
+ * @param body the body content publisher
+ * @return a publisher that indicates completion or error.
+ */
+ Mono writeAndFlushWith(Publisher> body);
+
/**
* Returns a {@link DataBufferFactory} that can be used for creating the body.
* @return a buffer factory
diff --git a/spring-web/src/main/java/org/springframework/http/client/reactive/ReactorClientHttpRequest.java b/spring-web/src/main/java/org/springframework/http/client/reactive/ReactorClientHttpRequest.java
index 039411970d..a1e8e38720 100644
--- a/spring-web/src/main/java/org/springframework/http/client/reactive/ReactorClientHttpRequest.java
+++ b/spring-web/src/main/java/org/springframework/http/client/reactive/ReactorClientHttpRequest.java
@@ -17,9 +17,9 @@
package org.springframework.http.client.reactive;
import java.net.URI;
+import java.util.Collection;
import io.netty.buffer.ByteBuf;
-import io.netty.buffer.Unpooled;
import io.netty.handler.codec.http.cookie.DefaultCookie;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
@@ -28,7 +28,6 @@ import reactor.io.netty.http.HttpClientRequest;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
-import org.springframework.core.io.buffer.NettyDataBuffer;
import org.springframework.core.io.buffer.NettyDataBufferFactory;
import org.springframework.http.HttpMethod;
@@ -50,7 +49,8 @@ public class ReactorClientHttpRequest extends AbstractClientHttpRequest {
private final NettyDataBufferFactory bufferFactory;
- public ReactorClientHttpRequest(HttpMethod httpMethod, URI uri, HttpClientRequest httpRequest) {
+ public ReactorClientHttpRequest(HttpMethod httpMethod, URI uri,
+ HttpClientRequest httpRequest) {
this.httpMethod = httpMethod;
this.uri = uri;
this.httpRequest = httpRequest;
@@ -75,8 +75,21 @@ public class ReactorClientHttpRequest extends AbstractClientHttpRequest {
@Override
public Mono writeWith(Publisher body) {
- return applyBeforeCommit()
- .then(httpRequest.send(Flux.from(body).map(this::toByteBuf)));
+ return applyBeforeCommit().then(this.httpRequest
+ .send(Flux.from(body).map(NettyDataBufferFactory::toByteBuf)));
+ }
+
+ @Override
+ public Mono writeAndFlushWith(Publisher> body) {
+ Publisher> byteBufs = Flux.from(body).
+ map(ReactorClientHttpRequest::toByteBufs);
+ return applyBeforeCommit().then(this.httpRequest
+ .sendAndFlush(byteBufs));
+ }
+
+ private static Publisher toByteBufs(Publisher dataBuffers) {
+ return Flux.from(dataBuffers).
+ map(NettyDataBufferFactory::toByteBuf);
}
@Override
@@ -84,27 +97,17 @@ public class ReactorClientHttpRequest extends AbstractClientHttpRequest {
return applyBeforeCommit().then(httpRequest.sendHeaders());
}
- private ByteBuf toByteBuf(DataBuffer buffer) {
- if (buffer instanceof NettyDataBuffer) {
- return ((NettyDataBuffer) buffer).getNativeBuffer();
- }
- else {
- return Unpooled.wrappedBuffer(buffer.asByteBuffer());
- }
- }
-
@Override
protected void writeHeaders() {
- getHeaders().entrySet().stream()
+ getHeaders().entrySet()
.forEach(e -> this.httpRequest.headers().set(e.getKey(), e.getValue()));
}
@Override
protected void writeCookies() {
- getCookies().values()
- .stream().flatMap(cookies -> cookies.stream())
+ getCookies().values().stream().flatMap(Collection::stream)
.map(cookie -> new DefaultCookie(cookie.getName(), cookie.getValue()))
- .forEach(cookie -> this.httpRequest.addCookie(cookie));
+ .forEach(this.httpRequest::addCookie);
}
}
\ No newline at end of file
diff --git a/spring-web/src/main/java/org/springframework/http/codec/SseEvent.java b/spring-web/src/main/java/org/springframework/http/codec/SseEvent.java
index 610d639b53..90dc5a23f6 100644
--- a/spring-web/src/main/java/org/springframework/http/codec/SseEvent.java
+++ b/spring-web/src/main/java/org/springframework/http/codec/SseEvent.java
@@ -17,7 +17,7 @@
package org.springframework.http.codec;
import org.springframework.http.MediaType;
-import org.springframework.http.codec.SseEventEncoder;
+import org.springframework.http.converter.reactive.SseEventHttpMessageWriter;
/**
* Representation for a Server-Sent Event for use with Spring's reactive Web
@@ -26,7 +26,7 @@ import org.springframework.http.codec.SseEventEncoder;
*
* @author Sebastien Deleuze
* @since 5.0
- * @see SseEventEncoder
+ * @see SseEventHttpMessageWriter
* @see Server-Sent Events W3C recommendation
*/
public class SseEvent {
@@ -96,10 +96,10 @@ public class SseEvent {
/**
* Set {@code data} SSE field. If a multiline {@code String} is provided, it will be
- * turned into multiple {@code data} field lines by as
- * defined in Server-Sent Events W3C recommendation.
+ * turned into multiple {@code data} field lines as defined in Server-Sent Events
+ * W3C recommendation.
*
- * If no {@code mediaType} is defined, default {@link SseEventEncoder} will:
+ * If no {@code mediaType} is defined, default {@link SseEventHttpMessageWriter} will:
* - Turn single line {@code String} to a single {@code data} field
* - Turn multiline line {@code String} to multiple {@code data} fields
* - Serialize other {@code Object} as JSON
@@ -119,7 +119,7 @@ public class SseEvent {
/**
* Set the {@link MediaType} used to serialize the {@code data}.
- * {@link SseEventEncoder} should be configured with the relevant encoder to be
+ * {@link SseEventHttpMessageWriter} should be configured with the relevant encoder to be
* able to serialize it.
*/
public void setMediaType(MediaType mediaType) {
@@ -149,7 +149,7 @@ public class SseEvent {
/**
* Set SSE comment. If a multiline comment is provided, it will be turned into multiple
- * SSE comment lines by {@link SseEventEncoder} as defined in Server-Sent Events W3C
+ * SSE comment lines by {@link SseEventHttpMessageWriter} as defined in Server-Sent Events W3C
* recommendation.
*/
public void setComment(String comment) {
diff --git a/spring-web/src/main/java/org/springframework/http/codec/SseEventEncoder.java b/spring-web/src/main/java/org/springframework/http/converter/reactive/SseEventHttpMessageWriter.java
similarity index 61%
rename from spring-web/src/main/java/org/springframework/http/codec/SseEventEncoder.java
rename to spring-web/src/main/java/org/springframework/http/converter/reactive/SseEventHttpMessageWriter.java
index 923a3d271d..b1458cc5a3 100644
--- a/spring-web/src/main/java/org/springframework/http/codec/SseEventEncoder.java
+++ b/spring-web/src/main/java/org/springframework/http/converter/reactive/SseEventHttpMessageWriter.java
@@ -14,9 +14,10 @@
* limitations under the License.
*/
-package org.springframework.http.codec;
+package org.springframework.http.converter.reactive;
import java.nio.charset.StandardCharsets;
+import java.util.Collections;
import java.util.List;
import java.util.Optional;
@@ -25,15 +26,14 @@ import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.core.ResolvableType;
-import org.springframework.core.codec.AbstractEncoder;
import org.springframework.core.codec.CodecException;
import org.springframework.core.codec.Encoder;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
-import org.springframework.core.io.buffer.FlushingDataBuffer;
import org.springframework.http.MediaType;
+import org.springframework.http.ReactiveHttpOutputMessage;
+import org.springframework.http.codec.SseEvent;
import org.springframework.util.Assert;
-import org.springframework.util.MimeType;
/**
* Encoder that supports a stream of {@link SseEvent}s and also plain
@@ -42,25 +42,53 @@ import org.springframework.util.MimeType;
*
* @author Sebastien Deleuze
* @since 5.0
+ * @author Arjen Poutsma
*/
-public class SseEventEncoder extends AbstractEncoder