Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -224,9 +224,19 @@ 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(getResponseBodyAsBytes(), charset);
return new String(sharedBodyBytes(), charset);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.cookie.Cookie;
import org.asynchttpclient.HttpResponseBodyPart;
import org.junit.jupiter.api.Test;

import java.io.IOException;
Comment thread
hyperxpro marked this conversation as resolved.
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.Date;
Expand All @@ -30,6 +33,7 @@

import static io.netty.handler.codec.http.HttpHeaderNames.SET_COOKIE;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class NettyAsyncResponseTest {
Expand Down Expand Up @@ -90,4 +94,73 @@ public void testGetResponseBodyAsByteBuffer() {
assertEquals("Hello World", body.toString(StandardCharsets.UTF_8));
body.release();
}

@Test
public void testGetResponseBodyDecodesOnePartAndSplitPartsIdentically() {
byte[] utf8 = {'c', 'a', 'f', (byte) 0xC3, (byte) 0xA9, ' ', (byte) 0xC3, (byte) 0xBC, 'b', 'e', 'r'};
String expected = new String(utf8, StandardCharsets.UTF_8);
// 0xC3 0xA9 encodes U+00E9; split between its two bytes so neither half decodes on its own
int split = 4;

List<HttpResponseBodyPart> onePart = new LinkedList<>();
onePart.add(new EagerResponseBodyPart(Unpooled.wrappedBuffer(utf8), true));
Comment thread
hyperxpro marked this conversation as resolved.
NettyResponse single = new NettyResponse(new NettyResponseStatus(null, null, null), null, onePart);

List<HttpResponseBodyPart> splitParts = new LinkedList<>();
splitParts.add(new EagerResponseBodyPart(Unpooled.wrappedBuffer(utf8, 0, split), false));
splitParts.add(new EagerResponseBodyPart(Unpooled.wrappedBuffer(utf8, split, utf8.length - split), true));
NettyResponse multiple = new NettyResponse(new NettyResponseStatus(null, null, null), null, splitParts);

assertEquals(expected, single.getResponseBody(StandardCharsets.UTF_8));
assertEquals(expected, multiple.getResponseBody(StandardCharsets.UTF_8));
}

@Test
public void testGetResponseBodyReadsOnlyALazyPartsReadableRegion() throws IOException {
// 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);
List<HttpResponseBodyPart> bodyParts = new LinkedList<>();
bodyParts.add(new LazyResponseBodyPart(Unpooled.wrappedBuffer(backing, 3, 11), true));
NettyResponse response = new NettyResponse(new NettyResponseStatus(null, null, null), null, bodyParts);

assertEquals("Hello World", response.getResponseBody(StandardCharsets.UTF_8));
assertEquals("Hello World",
new String(response.getResponseBodyAsStream().readAllBytes(), StandardCharsets.UTF_8));
}

@Test
public void testGetResponseBodyAsBytesDoesNotShareTheBodyPartArray() {
List<HttpResponseBodyPart> bodyParts = new LinkedList<>();
bodyParts.add(new EagerResponseBodyPart(Unpooled.wrappedBuffer("Hello World".getBytes(StandardCharsets.UTF_8)), true));
NettyResponse response = new NettyResponse(new NettyResponseStatus(null, null, null), null, bodyParts);

// getResponseBody may decode a lone part in place, but getResponseBodyAsBytes hands the array to the
// caller, so it must keep copying rather than expose the part's own array.
assertNotSame(response.getResponseBodyAsBytes(), response.getResponseBodyAsBytes());
Comment thread
hyperxpro marked this conversation as resolved.
assertNotSame(bodyParts.get(0).getBodyPartBytes(), response.getResponseBodyAsBytes());
}

@Test
public void testGetResponseBodyAsStreamDoesNotShareTheBodyPartArray() throws IOException {
List<HttpResponseBodyPart> bodyParts = new LinkedList<>();
bodyParts.add(new EagerResponseBodyPart(Unpooled.wrappedBuffer("Hello World".getBytes(StandardCharsets.UTF_8)), true));
NettyResponse response = new NettyResponse(new NettyResponseStatus(null, null, null), null, bodyParts);

// On JDK 11 ByteArrayInputStream.transferTo passes its own array to the OutputStream, so a stream over
// a part's array would put that array in the caller's hands.
byte[][] handedOut = new byte[1][];
response.getResponseBodyAsStream().transferTo(new OutputStream() {
@Override
public void write(int b) {
}

@Override
public void write(byte[] b, int off, int len) {
handedOut[0] = b;
}
});

assertNotSame(bodyParts.get(0).getBodyPartBytes(), handedOut[0]);
}
}
Comment thread
hyperxpro marked this conversation as resolved.
Loading