netty5/codec-http2/src/test/java/io/netty/handler/codec/http2/HttpToHttp2ConnectionHandlerTest.java
Lionel Li baf273aea8 Trigger user event when H2 conn preface & SETTINGS frame are sent
Motivation:
Previously client Http2ConnectionHandler trigger a user event
immediately when the HTTP/2 connection preface is sent. Any attempt to
immediately send a new request could cause the server to terminate the
connection, as it might not have received the SETTINGS frame from the
client. Per RFC7540 Section 3.5, the preface "MUST be followed by a
SETTINGS frame (Section 6.5), which MAY be empty."
(https://tools.ietf.org/html/rfc7540#section-3.5)

This event could be made more meaningful if it also indicates that the
initial client SETTINGS frame has been sent to signal that the channel
is ready to send new requests.

Modification:
- Renamed event to Http2ConnectionPrefaceAndSettingsFrameWrittenEvent.
- Modified Http2ConnectionHandler to trigger the user event only if it
  is a client and it has sent both the preface and SETTINGS frame.

Result:
It is now safe to use the event as an indicator that the HTTP/2
connection is ready to send new requests.
2017-10-24 09:17:06 +02:00

590 lines
30 KiB
Java

/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License, version 2.0 (the
* "License"); you may not use this file except in compliance with the License. You may obtain a
* copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package io.netty.handler.codec.http2;
import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelPromise;
import io.netty.channel.DefaultEventLoopGroup;
import io.netty.channel.local.LocalAddress;
import io.netty.channel.local.LocalChannel;
import io.netty.channel.local.LocalServerChannel;
import io.netty.handler.codec.http.DefaultFullHttpRequest;
import io.netty.handler.codec.http.DefaultHttpContent;
import io.netty.handler.codec.http.DefaultHttpRequest;
import io.netty.handler.codec.http.DefaultLastHttpContent;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.LastHttpContent;
import io.netty.handler.codec.http2.Http2TestUtil.FrameCountDown;
import io.netty.util.AsciiString;
import io.netty.util.concurrent.Future;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import static io.netty.handler.codec.http.HttpMethod.CONNECT;
import static io.netty.handler.codec.http.HttpMethod.GET;
import static io.netty.handler.codec.http.HttpMethod.OPTIONS;
import static io.netty.handler.codec.http.HttpMethod.POST;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
import static io.netty.handler.codec.http2.Http2TestUtil.of;
import static io.netty.util.CharsetUtil.UTF_8;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyBoolean;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.anyShort;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
/**
* Testing the {@link HttpToHttp2ConnectionHandler} for {@link FullHttpRequest} objects into HTTP/2 frames
*/
public class HttpToHttp2ConnectionHandlerTest {
private static final int WAIT_TIME_SECONDS = 5;
@Mock
private Http2FrameListener clientListener;
@Mock
private Http2FrameListener serverListener;
private ServerBootstrap sb;
private Bootstrap cb;
private Channel serverChannel;
private volatile Channel serverConnectedChannel;
private Channel clientChannel;
private CountDownLatch requestLatch;
private CountDownLatch serverSettingsAckLatch;
private CountDownLatch trailersLatch;
private FrameCountDown serverFrameCountDown;
@Before
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
}
@After
public void teardown() throws Exception {
if (clientChannel != null) {
clientChannel.close().syncUninterruptibly();
clientChannel = null;
}
if (serverChannel != null) {
serverChannel.close().syncUninterruptibly();
serverChannel = null;
}
final Channel serverConnectedChannel = this.serverConnectedChannel;
if (serverConnectedChannel != null) {
serverConnectedChannel.close().syncUninterruptibly();
this.serverConnectedChannel = null;
}
Future<?> serverGroup = sb.config().group().shutdownGracefully(0, 5, SECONDS);
Future<?> serverChildGroup = sb.config().childGroup().shutdownGracefully(0, 5, SECONDS);
Future<?> clientGroup = cb.config().group().shutdownGracefully(0, 5, SECONDS);
serverGroup.syncUninterruptibly();
serverChildGroup.syncUninterruptibly();
clientGroup.syncUninterruptibly();
}
@Test
public void testHeadersOnlyRequest() throws Exception {
bootstrapEnv(2, 1, 0);
final FullHttpRequest request = new DefaultFullHttpRequest(HTTP_1_1, GET,
"http://my-user_name@www.example.org:5555/example");
final HttpHeaders httpHeaders = request.headers();
httpHeaders.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), 5);
httpHeaders.set(HttpHeaderNames.HOST, "my-user_name@www.example.org:5555");
httpHeaders.set(HttpConversionUtil.ExtensionHeaderNames.SCHEME.text(), "http");
httpHeaders.add(of("foo"), of("goo"));
httpHeaders.add(of("foo"), of("goo2"));
httpHeaders.add(of("foo2"), of("goo2"));
final Http2Headers http2Headers =
new DefaultHttp2Headers().method(new AsciiString("GET")).path(new AsciiString("/example"))
.authority(new AsciiString("www.example.org:5555")).scheme(new AsciiString("http"))
.add(new AsciiString("foo"), new AsciiString("goo"))
.add(new AsciiString("foo"), new AsciiString("goo2"))
.add(new AsciiString("foo2"), new AsciiString("goo2"));
ChannelPromise writePromise = newPromise();
verifyHeadersOnly(http2Headers, writePromise, clientChannel.writeAndFlush(request, writePromise));
}
@Test
public void testMultipleCookieEntriesAreCombined() throws Exception {
bootstrapEnv(2, 1, 0);
final FullHttpRequest request = new DefaultFullHttpRequest(HTTP_1_1, GET,
"http://my-user_name@www.example.org:5555/example");
final HttpHeaders httpHeaders = request.headers();
httpHeaders.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), 5);
httpHeaders.set(HttpHeaderNames.HOST, "my-user_name@www.example.org:5555");
httpHeaders.set(HttpConversionUtil.ExtensionHeaderNames.SCHEME.text(), "http");
httpHeaders.set(HttpHeaderNames.COOKIE, "a=b; c=d; e=f");
final Http2Headers http2Headers =
new DefaultHttp2Headers().method(new AsciiString("GET")).path(new AsciiString("/example"))
.authority(new AsciiString("www.example.org:5555")).scheme(new AsciiString("http"))
.add(HttpHeaderNames.COOKIE, "a=b")
.add(HttpHeaderNames.COOKIE, "c=d")
.add(HttpHeaderNames.COOKIE, "e=f");
ChannelPromise writePromise = newPromise();
verifyHeadersOnly(http2Headers, writePromise, clientChannel.writeAndFlush(request, writePromise));
}
@Test
public void testOriginFormRequestTargetHandled() throws Exception {
bootstrapEnv(2, 1, 0);
final FullHttpRequest request = new DefaultFullHttpRequest(HTTP_1_1, GET, "/where?q=now&f=then#section1");
final HttpHeaders httpHeaders = request.headers();
httpHeaders.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), 5);
httpHeaders.set(HttpConversionUtil.ExtensionHeaderNames.SCHEME.text(), "http");
final Http2Headers http2Headers =
new DefaultHttp2Headers().method(new AsciiString("GET"))
.path(new AsciiString("/where?q=now&f=then#section1"))
.scheme(new AsciiString("http"));
ChannelPromise writePromise = newPromise();
verifyHeadersOnly(http2Headers, writePromise, clientChannel.writeAndFlush(request, writePromise));
}
@Test
public void testOriginFormRequestTargetHandledFromUrlencodedUri() throws Exception {
bootstrapEnv(2, 1, 0);
final FullHttpRequest request = new DefaultFullHttpRequest(
HTTP_1_1, GET, "/where%2B0?q=now%2B0&f=then%2B0#section1%2B0");
final HttpHeaders httpHeaders = request.headers();
httpHeaders.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), 5);
httpHeaders.set(HttpConversionUtil.ExtensionHeaderNames.SCHEME.text(), "http");
final Http2Headers http2Headers =
new DefaultHttp2Headers().method(new AsciiString("GET"))
.path(new AsciiString("/where%2B0?q=now%2B0&f=then%2B0#section1%2B0"))
.scheme(new AsciiString("http"));
ChannelPromise writePromise = newPromise();
verifyHeadersOnly(http2Headers, writePromise, clientChannel.writeAndFlush(request, writePromise));
}
@Test
public void testAbsoluteFormRequestTargetHandledFromHeaders() throws Exception {
bootstrapEnv(2, 1, 0);
final FullHttpRequest request = new DefaultFullHttpRequest(HTTP_1_1, GET, "/pub/WWW/TheProject.html");
final HttpHeaders httpHeaders = request.headers();
httpHeaders.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), 5);
httpHeaders.set(HttpHeaderNames.HOST, "foouser@www.example.org:5555");
httpHeaders.set(HttpConversionUtil.ExtensionHeaderNames.PATH.text(), "ignored_path");
httpHeaders.set(HttpConversionUtil.ExtensionHeaderNames.SCHEME.text(), "https");
final Http2Headers http2Headers =
new DefaultHttp2Headers().method(new AsciiString("GET"))
.path(new AsciiString("/pub/WWW/TheProject.html"))
.authority(new AsciiString("www.example.org:5555")).scheme(new AsciiString("https"));
ChannelPromise writePromise = newPromise();
verifyHeadersOnly(http2Headers, writePromise, clientChannel.writeAndFlush(request, writePromise));
}
@Test
public void testAbsoluteFormRequestTargetHandledFromRequestTargetUri() throws Exception {
bootstrapEnv(2, 1, 0);
final FullHttpRequest request = new DefaultFullHttpRequest(HTTP_1_1, GET,
"http://foouser@www.example.org:5555/pub/WWW/TheProject.html");
final HttpHeaders httpHeaders = request.headers();
httpHeaders.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), 5);
final Http2Headers http2Headers =
new DefaultHttp2Headers().method(new AsciiString("GET"))
.path(new AsciiString("/pub/WWW/TheProject.html"))
.authority(new AsciiString("www.example.org:5555")).scheme(new AsciiString("http"));
ChannelPromise writePromise = newPromise();
verifyHeadersOnly(http2Headers, writePromise, clientChannel.writeAndFlush(request, writePromise));
}
@Test
public void testAuthorityFormRequestTargetHandled() throws Exception {
bootstrapEnv(2, 1, 0);
final FullHttpRequest request = new DefaultFullHttpRequest(HTTP_1_1, CONNECT, "http://www.example.com:80");
final HttpHeaders httpHeaders = request.headers();
httpHeaders.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), 5);
final Http2Headers http2Headers =
new DefaultHttp2Headers().method(new AsciiString("CONNECT")).path(new AsciiString("/"))
.scheme(new AsciiString("http")).authority(new AsciiString("www.example.com:80"));
ChannelPromise writePromise = newPromise();
verifyHeadersOnly(http2Headers, writePromise, clientChannel.writeAndFlush(request, writePromise));
}
@Test
public void testAsterikFormRequestTargetHandled() throws Exception {
bootstrapEnv(2, 1, 0);
final FullHttpRequest request = new DefaultFullHttpRequest(HTTP_1_1, OPTIONS, "*");
final HttpHeaders httpHeaders = request.headers();
httpHeaders.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), 5);
httpHeaders.set(HttpHeaderNames.HOST, "www.example.com:80");
httpHeaders.set(HttpConversionUtil.ExtensionHeaderNames.SCHEME.text(), "http");
final Http2Headers http2Headers =
new DefaultHttp2Headers().method(new AsciiString("OPTIONS")).path(new AsciiString("*"))
.scheme(new AsciiString("http")).authority(new AsciiString("www.example.com:80"));
ChannelPromise writePromise = newPromise();
verifyHeadersOnly(http2Headers, writePromise, clientChannel.writeAndFlush(request, writePromise));
}
@Test
public void testHostIPv6FormRequestTargetHandled() throws Exception {
// Valid according to
// https://tools.ietf.org/html/rfc7230#section-2.7.1 -> https://tools.ietf.org/html/rfc3986#section-3.2.2
bootstrapEnv(2, 1, 0);
final FullHttpRequest request = new DefaultFullHttpRequest(HTTP_1_1, GET, "/");
final HttpHeaders httpHeaders = request.headers();
httpHeaders.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), 5);
httpHeaders.set(HttpHeaderNames.HOST, "[::1]:80");
httpHeaders.set(HttpConversionUtil.ExtensionHeaderNames.SCHEME.text(), "http");
final Http2Headers http2Headers =
new DefaultHttp2Headers().method(new AsciiString("GET")).path(new AsciiString("/"))
.scheme(new AsciiString("http")).authority(new AsciiString("[::1]:80"));
ChannelPromise writePromise = newPromise();
verifyHeadersOnly(http2Headers, writePromise, clientChannel.writeAndFlush(request, writePromise));
}
@Test
public void testHostFormRequestTargetHandled() throws Exception {
bootstrapEnv(2, 1, 0);
final FullHttpRequest request = new DefaultFullHttpRequest(HTTP_1_1, GET, "/");
final HttpHeaders httpHeaders = request.headers();
httpHeaders.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), 5);
httpHeaders.set(HttpHeaderNames.HOST, "localhost:80");
httpHeaders.set(HttpConversionUtil.ExtensionHeaderNames.SCHEME.text(), "http");
final Http2Headers http2Headers =
new DefaultHttp2Headers().method(new AsciiString("GET")).path(new AsciiString("/"))
.scheme(new AsciiString("http")).authority(new AsciiString("localhost:80"));
ChannelPromise writePromise = newPromise();
verifyHeadersOnly(http2Headers, writePromise, clientChannel.writeAndFlush(request, writePromise));
}
@Test
public void testHostIPv4FormRequestTargetHandled() throws Exception {
bootstrapEnv(2, 1, 0);
final FullHttpRequest request = new DefaultFullHttpRequest(HTTP_1_1, GET, "/");
final HttpHeaders httpHeaders = request.headers();
httpHeaders.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), 5);
httpHeaders.set(HttpHeaderNames.HOST, "1.2.3.4:80");
httpHeaders.set(HttpConversionUtil.ExtensionHeaderNames.SCHEME.text(), "http");
final Http2Headers http2Headers =
new DefaultHttp2Headers().method(new AsciiString("GET")).path(new AsciiString("/"))
.scheme(new AsciiString("http")).authority(new AsciiString("1.2.3.4:80"));
ChannelPromise writePromise = newPromise();
verifyHeadersOnly(http2Headers, writePromise, clientChannel.writeAndFlush(request, writePromise));
}
@Test
public void testNoSchemeRequestTargetHandled() throws Exception {
bootstrapEnv(2, 1, 0);
final FullHttpRequest request = new DefaultFullHttpRequest(HTTP_1_1, GET, "/");
final HttpHeaders httpHeaders = request.headers();
httpHeaders.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), 5);
httpHeaders.set(HttpHeaderNames.HOST, "localhost");
ChannelPromise writePromise = newPromise();
ChannelFuture writeFuture = clientChannel.writeAndFlush(request, writePromise);
assertTrue(writePromise.awaitUninterruptibly(WAIT_TIME_SECONDS, SECONDS));
assertTrue(writePromise.isDone());
assertFalse(writePromise.isSuccess());
assertTrue(writeFuture.isDone());
assertFalse(writeFuture.isSuccess());
}
@Test
public void testRequestWithBody() throws Exception {
final String text = "foooooogoooo";
final List<String> receivedBuffers = Collections.synchronizedList(new ArrayList<String>());
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock in) throws Throwable {
receivedBuffers.add(((ByteBuf) in.getArguments()[2]).toString(UTF_8));
return null;
}
}).when(serverListener).onDataRead(any(ChannelHandlerContext.class), eq(3),
any(ByteBuf.class), eq(0), eq(true));
bootstrapEnv(3, 1, 0);
final FullHttpRequest request = new DefaultFullHttpRequest(HTTP_1_1, POST,
"http://your_user-name123@www.example.org:5555/example",
Unpooled.copiedBuffer(text, UTF_8));
final HttpHeaders httpHeaders = request.headers();
httpHeaders.set(HttpHeaderNames.HOST, "www.example-origin.org:5555");
httpHeaders.add(of("foo"), of("goo"));
httpHeaders.add(of("foo"), of("goo2"));
httpHeaders.add(of("foo2"), of("goo2"));
final Http2Headers http2Headers =
new DefaultHttp2Headers().method(new AsciiString("POST")).path(new AsciiString("/example"))
.authority(new AsciiString("www.example-origin.org:5555")).scheme(new AsciiString("http"))
.add(new AsciiString("foo"), new AsciiString("goo"))
.add(new AsciiString("foo"), new AsciiString("goo2"))
.add(new AsciiString("foo2"), new AsciiString("goo2"));
ChannelPromise writePromise = newPromise();
ChannelFuture writeFuture = clientChannel.writeAndFlush(request, writePromise);
assertTrue(writePromise.awaitUninterruptibly(WAIT_TIME_SECONDS, SECONDS));
assertTrue(writePromise.isSuccess());
assertTrue(writeFuture.awaitUninterruptibly(WAIT_TIME_SECONDS, SECONDS));
assertTrue(writeFuture.isSuccess());
awaitRequests();
verify(serverListener).onHeadersRead(any(ChannelHandlerContext.class), eq(3), eq(http2Headers), eq(0),
anyShort(), anyBoolean(), eq(0), eq(false));
verify(serverListener).onDataRead(any(ChannelHandlerContext.class), eq(3), any(ByteBuf.class), eq(0),
eq(true));
assertEquals(1, receivedBuffers.size());
assertEquals(text, receivedBuffers.get(0));
}
@Test
public void testRequestWithBodyAndTrailingHeaders() throws Exception {
final String text = "foooooogoooo";
final List<String> receivedBuffers = Collections.synchronizedList(new ArrayList<String>());
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock in) throws Throwable {
receivedBuffers.add(((ByteBuf) in.getArguments()[2]).toString(UTF_8));
return null;
}
}).when(serverListener).onDataRead(any(ChannelHandlerContext.class), eq(3),
any(ByteBuf.class), eq(0), eq(false));
bootstrapEnv(4, 1, 1);
final FullHttpRequest request = new DefaultFullHttpRequest(HTTP_1_1, POST,
"http://your_user-name123@www.example.org:5555/example",
Unpooled.copiedBuffer(text, UTF_8));
final HttpHeaders httpHeaders = request.headers();
httpHeaders.set(HttpHeaderNames.HOST, "www.example.org:5555");
httpHeaders.add(of("foo"), of("goo"));
httpHeaders.add(of("foo"), of("goo2"));
httpHeaders.add(of("foo2"), of("goo2"));
final Http2Headers http2Headers =
new DefaultHttp2Headers().method(new AsciiString("POST")).path(new AsciiString("/example"))
.authority(new AsciiString("www.example.org:5555")).scheme(new AsciiString("http"))
.add(new AsciiString("foo"), new AsciiString("goo"))
.add(new AsciiString("foo"), new AsciiString("goo2"))
.add(new AsciiString("foo2"), new AsciiString("goo2"));
request.trailingHeaders().add(of("trailing"), of("bar"));
final Http2Headers http2TrailingHeaders = new DefaultHttp2Headers()
.add(new AsciiString("trailing"), new AsciiString("bar"));
ChannelPromise writePromise = newPromise();
ChannelFuture writeFuture = clientChannel.writeAndFlush(request, writePromise);
assertTrue(writePromise.awaitUninterruptibly(WAIT_TIME_SECONDS, SECONDS));
assertTrue(writePromise.isSuccess());
assertTrue(writeFuture.awaitUninterruptibly(WAIT_TIME_SECONDS, SECONDS));
assertTrue(writeFuture.isSuccess());
awaitRequests();
verify(serverListener).onHeadersRead(any(ChannelHandlerContext.class), eq(3), eq(http2Headers), eq(0),
anyShort(), anyBoolean(), eq(0), eq(false));
verify(serverListener).onDataRead(any(ChannelHandlerContext.class), eq(3), any(ByteBuf.class), eq(0),
eq(false));
verify(serverListener).onHeadersRead(any(ChannelHandlerContext.class), eq(3), eq(http2TrailingHeaders), eq(0),
anyShort(), anyBoolean(), eq(0), eq(true));
assertEquals(1, receivedBuffers.size());
assertEquals(text, receivedBuffers.get(0));
}
@Test
public void testChunkedRequestWithBodyAndTrailingHeaders() throws Exception {
final String text = "foooooo";
final String text2 = "goooo";
final List<String> receivedBuffers = Collections.synchronizedList(new ArrayList<String>());
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock in) throws Throwable {
receivedBuffers.add(((ByteBuf) in.getArguments()[2]).toString(UTF_8));
return null;
}
}).when(serverListener).onDataRead(any(ChannelHandlerContext.class), eq(3),
any(ByteBuf.class), eq(0), eq(false));
bootstrapEnv(4, 1, 1);
final HttpRequest request = new DefaultHttpRequest(HTTP_1_1, POST,
"http://your_user-name123@www.example.org:5555/example");
final HttpHeaders httpHeaders = request.headers();
httpHeaders.set(HttpHeaderNames.HOST, "www.example.org:5555");
httpHeaders.add(HttpHeaderNames.TRANSFER_ENCODING, "chunked");
httpHeaders.add(of("foo"), of("goo"));
httpHeaders.add(of("foo"), of("goo2"));
httpHeaders.add(of("foo2"), of("goo2"));
final Http2Headers http2Headers =
new DefaultHttp2Headers().method(new AsciiString("POST")).path(new AsciiString("/example"))
.authority(new AsciiString("www.example.org:5555")).scheme(new AsciiString("http"))
.add(new AsciiString("foo"), new AsciiString("goo"))
.add(new AsciiString("foo"), new AsciiString("goo2"))
.add(new AsciiString("foo2"), new AsciiString("goo2"));
final DefaultHttpContent httpContent = new DefaultHttpContent(Unpooled.copiedBuffer(text, UTF_8));
final LastHttpContent lastHttpContent = new DefaultLastHttpContent(Unpooled.copiedBuffer(text2, UTF_8));
lastHttpContent.trailingHeaders().add(of("trailing"), of("bar"));
final Http2Headers http2TrailingHeaders = new DefaultHttp2Headers()
.add(new AsciiString("trailing"), new AsciiString("bar"));
ChannelPromise writePromise = newPromise();
ChannelFuture writeFuture = clientChannel.write(request, writePromise);
ChannelPromise contentPromise = newPromise();
ChannelFuture contentFuture = clientChannel.write(httpContent, contentPromise);
ChannelPromise lastContentPromise = newPromise();
ChannelFuture lastContentFuture = clientChannel.write(lastHttpContent, lastContentPromise);
clientChannel.flush();
assertTrue(writePromise.awaitUninterruptibly(WAIT_TIME_SECONDS, SECONDS));
assertTrue(writePromise.isSuccess());
assertTrue(writeFuture.awaitUninterruptibly(WAIT_TIME_SECONDS, SECONDS));
assertTrue(writeFuture.isSuccess());
assertTrue(contentPromise.awaitUninterruptibly(WAIT_TIME_SECONDS, SECONDS));
assertTrue(contentPromise.isSuccess());
assertTrue(contentFuture.awaitUninterruptibly(WAIT_TIME_SECONDS, SECONDS));
assertTrue(contentFuture.isSuccess());
assertTrue(lastContentPromise.awaitUninterruptibly(WAIT_TIME_SECONDS, SECONDS));
assertTrue(lastContentPromise.isSuccess());
assertTrue(lastContentFuture.awaitUninterruptibly(WAIT_TIME_SECONDS, SECONDS));
assertTrue(lastContentFuture.isSuccess());
awaitRequests();
verify(serverListener).onHeadersRead(any(ChannelHandlerContext.class), eq(3), eq(http2Headers), eq(0),
anyShort(), anyBoolean(), eq(0), eq(false));
verify(serverListener).onDataRead(any(ChannelHandlerContext.class), eq(3), any(ByteBuf.class), eq(0),
eq(false));
verify(serverListener).onHeadersRead(any(ChannelHandlerContext.class), eq(3), eq(http2TrailingHeaders), eq(0),
anyShort(), anyBoolean(), eq(0), eq(true));
assertEquals(1, receivedBuffers.size());
assertEquals(text + text2, receivedBuffers.get(0));
}
private void bootstrapEnv(int requestCountDown, int serverSettingsAckCount, int trailersCount) throws Exception {
final CountDownLatch prefaceWrittenLatch = new CountDownLatch(1);
final CountDownLatch serverChannelLatch = new CountDownLatch(1);
requestLatch = new CountDownLatch(requestCountDown);
serverSettingsAckLatch = new CountDownLatch(serverSettingsAckCount);
trailersLatch = trailersCount == 0 ? null : new CountDownLatch(trailersCount);
sb = new ServerBootstrap();
cb = new Bootstrap();
sb.group(new DefaultEventLoopGroup());
sb.channel(LocalServerChannel.class);
sb.childHandler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) throws Exception {
serverConnectedChannel = ch;
ChannelPipeline p = ch.pipeline();
serverFrameCountDown =
new FrameCountDown(serverListener, serverSettingsAckLatch, requestLatch, null, trailersLatch);
p.addLast(new HttpToHttp2ConnectionHandlerBuilder()
.server(true)
.frameListener(serverFrameCountDown)
.build());
serverChannelLatch.countDown();
}
});
cb.group(new DefaultEventLoopGroup());
cb.channel(LocalChannel.class);
cb.handler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
HttpToHttp2ConnectionHandler handler = new HttpToHttp2ConnectionHandlerBuilder()
.server(false)
.frameListener(clientListener)
.gracefulShutdownTimeoutMillis(0)
.build();
p.addLast(handler);
p.addLast(new ChannelInboundHandlerAdapter() {
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt == Http2ConnectionPrefaceAndSettingsFrameWrittenEvent.INSTANCE) {
prefaceWrittenLatch.countDown();
ctx.pipeline().remove(this);
}
}
});
}
});
serverChannel = sb.bind(new LocalAddress("HttpToHttp2ConnectionHandlerTest")).sync().channel();
ChannelFuture ccf = cb.connect(serverChannel.localAddress());
assertTrue(ccf.awaitUninterruptibly().isSuccess());
clientChannel = ccf.channel();
assertTrue(prefaceWrittenLatch.await(5, SECONDS));
assertTrue(serverChannelLatch.await(WAIT_TIME_SECONDS, SECONDS));
}
private void verifyHeadersOnly(Http2Headers expected, ChannelPromise writePromise, ChannelFuture writeFuture)
throws Exception {
assertTrue(writePromise.awaitUninterruptibly(WAIT_TIME_SECONDS, SECONDS));
assertTrue(writePromise.isSuccess());
assertTrue(writeFuture.awaitUninterruptibly(WAIT_TIME_SECONDS, SECONDS));
assertTrue(writeFuture.isSuccess());
awaitRequests();
verify(serverListener).onHeadersRead(any(ChannelHandlerContext.class), eq(5),
eq(expected), eq(0), anyShort(), anyBoolean(), eq(0), eq(true));
verify(serverListener, never()).onDataRead(any(ChannelHandlerContext.class), anyInt(),
any(ByteBuf.class), anyInt(), anyBoolean());
}
private void awaitRequests() throws Exception {
assertTrue(requestLatch.await(WAIT_TIME_SECONDS, SECONDS));
if (trailersLatch != null) {
assertTrue(trailersLatch.await(WAIT_TIME_SECONDS, SECONDS));
}
assertTrue(serverSettingsAckLatch.await(WAIT_TIME_SECONDS, SECONDS));
}
private ChannelHandlerContext ctx() {
return clientChannel.pipeline().firstContext();
}
private ChannelPromise newPromise() {
return ctx().newPromise();
}
}