【问题标题】:How to close response body without reading the response body in okhttp3如何在不读取 okhttp3 中的响应正文的情况下关闭响应正文
【发布时间】:2021-04-12 02:19:57
【问题描述】:

环境

  • MacOS
  • JDK 11
  • okhttp-4.9.0

我工作的环境对网络带宽非常敏感。
有时,我不需要阅读所有响应正文。响应主体的一部分可能足以决定结果。

我想关闭响应(=响应正文)不阅读正文

我想做的如下所示

try (Response response = client.newCall(request).execute()) {
    assertThat(response.code(), is(200))
    // do nothing with the response body
}

但是,当与HTTP1建立连接时,ResponseBody::close最终会调用Http1ExchangeCodec.FixedLengthSource::close

Http1ExchangeCodec.FixedLengthSource::close

override fun close() {
    if (closed) return

    if (bytesRemaining != 0L &&
        !discard(ExchangeCodec.DISCARD_STREAM_TIMEOUT_MILLIS, MILLISECONDS)) {
    connection.noNewExchanges() // Unread bytes remain on the stream.
    responseBodyComplete()
    }

    closed = true
}

然后,discard 方法读取所有响应正文源,如下所示。

Util.kt

fun Source.discard(timeout: Int, timeUnit: TimeUnit): Boolean = try {
    this.skipAll(timeout, timeUnit)
} catch (_: IOException) {
    false
}

@Throws(IOException::class)
fun Source.skipAll(duration: Int, timeUnit: TimeUnit): Boolean {
    val nowNs = System.nanoTime()
    val originalDurationNs = if (timeout().hasDeadline()) {
        timeout().deadlineNanoTime() - nowNs
    } else {
        Long.MAX_VALUE
    }
    timeout().deadlineNanoTime(nowNs + minOf(originalDurationNs, timeUnit.toNanos(duration.toLong())))
    return try {
        val skipBuffer = Buffer()
        while (read(skipBuffer, 8192) != -1L) {
            skipBuffer.clear()
        }
        true // Success! The source has been exhausted.
    } catch (_: InterruptedIOException) {
        false // We ran out of time before exhausting the source.
    } finally {
        if (originalDurationNs == Long.MAX_VALUE) {
            timeout().clearDeadline()
        } else {
            timeout().deadlineNanoTime(nowNs + originalDurationNs)
        }
    }
}

它读取所有正文并清除缓冲区。就我而言,这是对 CPU 时间和网络带宽的浪费。

有什么方法可以直接关闭它吗?

【问题讨论】:

    标签: okhttp


    【解决方案1】:

    不,没有办法立即关闭响应正文。如果您从未在该连接上发出另一个 HTTP 请求,那将浪费带宽和 CPU。但是连接池是必须的。

    如果您对响应正文不感兴趣,请考虑使用 HEAD 方法或添加 range header。这样,您还可以节省服务器的 CPU 和带宽。

    【讨论】:

    • 感谢您的回答。我试图在没有discard 的情况下构建 okhttp。然后,我可以发现与连接重用相关的单元测试被破坏了。即使连接不能重用,我只想关闭连接。但是,我找不到处理它的方法。
    【解决方案2】:

    解决方法

    虽然我不能重用连接,但我想在不读取所有缓冲区的情况下关闭连接以节省网络带宽和 CPU。

    为了跳过全部阅读,我尝试将Http1ExchangeCodec.FixedLengthSourcebytesRemaining 字段更改为0。bytesRemaining 由“Content-Length”响应标头值设置。
    但是,即使使用自定义拦截器更改响应标头,也无法更改 bytesRemaining。 因为,在CallServerInterceptor(拦截器链的最后一个拦截器)处理请求时会检查“Content-Length”,所以bytesRemaining 由原始响应标头设置。

    最后发现可以使用Source的超时期限。 Source.skipAll() 可以中断属于超时期限。将超时期限设置为0 以确保始终发生超时。这使得连接在不读取剩余字节的情况下直接关闭。

    try (Response response = client.newCall(request).execute()) {
        assertThat(response.code(), is(200))
        
        response.body()
            .source()
            .timeout()
            .deadlineNanoTime(0);
        // close without reading remaining bytes
    }
    

    【讨论】:

      猜你喜欢
      • 2015-10-10
      • 2017-07-01
      • 2017-10-28
      • 2021-07-20
      • 1970-01-01
      • 2016-05-03
      • 1970-01-01
      • 1970-01-01
      • 2021-02-13
      相关资源
      最近更新 更多