HttpClientCodec need to keep request / response pairs in sync all the… (#9721)

Motivation:

At the moment we miss to poll the method queue when we see an Informational response code. This can lead to out-of-sync of request / response pairs when later try to compare these.

Modifications:

Always poll the queue correctly

Result:

Always compare the correct request / response pairs
This commit is contained in:
Norman Maurer 2019-10-29 19:48:43 +01:00
parent 8e57c00e1c
commit 6667b590c4
2 changed files with 50 additions and 4 deletions

View File

@ -222,6 +222,13 @@ public final class HttpClientCodec extends CombinedChannelDuplexHandler<HttpResp
@Override
protected boolean isContentAlwaysEmpty(HttpMessage msg) {
// Get the method of the HTTP request that corresponds to the
// current response.
//
// Even if we do not use the method to compare we still need to poll it to ensure we keep
// request / response pairs in sync.
HttpMethod method = queue.poll();
final int statusCode = ((HttpResponse) msg).status().code();
if (statusCode >= 100 && statusCode < 200) {
// An informational response should be excluded from paired comparison.
@ -229,10 +236,6 @@ public final class HttpClientCodec extends CombinedChannelDuplexHandler<HttpResp
return super.isContentAlwaysEmpty(msg);
}
// Get the getMethod of the HTTP request that corresponds to the
// current response.
HttpMethod method = queue.poll();
// If the remote peer did for example send multiple responses for one request (which is not allowed per
// spec but may still be possible) method will be null so guard against it.
if (method != null) {

View File

@ -36,6 +36,7 @@ import io.netty.handler.codec.CodecException;
import io.netty.handler.codec.PrematureChannelClosureException;
import io.netty.util.CharsetUtil;
import io.netty.util.NetUtil;
import org.hamcrest.CoreMatchers;
import org.junit.Test;
import java.net.InetSocketAddress;
@ -347,6 +348,48 @@ public class HttpClientCodecTest {
assertThat(ch.finish(), is(false));
}
@Test
public void testInformationalResponseKeepsPairsInSync() {
byte[] data = ("HTTP/1.1 102 Processing\r\n" +
"Status-URI: Status-URI:http://status.com; 404\r\n" +
"\r\n").getBytes();
byte[] data2 = ("HTTP/1.1 200 OK\r\n" +
"Content-Length: 8\r\n" +
"\r\n" +
"12345678").getBytes();
EmbeddedChannel ch = new EmbeddedChannel(new HttpClientCodec());
assertTrue(ch.writeOutbound(new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.HEAD, "/")));
ByteBuf buffer = ch.readOutbound();
buffer.release();
assertNull(ch.readOutbound());
assertTrue(ch.writeInbound(Unpooled.wrappedBuffer(data)));
HttpResponse res = ch.readInbound();
assertThat(res.protocolVersion(), sameInstance(HttpVersion.HTTP_1_1));
assertThat(res.status(), is(HttpResponseStatus.PROCESSING));
HttpContent content = ch.readInbound();
// HTTP 102 is not allowed to have content.
assertThat(content.content().readableBytes(), is(0));
assertThat(content, CoreMatchers.<HttpContent>instanceOf(LastHttpContent.class));
content.release();
assertTrue(ch.writeOutbound(new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/")));
buffer = ch.readOutbound();
buffer.release();
assertNull(ch.readOutbound());
assertTrue(ch.writeInbound(Unpooled.wrappedBuffer(data2)));
res = ch.readInbound();
assertThat(res.protocolVersion(), sameInstance(HttpVersion.HTTP_1_1));
assertThat(res.status(), is(HttpResponseStatus.OK));
content = ch.readInbound();
// HTTP 200 has content.
assertThat(content.content().readableBytes(), is(8));
assertThat(content, CoreMatchers.<HttpContent>instanceOf(LastHttpContent.class));
content.release();
assertThat(ch.finish(), is(false));
}
@Test
public void testMultipleResponses() {
String response = "HTTP/1.1 200 OK\r\n" +