Details
-
Bug
-
Status: Resolved
-
Critical
-
Resolution: Fixed
-
5.3.1, 5.4-beta1
-
None
Description
While migrating a proxy implementation from httpclient 4 to httpclient 5 we ran into some interesting issues regarding chunked responses.
There is a specific test that deals with the proxy behavior when the client closes the connection halfway through the response is being served.
Using chunked responses in httpclient 4, if we close the CloseableHttpResponse, the connection closes without issue.
However, in httpclient 5 the CloseableHttpResponse.close() hangs.
Debugging the issue, we see that ChunkedInputStream.close() hangs when called, because it tries to read from the socket, and the server is not actively sending any data:
{{"main@1" tid=0x1 nid=NA runnable}}
{{ java.lang.Thread.State: RUNNABLE}}
{{ at sun.nio.ch.Net.poll(Net.java:-1)}}
{{ at sun.nio.ch.NioSocketImpl.park(NioSocketImpl.java:191)}}
{{ at sun.nio.ch.NioSocketImpl.timedRead(NioSocketImpl.java:280)}}
{{ at sun.nio.ch.NioSocketImpl.implRead(NioSocketImpl.java:304)}}
{{ at sun.nio.ch.NioSocketImpl.read(NioSocketImpl.java:346)}}
{{ at sun.nio.ch.NioSocketImpl$1.read(NioSocketImpl.java:796)}}
{{ at java.net.Socket$SocketInputStream.read(Socket.java:1109)}}
{{ at org.apache.hc.client5.http.impl.io.LoggingInputStream.read(LoggingInputStream.java:81)}}
{{ at org.apache.hc.core5.http.impl.io.SessionInputBufferImpl.fillBuffer(SessionInputBufferImpl.java:149)}}
{{ at org.apache.hc.core5.http.impl.io.SessionInputBufferImpl.readLine(SessionInputBufferImpl.java:280)}}
{{ at org.apache.hc.core5.http.impl.io.ChunkedInputStream.getChunkSize(ChunkedInputStream.java:248)}}
{{ at org.apache.hc.core5.http.impl.io.ChunkedInputStream.nextChunk(ChunkedInputStream.java:222)}}
{{ at org.apache.hc.core5.http.impl.io.ChunkedInputStream.read(ChunkedInputStream.java:147)}}
{{ at org.apache.hc.core5.http.impl.io.ChunkedInputStream.close(ChunkedInputStream.java:314)}}
{{ at org.apache.hc.core5.io.Closer.close(Closer.java:48)}}
{{ at org.apache.hc.core5.http.impl.io.IncomingHttpEntity.close(IncomingHttpEntity.java:111)}}
{{ at org.apache.hc.core5.http.io.entity.HttpEntityWrapper.close(HttpEntityWrapper.java:120)}}
{{ at org.apache.hc.client5.http.impl.classic.ResponseEntityProxy.close(ResponseEntityProxy.java:180)}}
{{ at org.apache.hc.core5.io.Closer.close(Closer.java:48)}}
{{ at org.apache.hc.core5.http.message.BasicClassicHttpResponse.close(BasicClassicHttpResponse.java:93)}}
{{ at org.apache.hc.client5.http.impl.classic.CloseableHttpResponse.close(CloseableHttpResponse.java:205)}}
{{ at org.mitre.dsmiley.httpproxy.ChunkedTransferTest.testChunkedTransferClosing(ChunkedTransferTest.java:233)}}
Comparing to httpclient 4, we see that {{ChunkedInputStream.close()}} does not hang because when it tries to read from the socket, it is already closed, hence it fails. The resulting SocketException is caught in ResponseEntityProxy and discarded.
Tested in versions 5.4-beta1, 5.3.1, 5.2.3, 5.1.4 and 5.0.4, and they all hang in the same way.