Reputation: 71
Environment
The environment I'm working on is quite sensitive with the network bandwidth.
Sometimes, I don't need to read all the response body. A part of the response body could be enough to decide a result.
I want to close the response(=response body) without reading the body.
What do I want to do is like below
try (Response response = client.newCall(request).execute()) {
assertThat(response.code(), is(200))
// do nothing with the response body
}
But, when the connection is established with HTTP1, the ResponseBody::close
invokes Http1ExchangeCodec.FixedLengthSource::close
in the end.
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
}
Then, the discard
method reads all the response body source as below.
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)
}
}
}
It reads all the body and clears buffer. In my case, it's a waste of the CPU time and the network bandwidth.
Is there any way to just close it literally?
Upvotes: 2
Views: 2497
Reputation: 71
Even though I couldn't reuse the connection, I wanted to close connection without reading all the buffer to save network bandwidth and CPU.
To skip the reading all, I tried to change Http1ExchangeCodec.FixedLengthSource
's bytesRemaining
field to 0. bytesRemaining
is set by "Content-Length" response header value.
But, there's no way to change bytesRemaining
even if changing the response header with custom interceptor.
Because, "Content-Length" is check whne the CallServerInterceptor
(the last interceptor of interceptor chains) proceed the request so, bytesRemaining
is set by the original response header.
Finally, I found that I can use the timeout deadline of Source
. Source.skipAll()
can be interrupted belong to timeout deadline. Set the timeout deadline to 0
to ensure timeout always occurs. This makes the connection close literally without reading remaining bytes.
try (Response response = client.newCall(request).execute()) {
assertThat(response.code(), is(200))
response.body()
.source()
.timeout()
.deadlineNanoTime(0);
// close without reading remaining bytes
}
Upvotes: 2
Reputation: 40613
No, there's no way to immediately close the response body. If you never issue another HTTP request on that connection it is a waste of bandwidth and CPU. But it is necessary for the connection to be pooled.
Consider using a HEAD method or adding a range header if you're not interested in the response body. That way you're also saving the server’s CPU and bandwidth.
Upvotes: 4