Clean up HTTP/2 integration tests

Related pull request: #2481 written by @nmittler

Motivation:

Some tests were still sending requests from the test thread rather than
from the event loop.

Modifications:

- Modified the roundtrip tests to use a new utility Http2TestUtil to
  perform the write operations in the event loop thread.
- Modified the Http2Client under examples to write all requests in the
  event loop thread.
- Added HttpPrefaceHandler and its test which were missing.
- Fixed various inspector warnings

Result:

Tests and examples now send requests in the correct thread context.
This commit is contained in:
Trustin Lee 2014-07-02 16:56:09 +09:00
parent 66d8d8219a
commit cea3b6b2ab
5 changed files with 380 additions and 87 deletions

View File

@ -0,0 +1,121 @@
/*
* 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.buffer.ByteBuf;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import static io.netty.handler.codec.http2.Http2CodecUtil.*;
/**
* Reads and writes the HTTP/2 connection preface, which must be the first bytes sent by both
* endpoints upon successful establishment of an HTTP/2 connection. After receiving the preface from
* the remote endpoint, this handler removes itself from the pipeline.
*
* https://tools.ietf.org/html/draft-ietf-httpbis-http2-12#section-3.5
*/
public class Http2PrefaceHandler extends ChannelHandlerAdapter {
private final boolean server;
private final ByteBuf preface = connectionPrefaceBuf();
private boolean prefaceWritten;
public Http2PrefaceHandler(boolean server) {
this.server = server;
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
// The channel just became active - send the HTTP2 connection preface to the remote
// endpoint.
sendPreface(ctx);
super.channelActive(ctx);
}
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
// This handler was just added to the context. In case it was handled after
// the connection became active, send the HTTP2 connection preface now.
sendPreface(ctx);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (server) {
// Only servers receive the preface string.
if (preface.isReadable() && msg instanceof ByteBuf) {
ByteBuf buf = (ByteBuf) msg;
processHttp2Preface(ctx, buf);
if (preface.isReadable()) {
// More preface left to process.
buf.release();
return;
}
}
}
super.channelRead(ctx, msg);
}
/**
* Sends the HTTP2 connection preface to the remote endpoint, if not already sent.
*/
private void sendPreface(final ChannelHandlerContext ctx) {
if (server) {
// The preface string is only written by clients.
return;
}
if (!prefaceWritten && ctx.channel().isActive()) {
prefaceWritten = true;
ctx.writeAndFlush(connectionPrefaceBuf()).addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (!future.isSuccess() && ctx.channel().isOpen()) {
// The write failed, close the connection.
ctx.close();
} else {
ctx.pipeline().remove(Http2PrefaceHandler.this);
}
}
});
}
}
private void processHttp2Preface(ChannelHandlerContext ctx, ByteBuf in) {
int prefaceRemaining = preface.readableBytes();
int bytesRead = Math.min(in.readableBytes(), prefaceRemaining);
// Read the portion of the input up to the length of the preface, if reached.
ByteBuf sourceSlice = in.readSlice(bytesRead);
// Read the same number of bytes from the preface buffer.
ByteBuf prefaceSlice = preface.readSlice(bytesRead);
// If the input so far doesn't match the preface, break the connection.
if (bytesRead == 0 || !prefaceSlice.equals(sourceSlice)) {
ctx.close();
return;
}
if (!preface.isReadable()) {
// Entire preface has been read, remove ourselves from the pipeline.
ctx.pipeline().remove(this);
}
}
}

View File

