HttpResponse.BodyHandlers.ofFile may lose data on response with small chunk fragments
Chris Hegarty
chegar999 at gmail.com
Mon Dec 19 09:23:38 UTC 2022
Hi Dawid,
Thanks for raising this issue. I filed the following JIRA to track it:
https://bugs.openjdk.org/browse/JDK-8299015
-Chris.
On 17/12/2022 18:45, Dawid Weiss wrote:
> Hello,
>
> I've discovered a bug in the implementation of PathSubscriber in how
> byte buffers are flushed/ written to the file channel. Specifically,
> this method in PathSubscriber [2]:
>
> @Override
> public void onNext(List<ByteBuffer> items) {
> try {
> out.write(items.toArray(Utils.EMPTY_BB_ARRAY));
> ...
>
> the implementation assumes out.write(ByteBuffer[]) always writes the
> content of all buffers - this isn't the case [1] and the method can
> write any number of bytes from input buffers.
>
> The manifestation of this bug is that HttpClient can fail to write all
> the data in a heavily chunked http response (!).
>
> The longer story is that I discovered the above while attempting to
> debug data inconsistency errors in a HttpClient fetching chunked
> responses. I'm on Windows and the problem is fairly easy to reproduce.
> Wireshark (packet interceptor) shows full data being transmitted,
> HttpClient fails to write all of it to disk with:
>
> httpClient.send(request, HttpResponse.BodyHandlers.ofFile(outputFile));
>
> The bug is fairly evident - I'm not sure how it slipped undetected for
> so long. The non-exhaustive FileChannel.write(ByteBuffer[]) behavior
> can be demonstrated by this code:
>
> var file = Files.createTempFile("tmp", "tmp");
> try (var out = FileChannel.open(file, StandardOpenOption.WRITE,
> StandardOpenOption.CREATE)) {
> // Create a lot of smallish buffers.
> ByteBuffer[] buffers = new ByteBuffer[100];
> for (int i = 0; i < buffers.length; i++) {
> buffers[i] = ByteBuffer.wrap(new byte[100]);
> }
>
> System.out.println(
> "Data in buffers: " +
> Arrays.stream(buffers).mapToLong(Buffer::remaining).sum());
> long len = out.write(buffers);
> System.out.println("Written: " + len);
> System.out.println(
> "Data in buffers: " +
> Arrays.stream(buffers).mapToLong(Buffer::remaining).sum());
> } finally {
> Files.delete(file);
> }
>
> This prints the following for me on Windows:
>
> Data in buffers: 10000
> Written: 1600
> Data in buffers: 8400
>
> Clearly, PathSubscriber should have a loop to exhaust all buffers.
> Seems like a trivial fix, so I don't include the patch (and I can't
> file a proper Jira issue because I have no access to Jira).
>
> Dawid
>
> [1] https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/nio/channels/FileChannel.html#write(java.nio.ByteBuffer%5B%5D)
>
> [2] https://github.com/openjdk/jdk/blob/master/src/java.net.http/share/classes/jdk/internal/net/http/ResponseSubscribers.java#L285
More information about the net-dev
mailing list