From 0365927951e1a80083f384dbf3988cbfd5b393b0 Mon Sep 17 00:00:00 2001 From: Scott Mitchell Date: Thu, 27 Aug 2015 15:21:30 -0700 Subject: [PATCH] HTTP/2 InboundHttp2ToHttpAdapterTest race condition Motivation: The latches in InboundHttp2ToHttpAdapterTest were volatile and reset during the tests. This resulted in race conditions and sometimes the tests would be waiting on old latches that were not the same latches being counted down when messages were received. Modifications: - Remove volatile latches from tests Result: More reliable tests with less race conditions. --- .../codec/http2/Http2FrameRoundtripTest.java | 2 +- .../http2/InboundHttp2ToHttpAdapterTest.java | 1110 ++++++++--------- 2 files changed, 542 insertions(+), 570 deletions(-) diff --git a/codec-http2/src/test/java/io/netty/handler/codec/http2/Http2FrameRoundtripTest.java b/codec-http2/src/test/java/io/netty/handler/codec/http2/Http2FrameRoundtripTest.java index c8afad587b..98d5795c42 100644 --- a/codec-http2/src/test/java/io/netty/handler/codec/http2/Http2FrameRoundtripTest.java +++ b/codec-http2/src/test/java/io/netty/handler/codec/http2/Http2FrameRoundtripTest.java @@ -72,7 +72,7 @@ public class Http2FrameRoundtripTest { private Bootstrap cb; private Channel serverChannel; private Channel clientChannel; - private volatile CountDownLatch requestLatch; + private CountDownLatch requestLatch; private Http2TestUtil.FrameAdapter serverAdapter; @Before diff --git a/codec-http2/src/test/java/io/netty/handler/codec/http2/InboundHttp2ToHttpAdapterTest.java b/codec-http2/src/test/java/io/netty/handler/codec/http2/InboundHttp2ToHttpAdapterTest.java index 67e92a1c18..aaa709bc4c 100644 --- a/codec-http2/src/test/java/io/netty/handler/codec/http2/InboundHttp2ToHttpAdapterTest.java +++ b/codec-http2/src/test/java/io/netty/handler/codec/http2/InboundHttp2ToHttpAdapterTest.java @@ -63,7 +63,6 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.SECONDS; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -90,9 +89,9 @@ public class InboundHttp2ToHttpAdapterTest { private Channel serverChannel; private Channel serverConnectedChannel; private Channel clientChannel; - private volatile CountDownLatch serverLatch; - private volatile CountDownLatch clientLatch; - private volatile CountDownLatch settingsLatch; + private CountDownLatch serverLatch; + private CountDownLatch clientLatch; + private CountDownLatch settingsLatch; private int maxContentLength; private HttpResponseDelegator serverDelegator; private HttpResponseDelegator clientDelegator; @@ -102,14 +101,547 @@ public class InboundHttp2ToHttpAdapterTest { @Before public void setup() throws Exception { MockitoAnnotations.initMocks(this); + } + @After + public void teardown() throws Exception { + cleanupCapturedRequests(); + cleanupCapturedResponses(); + if (clientChannel != null) { + clientChannel.close().sync(); + clientChannel = null; + } + if (serverChannel != null) { + serverChannel.close().sync(); + serverChannel = null; + } + Future serverGroup = sb.group().shutdownGracefully(0, 0, MILLISECONDS); + Future serverChildGroup = sb.childGroup().shutdownGracefully(0, 0, MILLISECONDS); + Future clientGroup = cb.group().shutdownGracefully(0, 0, MILLISECONDS); + serverGroup.sync(); + serverChildGroup.sync(); + clientGroup.sync(); + clientDelegator = null; + serverDelegator = null; + serverConnectedChannel = null; + } + + @Test + public void clientRequestSingleHeaderNoDataFrames() throws Exception { + boostrapEnv(1, 1, 1); + final FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, + "/some/path/resource2", true); + try { + HttpHeaders httpHeaders = request.headers(); + httpHeaders.set(HttpConversionUtil.ExtensionHeaderNames.SCHEME.text(), "https"); + httpHeaders.set(HttpConversionUtil.ExtensionHeaderNames.AUTHORITY.text(), "example.org"); + httpHeaders.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), 3); + httpHeaders.setInt(HttpHeaderNames.CONTENT_LENGTH, 0); + final Http2Headers http2Headers = new DefaultHttp2Headers().method(new AsciiString("GET")). + scheme(new AsciiString("https")).authority(new AsciiString("example.org")) + .path(new AsciiString("/some/path/resource2")); + runInChannel(clientChannel, new Http2Runnable() { + @Override + public void run() { + frameWriter.writeHeaders(ctxClient(), 3, http2Headers, 0, true, newPromiseClient()); + ctxClient().flush(); + } + }); + awaitRequests(); + ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(FullHttpMessage.class); + verify(serverListener).messageReceived(requestCaptor.capture()); + capturedRequests = requestCaptor.getAllValues(); + assertEquals(request, capturedRequests.get(0)); + } finally { + request.release(); + } + } + + @Test + public void clientRequestSingleHeaderNonAsciiShouldThrow() throws Exception { + boostrapEnv(1, 1, 1); + final Http2Headers http2Headers = new DefaultHttp2Headers() + .method(new AsciiString("GET")) + .scheme(new AsciiString("https")) + .authority(new AsciiString("example.org")) + .path(new AsciiString("/some/path/resource2")) + .add(new AsciiString("çã".getBytes(CharsetUtil.UTF_8)), + new AsciiString("Ãã".getBytes(CharsetUtil.UTF_8))); + runInChannel(clientChannel, new Http2Runnable() { + @Override + public void run() { + frameWriter.writeHeaders(ctxClient(), 3, http2Headers, 0, true, newPromiseClient()); + ctxClient().flush(); + } + }); + awaitRequests(); + assertTrue(isStreamError(serverException)); + } + + @Test + public void clientRequestOneDataFrame() throws Exception { + boostrapEnv(1, 1, 1); + final String text = "hello world"; + final ByteBuf content = Unpooled.copiedBuffer(text.getBytes()); + final FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, + "/some/path/resource2", content, true); + try { + HttpHeaders httpHeaders = request.headers(); + httpHeaders.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), 3); + httpHeaders.setInt(HttpHeaderNames.CONTENT_LENGTH, text.length()); + final Http2Headers http2Headers = new DefaultHttp2Headers().method(new AsciiString("GET")).path( + new AsciiString("/some/path/resource2")); + runInChannel(clientChannel, new Http2Runnable() { + @Override + public void run() { + frameWriter.writeHeaders(ctxClient(), 3, http2Headers, 0, false, newPromiseClient()); + frameWriter.writeData(ctxClient(), 3, content.duplicate().retain(), 0, true, newPromiseClient()); + ctxClient().flush(); + } + }); + awaitRequests(); + ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(FullHttpMessage.class); + verify(serverListener).messageReceived(requestCaptor.capture()); + capturedRequests = requestCaptor.getAllValues(); + assertEquals(request, capturedRequests.get(0)); + } finally { + request.release(); + } + } + + @Test + public void clientRequestMultipleDataFrames() throws Exception { + boostrapEnv(1, 1, 1); + final String text = "hello world big time data!"; + final ByteBuf content = Unpooled.copiedBuffer(text.getBytes()); + final FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, + "/some/path/resource2", content, true); + try { + HttpHeaders httpHeaders = request.headers(); + httpHeaders.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), 3); + httpHeaders.setInt(HttpHeaderNames.CONTENT_LENGTH, text.length()); + final Http2Headers http2Headers = new DefaultHttp2Headers().method(new AsciiString("GET")).path( + new AsciiString("/some/path/resource2")); + final int midPoint = text.length() / 2; + runInChannel(clientChannel, new Http2Runnable() { + @Override + public void run() { + frameWriter.writeHeaders(ctxClient(), 3, http2Headers, 0, false, newPromiseClient()); + frameWriter.writeData(ctxClient(), 3, content.slice(0, midPoint).retain(), 0, false, + newPromiseClient()); + frameWriter.writeData(ctxClient(), 3, content.slice(midPoint, text.length() - midPoint).retain(), + 0, true, newPromiseClient()); + ctxClient().flush(); + } + }); + awaitRequests(); + ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(FullHttpMessage.class); + verify(serverListener).messageReceived(requestCaptor.capture()); + capturedRequests = requestCaptor.getAllValues(); + assertEquals(request, capturedRequests.get(0)); + } finally { + request.release(); + } + } + + @Test + public void clientRequestMultipleEmptyDataFrames() throws Exception { + boostrapEnv(1, 1, 1); + final String text = ""; + final ByteBuf content = Unpooled.copiedBuffer(text.getBytes()); + final FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, + "/some/path/resource2", content, true); + try { + HttpHeaders httpHeaders = request.headers(); + httpHeaders.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), 3); + httpHeaders.setInt(HttpHeaderNames.CONTENT_LENGTH, text.length()); + final Http2Headers http2Headers = new DefaultHttp2Headers().method(new AsciiString("GET")).path( + new AsciiString("/some/path/resource2")); + runInChannel(clientChannel, new Http2Runnable() { + @Override + public void run() { + frameWriter.writeHeaders(ctxClient(), 3, http2Headers, 0, false, newPromiseClient()); + frameWriter.writeData(ctxClient(), 3, content.retain(), 0, false, newPromiseClient()); + frameWriter.writeData(ctxClient(), 3, content.retain(), 0, false, newPromiseClient()); + frameWriter.writeData(ctxClient(), 3, content.retain(), 0, true, newPromiseClient()); + ctxClient().flush(); + } + }); + awaitRequests(); + ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(FullHttpMessage.class); + verify(serverListener).messageReceived(requestCaptor.capture()); + capturedRequests = requestCaptor.getAllValues(); + assertEquals(request, capturedRequests.get(0)); + } finally { + request.release(); + } + } + + @Test + public void clientRequestMultipleHeaders() throws Exception { + boostrapEnv(1, 1, 1); + // writeHeaders will implicitly add an END_HEADERS tag each time and so this test does not follow the HTTP + // message flow. We currently accept this message flow and just add the second headers to the trailing headers. + final String text = ""; + final ByteBuf content = Unpooled.copiedBuffer(text.getBytes()); + final FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, + "/some/path/resource2", content, true); + try { + HttpHeaders httpHeaders = request.headers(); + httpHeaders.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), 3); + httpHeaders.setInt(HttpHeaderNames.CONTENT_LENGTH, text.length()); + HttpHeaders trailingHeaders = request.trailingHeaders(); + trailingHeaders.set("FoO", "goo"); + trailingHeaders.set("foO2", "goo2"); + trailingHeaders.add("fOo2", "goo3"); + final Http2Headers http2Headers = new DefaultHttp2Headers().method(new AsciiString("GET")).path( + new AsciiString("/some/path/resource2")); + final Http2Headers http2Headers2 = new DefaultHttp2Headers() + .set(new AsciiString("foo"), new AsciiString("goo")) + .set(new AsciiString("foo2"), new AsciiString("goo2")) + .add(new AsciiString("foo2"), new AsciiString("goo3")); + runInChannel(clientChannel, new Http2Runnable() { + @Override + public void run() { + frameWriter.writeHeaders(ctxClient(), 3, http2Headers, 0, false, newPromiseClient()); + frameWriter.writeHeaders(ctxClient(), 3, http2Headers2, 0, false, newPromiseClient()); + frameWriter.writeData(ctxClient(), 3, content.retain(), 0, true, newPromiseClient()); + ctxClient().flush(); + } + }); + awaitRequests(); + ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(FullHttpMessage.class); + verify(serverListener).messageReceived(requestCaptor.capture()); + capturedRequests = requestCaptor.getAllValues(); + assertEquals(request, capturedRequests.get(0)); + } finally { + request.release(); + } + } + + @Test + public void clientRequestTrailingHeaders() throws Exception { + boostrapEnv(1, 1, 1); + final String text = "some data"; + final ByteBuf content = Unpooled.copiedBuffer(text.getBytes()); + final FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, + "/some/path/resource2", content, true); + try { + HttpHeaders httpHeaders = request.headers(); + httpHeaders.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), 3); + httpHeaders.setInt(HttpHeaderNames.CONTENT_LENGTH, text.length()); + HttpHeaders trailingHeaders = request.trailingHeaders(); + trailingHeaders.set("Foo", "goo"); + trailingHeaders.set("fOo2", "goo2"); + trailingHeaders.add("foO2", "goo3"); + final Http2Headers http2Headers = new DefaultHttp2Headers().method(new AsciiString("GET")).path( + new AsciiString("/some/path/resource2")); + final Http2Headers http2Headers2 = new DefaultHttp2Headers() + .set(new AsciiString("foo"), new AsciiString("goo")) + .set(new AsciiString("foo2"), new AsciiString("goo2")) + .add(new AsciiString("foo2"), new AsciiString("goo3")); + runInChannel(clientChannel, new Http2Runnable() { + @Override + public void run() { + frameWriter.writeHeaders(ctxClient(), 3, http2Headers, 0, false, newPromiseClient()); + frameWriter.writeData(ctxClient(), 3, content.duplicate().retain(), 0, false, newPromiseClient()); + frameWriter.writeHeaders(ctxClient(), 3, http2Headers2, 0, true, newPromiseClient()); + ctxClient().flush(); + } + }); + awaitRequests(); + ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(FullHttpMessage.class); + verify(serverListener).messageReceived(requestCaptor.capture()); + capturedRequests = requestCaptor.getAllValues(); + assertEquals(request, capturedRequests.get(0)); + } finally { + request.release(); + } + } + + @Test + public void clientRequestStreamDependencyInHttpMessageFlow() throws Exception { + boostrapEnv(1, 2, 1); + final String text = "hello world big time data!"; + final ByteBuf content = Unpooled.copiedBuffer(text.getBytes()); + final String text2 = "hello world big time data...number 2!!"; + final ByteBuf content2 = Unpooled.copiedBuffer(text2.getBytes()); + final FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.PUT, + "/some/path/resource", content, true); + final FullHttpMessage request2 = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.PUT, + "/some/path/resource2", content2, true); + try { + HttpHeaders httpHeaders = request.headers(); + httpHeaders.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), 3); + httpHeaders.setInt(HttpHeaderNames.CONTENT_LENGTH, text.length()); + HttpHeaders httpHeaders2 = request2.headers(); + httpHeaders2.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), 5); + httpHeaders2.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_DEPENDENCY_ID.text(), 3); + httpHeaders2.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_WEIGHT.text(), 123); + httpHeaders2.setInt(HttpHeaderNames.CONTENT_LENGTH, text2.length()); + final Http2Headers http2Headers = new DefaultHttp2Headers().method(new AsciiString("PUT")).path( + new AsciiString("/some/path/resource")); + final Http2Headers http2Headers2 = new DefaultHttp2Headers().method(new AsciiString("PUT")).path( + new AsciiString("/some/path/resource2")); + runInChannel(clientChannel, new Http2Runnable() { + @Override + public void run() { + frameWriter.writeHeaders(ctxClient(), 3, http2Headers, 0, false, newPromiseClient()); + frameWriter.writeHeaders(ctxClient(), 5, http2Headers2, 0, false, newPromiseClient()); + frameWriter.writePriority(ctxClient(), 5, 3, (short) 123, true, newPromiseClient()); + frameWriter.writeData(ctxClient(), 3, content.duplicate().retain(), 0, true, newPromiseClient()); + frameWriter.writeData(ctxClient(), 5, content2.duplicate().retain(), 0, true, newPromiseClient()); + ctxClient().flush(); + } + }); + awaitRequests(); + ArgumentCaptor httpObjectCaptor = ArgumentCaptor.forClass(FullHttpMessage.class); + verify(serverListener, times(2)).messageReceived(httpObjectCaptor.capture()); + capturedRequests = httpObjectCaptor.getAllValues(); + assertEquals(request, capturedRequests.get(0)); + assertEquals(request2, capturedRequests.get(1)); + } finally { + request.release(); + request2.release(); + } + } + + @Test + public void clientRequestStreamDependencyOutsideHttpMessageFlow() throws Exception { + boostrapEnv(1, 3, 1); + final String text = "hello world big time data!"; + final ByteBuf content = Unpooled.copiedBuffer(text.getBytes()); + final String text2 = "hello world big time data...number 2!!"; + final ByteBuf content2 = Unpooled.copiedBuffer(text2.getBytes()); + final FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.PUT, + "/some/path/resource", content, true); + final FullHttpMessage request2 = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.PUT, + "/some/path/resource2", content2, true); + final FullHttpMessage request3 = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, + HttpConversionUtil.OUT_OF_MESSAGE_SEQUENCE_METHOD, + HttpConversionUtil.OUT_OF_MESSAGE_SEQUENCE_PATH, true); + try { + HttpHeaders httpHeaders = request.headers(); + httpHeaders.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), 3); + httpHeaders.setInt(HttpHeaderNames.CONTENT_LENGTH, text.length()); + HttpHeaders httpHeaders2 = request2.headers(); + httpHeaders2.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), 5); + httpHeaders2.setInt(HttpHeaderNames.CONTENT_LENGTH, text2.length()); + final Http2Headers http2Headers = new DefaultHttp2Headers().method(new AsciiString("PUT")).path( + new AsciiString("/some/path/resource")); + final Http2Headers http2Headers2 = new DefaultHttp2Headers().method(new AsciiString("PUT")).path( + new AsciiString("/some/path/resource2")); + HttpHeaders httpHeaders3 = request3.headers(); + httpHeaders3.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), 5); + httpHeaders3.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_DEPENDENCY_ID.text(), 3); + httpHeaders3.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_WEIGHT.text(), 222); + httpHeaders3.setInt(HttpHeaderNames.CONTENT_LENGTH, 0); + runInChannel(clientChannel, new Http2Runnable() { + @Override + public void run() { + frameWriter.writeHeaders(ctxClient(), 3, http2Headers, 0, false, newPromiseClient()); + frameWriter.writeHeaders(ctxClient(), 5, http2Headers2, 0, false, newPromiseClient()); + frameWriter.writeData(ctxClient(), 3, content.duplicate().retain(), 0, true, newPromiseClient()); + frameWriter.writeData(ctxClient(), 5, content2.duplicate().retain(), 0, true, newPromiseClient()); + frameWriter.writePriority(ctxClient(), 5, 3, (short) 222, false, newPromiseClient()); + ctxClient().flush(); + } + }); + awaitRequests(); + ArgumentCaptor httpObjectCaptor = ArgumentCaptor.forClass(FullHttpMessage.class); + verify(serverListener, times(3)).messageReceived(httpObjectCaptor.capture()); + capturedRequests = httpObjectCaptor.getAllValues(); + assertEquals(request, capturedRequests.get(0)); + assertEquals(request2, capturedRequests.get(1)); + assertEquals(request3, capturedRequests.get(2)); + } finally { + request.release(); + request2.release(); + request3.release(); + } + } + + @Test + public void serverRequestPushPromise() throws Exception { + boostrapEnv(2, 1, 1); + final String text = "hello world big time data!"; + final ByteBuf content = Unpooled.copiedBuffer(text.getBytes()); + final String text2 = "hello world smaller data?"; + final ByteBuf content2 = Unpooled.copiedBuffer(text2.getBytes()); + final FullHttpMessage response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, + content, true); + final FullHttpMessage response2 = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.CREATED, + content2, true); + final FullHttpMessage request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/push/test", + true); + try { + HttpHeaders httpHeaders = response.headers(); + httpHeaders.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), 3); + httpHeaders.setInt(HttpHeaderNames.CONTENT_LENGTH, text.length()); + HttpHeaders httpHeaders2 = response2.headers(); + httpHeaders2.set(HttpConversionUtil.ExtensionHeaderNames.SCHEME.text(), "https"); + httpHeaders2.set(HttpConversionUtil.ExtensionHeaderNames.AUTHORITY.text(), "example.org"); + httpHeaders2.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), 5); + httpHeaders2.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_PROMISE_ID.text(), 3); + httpHeaders2.setInt(HttpHeaderNames.CONTENT_LENGTH, text2.length()); + + httpHeaders = request.headers(); + httpHeaders.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), 3); + httpHeaders.setInt(HttpHeaderNames.CONTENT_LENGTH, 0); + final Http2Headers http2Headers3 = new DefaultHttp2Headers().method(new AsciiString("GET")) + .path(new AsciiString("/push/test")); + runInChannel(clientChannel, new Http2Runnable() { + @Override + public void run() { + frameWriter.writeHeaders(ctxClient(), 3, http2Headers3, 0, true, newPromiseClient()); + ctxClient().flush(); + } + }); + awaitRequests(); + ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(FullHttpMessage.class); + verify(serverListener).messageReceived(requestCaptor.capture()); + capturedRequests = requestCaptor.getAllValues(); + assertEquals(request, capturedRequests.get(0)); + + final Http2Headers http2Headers = new DefaultHttp2Headers().status(new AsciiString("200")); + final Http2Headers http2Headers2 = new DefaultHttp2Headers().status(new AsciiString("201")) + .scheme(new AsciiString("https")) + .authority(new AsciiString("example.org")); + runInChannel(serverConnectedChannel, new Http2Runnable() { + @Override + public void run() { + frameWriter.writeHeaders(ctxServer(), 3, http2Headers, 0, false, newPromiseServer()); + frameWriter.writePushPromise(ctxServer(), 3, 5, http2Headers2, 0, newPromiseServer()); + frameWriter.writeData(ctxServer(), 3, content.duplicate().retain(), 0, true, newPromiseServer()); + frameWriter.writeData(ctxServer(), 5, content2.duplicate().retain(), 0, true, newPromiseServer()); + ctxServer().flush(); + } + }); + awaitResponses(); + ArgumentCaptor responseCaptor = ArgumentCaptor.forClass(FullHttpMessage.class); + verify(clientListener, times(2)).messageReceived(responseCaptor.capture()); + capturedResponses = responseCaptor.getAllValues(); + assertEquals(response, capturedResponses.get(0)); + assertEquals(response2, capturedResponses.get(1)); + } finally { + request.release(); + response.release(); + response2.release(); + } + } + + @Test + public void serverResponseHeaderInformational() throws Exception { + boostrapEnv(2, 2, 1); + final FullHttpMessage request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.PUT, "/info/test", + true); + HttpHeaders httpHeaders = request.headers(); + httpHeaders.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), 3); + httpHeaders.set(HttpHeaderNames.EXPECT, HttpHeaderValues.CONTINUE); + httpHeaders.setInt(HttpHeaderNames.CONTENT_LENGTH, 0); + final Http2Headers http2Headers = new DefaultHttp2Headers().method(new AsciiString("PUT")) + .path(new AsciiString("/info/test")) + .set(new AsciiString(HttpHeaderNames.EXPECT.toString()), + new AsciiString(HttpHeaderValues.CONTINUE.toString())); + final FullHttpMessage response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.CONTINUE); + final String text = "a big payload"; + final ByteBuf payload = Unpooled.copiedBuffer(text.getBytes()); + final FullHttpMessage request2 = request.copy(payload); + final FullHttpMessage response2 = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK); + + try { + runInChannel(clientChannel, new Http2Runnable() { + @Override + public void run() { + frameWriter.writeHeaders(ctxClient(), 3, http2Headers, 0, false, newPromiseClient()); + ctxClient().flush(); + } + }); + + httpHeaders = response.headers(); + httpHeaders.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), 3); + httpHeaders.setInt(HttpHeaderNames.CONTENT_LENGTH, 0); + final Http2Headers http2HeadersResponse = new DefaultHttp2Headers().status(new AsciiString("100")); + runInChannel(serverConnectedChannel, new Http2Runnable() { + @Override + public void run() { + frameWriter.writeHeaders(ctxServer(), 3, http2HeadersResponse, 0, false, newPromiseServer()); + ctxServer().flush(); + } + }); + + httpHeaders = request2.headers(); + httpHeaders.setInt(HttpHeaderNames.CONTENT_LENGTH, text.length()); + httpHeaders.remove(HttpHeaderNames.EXPECT); + runInChannel(clientChannel, new Http2Runnable() { + @Override + public void run() { + frameWriter.writeData(ctxClient(), 3, payload.duplicate().retain(), 0, true, newPromiseClient()); + ctxClient().flush(); + } + }); + + httpHeaders = response2.headers(); + httpHeaders.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), 3); + httpHeaders.setInt(HttpHeaderNames.CONTENT_LENGTH, 0); + final Http2Headers http2HeadersResponse2 = new DefaultHttp2Headers().status(new AsciiString("200")); + runInChannel(serverConnectedChannel, new Http2Runnable() { + @Override + public void run() { + frameWriter.writeHeaders(ctxServer(), 3, http2HeadersResponse2, 0, true, newPromiseServer()); + ctxServer().flush(); + } + }); + + awaitRequests(); + ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(FullHttpMessage.class); + verify(serverListener, times(2)).messageReceived(requestCaptor.capture()); + capturedRequests = requestCaptor.getAllValues(); + assertEquals(2, capturedRequests.size()); + assertEquals(request, capturedRequests.get(0)); + assertEquals(request2, capturedRequests.get(1)); + + awaitResponses(); + ArgumentCaptor responseCaptor = ArgumentCaptor.forClass(FullHttpMessage.class); + verify(clientListener, times(2)).messageReceived(responseCaptor.capture()); + capturedResponses = responseCaptor.getAllValues(); + assertEquals(2, capturedResponses.size()); + assertEquals(response, capturedResponses.get(0)); + assertEquals(response2, capturedResponses.get(1)); + } finally { + request.release(); + request2.release(); + response.release(); + response2.release(); + } + } + + @Test + public void propagateSettings() throws Exception { + boostrapEnv(1, 1, 1); + final Http2Settings settings = new Http2Settings().pushEnabled(true); + runInChannel(clientChannel, new Http2Runnable() { + @Override + public void run() { + frameWriter.writeSettings(ctxClient(), settings, newPromiseClient()); + ctxClient().flush(); + } + }); + assertTrue(settingsLatch.await(3, SECONDS)); + ArgumentCaptor settingsCaptor = ArgumentCaptor.forClass(Http2Settings.class); + verify(settingsListener).messageReceived(settingsCaptor.capture()); + assertEquals(settings, settingsCaptor.getValue()); + } + + private void boostrapEnv(int clientLatchCount, int serverLatchCount, int settingsLatchCount) + throws InterruptedException { clientDelegator = null; serverDelegator = null; serverConnectedChannel = null; maxContentLength = 1024; - setServerLatch(1); - setClientLatch(1); - setSettingsLatch(1); + serverLatch = new CountDownLatch(serverLatchCount); + clientLatch = new CountDownLatch(clientLatchCount); + settingsLatch = new CountDownLatch(settingsLatchCount); frameWriter = new DefaultHttp2FrameWriter(); sb = new ServerBootstrap(); @@ -179,537 +711,6 @@ public class InboundHttp2ToHttpAdapterTest { clientChannel = ccf.channel(); } - @After - public void teardown() throws Exception { - cleanupCapturedRequests(); - cleanupCapturedResponses(); - if (clientChannel != null) { - clientChannel.close().sync(); - clientChannel = null; - } - if (serverChannel != null) { - serverChannel.close().sync(); - serverChannel = null; - } - Future serverGroup = sb.group().shutdownGracefully(0, 0, MILLISECONDS); - Future serverChildGroup = sb.childGroup().shutdownGracefully(0, 0, MILLISECONDS); - Future clientGroup = cb.group().shutdownGracefully(0, 0, MILLISECONDS); - serverGroup.sync(); - serverChildGroup.sync(); - clientGroup.sync(); - clientDelegator = null; - serverDelegator = null; - serverConnectedChannel = null; - } - - @Test - public void clientRequestSingleHeaderNoDataFrames() throws Exception { - final FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, - "/some/path/resource2", true); - try { - HttpHeaders httpHeaders = request.headers(); - httpHeaders.set(HttpConversionUtil.ExtensionHeaderNames.SCHEME.text(), "https"); - httpHeaders.set(HttpConversionUtil.ExtensionHeaderNames.AUTHORITY.text(), "example.org"); - httpHeaders.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), 3); - httpHeaders.setInt(HttpHeaderNames.CONTENT_LENGTH, 0); - final Http2Headers http2Headers = new DefaultHttp2Headers().method(new AsciiString("GET")). - scheme(new AsciiString("https")).authority(new AsciiString("example.org")) - .path(new AsciiString("/some/path/resource2")); - runInChannel(clientChannel, new Http2Runnable() { - @Override - public void run() { - frameWriter.writeHeaders(ctxClient(), 3, http2Headers, 0, true, newPromiseClient()); - ctxClient().flush(); - } - }); - awaitRequests(); - ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(FullHttpMessage.class); - verify(serverListener).messageReceived(requestCaptor.capture()); - capturedRequests = requestCaptor.getAllValues(); - assertEquals(request, capturedRequests.get(0)); - } finally { - request.release(); - } - } - - @Test - public void clientRequestSingleHeaderNonAsciiShouldThrow() throws Exception { - final Http2Headers http2Headers = new DefaultHttp2Headers() - .method(new AsciiString("GET")) - .scheme(new AsciiString("https")) - .authority(new AsciiString("example.org")) - .path(new AsciiString("/some/path/resource2")) - .add(new AsciiString("çã".getBytes(CharsetUtil.UTF_8)), - new AsciiString("Ãã".getBytes(CharsetUtil.UTF_8))); - runInChannel(clientChannel, new Http2Runnable() { - @Override - public void run() { - frameWriter.writeHeaders(ctxClient(), 3, http2Headers, 0, true, newPromiseClient()); - ctxClient().flush(); - } - }); - awaitRequests(); - assertTrue(isStreamError(serverException)); - } - - @Test - public void clientRequestOneDataFrame() throws Exception { - final String text = "hello world"; - final ByteBuf content = Unpooled.copiedBuffer(text.getBytes()); - final FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, - "/some/path/resource2", content, true); - try { - HttpHeaders httpHeaders = request.headers(); - httpHeaders.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), 3); - httpHeaders.setInt(HttpHeaderNames.CONTENT_LENGTH, text.length()); - final Http2Headers http2Headers = new DefaultHttp2Headers().method(new AsciiString("GET")).path( - new AsciiString("/some/path/resource2")); - runInChannel(clientChannel, new Http2Runnable() { - @Override - public void run() { - frameWriter.writeHeaders(ctxClient(), 3, http2Headers, 0, false, newPromiseClient()); - frameWriter.writeData(ctxClient(), 3, content.duplicate().retain(), 0, true, newPromiseClient()); - ctxClient().flush(); - } - }); - awaitRequests(); - ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(FullHttpMessage.class); - verify(serverListener).messageReceived(requestCaptor.capture()); - capturedRequests = requestCaptor.getAllValues(); - assertEquals(request, capturedRequests.get(0)); - } finally { - request.release(); - } - } - - @Test - public void clientRequestMultipleDataFrames() throws Exception { - final String text = "hello world big time data!"; - final ByteBuf content = Unpooled.copiedBuffer(text.getBytes()); - final FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, - "/some/path/resource2", content, true); - try { - HttpHeaders httpHeaders = request.headers(); - httpHeaders.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), 3); - httpHeaders.setInt(HttpHeaderNames.CONTENT_LENGTH, text.length()); - final Http2Headers http2Headers = new DefaultHttp2Headers().method(new AsciiString("GET")).path( - new AsciiString("/some/path/resource2")); - final int midPoint = text.length() / 2; - runInChannel(clientChannel, new Http2Runnable() { - @Override - public void run() { - frameWriter.writeHeaders(ctxClient(), 3, http2Headers, 0, false, newPromiseClient()); - frameWriter.writeData(ctxClient(), 3, content.slice(0, midPoint).retain(), 0, false, - newPromiseClient()); - frameWriter.writeData(ctxClient(), 3, content.slice(midPoint, text.length() - midPoint).retain(), - 0, true, newPromiseClient()); - ctxClient().flush(); - } - }); - awaitRequests(); - ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(FullHttpMessage.class); - verify(serverListener).messageReceived(requestCaptor.capture()); - capturedRequests = requestCaptor.getAllValues(); - assertEquals(request, capturedRequests.get(0)); - } finally { - request.release(); - } - } - - @Test - public void clientRequestMultipleEmptyDataFrames() throws Exception { - final String text = ""; - final ByteBuf content = Unpooled.copiedBuffer(text.getBytes()); - final FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, - "/some/path/resource2", content, true); - try { - HttpHeaders httpHeaders = request.headers(); - httpHeaders.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), 3); - httpHeaders.setInt(HttpHeaderNames.CONTENT_LENGTH, text.length()); - final Http2Headers http2Headers = new DefaultHttp2Headers().method(new AsciiString("GET")).path( - new AsciiString("/some/path/resource2")); - runInChannel(clientChannel, new Http2Runnable() { - @Override - public void run() { - frameWriter.writeHeaders(ctxClient(), 3, http2Headers, 0, false, newPromiseClient()); - frameWriter.writeData(ctxClient(), 3, content.retain(), 0, false, newPromiseClient()); - frameWriter.writeData(ctxClient(), 3, content.retain(), 0, false, newPromiseClient()); - frameWriter.writeData(ctxClient(), 3, content.retain(), 0, true, newPromiseClient()); - ctxClient().flush(); - } - }); - awaitRequests(); - ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(FullHttpMessage.class); - verify(serverListener).messageReceived(requestCaptor.capture()); - capturedRequests = requestCaptor.getAllValues(); - assertEquals(request, capturedRequests.get(0)); - } finally { - request.release(); - } - } - - @Test - public void clientRequestMultipleHeaders() throws Exception { - // writeHeaders will implicitly add an END_HEADERS tag each time and so this test does not follow the HTTP - // message flow. We currently accept this message flow and just add the second headers to the trailing headers. - final String text = ""; - final ByteBuf content = Unpooled.copiedBuffer(text.getBytes()); - final FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, - "/some/path/resource2", content, true); - try { - HttpHeaders httpHeaders = request.headers(); - httpHeaders.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), 3); - httpHeaders.setInt(HttpHeaderNames.CONTENT_LENGTH, text.length()); - HttpHeaders trailingHeaders = request.trailingHeaders(); - trailingHeaders.set("FoO", "goo"); - trailingHeaders.set("foO2", "goo2"); - trailingHeaders.add("fOo2", "goo3"); - final Http2Headers http2Headers = new DefaultHttp2Headers().method(new AsciiString("GET")).path( - new AsciiString("/some/path/resource2")); - final Http2Headers http2Headers2 = new DefaultHttp2Headers() - .set(new AsciiString("foo"), new AsciiString("goo")) - .set(new AsciiString("foo2"), new AsciiString("goo2")) - .add(new AsciiString("foo2"), new AsciiString("goo3")); - runInChannel(clientChannel, new Http2Runnable() { - @Override - public void run() { - frameWriter.writeHeaders(ctxClient(), 3, http2Headers, 0, false, newPromiseClient()); - frameWriter.writeHeaders(ctxClient(), 3, http2Headers2, 0, false, newPromiseClient()); - frameWriter.writeData(ctxClient(), 3, content.retain(), 0, true, newPromiseClient()); - ctxClient().flush(); - } - }); - awaitRequests(); - ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(FullHttpMessage.class); - verify(serverListener).messageReceived(requestCaptor.capture()); - capturedRequests = requestCaptor.getAllValues(); - assertEquals(request, capturedRequests.get(0)); - } finally { - request.release(); - } - } - - @Test - public void clientRequestTrailingHeaders() throws Exception { - final String text = "some data"; - final ByteBuf content = Unpooled.copiedBuffer(text.getBytes()); - final FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, - "/some/path/resource2", content, true); - try { - HttpHeaders httpHeaders = request.headers(); - httpHeaders.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), 3); - httpHeaders.setInt(HttpHeaderNames.CONTENT_LENGTH, text.length()); - HttpHeaders trailingHeaders = request.trailingHeaders(); - trailingHeaders.set("Foo", "goo"); - trailingHeaders.set("fOo2", "goo2"); - trailingHeaders.add("foO2", "goo3"); - final Http2Headers http2Headers = new DefaultHttp2Headers().method(new AsciiString("GET")).path( - new AsciiString("/some/path/resource2")); - final Http2Headers http2Headers2 = new DefaultHttp2Headers() - .set(new AsciiString("foo"), new AsciiString("goo")) - .set(new AsciiString("foo2"), new AsciiString("goo2")) - .add(new AsciiString("foo2"), new AsciiString("goo3")); - runInChannel(clientChannel, new Http2Runnable() { - @Override - public void run() { - frameWriter.writeHeaders(ctxClient(), 3, http2Headers, 0, false, newPromiseClient()); - frameWriter.writeData(ctxClient(), 3, content.duplicate().retain(), 0, false, newPromiseClient()); - frameWriter.writeHeaders(ctxClient(), 3, http2Headers2, 0, true, newPromiseClient()); - ctxClient().flush(); - } - }); - awaitRequests(); - ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(FullHttpMessage.class); - verify(serverListener).messageReceived(requestCaptor.capture()); - capturedRequests = requestCaptor.getAllValues(); - assertEquals(request, capturedRequests.get(0)); - } finally { - request.release(); - } - } - - @Test - public void clientRequestStreamDependencyInHttpMessageFlow() throws Exception { - setServerLatch(2); - final String text = "hello world big time data!"; - final ByteBuf content = Unpooled.copiedBuffer(text.getBytes()); - final String text2 = "hello world big time data...number 2!!"; - final ByteBuf content2 = Unpooled.copiedBuffer(text2.getBytes()); - final FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.PUT, - "/some/path/resource", content, true); - final FullHttpMessage request2 = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.PUT, - "/some/path/resource2", content2, true); - try { - HttpHeaders httpHeaders = request.headers(); - httpHeaders.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), 3); - httpHeaders.setInt(HttpHeaderNames.CONTENT_LENGTH, text.length()); - HttpHeaders httpHeaders2 = request2.headers(); - httpHeaders2.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), 5); - httpHeaders2.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_DEPENDENCY_ID.text(), 3); - httpHeaders2.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_WEIGHT.text(), 123); - httpHeaders2.setInt(HttpHeaderNames.CONTENT_LENGTH, text2.length()); - final Http2Headers http2Headers = new DefaultHttp2Headers().method(new AsciiString("PUT")).path( - new AsciiString("/some/path/resource")); - final Http2Headers http2Headers2 = new DefaultHttp2Headers().method(new AsciiString("PUT")).path( - new AsciiString("/some/path/resource2")); - runInChannel(clientChannel, new Http2Runnable() { - @Override - public void run() { - frameWriter.writeHeaders(ctxClient(), 3, http2Headers, 0, false, newPromiseClient()); - frameWriter.writeHeaders(ctxClient(), 5, http2Headers2, 0, false, newPromiseClient()); - frameWriter.writePriority(ctxClient(), 5, 3, (short) 123, true, newPromiseClient()); - frameWriter.writeData(ctxClient(), 3, content.duplicate().retain(), 0, true, newPromiseClient()); - frameWriter.writeData(ctxClient(), 5, content2.duplicate().retain(), 0, true, newPromiseClient()); - ctxClient().flush(); - } - }); - awaitRequests(); - ArgumentCaptor httpObjectCaptor = ArgumentCaptor.forClass(FullHttpMessage.class); - verify(serverListener, times(2)).messageReceived(httpObjectCaptor.capture()); - capturedRequests = httpObjectCaptor.getAllValues(); - assertEquals(request, capturedRequests.get(0)); - assertEquals(request2, capturedRequests.get(1)); - } finally { - request.release(); - request2.release(); - } - } - - @Test - public void clientRequestStreamDependencyOutsideHttpMessageFlow() throws Exception { - setServerLatch(3); - final String text = "hello world big time data!"; - final ByteBuf content = Unpooled.copiedBuffer(text.getBytes()); - final String text2 = "hello world big time data...number 2!!"; - final ByteBuf content2 = Unpooled.copiedBuffer(text2.getBytes()); - final FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.PUT, - "/some/path/resource", content, true); - final FullHttpMessage request2 = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.PUT, - "/some/path/resource2", content2, true); - final FullHttpMessage request3 = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, - HttpConversionUtil.OUT_OF_MESSAGE_SEQUENCE_METHOD, - HttpConversionUtil.OUT_OF_MESSAGE_SEQUENCE_PATH, true); - try { - HttpHeaders httpHeaders = request.headers(); - httpHeaders.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), 3); - httpHeaders.setInt(HttpHeaderNames.CONTENT_LENGTH, text.length()); - HttpHeaders httpHeaders2 = request2.headers(); - httpHeaders2.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), 5); - httpHeaders2.setInt(HttpHeaderNames.CONTENT_LENGTH, text2.length()); - final Http2Headers http2Headers = new DefaultHttp2Headers().method(new AsciiString("PUT")).path( - new AsciiString("/some/path/resource")); - final Http2Headers http2Headers2 = new DefaultHttp2Headers().method(new AsciiString("PUT")).path( - new AsciiString("/some/path/resource2")); - HttpHeaders httpHeaders3 = request3.headers(); - httpHeaders3.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), 5); - httpHeaders3.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_DEPENDENCY_ID.text(), 3); - httpHeaders3.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_WEIGHT.text(), 222); - httpHeaders3.setInt(HttpHeaderNames.CONTENT_LENGTH, 0); - runInChannel(clientChannel, new Http2Runnable() { - @Override - public void run() { - frameWriter.writeHeaders(ctxClient(), 3, http2Headers, 0, false, newPromiseClient()); - frameWriter.writeHeaders(ctxClient(), 5, http2Headers2, 0, false, newPromiseClient()); - frameWriter.writeData(ctxClient(), 3, content.duplicate().retain(), 0, true, newPromiseClient()); - frameWriter.writeData(ctxClient(), 5, content2.duplicate().retain(), 0, true, newPromiseClient()); - frameWriter.writePriority(ctxClient(), 5, 3, (short) 222, false, newPromiseClient()); - ctxClient().flush(); - } - }); - awaitRequests(); - ArgumentCaptor httpObjectCaptor = ArgumentCaptor.forClass(FullHttpMessage.class); - verify(serverListener, times(3)).messageReceived(httpObjectCaptor.capture()); - capturedRequests = httpObjectCaptor.getAllValues(); - assertEquals(request, capturedRequests.get(0)); - assertEquals(request2, capturedRequests.get(1)); - assertEquals(request3, capturedRequests.get(2)); - } finally { - request.release(); - request2.release(); - request3.release(); - } - } - - @Test - public void serverRequestPushPromise() throws Exception { - setClientLatch(2); - final String text = "hello world big time data!"; - final ByteBuf content = Unpooled.copiedBuffer(text.getBytes()); - final String text2 = "hello world smaller data?"; - final ByteBuf content2 = Unpooled.copiedBuffer(text2.getBytes()); - final FullHttpMessage response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, - content, true); - final FullHttpMessage response2 = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.CREATED, - content2, true); - final FullHttpMessage request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/push/test", - true); - try { - HttpHeaders httpHeaders = response.headers(); - httpHeaders.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), 3); - httpHeaders.setInt(HttpHeaderNames.CONTENT_LENGTH, text.length()); - HttpHeaders httpHeaders2 = response2.headers(); - httpHeaders2.set(HttpConversionUtil.ExtensionHeaderNames.SCHEME.text(), "https"); - httpHeaders2.set(HttpConversionUtil.ExtensionHeaderNames.AUTHORITY.text(), "example.org"); - httpHeaders2.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), 5); - httpHeaders2.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_PROMISE_ID.text(), 3); - httpHeaders2.setInt(HttpHeaderNames.CONTENT_LENGTH, text2.length()); - - httpHeaders = request.headers(); - httpHeaders.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), 3); - httpHeaders.setInt(HttpHeaderNames.CONTENT_LENGTH, 0); - final Http2Headers http2Headers3 = new DefaultHttp2Headers().method(new AsciiString("GET")) - .path(new AsciiString("/push/test")); - runInChannel(clientChannel, new Http2Runnable() { - @Override - public void run() { - frameWriter.writeHeaders(ctxClient(), 3, http2Headers3, 0, true, newPromiseClient()); - ctxClient().flush(); - } - }); - awaitRequests(); - ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(FullHttpMessage.class); - verify(serverListener).messageReceived(requestCaptor.capture()); - capturedRequests = requestCaptor.getAllValues(); - assertEquals(request, capturedRequests.get(0)); - - final Http2Headers http2Headers = new DefaultHttp2Headers().status(new AsciiString("200")); - final Http2Headers http2Headers2 = new DefaultHttp2Headers().status(new AsciiString("201")) - .scheme(new AsciiString("https")) - .authority(new AsciiString("example.org")); - runInChannel(serverConnectedChannel, new Http2Runnable() { - @Override - public void run() { - frameWriter.writeHeaders(ctxServer(), 3, http2Headers, 0, false, newPromiseServer()); - frameWriter.writePushPromise(ctxServer(), 3, 5, http2Headers2, 0, newPromiseServer()); - frameWriter.writeData(ctxServer(), 3, content.duplicate().retain(), 0, true, newPromiseServer()); - frameWriter.writeData(ctxServer(), 5, content2.duplicate().retain(), 0, true, newPromiseServer()); - ctxServer().flush(); - } - }); - awaitResponses(); - ArgumentCaptor responseCaptor = ArgumentCaptor.forClass(FullHttpMessage.class); - verify(clientListener, times(2)).messageReceived(responseCaptor.capture()); - capturedResponses = responseCaptor.getAllValues(); - assertEquals(response, capturedResponses.get(0)); - assertEquals(response2, capturedResponses.get(1)); - } finally { - request.release(); - response.release(); - response2.release(); - } - } - - @Test - public void serverResponseHeaderInformational() throws Exception { - final FullHttpMessage request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.PUT, "/info/test", - true); - HttpHeaders httpHeaders = request.headers(); - httpHeaders.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), 3); - httpHeaders.set(HttpHeaderNames.EXPECT, HttpHeaderValues.CONTINUE); - httpHeaders.setInt(HttpHeaderNames.CONTENT_LENGTH, 0); - final Http2Headers http2Headers = new DefaultHttp2Headers().method(new AsciiString("PUT")) - .path(new AsciiString("/info/test")) - .set(new AsciiString(HttpHeaderNames.EXPECT.toString()), - new AsciiString(HttpHeaderValues.CONTINUE.toString())); - final FullHttpMessage response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.CONTINUE); - final String text = "a big payload"; - final ByteBuf payload = Unpooled.copiedBuffer(text.getBytes()); - final FullHttpMessage request2 = request.copy(payload); - final FullHttpMessage response2 = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK); - - try { - runInChannel(clientChannel, new Http2Runnable() { - @Override - public void run() { - frameWriter.writeHeaders(ctxClient(), 3, http2Headers, 0, false, newPromiseClient()); - ctxClient().flush(); - } - }); - awaitRequests(); - ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(FullHttpMessage.class); - verify(serverListener).messageReceived(requestCaptor.capture()); - capturedRequests = requestCaptor.getAllValues(); - assertEquals(request, capturedRequests.get(0)); - cleanupCapturedRequests(); - reset(serverListener); - - httpHeaders = response.headers(); - httpHeaders.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), 3); - httpHeaders.setInt(HttpHeaderNames.CONTENT_LENGTH, 0); - final Http2Headers http2HeadersResponse = new DefaultHttp2Headers().status(new AsciiString("100")); - runInChannel(serverConnectedChannel, new Http2Runnable() { - @Override - public void run() { - frameWriter.writeHeaders(ctxServer(), 3, http2HeadersResponse, 0, false, newPromiseServer()); - ctxServer().flush(); - } - }); - awaitResponses(); - ArgumentCaptor responseCaptor = ArgumentCaptor.forClass(FullHttpMessage.class); - verify(clientListener).messageReceived(responseCaptor.capture()); - capturedResponses = responseCaptor.getAllValues(); - assertEquals(response, capturedResponses.get(0)); - cleanupCapturedResponses(); - reset(clientListener); - - setServerLatch(1); - httpHeaders = request2.headers(); - httpHeaders.setInt(HttpHeaderNames.CONTENT_LENGTH, text.length()); - httpHeaders.remove(HttpHeaderNames.EXPECT); - runInChannel(clientChannel, new Http2Runnable() { - @Override - public void run() { - frameWriter.writeData(ctxClient(), 3, payload.duplicate().retain(), 0, true, newPromiseClient()); - ctxClient().flush(); - } - }); - awaitRequests(); - requestCaptor = ArgumentCaptor.forClass(FullHttpMessage.class); - verify(serverListener).messageReceived(requestCaptor.capture()); - capturedRequests = requestCaptor.getAllValues(); - assertEquals(request2, capturedRequests.get(0)); - - setClientLatch(1); - httpHeaders = response2.headers(); - httpHeaders.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), 3); - httpHeaders.setInt(HttpHeaderNames.CONTENT_LENGTH, 0); - final Http2Headers http2HeadersResponse2 = new DefaultHttp2Headers().status(new AsciiString("200")); - runInChannel(serverConnectedChannel, new Http2Runnable() { - @Override - public void run() { - frameWriter.writeHeaders(ctxServer(), 3, http2HeadersResponse2, 0, true, newPromiseServer()); - ctxServer().flush(); - } - }); - awaitResponses(); - responseCaptor = ArgumentCaptor.forClass(FullHttpMessage.class); - verify(clientListener).messageReceived(responseCaptor.capture()); - capturedResponses = responseCaptor.getAllValues(); - assertEquals(response2, capturedResponses.get(0)); - } finally { - request.release(); - request2.release(); - response.release(); - response2.release(); - } - } - - @Test - public void propagateSettings() throws Exception { - final Http2Settings settings = new Http2Settings().pushEnabled(true); - runInChannel(clientChannel, new Http2Runnable() { - @Override - public void run() { - frameWriter.writeSettings(ctxClient(), settings, newPromiseClient()); - ctxClient().flush(); - } - }); - assertTrue(settingsLatch.await(3, SECONDS)); - ArgumentCaptor settingsCaptor = ArgumentCaptor.forClass(Http2Settings.class); - verify(settingsListener).messageReceived(settingsCaptor.capture()); - assertEquals(settings, settingsCaptor.getValue()); - } - private void cleanupCapturedRequests() { if (capturedRequests != null) { for (FullHttpMessage capturedRequest : capturedRequests) { @@ -728,27 +729,6 @@ public class InboundHttp2ToHttpAdapterTest { } } - private void setServerLatch(int count) { - serverLatch = new CountDownLatch(count); - if (serverDelegator != null) { - serverDelegator.latch(serverLatch); - } - } - - private void setClientLatch(int count) { - clientLatch = new CountDownLatch(count); - if (clientDelegator != null) { - clientDelegator.latch(clientLatch); - } - } - - private void setSettingsLatch(int count) { - settingsLatch = new CountDownLatch(count); - if (settingsDelegator != null) { - settingsDelegator.latch(settingsLatch); - } - } - private void awaitRequests() throws Exception { assertTrue(serverLatch.await(2, SECONDS)); } @@ -783,7 +763,7 @@ public class InboundHttp2ToHttpAdapterTest { private static final class HttpResponseDelegator extends SimpleChannelInboundHandler { private final HttpResponseListener listener; - private volatile CountDownLatch latch; + private final CountDownLatch latch; HttpResponseDelegator(HttpResponseListener listener, CountDownLatch latch) { super(false); @@ -796,15 +776,11 @@ public class InboundHttp2ToHttpAdapterTest { listener.messageReceived(msg); latch.countDown(); } - - public void latch(CountDownLatch latch) { - this.latch = latch; - } } private static final class HttpSettingsDelegator extends SimpleChannelInboundHandler { private final HttpSettingsListener listener; - private volatile CountDownLatch latch; + private final CountDownLatch latch; HttpSettingsDelegator(HttpSettingsListener listener, CountDownLatch latch) { super(false); @@ -817,10 +793,6 @@ public class InboundHttp2ToHttpAdapterTest { listener.messageReceived(settings); latch.countDown(); } - - public void latch(CountDownLatch latch) { - this.latch = latch; - } } private static final class HttpAdapterFrameAdapter extends FrameAdapter {