@Override public void onNext(DataChunk data) { if (data == null) { return; } ByteBuffer bb = data.data(); try { synchronized (baos) { byte[] buff = new byte[bb.remaining()]; bb.get(buff); baos.write(buff); } } catch (IOException e) { onError(new IllegalStateException("Cannot write data into the ByteArrayOutputStream!", e)); } }
/** * Creates a reusable data chunk. * * @param flush a signal that chunk should be written and flushed from any cache if possible * @param data a data chunk. Should not be reused until {@code releaseCallback} is used * @return a reusable data chunk with no release callback */ static DataChunk create(boolean flush, ByteBuffer data) { return create(flush, data, Utils.EMPTY_RUNNABLE); }
private static void releaseChunk(DataChunk chunk) { if (chunk != null && !chunk.isReleased()) { LOGGER.finest(() -> "Releasing chunk: " + chunk.id()); chunk.release(); } }
@Override public void onNext(DataChunk data) { if (internallyClosed.get()) { throw new IllegalStateException("Response is already closed!"); } if (data != null) { LOGGER.finest(() -> log("Sending data chunk")); DefaultHttpContent httpContent = new DefaultHttpContent(Unpooled.wrappedBuffer(data.data())); runOnOutboundEventLoopThread(() -> { LOGGER.finest(() -> log("Sending data chunk on event loop thread.")); ChannelFuture channelFuture; if (data.flush()) { channelFuture = ctx.writeAndFlush(httpContent); } else { channelFuture = ctx.write(httpContent); } channelFuture .addListener(future -> { data.release(); LOGGER.finest(() -> log("Data chunk sent with result: " + future.isSuccess())); }) .addListener(completeOnFailureListener("Failure when sending a content!")) .addListener(ChannelFutureListener.CLOSE_ON_FAILURE); }); } }
ByteBuffer currentBuffer = chunk != null && !chunk.isReleased() ? chunk.data() : null; LOGGER.finest(() -> "Reading chunk ID: " + chunk.id());
@Override public void onNext(DataChunk data) { if (internallyClosed.get()) { throw new IllegalStateException("Response is already closed!"); } if (data != null) { LOGGER.finest(() -> log("Sending data chunk")); DefaultHttpContent httpContent = new DefaultHttpContent(Unpooled.wrappedBuffer(data.data())); runOnOutboundEventLoopThread(() -> { LOGGER.finest(() -> log("Sending data chunk on event loop thread.")); ChannelFuture channelFuture; if (data.flush()) { channelFuture = ctx.writeAndFlush(httpContent); } else { channelFuture = ctx.write(httpContent); } channelFuture .addListener(future -> { data.release(); LOGGER.finest(() -> log("Data chunk sent with result: " + future.isSuccess())); }) .addListener(completeOnFailureListener("Failure when sending a content!")) .addListener(ChannelFutureListener.CLOSE_ON_FAILURE); }); } }
ByteBuffer currentBuffer = chunk != null && !chunk.isReleased() ? chunk.data() : null; LOGGER.finest(() -> "Reading chunk ID: " + chunk.id());
/** * Creates a simple {@link ByteBuffer} backed data chunk. The resulting * instance doesn't have any kind of a lifecycle and as such, it doesn't need * to be released. * * @param byteBuffer a byte buffer to create the request chunk from * @return a request chunk */ static DataChunk create(ByteBuffer byteBuffer) { return create(false, byteBuffer); }
private static void releaseChunk(DataChunk chunk) { if (chunk != null && !chunk.isReleased()) { LOGGER.finest(() -> "Releasing chunk: " + chunk.id()); chunk.release(); } }
/** * Gets the content of the underlying {@link ByteBuffer} as an array of bytes. * If the the ByteBuffer was read, the returned array contains only the part of * data that wasn't read yet. On the other hand, calling this method doesn't cause * the underlying {@link ByteBuffer} to be read. * <p> * It is expected the returned byte array holds a reference to data that * will become stale upon calling method {@link #release()}. (For instance, * the memory segment is pooled by the underlying TCP server and is reused * for a subsequent request chunk.) The idea behind this class is to be able to * minimize data copying; ideally, in order to achieve the best performance, * to not copy them at all. However, the implementations may choose otherwise. * <p> * Note that the methods of this instance are expected to be called by a single * thread; if not, external synchronization must be used. * * @return an array of bytes that is guarantied to stay immutable as long as * method {@link #release()} is not called */ default byte[] bytes() { return Utils.toByteArray(data().asReadOnlyBuffer()); }
private DataChunk allocateNewChunk() { return DataChunk.create(false, ByteBuffer.allocate(chunkCapacity)); }
/** * Creates a simple byte array backed data chunk. The resulting * instance doesn't have any kind of a lifecycle and as such, it doesn't need * to be released. * * @param bytes a byte array to create the request chunk from * @return a request chunk */ static DataChunk create(byte[] bytes) { return create(false, ByteBuffer.wrap(bytes)); }
@Override public Flow.Publisher<DataChunk> apply(CharSequence s) { if (s == null || s.length() == 0) { return ReactiveStreamsAdapter.publisherToFlow(Mono.empty()); } DataChunk chunk = DataChunk.create(false, charset.encode(s.toString())); return ReactiveStreamsAdapter.publisherToFlow(Mono.just(chunk)); } }
@Override public Flow.Publisher<DataChunk> apply(byte[] bytes) { if ((bytes == null) || (bytes.length == 0)) { return ReactiveStreamsAdapter.publisherToFlow(Mono.empty()); } byte[] bs; if (copy) { bs = new byte[bytes.length]; System.arraycopy(bytes, 0, bs, 0, bytes.length); } else { bs = bytes; } DataChunk chunk = DataChunk.create(false, ByteBuffer.wrap(bs)); return ReactiveStreamsAdapter.publisherToFlow(Mono.just(chunk)); } }
/** * Creates a publisher of single {@link String string}. * * @param publishedType a type. If contains charset then it is used, otherwise use {@code UTF-8}. If {@code null} then * {@code text/plain} is used as a default. * @param charSequence A sequence to publish. * @return new publisher. */ static MediaPublisher create(MediaType publishedType, CharSequence charSequence) { ByteBuffer data = Optional.ofNullable(publishedType) .flatMap(MediaType::charset) .map(Charset::forName) .orElse(StandardCharsets.UTF_8) .encode(charSequence.toString()); Flow.Publisher<DataChunk> publisher = ReactiveStreamsAdapter.publisherToFlow(Flux.just(DataChunk.create(data))); return new MediaPublisher() { @Override public MediaType mediaType() { return publishedType; } @Override public void subscribe(Flow.Subscriber<? super DataChunk> subscriber) { publisher.subscribe(subscriber); } }; } }
WritableByteChannel ch = Channels.newChannel(stream); ch.write(byteBuffer); return DataChunk.create(doFlush, ByteBuffer.wrap(stream.toByteArray())); } catch (IOException e) { throw new IllegalStateException("this never happens", e);
private DataChunk allocateNewChunk() { return DataChunk.create(false, ByteBuffer.allocate(chunkCapacity)); }
@Override public Flow.Publisher<DataChunk> apply(CharSequence s) { if (s == null || s.length() == 0) { return ReactiveStreamsAdapter.publisherToFlow(Mono.empty()); } DataChunk chunk = DataChunk.create(false, charset.encode(s.toString())); return ReactiveStreamsAdapter.publisherToFlow(Mono.just(chunk)); } }