diff --git a/client/src/main/java/org/asynchttpclient/Response.java b/client/src/main/java/org/asynchttpclient/Response.java index 77512094d..9835d2cf4 100644 --- a/client/src/main/java/org/asynchttpclient/Response.java +++ b/client/src/main/java/org/asynchttpclient/Response.java @@ -55,6 +55,23 @@ public interface Response { */ byte[] getResponseBodyAsBytes(); + /** + * Returns the entire response body as a byte array that may share its storage with this response. + * + *
The returned array must be treated as read-only. Modifying it may change the content subsequently returned + * by this response's other body accessors. + * + *
The implementation is not required to return shared storage. Depending on the response representation, this + * method may still return a copy. No array identity is guaranteed between calls. + * + *
Use {@link #getResponseBodyAsBytes()} when an independently owned, mutable array is required.
+ *
+ * @return the entire response body as a possibly shared byte array
+ */
+ default byte[] getResponseBodyAsBytesView() {
+ return getResponseBodyAsBytes();
+ }
+
/**
* Return the entire response body as a ByteBuffer.
*
diff --git a/client/src/main/java/org/asynchttpclient/netty/NettyResponse.java b/client/src/main/java/org/asynchttpclient/netty/NettyResponse.java
index 8d80bcbb1..edf97c8e2 100755
--- a/client/src/main/java/org/asynchttpclient/netty/NettyResponse.java
+++ b/client/src/main/java/org/asynchttpclient/netty/NettyResponse.java
@@ -193,6 +193,11 @@ public byte[] getResponseBodyAsBytes() {
return getResponseBodyAsByteBuffer().array();
}
+ @Override
+ public byte[] getResponseBodyAsBytesView() {
+ return bodyParts.size() == 1 ? bodyParts.get(0).getBodyPartBytes() : getResponseBodyAsBytes();
+ }
+
@Override
public ByteBuffer getResponseBodyAsByteBuffer() {
@@ -224,19 +229,9 @@ public String getResponseBody() {
return getResponseBody(withDefault(extractContentTypeCharsetAttribute(getContentType()), UTF_8));
}
- /**
- * The body as bytes, for callers that keep the array to themselves. A lone part's own array is returned
- * rather than a copy of it, so a caller that let it out would let the part's buffer be mutated through it;
- * {@link #getResponseBodyAsBytes()} is the copying variant for those. Several parts are concatenated
- * because a multi-byte character can straddle a part boundary.
- */
- private byte[] sharedBodyBytes() {
- return bodyParts.size() == 1 ? bodyParts.get(0).getBodyPartBytes() : getResponseBodyAsBytes();
- }
-
@Override
public String getResponseBody(Charset charset) {
- return new String(sharedBodyBytes(), charset);
+ return new String(getResponseBodyAsBytesView(), charset);
}
@Override
diff --git a/client/src/test/java/org/asynchttpclient/netty/NettyAsyncResponseTest.java b/client/src/test/java/org/asynchttpclient/netty/NettyAsyncResponseTest.java
index 5ce4982d5..70edcfaa0 100644
--- a/client/src/test/java/org/asynchttpclient/netty/NettyAsyncResponseTest.java
+++ b/client/src/test/java/org/asynchttpclient/netty/NettyAsyncResponseTest.java
@@ -19,10 +19,13 @@
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.cookie.Cookie;
import org.asynchttpclient.HttpResponseBodyPart;
+import org.asynchttpclient.Response;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.io.OutputStream;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.Date;
@@ -32,9 +35,13 @@
import java.util.TimeZone;
import static io.netty.handler.codec.http.HttpHeaderNames.SET_COOKIE;
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotSame;
+import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
public class NettyAsyncResponseTest {
@@ -113,6 +120,8 @@ public void testGetResponseBodyDecodesOnePartAndSplitPartsIdentically() {
assertEquals(expected, single.getResponseBody(StandardCharsets.UTF_8));
assertEquals(expected, multiple.getResponseBody(StandardCharsets.UTF_8));
+ assertArrayEquals(utf8, single.getResponseBodyAsBytesView());
+ assertArrayEquals(utf8, multiple.getResponseBodyAsBytesView());
}
@Test
@@ -120,25 +129,99 @@ public void testGetResponseBodyReadsOnlyALazyPartsReadableRegion() throws IOExce
// A Lazy part's getBodyPartBytes returns just the readable region, not the whole backing array, so a
// single-part shortcut must go through it rather than reach for getBodyByteBuf().array().
byte[] backing = "XXXHello WorldYYY".getBytes(StandardCharsets.UTF_8);
+ ByteBuf slice = Unpooled.wrappedBuffer(backing).slice(3, 11);
+ int readerIndex = slice.readerIndex();
+ int writerIndex = slice.writerIndex();
+ int refCnt = slice.refCnt();
+ try {
+ List