Offer restricted access to DataBuffer's ByteBuffer

This commit introduces DataBuffer::readableByteBuffers and
DataBuffer::writableByteBuffers, allowing restricted access to the
ByteBuffer used internally by DataBuffer implementations.

Closes gh-29943
This commit is contained in:
Arjen Poutsma
2022-11-30 12:30:10 +01:00
parent 72926c29f9
commit 3e2f58cdd2
28 changed files with 773 additions and 275 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 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.
@@ -102,7 +102,11 @@ class HttpComponentsClientHttpRequest extends AbstractClientHttpRequest {
@Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
return doCommit(() -> {
this.byteBufferFlux = Flux.from(body).map(DataBuffer::toByteBuffer);
this.byteBufferFlux = Flux.from(body).map(dataBuffer -> {
ByteBuffer byteBuffer = ByteBuffer.allocate(dataBuffer.readableByteCount());
dataBuffer.toByteBuffer(byteBuffer);
return byteBuffer;
});
return Mono.empty();
});
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 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.
@@ -122,8 +122,8 @@ class JdkClientHttpRequest extends AbstractClientHttpRequest {
private HttpRequest.BodyPublisher toBodyPublisher(Publisher<? extends DataBuffer> body) {
Publisher<ByteBuffer> byteBufferBody = (body instanceof Mono ?
Mono.from(body).map(DataBuffer::toByteBuffer) :
Flux.from(body).map(DataBuffer::toByteBuffer));
Mono.from(body).map(this::toByteBuffer) :
Flux.from(body).map(this::toByteBuffer));
Flow.Publisher<ByteBuffer> bodyFlow = JdkFlowAdapter.publisherToFlowPublisher(byteBufferBody);
@@ -132,6 +132,12 @@ class JdkClientHttpRequest extends AbstractClientHttpRequest {
HttpRequest.BodyPublishers.fromPublisher(bodyFlow));
}
private ByteBuffer toByteBuffer(DataBuffer dataBuffer) {
ByteBuffer byteBuffer = ByteBuffer.allocate(dataBuffer.readableByteCount());
dataBuffer.toByteBuffer(byteBuffer);
return byteBuffer;
}
@Override
public Mono<Void> writeAndFlushWith(final Publisher<? extends Publisher<? extends DataBuffer>> body) {
return writeWith(Flux.from(body).flatMap(Function.identity()));

View File

@@ -18,6 +18,7 @@ package org.springframework.http.client.reactive;
import java.net.HttpCookie;
import java.net.URI;
import java.nio.ByteBuffer;
import java.util.Collection;
import java.util.function.Function;
@@ -109,15 +110,17 @@ class JettyClientHttpRequest extends AbstractClientHttpRequest {
return contentType != null ? contentType.toString() : MediaType.APPLICATION_OCTET_STREAM_VALUE;
}
private ContentChunk toContentChunk(DataBuffer buffer, MonoSink<Void> sink) {
return new ContentChunk(buffer.toByteBuffer(), new Callback() {
private ContentChunk toContentChunk(DataBuffer dataBuffer, MonoSink<Void> sink) {
ByteBuffer byteBuffer = ByteBuffer.allocate(dataBuffer.readableByteCount());
dataBuffer.toByteBuffer(byteBuffer);
return new ContentChunk(byteBuffer, new Callback() {
@Override
public void succeeded() {
DataBufferUtils.release(buffer);
DataBufferUtils.release(dataBuffer);
}
@Override
public void failed(Throwable t) {
DataBufferUtils.release(buffer);
DataBufferUtils.release(dataBuffer);
sink.error(t);
}
});

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 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.
@@ -17,7 +17,6 @@
package org.springframework.http.codec.json;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
@@ -93,8 +92,11 @@ final class Jackson2Tokenizer {
try {
int bufferSize = dataBuffer.readableByteCount();
if (this.inputFeeder instanceof ByteBufferFeeder byteBufferFeeder) {
ByteBuffer byteBuffer = dataBuffer.toByteBuffer();
byteBufferFeeder.feedInput(byteBuffer);
try (DataBuffer.ByteBufferIterator iterator = dataBuffer.readableByteBuffers()) {
while (iterator.hasNext()) {
byteBufferFeeder.feedInput(iterator.next());
}
}
}
else if (this.inputFeeder instanceof ByteArrayFeeder byteArrayFeeder) {
byte[] bytes = new byte[bufferSize];

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 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.
@@ -747,9 +747,13 @@ final class PartGenerator extends BaseSubscriber<MultipartParser.Token> {
@SuppressWarnings("BlockingMethodInNonBlockingContext")
private Mono<Void> writeInternal(DataBuffer dataBuffer) {
try {
ByteBuffer byteBuffer = dataBuffer.toByteBuffer();
while (byteBuffer.hasRemaining()) {
this.channel.write(byteBuffer);
try (DataBuffer.ByteBufferIterator iterator = dataBuffer.readableByteBuffers()) {
while (iterator.hasNext()) {
ByteBuffer byteBuffer = iterator.next();
while (byteBuffer.hasRemaining()) {
this.channel.write(byteBuffer);
}
}
}
return Mono.empty();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 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.
@@ -151,8 +151,9 @@ public class ProtobufDecoder extends ProtobufCodecSupport implements Decoder<Mes
try {
Message.Builder builder = getMessageBuilder(targetType.toClass());
ByteBuffer buffer = dataBuffer.toByteBuffer();
builder.mergeFrom(CodedInputStream.newInstance(buffer), this.extensionRegistry);
ByteBuffer byteBuffer = ByteBuffer.allocate(dataBuffer.readableByteCount());
dataBuffer.toByteBuffer(byteBuffer);
builder.mergeFrom(CodedInputStream.newInstance(byteBuffer), this.extensionRegistry);
return builder.build();
}
catch (IOException ex) {
@@ -236,7 +237,9 @@ public class ProtobufDecoder extends ProtobufCodecSupport implements Decoder<Mes
this.messageBytesToRead -= chunkBytesToRead;
if (this.messageBytesToRead == 0) {
CodedInputStream stream = CodedInputStream.newInstance(this.output.toByteBuffer());
ByteBuffer byteBuffer = ByteBuffer.allocate(this.output.readableByteCount());
this.output.toByteBuffer(byteBuffer);
CodedInputStream stream = CodedInputStream.newInstance(byteBuffer);
DataBufferUtils.release(this.output);
this.output = null;
Message message = getMessageBuilder(this.elementType.toClass())

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 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.
@@ -181,7 +181,12 @@ public class XmlEventDecoder extends AbstractDecoder<XMLEvent> {
public List<? extends XMLEvent> apply(DataBuffer dataBuffer) {
try {
increaseByteCount(dataBuffer);
this.streamReader.getInputFeeder().feedInput(dataBuffer.toByteBuffer());
AsyncByteBufferFeeder inputFeeder = this.streamReader.getInputFeeder();
try (DataBuffer.ByteBufferIterator iterator = dataBuffer.readableByteBuffers()) {
while (iterator.hasNext()) {
inputFeeder.feedInput(iterator.next());
}
}
List<XMLEvent> events = new ArrayList<>();
while (true) {
if (this.streamReader.next() == AsyncXMLStreamReader.EVENT_INCOMPLETE) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 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.
@@ -17,7 +17,6 @@
package org.springframework.http.server.reactive;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URISyntaxException;
import java.nio.ByteBuffer;
@@ -158,11 +157,15 @@ public class JettyHttpHandlerAdapter extends ServletHttpHandlerAdapter {
@Override
protected int writeToOutputStream(DataBuffer dataBuffer) throws IOException {
OutputStream output = getOutputStream();
if (output instanceof HttpOutput httpOutput) {
ByteBuffer input = dataBuffer.toByteBuffer();
int len = input.remaining();
httpOutput.write(input);
if (getOutputStream() instanceof HttpOutput httpOutput) {
int len = 0;
try (DataBuffer.ByteBufferIterator iterator = dataBuffer.readableByteBuffers()) {
while (iterator.hasNext() && httpOutput.isReady()) {
ByteBuffer byteBuffer = iterator.next();
len += byteBuffer.remaining();
httpOutput.write(byteBuffer);
}
}
return len;
}
return super.writeToOutputStream(dataBuffer);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 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.
@@ -35,6 +35,7 @@ import org.apache.coyote.Response;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.util.Assert;
import org.springframework.util.MultiValueMap;
@@ -125,26 +126,38 @@ public class TomcatHttpHandlerAdapter extends ServletHttpHandlerAdapter {
@Override
protected DataBuffer readFromInputStream() throws IOException {
if (!(getInputStream() instanceof CoyoteInputStream coyoteInputStream)) {
if (getInputStream() instanceof CoyoteInputStream coyoteInputStream) {
DataBuffer dataBuffer = this.factory.allocateBuffer(this.bufferSize);
int read = -1;
try {
try (DataBuffer.ByteBufferIterator iterator = dataBuffer.writableByteBuffers()) {
Assert.state(iterator.hasNext(), "No ByteBuffer available");
ByteBuffer byteBuffer = iterator.next();
read = coyoteInputStream.read(byteBuffer);
}
logBytesRead(read);
if (read > 0) {
dataBuffer.writePosition(read);
return dataBuffer;
}
else if (read == -1) {
return EOF_BUFFER;
}
else {
return AbstractListenerReadPublisher.EMPTY_BUFFER;
}
}
finally {
if (read <= 0) {
DataBufferUtils.release(dataBuffer);
}
}
}
else {
// It's possible InputStream can be wrapped, preventing use of CoyoteInputStream
return super.readFromInputStream();
}
ByteBuffer byteBuffer = this.factory.isDirect() ?
ByteBuffer.allocateDirect(this.bufferSize) :
ByteBuffer.allocate(this.bufferSize);
int read = coyoteInputStream.read(byteBuffer);
logBytesRead(read);
if (read > 0) {
return this.factory.wrap(byteBuffer);
}
else if (read == -1) {
return EOF_BUFFER;
}
else {
return AbstractListenerReadPublisher.EMPTY_BUFFER;
}
}
}
@@ -197,14 +210,20 @@ public class TomcatHttpHandlerAdapter extends ServletHttpHandlerAdapter {
@Override
protected int writeToOutputStream(DataBuffer dataBuffer) throws IOException {
if (!(getOutputStream() instanceof CoyoteOutputStream coyoteOutputStream)) {
if (getOutputStream() instanceof CoyoteOutputStream coyoteOutputStream) {
int len = 0;
try (DataBuffer.ByteBufferIterator iterator = dataBuffer.readableByteBuffers()) {
while (iterator.hasNext() && coyoteOutputStream.isReady()) {
ByteBuffer byteBuffer = iterator.next();
len += byteBuffer.remaining();
coyoteOutputStream.write(byteBuffer);
}
}
return len;
}
else {
return super.writeToOutputStream(dataBuffer);
}
ByteBuffer input = dataBuffer.toByteBuffer();
int len = input.remaining();
coyoteOutputStream.write(input);
return len;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 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.
@@ -230,7 +230,9 @@ class UndertowServerHttpResponse extends AbstractListenerServerHttpResponse impl
@Override
protected void dataReceived(DataBuffer dataBuffer) {
super.dataReceived(dataBuffer);
this.byteBuffer = dataBuffer.toByteBuffer();
ByteBuffer byteBuffer = ByteBuffer.allocate(dataBuffer.readableByteCount());
dataBuffer.toByteBuffer(byteBuffer);
this.byteBuffer = byteBuffer;
}
@Override