@ -15,6 +15,7 @@
package io.netty.handler.codec.http2; package io.netty.handler.codec.http2;
import static io.netty.handler.codec.http2.Http2TestUtil.runInChannel;
import static java.util.concurrent.TimeUnit.SECONDS; import static java.util.concurrent.TimeUnit.SECONDS;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any; import static org.mockito.Matchers.any;
@ -78,6 +79,7 @@ public class Http2ConnectionRoundtripTest {
@Override @Override
protected void initChannel(Channel ch) throws Exception { protected void initChannel(Channel ch) throws Exception {
ChannelPipeline p = ch.pipeline(); ChannelPipeline p = ch.pipeline();
p.addLast(new Http2PrefaceHandler(true));
p.addLast(new DelegatingHttp2ConnectionHandler(true, new FrameCountDown())); p.addLast(new DelegatingHttp2ConnectionHandler(true, new FrameCountDown()));
} }
}); });
@ -88,6 +90,7 @@ public class Http2ConnectionRoundtripTest {
@Override @Override
protected void initChannel(Channel ch) throws Exception { protected void initChannel(Channel ch) throws Exception {
ChannelPipeline p = ch.pipeline(); ChannelPipeline p = ch.pipeline();
p.addLast(new Http2PrefaceHandler(false));
p.addLast(new DelegatingHttp2ConnectionHandler(false, serverObserver)); p.addLast(new DelegatingHttp2ConnectionHandler(false, serverObserver));
} }
}); });
@ -114,20 +117,18 @@ public class Http2ConnectionRoundtripTest {
new DefaultHttp2Headers.Builder().method("GET").scheme("https") new DefaultHttp2Headers.Builder().method("GET").scheme("https")
.authority("example.org").path("/some/path/resource2").build(); .authority("example.org").path("/some/path/resource2").build();
final String text = "hello world"; final String text = "hello world";
runInChannel(clientChannel, new Http2TestUtil.Http2Runnable() {
for (int i = 0, nextStream = 3; i < NUM_STREAMS; ++i, nextStream += 2) { @Override
final int streamId = nextStream; public void run() throws Http2Exception {
clientChannel.eventLoop().execute(new Runnable() { for (int i = 0, nextStream = 3; i < NUM_STREAMS; ++i, nextStream += 2) {
@Override final int streamId = nextStream;
public void run() { http2Client.writeHeaders(ctx(), newPromise(), streamId, headers, 0, (short) 16,
http2Client.writeHeaders(ctx(), newPromise(), streamId, headers, 0, false, 0, false, false);
(short) 16, false, 0, false, false);
http2Client.writeData(ctx(), newPromise(), streamId, http2Client.writeData(ctx(), newPromise(), streamId,
Unpooled.copiedBuffer(text.getBytes()), 0, true, true, false); Unpooled.copiedBuffer(text.getBytes()), 0, true, true, false);
} }
}); }
} });
// Wait for all frames to be received. // Wait for all frames to be received.
awaitRequests(); awaitRequests();
verify(serverObserver, times(NUM_STREAMS)).onHeadersRead(any(ChannelHandlerContext.class), verify(serverObserver, times(NUM_STREAMS)).onHeadersRead(any(ChannelHandlerContext.class),
@ -157,7 +158,7 @@ public class Http2ConnectionRoundtripTest {
@Override @Override
public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding, public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
boolean endOfStream, boolean endOfSegment, boolean compressed) boolean endOfStream, boolean endOfSegment, boolean compressed)
throws Http2Exception { throws Http2Exception {
serverObserver.onDataRead(ctx, streamId, copy(data), padding, endOfStream, serverObserver.onDataRead(ctx, streamId, copy(data), padding, endOfStream,
endOfSegment, compressed); endOfSegment, compressed);
@ -166,15 +167,15 @@ public class Http2ConnectionRoundtripTest {
@Override @Override
public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers,
int padding, boolean endStream, boolean endSegment) throws Http2Exception { int padding, boolean endStream, boolean endSegment) throws Http2Exception {
serverObserver.onHeadersRead(ctx, streamId, headers, padding, endStream, endSegment); serverObserver.onHeadersRead(ctx, streamId, headers, padding, endStream, endSegment);
requestLatch.countDown(); requestLatch.countDown();
} }
@Override @Override
public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers,
int streamDependency, short weight, boolean exclusive, int padding, int streamDependency, short weight, boolean exclusive, int padding,
boolean endStream, boolean endSegment) throws Http2Exception { boolean endStream, boolean endSegment) throws Http2Exception {
serverObserver.onHeadersRead(ctx, streamId, headers, streamDependency, weight, serverObserver.onHeadersRead(ctx, streamId, headers, streamDependency, weight,
exclusive, padding, endStream, endSegment); exclusive, padding, endStream, endSegment);
requestLatch.countDown(); requestLatch.countDown();
@ -182,7 +183,7 @@ public class Http2ConnectionRoundtripTest {
@Override @Override
public void onPriorityRead(ChannelHandlerContext ctx, int streamId, int streamDependency, public void onPriorityRead(ChannelHandlerContext ctx, int streamId, int streamDependency,
short weight, boolean exclusive) throws Http2Exception { short weight, boolean exclusive) throws Http2Exception {
serverObserver.onPriorityRead(ctx, streamId, streamDependency, weight, exclusive); serverObserver.onPriorityRead(ctx, streamId, streamDependency, weight, exclusive);
requestLatch.countDown(); requestLatch.countDown();
} }
@ -220,7 +221,7 @@ public class Http2ConnectionRoundtripTest {
@Override @Override
public void onPushPromiseRead(ChannelHandlerContext ctx, int streamId, public void onPushPromiseRead(ChannelHandlerContext ctx, int streamId,
int promisedStreamId, Http2Headers headers, int padding) throws Http2Exception { int promisedStreamId, Http2Headers headers, int padding) throws Http2Exception {
serverObserver.onPushPromiseRead(ctx, streamId, promisedStreamId, headers, padding); serverObserver.onPushPromiseRead(ctx, streamId, promisedStreamId, headers, padding);
requestLatch.countDown(); requestLatch.countDown();
} }
@ -234,14 +235,14 @@ public class Http2ConnectionRoundtripTest {
@Override @Override
public void onWindowUpdateRead(ChannelHandlerContext ctx, int streamId, public void onWindowUpdateRead(ChannelHandlerContext ctx, int streamId,
int windowSizeIncrement) throws Http2Exception { int windowSizeIncrement) throws Http2Exception {
serverObserver.onWindowUpdateRead(ctx, streamId, windowSizeIncrement); serverObserver.onWindowUpdateRead(ctx, streamId, windowSizeIncrement);
requestLatch.countDown(); requestLatch.countDown();
} }
@Override @Override
public void onAltSvcRead(ChannelHandlerContext ctx, int streamId, long maxAge, int port, public void onAltSvcRead(ChannelHandlerContext ctx, int streamId, long maxAge, int port,
ByteBuf protocolId, String host, String origin) throws Http2Exception { ByteBuf protocolId, String host, String origin) throws Http2Exception {
serverObserver serverObserver
.onAltSvcRead(ctx, streamId, maxAge, port, copy(protocolId), host, origin); .onAltSvcRead(ctx, streamId, maxAge, port, copy(protocolId), host, origin);
requestLatch.countDown(); requestLatch.countDown();

View File

@ -15,12 +15,6 @@
package io.netty.handler.codec.http2; package io.netty.handler.codec.http2;
import static io.netty.util.CharsetUtil.UTF_8;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.verify;
import io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap; import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
@ -36,11 +30,6 @@ import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.ByteToMessageDecoder; import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.util.NetUtil; import io.netty.util.NetUtil;
import java.net.InetSocketAddress;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@ -48,6 +37,16 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import java.net.InetSocketAddress;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import static io.netty.handler.codec.http2.Http2TestUtil.*;
import static io.netty.util.CharsetUtil.*;
import static java.util.concurrent.TimeUnit.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
/** /**
* Tests encoding/decoding each HTTP2 frame type. * Tests encoding/decoding each HTTP2 frame type.
*/ */
@ -112,11 +111,14 @@ public class Http2FrameRoundtripTest {
@Test @Test
public void dataFrameShouldMatch() throws Exception { public void dataFrameShouldMatch() throws Exception {
String text = "hello world"; final String text = "hello world";
runInChannel(clientChannel, new Http2Runnable() {
frameWriter.writeData(ctx(), newPromise(), 0x7FFFFFFF, @Override
Unpooled.copiedBuffer(text.getBytes()), 100, true, false, false); public void run() {
frameWriter.writeData(ctx(), newPromise(), 0x7FFFFFFF,
Unpooled.copiedBuffer(text.getBytes()), 100, true, false, false);
}
});
awaitRequests(); awaitRequests();
verify(serverObserver).onDataRead(any(ChannelHandlerContext.class), eq(0x7FFFFFFF), verify(serverObserver).onDataRead(any(ChannelHandlerContext.class), eq(0x7FFFFFFF),
dataCaptor.capture(), eq(100), eq(true), eq(false), eq(false)); dataCaptor.capture(), eq(100), eq(true), eq(false), eq(false));
@ -124,11 +126,15 @@ public class Http2FrameRoundtripTest {
@Test @Test
public void headersFrameWithoutPriorityShouldMatch() throws Exception { public void headersFrameWithoutPriorityShouldMatch() throws Exception {
Http2Headers headers = final Http2Headers headers =
new DefaultHttp2Headers.Builder().method("GET").scheme("https") new DefaultHttp2Headers.Builder().method("GET").scheme("https")
.authority("example.org").path("/some/path/resource2").build(); .authority("example.org").path("/some/path/resource2").build();
frameWriter.writeHeaders(ctx(), newPromise(), 0x7FFFFFFF, headers, 0, true, false); runInChannel(clientChannel, new Http2Runnable() {
@Override
public void run() {
frameWriter.writeHeaders(ctx(), newPromise(), 0x7FFFFFFF, headers, 0, true, false);
}
});
awaitRequests(); awaitRequests();
verify(serverObserver).onHeadersRead(any(ChannelHandlerContext.class), eq(0x7FFFFFFF), verify(serverObserver).onHeadersRead(any(ChannelHandlerContext.class), eq(0x7FFFFFFF),
eq(headers), eq(0), eq(true), eq(false)); eq(headers), eq(0), eq(true), eq(false));
@ -136,12 +142,16 @@ public class Http2FrameRoundtripTest {
@Test @Test
public void headersFrameWithPriorityShouldMatch() throws Exception { public void headersFrameWithPriorityShouldMatch() throws Exception {
Http2Headers headers = final Http2Headers headers =
new DefaultHttp2Headers.Builder().method("GET").scheme("https") new DefaultHttp2Headers.Builder().method("GET").scheme("https")
.authority("example.org").path("/some/path/resource2").build(); .authority("example.org").path("/some/path/resource2").build();
frameWriter.writeHeaders(ctx(), newPromise(), 0x7FFFFFFF, headers, 4, (short) 255, true, 0, runInChannel(clientChannel, new Http2Runnable() {
true, false); @Override
public void run() {
frameWriter.writeHeaders(ctx(), newPromise(), 0x7FFFFFFF, headers, 4, (short) 255,
true, 0, true, false);
}
});
awaitRequests(); awaitRequests();
verify(serverObserver).onHeadersRead(any(ChannelHandlerContext.class), eq(0x7FFFFFFF), verify(serverObserver).onHeadersRead(any(ChannelHandlerContext.class), eq(0x7FFFFFFF),
eq(headers), eq(4), eq((short) 255), eq(true), eq(0), eq(true), eq(false)); eq(headers), eq(4), eq((short) 255), eq(true), eq(0), eq(true), eq(false));
@ -149,10 +159,14 @@ public class Http2FrameRoundtripTest {
@Test @Test
public void goAwayFrameShouldMatch() throws Exception { public void goAwayFrameShouldMatch() throws Exception {
String text = "test"; final String text = "test";
frameWriter.writeGoAway(ctx(), newPromise(), 0x7FFFFFFF, 0xFFFFFFFFL, runInChannel(clientChannel, new Http2Runnable() {
Unpooled.copiedBuffer(text.getBytes())); @Override
public void run() {
frameWriter.writeGoAway(ctx(), newPromise(), 0x7FFFFFFF, 0xFFFFFFFFL,
Unpooled.copiedBuffer(text.getBytes()));
}
});
awaitRequests(); awaitRequests();
verify(serverObserver).onGoAwayRead(any(ChannelHandlerContext.class), eq(0x7FFFFFFF), verify(serverObserver).onGoAwayRead(any(ChannelHandlerContext.class), eq(0x7FFFFFFF),
eq(0xFFFFFFFFL), dataCaptor.capture()); eq(0xFFFFFFFFL), dataCaptor.capture());
@ -160,17 +174,26 @@ public class Http2FrameRoundtripTest {
@Test @Test
public void pingFrameShouldMatch() throws Exception { public void pingFrameShouldMatch() throws Exception {
ByteBuf buf = Unpooled.copiedBuffer("01234567", UTF_8); final ByteBuf buf = Unpooled.copiedBuffer("01234567", UTF_8);
frameWriter.writePing(ctx(), ctx().newPromise(), true, buf); runInChannel(clientChannel, new Http2Runnable() {
@Override
public void run() {
frameWriter.writePing(ctx(), ctx().newPromise(), true, buf);
}
});
awaitRequests(); awaitRequests();
verify(serverObserver).onPingAckRead(any(ChannelHandlerContext.class), dataCaptor.capture()); verify(serverObserver)
.onPingAckRead(any(ChannelHandlerContext.class), dataCaptor.capture());
} }
@Test @Test
public void priorityFrameShouldMatch() throws Exception { public void priorityFrameShouldMatch() throws Exception {
frameWriter.writePriority(ctx(), newPromise(), 0x7FFFFFFF, 1, (short) 1, true); runInChannel(clientChannel, new Http2Runnable() {
@Override
public void run() {
frameWriter.writePriority(ctx(), newPromise(), 0x7FFFFFFF, 1, (short) 1, true);
}
});
awaitRequests(); awaitRequests();
verify(serverObserver).onPriorityRead(any(ChannelHandlerContext.class), eq(0x7FFFFFFF), verify(serverObserver).onPriorityRead(any(ChannelHandlerContext.class), eq(0x7FFFFFFF),
eq(1), eq((short) 1), eq(true)); eq(1), eq((short) 1), eq(true));
@ -178,11 +201,15 @@ public class Http2FrameRoundtripTest {
@Test @Test
public void pushPromiseFrameShouldMatch() throws Exception { public void pushPromiseFrameShouldMatch() throws Exception {
Http2Headers headers = final Http2Headers headers =
new DefaultHttp2Headers.Builder().method("GET").scheme("https") new DefaultHttp2Headers.Builder().method("GET").scheme("https")
.authority("example.org").path("/some/path/resource2").build(); .authority("example.org").path("/some/path/resource2").build();
frameWriter.writePushPromise(ctx(), newPromise(), 0x7FFFFFFF, 1, headers, 5); runInChannel(clientChannel, new Http2Runnable() {
@Override
public void run() {
frameWriter.writePushPromise(ctx(), newPromise(), 0x7FFFFFFF, 1, headers, 5);
}
});
awaitRequests(); awaitRequests();
verify(serverObserver).onPushPromiseRead(any(ChannelHandlerContext.class), eq(0x7FFFFFFF), verify(serverObserver).onPushPromiseRead(any(ChannelHandlerContext.class), eq(0x7FFFFFFF),
eq(1), eq(headers), eq(5)); eq(1), eq(headers), eq(5));
@ -190,8 +217,12 @@ public class Http2FrameRoundtripTest {
@Test @Test
public void rstStreamFrameShouldMatch() throws Exception { public void rstStreamFrameShouldMatch() throws Exception {
frameWriter.writeRstStream(ctx(), newPromise(), 0x7FFFFFFF, 0xFFFFFFFFL); runInChannel(clientChannel, new Http2Runnable() {
@Override
public void run() {
frameWriter.writeRstStream(ctx(), newPromise(), 0x7FFFFFFF, 0xFFFFFFFFL);
}
});
awaitRequests(); awaitRequests();
verify(serverObserver).onRstStreamRead(any(ChannelHandlerContext.class), eq(0x7FFFFFFF), verify(serverObserver).onRstStreamRead(any(ChannelHandlerContext.class), eq(0x7FFFFFFF),
eq(0xFFFFFFFFL)); eq(0xFFFFFFFFL));
@ -199,21 +230,29 @@ public class Http2FrameRoundtripTest {
@Test @Test
public void settingsFrameShouldMatch() throws Exception { public void settingsFrameShouldMatch() throws Exception {
Http2Settings settings = new Http2Settings(); final Http2Settings settings = new Http2Settings();
settings.allowCompressedData(true); settings.allowCompressedData(true);
settings.initialWindowSize(10); settings.initialWindowSize(10);
settings.maxConcurrentStreams(1000); settings.maxConcurrentStreams(1000);
settings.maxHeaderTableSize(4096); settings.maxHeaderTableSize(4096);
frameWriter.writeSettings(ctx(), newPromise(), settings); runInChannel(clientChannel, new Http2Runnable() {
@Override
public void run() {
frameWriter.writeSettings(ctx(), newPromise(), settings);
}
});
awaitRequests(); awaitRequests();
verify(serverObserver).onSettingsRead(any(ChannelHandlerContext.class), eq(settings)); verify(serverObserver).onSettingsRead(any(ChannelHandlerContext.class), eq(settings));
} }
@Test @Test
public void windowUpdateFrameShouldMatch() throws Exception { public void windowUpdateFrameShouldMatch() throws Exception {
frameWriter.writeWindowUpdate(ctx(), newPromise(), 0x7FFFFFFF, 0x7FFFFFFF); runInChannel(clientChannel, new Http2Runnable() {
@Override
public void run() {
frameWriter.writeWindowUpdate(ctx(), newPromise(), 0x7FFFFFFF, 0x7FFFFFFF);
}
});
awaitRequests(); awaitRequests();
verify(serverObserver).onWindowUpdateRead(any(ChannelHandlerContext.class), eq(0x7FFFFFFF), verify(serverObserver).onWindowUpdateRead(any(ChannelHandlerContext.class), eq(0x7FFFFFFF),
eq(0x7FFFFFFF)); eq(0x7FFFFFFF));
@ -221,22 +260,24 @@ public class Http2FrameRoundtripTest {
@Test @Test
public void stressTest() throws Exception { public void stressTest() throws Exception {
Http2Headers headers = final Http2Headers headers =
new DefaultHttp2Headers.Builder().method("GET").scheme("https") new DefaultHttp2Headers.Builder().method("GET").scheme("https")
.authority("example.org").path("/some/path/resource2").build(); .authority("example.org").path("/some/path/resource2").build();
String text = "hello world"; final String text = "hello world";
int numStreams = 1000; final int numStreams = 10000;
int expectedFrames = numStreams * 2; int expectedFrames = numStreams * 2;
requestLatch = new CountDownLatch(expectedFrames); requestLatch = new CountDownLatch(expectedFrames);
runInChannel(clientChannel, new Http2Runnable() {
for (int i = 1; i < numStreams + 1; ++i) { @Override
frameWriter.writeHeaders(ctx(), newPromise(), i, headers, 0, (short) 16, false, 0, public void run() {
false, false); for (int i = 1; i < numStreams + 1; ++i) {
frameWriter.writeData(ctx(), newPromise(), i, Unpooled.copiedBuffer(text.getBytes()), frameWriter.writeHeaders(ctx(), newPromise(), i, headers, 0, (short) 16, false,
0, true, true, false); 0, false, false);
} frameWriter.writeData(ctx(), newPromise(), i,
Unpooled.copiedBuffer(text.getBytes()), 0, true, true, false);
// Wait for all frames to be received. }
}
});
awaitRequests(); awaitRequests();
} }
@ -269,7 +310,7 @@ public class Http2FrameRoundtripTest {
@Override @Override
public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data,
int padding, boolean endOfStream, boolean endOfSegment, boolean compressed) int padding, boolean endOfStream, boolean endOfSegment, boolean compressed)
throws Http2Exception { throws Http2Exception {
observer.onDataRead(ctx, streamId, copy(data), padding, endOfStream, observer.onDataRead(ctx, streamId, copy(data), padding, endOfStream,
endOfSegment, compressed); endOfSegment, compressed);
@ -278,7 +319,7 @@ public class Http2FrameRoundtripTest {
@Override @Override
public void onHeadersRead(ChannelHandlerContext ctx, int streamId, public void onHeadersRead(ChannelHandlerContext ctx, int streamId,
Http2Headers headers, int padding, boolean endStream, boolean endSegment) Http2Headers headers, int padding, boolean endStream, boolean endSegment)
throws Http2Exception { throws Http2Exception {
observer.onHeadersRead(ctx, streamId, headers, padding, endStream, endSegment); observer.onHeadersRead(ctx, streamId, headers, padding, endStream, endSegment);
requestLatch.countDown(); requestLatch.countDown();
@ -286,8 +327,8 @@ public class Http2FrameRoundtripTest {
@Override @Override
public void onHeadersRead(ChannelHandlerContext ctx, int streamId, public void onHeadersRead(ChannelHandlerContext ctx, int streamId,
Http2Headers headers, int streamDependency, short weight, Http2Headers headers, int streamDependency, short weight,
boolean exclusive, int padding, boolean endStream, boolean endSegment) boolean exclusive, int padding, boolean endStream, boolean endSegment)
throws Http2Exception { throws Http2Exception {
observer.onHeadersRead(ctx, streamId, headers, streamDependency, weight, observer.onHeadersRead(ctx, streamId, headers, streamDependency, weight,
exclusive, padding, endStream, endSegment); exclusive, padding, endStream, endSegment);
@ -296,7 +337,7 @@ public class Http2FrameRoundtripTest {
@Override @Override
public void onPriorityRead(ChannelHandlerContext ctx, int streamId, public void onPriorityRead(ChannelHandlerContext ctx, int streamId,
int streamDependency, short weight, boolean exclusive) int streamDependency, short weight, boolean exclusive)
throws Http2Exception { throws Http2Exception {
observer.onPriorityRead(ctx, streamId, streamDependency, weight, exclusive); observer.onPriorityRead(ctx, streamId, streamDependency, weight, exclusive);
requestLatch.countDown(); requestLatch.countDown();
@ -323,41 +364,44 @@ public class Http2FrameRoundtripTest {
} }
@Override @Override
public void onPingRead(ChannelHandlerContext ctx, ByteBuf data) throws Http2Exception { public void onPingRead(ChannelHandlerContext ctx, ByteBuf data)
throws Http2Exception {
observer.onPingRead(ctx, copy(data)); observer.onPingRead(ctx, copy(data));
requestLatch.countDown(); requestLatch.countDown();
} }
@Override @Override
public void onPingAckRead(ChannelHandlerContext ctx, ByteBuf data) throws Http2Exception { public void onPingAckRead(ChannelHandlerContext ctx, ByteBuf data)
throws Http2Exception {
observer.onPingAckRead(ctx, copy(data)); observer.onPingAckRead(ctx, copy(data));
requestLatch.countDown(); requestLatch.countDown();
} }
@Override @Override
public void onPushPromiseRead(ChannelHandlerContext ctx, int streamId, int promisedStreamId, public void onPushPromiseRead(ChannelHandlerContext ctx, int streamId,
Http2Headers headers, int padding) throws Http2Exception { int promisedStreamId, Http2Headers headers, int padding)
throws Http2Exception {
observer.onPushPromiseRead(ctx, streamId, promisedStreamId, headers, padding); observer.onPushPromiseRead(ctx, streamId, promisedStreamId, headers, padding);
requestLatch.countDown(); requestLatch.countDown();
} }
@Override @Override
public void onGoAwayRead(ChannelHandlerContext ctx, int lastStreamId, public void onGoAwayRead(ChannelHandlerContext ctx, int lastStreamId,
long errorCode, ByteBuf debugData) throws Http2Exception { long errorCode, ByteBuf debugData) throws Http2Exception {
observer.onGoAwayRead(ctx, lastStreamId, errorCode, copy(debugData)); observer.onGoAwayRead(ctx, lastStreamId, errorCode, copy(debugData));
requestLatch.countDown(); requestLatch.countDown();
} }
@Override @Override
public void onWindowUpdateRead(ChannelHandlerContext ctx, int streamId, public void onWindowUpdateRead(ChannelHandlerContext ctx, int streamId,
int windowSizeIncrement) throws Http2Exception { int windowSizeIncrement) throws Http2Exception {
observer.onWindowUpdateRead(ctx, streamId, windowSizeIncrement); observer.onWindowUpdateRead(ctx, streamId, windowSizeIncrement);
requestLatch.countDown(); requestLatch.countDown();
} }
@Override @Override
public void onAltSvcRead(ChannelHandlerContext ctx, int streamId, long maxAge, public void onAltSvcRead(ChannelHandlerContext ctx, int streamId, long maxAge,
int port, ByteBuf protocolId, String host, String origin) int port, ByteBuf protocolId, String host, String origin)
throws Http2Exception { throws Http2Exception {
observer.onAltSvcRead(ctx, streamId, maxAge, port, copy(protocolId), host, observer.onAltSvcRead(ctx, streamId, maxAge, port, copy(protocolId), host,
origin); origin);
@ -365,7 +409,8 @@ public class Http2FrameRoundtripTest {
} }
@Override @Override
public void onBlockedRead(ChannelHandlerContext ctx, int streamId) throws Http2Exception { public void onBlockedRead(ChannelHandlerContext ctx, int streamId)
throws Http2Exception {
observer.onBlockedRead(ctx, streamId); observer.onBlockedRead(ctx, streamId);
requestLatch.countDown(); requestLatch.countDown();
} }

View File

@ -0,0 +1,78 @@
/*
* 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.buffer.Unpooled;
import io.netty.channel.embedded.EmbeddedChannel;
import org.junit.Test;
import java.util.Queue;
import static io.netty.handler.codec.http2.Http2CodecUtil.*;
import static io.netty.util.CharsetUtil.*;
import static org.junit.Assert.*;
/**
* Tests for {@link Http2PrefaceHandler}.
*/
public class Http2PrefaceHandlerTest {
@Test
public void clientShouldWritePrefaceAtStartup() {
EmbeddedChannel channel = createChannel(false);
// Ensure that the preface was automatically written at startup.
Queue<Object> outboundMessages = channel.outboundMessages();
assertTrue(channel.isOpen());
assertNull(channel.pipeline().get(Http2PrefaceHandler.class));
assertTrue(channel.finish());
assertEquals(1, outboundMessages.size());
assertEquals(connectionPrefaceBuf(), outboundMessages.peek());
}
@Test
public void serverShouldNotWritePrefaceAtStartup() {
EmbeddedChannel channel = createChannel(true);
// Ensure that the preface was automatically written at startup.
Queue<Object> outboundMessages = channel.outboundMessages();
assertTrue(channel.isOpen());
assertNotNull(channel.pipeline().get(Http2PrefaceHandler.class));
assertFalse(channel.finish());
assertTrue(outboundMessages.isEmpty());
}
@Test
public void serverShouldBeRemovedAfterReceivingPreface() {
EmbeddedChannel channel = createChannel(true);
// Simulate receiving the preface.
assertTrue(channel.writeInbound(connectionPrefaceBuf()));
assertNull(channel.pipeline().get(Http2PrefaceHandler.class));
assertTrue(channel.isOpen());
assertTrue(channel.finish());
}
@Test
public void serverReceivingBadPrefaceShouldCloseTheConnection() {
EmbeddedChannel channel = createChannel(true);
// Simulate receiving the bad preface.
assertFalse(channel.writeInbound(Unpooled.copiedBuffer("BAD_PREFACE", UTF_8)));
assertFalse(channel.isOpen());
assertFalse(channel.finish());
}
private static EmbeddedChannel createChannel(boolean server) {
return new EmbeddedChannel(new Http2PrefaceHandler(server));
}
}

View File

@ -0,0 +1,48 @@
/*
* 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.channel.Channel;
/**
* Utilities for the integration tests.
*/
final class Http2TestUtil {
/**
* Interface that allows for running a operation that throws a {@link Http2Exception}.
*/
interface Http2Runnable {
void run() throws Http2Exception;
}
/**
* Runs the given operation within the event loop thread of the given {@link Channel}.
*/
static void runInChannel(Channel channel, final Http2Runnable runnable) {
channel.eventLoop().execute(new Runnable() {
@Override
public void run() {
try {
runnable.run();
} catch (Http2Exception e) {
throw new RuntimeException(e);
}
}
});
}
private Http2TestUtil() {
}
}