Skip to content
Open
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 @@ -108,7 +108,12 @@ public BodyState transferTo(ByteBuf target) throws IOException {
}
}

return BodyState.CONTINUE;
// Signal STOP on the same call that finishes the last part (done just became true), so consumers
// don't need an extra empty readChunk/nextChunk cycle to discover the end. Both ByteBuf consumers
// send any bytes written this call before honouring STOP — BodyChunkedInput returns the buffer and
// sets endOfInput; the HTTP/2 pump (BodyChunkSource) returns a readable buffer before checking the
// state — so the terminal chunk is never dropped.
return done ? BodyState.STOP : BodyState.CONTINUE;
}

// RandomAccessBody API, suited for HTTP but not for HTTPS (zero-copy)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,15 @@ private static long transferWithCopy(MultipartBody multipartBody, int bufferSize
long transferred = 0;
final ByteBuf buffer = Unpooled.buffer(bufferSize);
try {
while (multipartBody.transferTo(buffer) != BodyState.STOP) {
// Count bytes written on EVERY call, including the terminal STOP: transferTo now reports STOP on
// the same call that writes the body's last bytes (mirroring the real consumers, which send the
// buffer's contents before honouring STOP).
BodyState state;
do {
state = multipartBody.transferTo(buffer);
transferred += buffer.readableBytes();
buffer.clear();
}
} while (state != BodyState.STOP);
return transferred;
} finally {
buffer.release();
Expand Down Expand Up @@ -144,4 +149,23 @@ public void transferZeroCopy() throws Exception {
}
}
}

@RepeatedIfExceptionsTest(repeats = 5)
public void finishingChunkReportsStopAndCarriesAllBytes() throws Exception {
try (MultipartBody multipartBody = buildMultipart()) {
// A buffer large enough for the whole body: the single transferTo that writes the last bytes must
// report STOP (previously it returned CONTINUE and forced an extra empty readChunk to reach STOP),
// and that STOP call must still carry every byte of the body.
int capacity = (int) MAX_MULTIPART_CONTENT_LENGTH_ESTIMATE + 100;
ByteBuf buffer = Unpooled.buffer(capacity);
try {
BodyState state = multipartBody.transferTo(buffer);
assertEquals(BodyState.STOP, state, "the call that finishes the body must report STOP");
assertEquals(multipartBody.getContentLength(), buffer.readableBytes(),
"the finishing STOP call must still carry all of the body's bytes");
} finally {
buffer.release();
}
}
}
}
Loading