Skip to content

Commit 59d123a

Browse files
committed
Introduce OutputStream BodyInserter
This commit introduces a BodyInserter that inssert any bytes written to an output stream to the body of an output message. Closes gh-31184
1 parent 913dc86 commit 59d123a

File tree

5 files changed

+634
-1
lines changed

5 files changed

+634
-1
lines changed

spring-core/src/main/java/org/springframework/core/io/buffer/DataBufferUtils.java

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import java.util.HashSet;
3434
import java.util.Set;
3535
import java.util.concurrent.Callable;
36+
import java.util.concurrent.Executor;
3637
import java.util.concurrent.atomic.AtomicBoolean;
3738
import java.util.concurrent.atomic.AtomicLong;
3839
import java.util.concurrent.atomic.AtomicReference;
@@ -41,6 +42,7 @@
4142
import org.apache.commons.logging.Log;
4243
import org.apache.commons.logging.LogFactory;
4344
import org.reactivestreams.Publisher;
45+
import org.reactivestreams.Subscriber;
4446
import org.reactivestreams.Subscription;
4547
import reactor.core.publisher.BaseSubscriber;
4648
import reactor.core.publisher.Flux;
@@ -66,6 +68,9 @@ public abstract class DataBufferUtils {
6668

6769
private static final Consumer<DataBuffer> RELEASE_CONSUMER = DataBufferUtils::release;
6870

71+
private static final int DEFAULT_CHUNK_SIZE = 1024;
72+
73+
6974

7075
//---------------------------------------------------------------------
7176
// Reading
@@ -405,6 +410,83 @@ static void closeChannel(@Nullable Channel channel) {
405410
}
406411

407412

413+
/**
414+
* Create a new {@code Publisher<DataBuffer>} based on bytes written to a
415+
* {@code OutputStream}.
416+
* <ul>
417+
* <li>The parameter {@code outputStreamConsumer} is invoked once per
418+
* subscription of the returned {@code Publisher}, when the first
419+
* item is
420+
* {@linkplain Subscription#request(long) requested}.</li>
421+
* <li>{@link OutputStream#write(byte[], int, int) OutputStream.write()}
422+
* invocations made by {@code outputStreamConsumer} are buffered until they
423+
* exceed the default chunk size of 1024, or when the stream is
424+
* {@linkplain OutputStream#flush() flushed} and then result in a
425+
* {@linkplain Subscriber#onNext(Object) published} item
426+
* if there is {@linkplain Subscription#request(long) demand}.</li>
427+
* <li>If there is <em>no demand</em>, {@code OutputStream.write()} will block
428+
* until there is.</li>
429+
* <li>If the subscription is {@linkplain Subscription#cancel() cancelled},
430+
* {@code OutputStream.write()} will throw a {@code IOException}.</li>
431+
* <li>The subscription is
432+
* {@linkplain Subscriber#onComplete() completed} when
433+
* {@code outputStreamHandler} completes.</li>
434+
* <li>Any exceptions thrown from {@code outputStreamHandler} will
435+
* be dispatched to the {@linkplain Subscriber#onError(Throwable) Subscriber}.
436+
* </ul>
437+
* @param outputStreamConsumer invoked when the first buffer is requested
438+
* @param executor used to invoke the {@code outputStreamHandler}
439+
* @return a {@code Publisher<DataBuffer>} based on bytes written by
440+
* {@code outputStreamHandler}
441+
*/
442+
public static Publisher<DataBuffer> outputStreamPublisher(Consumer<OutputStream> outputStreamConsumer,
443+
DataBufferFactory bufferFactory, Executor executor) {
444+
445+
return outputStreamPublisher(outputStreamConsumer, bufferFactory, executor, DEFAULT_CHUNK_SIZE);
446+
}
447+
448+
/**
449+
* Creates a new {@code Publisher<DataBuffer>} based on bytes written to a
450+
* {@code OutputStream}.
451+
* <ul>
452+
* <li>The parameter {@code outputStreamConsumer} is invoked once per
453+
* subscription of the returned {@code Publisher}, when the first
454+
* item is
455+
* {@linkplain Subscription#request(long) requested}.</li>
456+
* <li>{@link OutputStream#write(byte[], int, int) OutputStream.write()}
457+
* invocations made by {@code outputStreamHandler} are buffered until they
458+
* reach or exceed {@code chunkSize}, or when the stream is
459+
* {@linkplain OutputStream#flush() flushed} and then result in a
460+
* {@linkplain Subscriber#onNext(Object) published} item
461+
* if there is {@linkplain Subscription#request(long) demand}.</li>
462+
* <li>If there is <em>no demand</em>, {@code OutputStream.write()} will block
463+
* until there is.</li>
464+
* <li>If the subscription is {@linkplain Subscription#cancel() cancelled},
465+
* {@code OutputStream.write()} will throw a {@code IOException}.</li>
466+
* <li>The subscription is
467+
* {@linkplain Subscriber#onComplete() completed} when
468+
* {@code outputStreamHandler} completes.</li>
469+
* <li>Any exceptions thrown from {@code outputStreamHandler} will
470+
* be dispatched to the {@linkplain Subscriber#onError(Throwable) Subscriber}.
471+
* </ul>
472+
* @param outputStreamConsumer invoked when the first buffer is requested
473+
* @param executor used to invoke the {@code outputStreamHandler}
474+
* @param chunkSize minimum size of the buffer produced by the publisher
475+
* @return a {@code Publisher<DataBuffer>} based on bytes written by
476+
* {@code outputStreamHandler}
477+
*/
478+
public static Publisher<DataBuffer> outputStreamPublisher(Consumer<OutputStream> outputStreamConsumer,
479+
DataBufferFactory bufferFactory, Executor executor, int chunkSize) {
480+
481+
Assert.notNull(outputStreamConsumer, "OutputStreamConsumer must not be null");
482+
Assert.notNull(bufferFactory, "BufferFactory must not be null");
483+
Assert.notNull(executor, "Executor must not be null");
484+
Assert.isTrue(chunkSize > 0, "Chunk size must be > 0");
485+
486+
return new OutputStreamPublisher(outputStreamConsumer, bufferFactory, executor, chunkSize);
487+
}
488+
489+
408490
//---------------------------------------------------------------------
409491
// Various
410492
//---------------------------------------------------------------------

0 commit comments

Comments
 (0)