Java 11的HttpClient下载失败了吗?(负面的content-length)

7

这对于一些URL(如https://speed.hetzner.de/10GB.bin)非常有效,它会在我的订阅者中打印大量的调试消息,因此我的控制台看起来像这样:

I got 16384 bytes (6396)
Got data!
I got 16384 bytes (6397)
Got data!
I got 16384 bytes (6398)
Got data!
I got 16384 bytes (6399)

任务管理器显示Java正在以约50Mbit/s(我的完整下载速度)下载。

然而,使用此处提供的文件下载链接https://map.usbcraft.net/file/usbpc-downloads/java_http_client.fail却非常不同。控制台只显示以下信息:

Starting download...
Version: HTTP_1_1 Status code 200 Got headers back java.net.http.HttpHeaders@d4ccf412 { {accept-ranges=[bytes], cache-control=[max-age=0, no-cache, no-store], cf-ray=[46ed5b5aeaf9721d-AMS], connection=[keep-alive], content-length=[2147483648], content-type=[application/octet-stream], date=[Wed, 24 Oct 2018 15:08:07 GMT], expect-ct=[max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"], server=[cloudflare], set-cookie=[__cfduid=dcba8bb98269e665c521493baf25d0b6d1540393686; expires=Thu, 24-Oct-19 15:08:06 GMT; path=/; domain=.usbcraft.net; HttpOnly], x-bz-content-sha1=[none], x-bz-file-id=[4_zd729cdbbb41cd50850150a12_f201d3478bea24efd_d20181024_m130349_c001_v0001109_t0059], x-bz-file-name=[java_http_client.fail], x-bz-info-src_last_modified_millis=[1540386142160], x-bz-upload-timestamp=[1540386229000]} }
Content-Length parsed to int: -2147483648

我的MySubscriber::onNext从未被调用。然而,任务管理器仍显示我的文件正在以全速下载Taskmanager

编辑: 让它运行了一段时间后,它会打印更多的内容(这是针对另一个URL的,但对于我当前正在运行的更新的URL应该是相同的):

I got an error fixed content-length: -298366259, bytes received: 0
Exception in thread "main" java.io.IOException: fixed content-length: -298366259, bytes received: 0
    at java.net.http/jdk.internal.net.http.HttpClientImpl.send(HttpClientImpl.java:565)
    at java.net.http/jdk.internal.net.http.HttpClientFacade.send(HttpClientFacade.java:119)
    at TestKt.main(Test.kt:29)
Caused by: java.io.IOException: fixed content-length: -298366259, bytes received: 0
    at java.net.http/jdk.internal.net.http.common.Utils.wrapWithExtraDetail(Utils.java:296)
    at java.net.http/jdk.internal.net.http.Http1Response$BodyReader.onReadError(Http1Response.java:742)
    at java.net.http/jdk.internal.net.http.Http1AsyncReceiver.checkForErrors(Http1AsyncReceiver.java:297)
    at java.net.http/jdk.internal.net.http.Http1AsyncReceiver.flush(Http1AsyncReceiver.java:263)
    at java.net.http/jdk.internal.net.http.common.SequentialScheduler$SynchronizedRestartableTask.run(SequentialScheduler.java:175)
    at java.net.http/jdk.internal.net.http.common.SequentialScheduler$CompleteRestartableTask.run(SequentialScheduler.java:147)
    at java.net.http/jdk.internal.net.http.common.SequentialScheduler$SchedulableTask.run(SequentialScheduler.java:198)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
    at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: java.io.EOFException: EOF reached while reading
    at java.net.http/jdk.internal.net.http.Http1AsyncReceiver$Http1TubeSubscriber.onComplete(Http1AsyncReceiver.java:591)
    at java.net.http/jdk.internal.net.http.common.SSLTube$DelegateWrapper.onComplete(SSLTube.java:268)
    at java.net.http/jdk.internal.net.http.common.SSLTube$SSLSubscriberWrapper.complete(SSLTube.java:411)
    at java.net.http/jdk.internal.net.http.common.SSLTube$SSLSubscriberWrapper.onComplete(SSLTube.java:540)
    at java.net.http/jdk.internal.net.http.common.SubscriberWrapper.checkCompletion(SubscriberWrapper.java:443)
    at java.net.http/jdk.internal.net.http.common.SubscriberWrapper$DownstreamPusher.run1(SubscriberWrapper.java:322)
    at java.net.http/jdk.internal.net.http.common.SubscriberWrapper$DownstreamPusher.run(SubscriberWrapper.java:261)
    at java.net.http/jdk.internal.net.http.common.SequentialScheduler$SynchronizedRestartableTask.run(SequentialScheduler.java:175)
    at java.net.http/jdk.internal.net.http.common.SequentialScheduler$CompleteRestartableTask.run(SequentialScheduler.java:147)
    at java.net.http/jdk.internal.net.http.common.SequentialScheduler$SchedulableTask.run(SequentialScheduler.java:198)
    at java.net.http/jdk.internal.net.http.common.SequentialScheduler.runOrSchedule(SequentialScheduler.java:271)
    at java.net.http/jdk.internal.net.http.common.SequentialScheduler.runOrSchedule(SequentialScheduler.java:224)
    at java.net.http/jdk.internal.net.http.common.SubscriberWrapper.onComplete(SubscriberWrapper.java:419)
    at java.net.http/jdk.internal.net.http.SocketTube$InternalReadPublisher$ReadSubscription.signalCompletion(SocketTube.java:632)
    at java.net.http/jdk.internal.net.http.SocketTube$InternalReadPublisher$InternalReadSubscription.read(SocketTube.java:833)
    at java.net.http/jdk.internal.net.http.SocketTube$SocketFlowTask.run(SocketTube.java:175)
    at java.net.http/jdk.internal.net.http.common.SequentialScheduler$SchedulableTask.run(SequentialScheduler.java:198)
    at java.net.http/jdk.internal.net.http.common.SequentialScheduler.runOrSchedule(SequentialScheduler.java:271)
    at java.net.http/jdk.internal.net.http.common.SequentialScheduler.runOrSchedule(SequentialScheduler.java:224)
    at java.net.http/jdk.internal.net.http.SocketTube$InternalReadPublisher$InternalReadSubscription.signalReadable(SocketTube.java:763)
    at java.net.http/jdk.internal.net.http.SocketTube$InternalReadPublisher$ReadEvent.signalEvent(SocketTube.java:941)
    at java.net.http/jdk.internal.net.http.SocketTube$SocketFlowEvent.handle(SocketTube.java:245)
    at java.net.http/jdk.internal.net.http.HttpClientImpl$SelectorManager.handleEvent(HttpClientImpl.java:957)
    at java.net.http/jdk.internal.net.http.HttpClientImpl$SelectorManager.lambda$run$3(HttpClientImpl.java:912)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1540)
    at java.net.http/jdk.internal.net.http.HttpClientImpl$SelectorManager.run(HttpClientImpl.java:912)

因某种原因,Java将内容长度解析为负数,显然将头文件解析为Int会导致整数溢出,但是只有该URL存在此问题,而另一个10GB大小的文件却能正常工作。

使用类似于wget或火狐浏览器这样的工具下载该文件没有任何问题。并不是所有1fichier上的文件都存在这个问题,只是一些文件会出现这种情况。

有人能解释一下发生了什么吗?

我的代码:

import java.io.File
import java.net.CookieHandler
import java.net.URI
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
import java.nio.ByteBuffer
import java.nio.file.Path
import java.time.Duration
import java.util.concurrent.CompletionStage
import java.util.concurrent.Flow

fun main(args: Array<String>) {
    val dir = File("C:\\Users\\kjh\\Downloads")
    val file = File(dir, "test.bin")

    file.delete()

    val client = HttpClient.newBuilder()
            .version(HttpClient.Version.HTTP_1_1)
            .connectTimeout(Duration.ofSeconds(10))
            .build()

    val request = HttpRequest.newBuilder()
            .url("https://map.usbcraft.net/file/usbpc-downloads/java_http_client.fail")
            //.url("https://speed.hetzner.de/10GB.bin")
            .build()

    println("Starting download...")
    client.send(request) {info ->
        println("Version: ${info.version()} Status code ${info.statusCode()} Got headers back ${info.headers()}")
        println("Content-Length parsed to int: ${info.headers().get("content-length")?.toLong()?.toInt()}")
        val responseHandler = HttpResponse.BodyHandlers.ofFile(file.toPath())
        MySubscriber(responseHandler.apply(info))
    }
    println("Done with download...")
}

class MySubscriber(val subscriber: HttpResponse.BodySubscriber<Path>) : HttpResponse.BodySubscriber<Path> {
    private var counter = 0
    private var total = 0L
    override fun onComplete() {
        println("Total bytes: $total")
        subscriber.onComplete()
    }

    override fun onSubscribe(subscription: Flow.Subscription?) {
        println("onSubscribe")
        subscriber.onSubscribe(subscription)
    }

    override fun onNext(item: MutableList<ByteBuffer>) {
        println("Got data!")
        item.forEach { size ->
            println("I got ${size.remaining()} bytes ($counter)")
            total += size.remaining()
        }
        counter++
        subscriber.onNext(item)
    }

    override fun onError(throwable: Throwable) {
        println("I got an error ${throwable.localizedMessage}")
        subscriber.onError(throwable)
    }

    override fun getBody(): CompletionStage<Path> {
        return subscriber.body
    }
}

fun HttpRequest.Builder.url(url: String) = this.uri(URI.create(url))

不错的理论,但是响应头报告了两个文件的正确大小。这确实是HttpClient解析content-length头错误的一个bug。但是,这只是HTTP 1.1连接的问题。我已经向bugs.java.com报告了此问题,但它还没有公开,我也不想在这里再写一遍我在报告中写的所有内容,所以我只能等待并希望它很快就会公开,如果被批准,该bug报告应该会显示在这里:https://bugs.java.com/view_bug.do?bug_id=9057794 - usbpc102
我还更新了代码(强制使用HTTP 1.1),并提供了一个URL,其中包含一个无法下载的文件(https://map.usbcraft.net/file/usbpc-downloads/java_http_client.fail)。如果有人想要自行测试,可以使用现在更新的代码进行测试 :) - usbpc102
2
这确实是新HTTP客户端的HTTP/1.1堆栈中的一个错误。@usbpc102感谢您向我们报告此问题。我已将您的错误移动到JDK项目,并可以在以下网址上跟踪:https://bugs.openjdk.java.net/browse/JDK-8212926。我们正在处理中,希望能够很快解决它。 - chegar999
谢谢,我会用那个链接写一个简短的答案 :) - usbpc102
1个回答

4

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接