ResponseEntityProxy.writeTo(null) leaves connections in the correct state
Previously writeTo would conditionally delegate to the wrapped entity if the provided outputstream was non-null, however in the null case the entity would not be drained and the connection would be released potentially with bytes remaining. If this occurs in practice, it may result in timeouts as the server expects to write data to the response while the client is attempting to send a request.
This commit is contained in:
parent
3bd017cb0a
commit
944e308a52
|
@ -94,9 +94,7 @@ class ResponseEntityProxy extends HttpEntityWrapper implements EofSensorWatcher
|
|||
@Override
|
||||
public void writeTo(final OutputStream outStream) throws IOException {
|
||||
try {
|
||||
if (outStream != null) {
|
||||
super.writeTo(outStream);
|
||||
}
|
||||
super.writeTo(outStream != null ? outStream : NullOutputStream.INSTANCE);
|
||||
releaseConnection();
|
||||
} catch (final IOException | RuntimeException ex) {
|
||||
discardConnection();
|
||||
|
@ -188,4 +186,43 @@ class ResponseEntityProxy extends HttpEntityWrapper implements EofSensorWatcher
|
|||
cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
private static final class NullOutputStream extends OutputStream {
|
||||
private static final NullOutputStream INSTANCE = new NullOutputStream();
|
||||
|
||||
private NullOutputStream() {}
|
||||
|
||||
@Override
|
||||
public void write(@SuppressWarnings("unused") final int byteValue) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(@SuppressWarnings("unused") final byte[] buffer) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(
|
||||
@SuppressWarnings("unused") final byte[] buffer,
|
||||
@SuppressWarnings("unused") final int off,
|
||||
@SuppressWarnings("unused") final int len) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() {
|
||||
// no-op
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
// no-op
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "NullOutputStream{}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,6 +39,8 @@ import org.apache.hc.core5.http.HttpEntity;
|
|||
import org.apache.hc.core5.http.impl.io.ChunkedInputStream;
|
||||
import org.apache.hc.core5.http.impl.io.SessionInputBufferImpl;
|
||||
import org.apache.hc.core5.http.io.SessionInputBuffer;
|
||||
import org.apache.hc.core5.http.io.entity.BasicHttpEntity;
|
||||
import org.apache.hc.core5.http.message.BasicClassicHttpResponse;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
@ -105,4 +107,32 @@ public class TestResponseEntityProxy {
|
|||
Assertions.assertEquals("X-Test-Trailer-Header", header.getName());
|
||||
Assertions.assertEquals("test", header.getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteToNullDrainsAndReleasesStream() throws Exception {
|
||||
final SessionInputBuffer sessionInputBuffer = new SessionInputBufferImpl(100);
|
||||
final ByteArrayInputStream inputStream = new ByteArrayInputStream("0\r\nX-Test-Trailer-Header: test\r\n".getBytes());
|
||||
final ChunkedInputStream chunkedInputStream = new ChunkedInputStream(sessionInputBuffer, inputStream);
|
||||
final CloseableHttpResponse resp = new CloseableHttpResponse(new BasicClassicHttpResponse(200), execRuntime);
|
||||
final HttpEntity entity = new BasicHttpEntity(chunkedInputStream, null, true);
|
||||
Assertions.assertTrue(entity.isStreaming());
|
||||
resp.setEntity(entity);
|
||||
|
||||
ResponseEntityProxy.enhance(resp, execRuntime);
|
||||
|
||||
final HttpEntity wrappedEntity = resp.getEntity();
|
||||
|
||||
wrappedEntity.writeTo(null);
|
||||
Mockito.verify(execRuntime).releaseEndpoint();
|
||||
|
||||
final Supplier<List<? extends Header>> trailers = wrappedEntity.getTrailers();
|
||||
final List<? extends Header> headers = trailers.get();
|
||||
|
||||
Assertions.assertEquals(1, headers.size());
|
||||
final Header header = headers.get(0);
|
||||
Assertions.assertEquals("X-Test-Trailer-Header", header.getName());
|
||||
Assertions.assertEquals("test", header.getValue());
|
||||
|
||||
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue