okhttp3中如何在不读取响应体的情况下关闭响应体
How to close response body without reading the response body in okhttp3
环境
- 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::关闭
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 时间和网络带宽。
有没有办法直接关闭它?
不,无法立即关闭响应正文。如果您从不在该连接上发出另一个 HTTP 请求,那将是对带宽和 CPU 的浪费。但是连接池是必须的。
如果您对响应正文不感兴趣,请考虑使用 HEAD 方法或添加 range header。这样您还可以节省服务器的 CPU 和带宽。
解决方法
尽管我不能重用连接,但我想在不读取所有缓冲区的情况下关闭连接以节省网络带宽和CPU。
为了跳过全部读取,我尝试将 Http1ExchangeCodec.FixedLengthSource
的 bytesRemaining
字段更改为 0。bytesRemaining
由“Content-Length”响应设置 header值。
但是,即使使用自定义拦截器更改响应 header,也无法更改 bytesRemaining
。
因为,“Content-Length”是在 CallServerInterceptor
(拦截器链的最后一个拦截器)处理请求时检查的,所以,bytesRemaining
由原始响应设置 header。
终于发现可以使用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
}
环境
- 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::关闭
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 时间和网络带宽。
有没有办法直接关闭它?
不,无法立即关闭响应正文。如果您从不在该连接上发出另一个 HTTP 请求,那将是对带宽和 CPU 的浪费。但是连接池是必须的。
如果您对响应正文不感兴趣,请考虑使用 HEAD 方法或添加 range header。这样您还可以节省服务器的 CPU 和带宽。
解决方法
尽管我不能重用连接,但我想在不读取所有缓冲区的情况下关闭连接以节省网络带宽和CPU。
为了跳过全部读取,我尝试将 Http1ExchangeCodec.FixedLengthSource
的 bytesRemaining
字段更改为 0。bytesRemaining
由“Content-Length”响应设置 header值。
但是,即使使用自定义拦截器更改响应 header,也无法更改 bytesRemaining
。
因为,“Content-Length”是在 CallServerInterceptor
(拦截器链的最后一个拦截器)处理请求时检查的,所以,bytesRemaining
由原始响应设置 header。
终于发现可以使用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
}