708 lines
28 KiB
Java
708 lines
28 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:
|
|
*
|
|
* https://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.buffer.Unpooled;
|
|
import io.netty.channel.EventLoopGroup;
|
|
import io.netty.channel.MultithreadEventLoopGroup;
|
|
import io.netty.channel.local.LocalHandler;
|
|
import io.netty.handler.codec.http2.Http2Connection.Endpoint;
|
|
import io.netty.handler.codec.http2.Http2Stream.State;
|
|
import io.netty.util.concurrent.Promise;
|
|
import org.junit.jupiter.api.AfterAll;
|
|
import org.junit.jupiter.api.BeforeAll;
|
|
import org.junit.jupiter.api.BeforeEach;
|
|
import org.junit.jupiter.api.Test;
|
|
import org.junit.jupiter.api.function.Executable;
|
|
import org.mockito.ArgumentMatchers;
|
|
import org.mockito.Mock;
|
|
import org.mockito.MockitoAnnotations;
|
|
import org.mockito.invocation.InvocationOnMock;
|
|
import org.mockito.stubbing.Answer;
|
|
|
|
import java.util.concurrent.CountDownLatch;
|
|
import java.util.concurrent.TimeUnit;
|
|
|
|
import static java.lang.Integer.MAX_VALUE;
|
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
import static org.junit.jupiter.api.Assertions.fail;
|
|
import static org.mockito.Mockito.any;
|
|
import static org.mockito.Mockito.anyInt;
|
|
import static org.mockito.Mockito.anyLong;
|
|
import static org.mockito.Mockito.doAnswer;
|
|
import static org.mockito.Mockito.eq;
|
|
import static org.mockito.Mockito.never;
|
|
import static org.mockito.Mockito.verify;
|
|
|
|
/**
|
|
* Tests for {@link DefaultHttp2Connection}.
|
|
*/
|
|
public class DefaultHttp2ConnectionTest {
|
|
|
|
private DefaultHttp2Connection server;
|
|
private DefaultHttp2Connection client;
|
|
private static EventLoopGroup group;
|
|
|
|
@Mock
|
|
private Http2Connection.Listener clientListener;
|
|
|
|
@Mock
|
|
private Http2Connection.Listener clientListener2;
|
|
|
|
@BeforeAll
|
|
public static void beforeClass() {
|
|
group = new MultithreadEventLoopGroup(2, LocalHandler.newFactory());
|
|
}
|
|
|
|
@AfterAll
|
|
public static void afterClass() {
|
|
group.shutdownGracefully();
|
|
}
|
|
|
|
@BeforeEach
|
|
public void setup() {
|
|
MockitoAnnotations.initMocks(this);
|
|
|
|
server = new DefaultHttp2Connection(true);
|
|
client = new DefaultHttp2Connection(false);
|
|
client.addListener(clientListener);
|
|
doAnswer((Answer<Void>) invocation -> {
|
|
assertNotNull(client.stream(((Http2Stream) invocation.getArgument(0)).id()));
|
|
return null;
|
|
}).when(clientListener).onStreamClosed(any(Http2Stream.class));
|
|
doAnswer((Answer<Void>) invocation -> {
|
|
assertNull(client.stream(((Http2Stream) invocation.getArgument(0)).id()));
|
|
return null;
|
|
}).when(clientListener).onStreamRemoved(any(Http2Stream.class));
|
|
}
|
|
|
|
@Test
|
|
public void getStreamWithoutStreamShouldReturnNull() {
|
|
assertNull(server.stream(100));
|
|
}
|
|
|
|
@Test
|
|
public void removeAllStreamsWithEmptyStreams() throws InterruptedException {
|
|
testRemoveAllStreams();
|
|
}
|
|
|
|
@Test
|
|
public void removeAllStreamsWithJustOneLocalStream() throws Exception {
|
|
client.local().createStream(3, false);
|
|
testRemoveAllStreams();
|
|
}
|
|
|
|
@Test
|
|
public void removeAllStreamsWithJustOneRemoveStream() throws Exception {
|
|
client.remote().createStream(2, false);
|
|
testRemoveAllStreams();
|
|
}
|
|
|
|
@Test
|
|
public void removeAllStreamsWithManyActiveStreams() throws Exception {
|
|
Endpoint<Http2RemoteFlowController> remote = client.remote();
|
|
Endpoint<Http2LocalFlowController> local = client.local();
|
|
for (int c = 3, s = 2; c < 5000; c += 2, s += 2) {
|
|
local.createStream(c, false);
|
|
remote.createStream(s, false);
|
|
}
|
|
testRemoveAllStreams();
|
|
}
|
|
|
|
@Test
|
|
public void removeIndividualStreamsWhileCloseDoesNotNPE() throws Exception {
|
|
final Http2Stream streamA = client.local().createStream(3, false);
|
|
final Http2Stream streamB = client.remote().createStream(2, false);
|
|
doAnswer((Answer<Void>) invocation -> {
|
|
streamA.close();
|
|
streamB.close();
|
|
return null;
|
|
}).when(clientListener2).onStreamClosed(any(Http2Stream.class));
|
|
try {
|
|
client.addListener(clientListener2);
|
|
testRemoveAllStreams();
|
|
} finally {
|
|
client.removeListener(clientListener2);
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void removeAllStreamsWhileIteratingActiveStreams() throws Exception {
|
|
final Endpoint<Http2RemoteFlowController> remote = client.remote();
|
|
final Endpoint<Http2LocalFlowController> local = client.local();
|
|
for (int c = 3, s = 2; c < 5000; c += 2, s += 2) {
|
|
local.createStream(c, false);
|
|
remote.createStream(s, false);
|
|
}
|
|
final Promise<Void> promise = group.next().newPromise();
|
|
final CountDownLatch latch = new CountDownLatch(client.numActiveStreams());
|
|
client.forEachActiveStream(stream -> {
|
|
promise.asFuture().addListener(future -> {
|
|
latch.countDown();
|
|
});
|
|
client.close(promise);
|
|
return true;
|
|
});
|
|
assertTrue(latch.await(5, TimeUnit.SECONDS));
|
|
}
|
|
|
|
@Test
|
|
public void removeAllStreamsWhileIteratingActiveStreamsAndExceptionOccurs()
|
|
throws Exception {
|
|
final Endpoint<Http2RemoteFlowController> remote = client.remote();
|
|
final Endpoint<Http2LocalFlowController> local = client.local();
|
|
for (int c = 3, s = 2; c < 5000; c += 2, s += 2) {
|
|
local.createStream(c, false);
|
|
remote.createStream(s, false);
|
|
}
|
|
final Promise<Void> promise = group.next().newPromise();
|
|
final CountDownLatch latch = new CountDownLatch(1);
|
|
try {
|
|
client.forEachActiveStream(stream -> {
|
|
// This close call is basically a noop, because the following statement will throw an exception.
|
|
client.close(promise);
|
|
// Do an invalid operation while iterating.
|
|
remote.createStream(3, false);
|
|
return true;
|
|
});
|
|
} catch (Http2Exception ignored) {
|
|
promise.asFuture().addListener(future -> {
|
|
latch.countDown();
|
|
});
|
|
client.close(promise);
|
|
}
|
|
assertTrue(latch.await(5, TimeUnit.SECONDS));
|
|
}
|
|
|
|
@Test
|
|
public void goAwayReceivedShouldCloseStreamsGreaterThanLastStream() throws Exception {
|
|
Http2Stream stream1 = client.local().createStream(3, false);
|
|
Http2Stream stream2 = client.local().createStream(5, false);
|
|
Http2Stream remoteStream = client.remote().createStream(4, false);
|
|
|
|
assertEquals(State.OPEN, stream1.state());
|
|
assertEquals(State.OPEN, stream2.state());
|
|
|
|
client.goAwayReceived(3, 8, null);
|
|
|
|
assertEquals(State.OPEN, stream1.state());
|
|
assertEquals(State.CLOSED, stream2.state());
|
|
assertEquals(State.OPEN, remoteStream.state());
|
|
assertEquals(3, client.local().lastStreamKnownByPeer());
|
|
assertEquals(5, client.local().lastStreamCreated());
|
|
// The remote endpoint must not be affected by a received GOAWAY frame.
|
|
assertEquals(-1, client.remote().lastStreamKnownByPeer());
|
|
assertEquals(State.OPEN, remoteStream.state());
|
|
}
|
|
|
|
@Test
|
|
public void goAwaySentShouldCloseStreamsGreaterThanLastStream() throws Exception {
|
|
Http2Stream stream1 = server.remote().createStream(3, false);
|
|
Http2Stream stream2 = server.remote().createStream(5, false);
|
|
Http2Stream localStream = server.local().createStream(4, false);
|
|
|
|
server.goAwaySent(3, 8, null);
|
|
|
|
assertEquals(State.OPEN, stream1.state());
|
|
assertEquals(State.CLOSED, stream2.state());
|
|
|
|
assertEquals(3, server.remote().lastStreamKnownByPeer());
|
|
assertEquals(5, server.remote().lastStreamCreated());
|
|
// The local endpoint must not be affected by a sent GOAWAY frame.
|
|
assertEquals(-1, server.local().lastStreamKnownByPeer());
|
|
assertEquals(State.OPEN, localStream.state());
|
|
}
|
|
|
|
@Test
|
|
public void serverCreateStreamShouldSucceed() throws Http2Exception {
|
|
Http2Stream stream = server.local().createStream(2, false);
|
|
assertEquals(2, stream.id());
|
|
assertEquals(State.OPEN, stream.state());
|
|
assertEquals(1, server.numActiveStreams());
|
|
assertEquals(2, server.local().lastStreamCreated());
|
|
|
|
stream = server.local().createStream(4, true);
|
|
assertEquals(4, stream.id());
|
|
assertEquals(State.HALF_CLOSED_LOCAL, stream.state());
|
|
assertEquals(2, server.numActiveStreams());
|
|
assertEquals(4, server.local().lastStreamCreated());
|
|
|
|
stream = server.remote().createStream(3, true);
|
|
assertEquals(3, stream.id());
|
|
assertEquals(State.HALF_CLOSED_REMOTE, stream.state());
|
|
assertEquals(3, server.numActiveStreams());
|
|
assertEquals(3, server.remote().lastStreamCreated());
|
|
|
|
stream = server.remote().createStream(5, false);
|
|
assertEquals(5, stream.id());
|
|
assertEquals(State.OPEN, stream.state());
|
|
assertEquals(4, server.numActiveStreams());
|
|
assertEquals(5, server.remote().lastStreamCreated());
|
|
}
|
|
|
|
@Test
|
|
public void clientCreateStreamShouldSucceed() throws Http2Exception {
|
|
Http2Stream stream = client.remote().createStream(2, false);
|
|
assertEquals(2, stream.id());
|
|
assertEquals(State.OPEN, stream.state());
|
|
assertEquals(1, client.numActiveStreams());
|
|
assertEquals(2, client.remote().lastStreamCreated());
|
|
|
|
stream = client.remote().createStream(4, true);
|
|
assertEquals(4, stream.id());
|
|
assertEquals(State.HALF_CLOSED_REMOTE, stream.state());
|
|
assertEquals(2, client.numActiveStreams());
|
|
assertEquals(4, client.remote().lastStreamCreated());
|
|
assertTrue(stream.isHeadersReceived());
|
|
|
|
stream = client.local().createStream(3, true);
|
|
assertEquals(3, stream.id());
|
|
assertEquals(State.HALF_CLOSED_LOCAL, stream.state());
|
|
assertEquals(3, client.numActiveStreams());
|
|
assertEquals(3, client.local().lastStreamCreated());
|
|
assertTrue(stream.isHeadersSent());
|
|
|
|
stream = client.local().createStream(5, false);
|
|
assertEquals(5, stream.id());
|
|
assertEquals(State.OPEN, stream.state());
|
|
assertEquals(4, client.numActiveStreams());
|
|
assertEquals(5, client.local().lastStreamCreated());
|
|
}
|
|
|
|
@Test
|
|
public void serverReservePushStreamShouldSucceed() throws Http2Exception {
|
|
Http2Stream stream = server.remote().createStream(3, true);
|
|
Http2Stream pushStream = server.local().reservePushStream(2, stream);
|
|
assertEquals(2, pushStream.id());
|
|
assertEquals(State.RESERVED_LOCAL, pushStream.state());
|
|
assertEquals(1, server.numActiveStreams());
|
|
assertEquals(2, server.local().lastStreamCreated());
|
|
}
|
|
|
|
@Test
|
|
public void clientReservePushStreamShouldSucceed() throws Http2Exception {
|
|
Http2Stream stream = server.remote().createStream(3, true);
|
|
Http2Stream pushStream = server.local().reservePushStream(4, stream);
|
|
assertEquals(4, pushStream.id());
|
|
assertEquals(State.RESERVED_LOCAL, pushStream.state());
|
|
assertEquals(1, server.numActiveStreams());
|
|
assertEquals(4, server.local().lastStreamCreated());
|
|
}
|
|
|
|
@Test
|
|
public void serverRemoteIncrementAndGetStreamShouldSucceed() throws Http2Exception {
|
|
incrementAndGetStreamShouldSucceed(server.remote());
|
|
}
|
|
|
|
@Test
|
|
public void serverLocalIncrementAndGetStreamShouldSucceed() throws Http2Exception {
|
|
incrementAndGetStreamShouldSucceed(server.local());
|
|
}
|
|
|
|
@Test
|
|
public void clientRemoteIncrementAndGetStreamShouldSucceed() throws Http2Exception {
|
|
incrementAndGetStreamShouldSucceed(client.remote());
|
|
}
|
|
|
|
@Test
|
|
public void clientLocalIncrementAndGetStreamShouldSucceed() throws Http2Exception {
|
|
incrementAndGetStreamShouldSucceed(client.local());
|
|
}
|
|
|
|
@Test
|
|
public void serverRemoteIncrementAndGetStreamShouldRespectOverflow() throws Http2Exception {
|
|
incrementAndGetStreamShouldRespectOverflow(server.remote(), MAX_VALUE);
|
|
}
|
|
|
|
@Test
|
|
public void serverLocalIncrementAndGetStreamShouldRespectOverflow() throws Http2Exception {
|
|
incrementAndGetStreamShouldRespectOverflow(server.local(), MAX_VALUE - 1);
|
|
}
|
|
|
|
@Test
|
|
public void clientRemoteIncrementAndGetStreamShouldRespectOverflow() throws Http2Exception {
|
|
incrementAndGetStreamShouldRespectOverflow(client.remote(), MAX_VALUE - 1);
|
|
}
|
|
|
|
@Test
|
|
public void clientLocalIncrementAndGetStreamShouldRespectOverflow() throws Http2Exception {
|
|
incrementAndGetStreamShouldRespectOverflow(client.local(), MAX_VALUE);
|
|
}
|
|
|
|
@Test
|
|
public void clientLocalCreateStreamExhaustedSpace() throws Http2Exception {
|
|
client.local().createStream(MAX_VALUE, true);
|
|
Http2Exception expected = assertThrows(Http2Exception.class, new Executable() {
|
|
@Override
|
|
public void execute() throws Throwable {
|
|
client.local().createStream(MAX_VALUE, true);
|
|
}
|
|
});
|
|
assertEquals(Http2Error.REFUSED_STREAM, expected.error());
|
|
assertEquals(Http2Exception.ShutdownHint.GRACEFUL_SHUTDOWN, expected.shutdownHint());
|
|
}
|
|
|
|
@Test
|
|
public void newStreamBehindExpectedShouldThrow() throws Http2Exception {
|
|
assertThrows(Http2Exception.class, new Executable() {
|
|
@Override
|
|
public void execute() throws Throwable {
|
|
server.local().createStream(0, true);
|
|
}
|
|
});
|
|
}
|
|
|
|
@Test
|
|
public void newStreamNotForServerShouldThrow() throws Http2Exception {
|
|
assertThrows(Http2Exception.class, new Executable() {
|
|
@Override
|
|
public void execute() throws Throwable {
|
|
server.local().createStream(11, true);
|
|
}
|
|
});
|
|
}
|
|
|
|
@Test
|
|
public void newStreamNotForClientShouldThrow() throws Http2Exception {
|
|
assertThrows(Http2Exception.class, new Executable() {
|
|
@Override
|
|
public void execute() throws Throwable {
|
|
client.local().createStream(10, true);
|
|
}
|
|
});
|
|
}
|
|
|
|
@Test
|
|
public void createShouldThrowWhenMaxAllowedStreamsOpenExceeded() throws Http2Exception {
|
|
server.local().maxActiveStreams(0);
|
|
assertThrows(Http2Exception.class, new Executable() {
|
|
@Override
|
|
public void execute() throws Throwable {
|
|
server.local().createStream(2, true);
|
|
}
|
|
});
|
|
}
|
|
|
|
@Test
|
|
public void serverCreatePushShouldFailOnRemoteEndpointWhenMaxAllowedStreamsExceeded() throws Http2Exception {
|
|
server = new DefaultHttp2Connection(true, 0);
|
|
server.remote().maxActiveStreams(1);
|
|
final Http2Stream requestStream = server.remote().createStream(3, false);
|
|
assertThrows(Http2Exception.class, new Executable() {
|
|
@Override
|
|
public void execute() throws Throwable {
|
|
server.remote().reservePushStream(2, requestStream);
|
|
}
|
|
});
|
|
}
|
|
|
|
@Test
|
|
public void clientCreatePushShouldFailOnRemoteEndpointWhenMaxAllowedStreamsExceeded() throws Http2Exception {
|
|
client = new DefaultHttp2Connection(false, 0);
|
|
client.remote().maxActiveStreams(1);
|
|
final Http2Stream requestStream = client.remote().createStream(2, false);
|
|
assertThrows(Http2Exception.class, new Executable() {
|
|
@Override
|
|
public void execute() throws Throwable {
|
|
client.remote().reservePushStream(4, requestStream);
|
|
}
|
|
});
|
|
}
|
|
|
|
@Test
|
|
public void serverCreatePushShouldSucceedOnLocalEndpointWhenMaxAllowedStreamsExceeded() throws Http2Exception {
|
|
server = new DefaultHttp2Connection(true, 0);
|
|
server.local().maxActiveStreams(1);
|
|
Http2Stream requestStream = server.remote().createStream(3, false);
|
|
assertNotNull(server.local().reservePushStream(2, requestStream));
|
|
}
|
|
|
|
@Test
|
|
public void reserveWithPushDisallowedShouldThrow() throws Http2Exception {
|
|
final Http2Stream stream = server.remote().createStream(3, true);
|
|
server.remote().allowPushTo(false);
|
|
assertThrows(Http2Exception.class, new Executable() {
|
|
@Override
|
|
public void execute() throws Throwable {
|
|
server.local().reservePushStream(2, stream);
|
|
}
|
|
});
|
|
}
|
|
|
|
@Test
|
|
public void goAwayReceivedShouldDisallowLocalCreation() throws Http2Exception {
|
|
server.goAwayReceived(0, 1L, Unpooled.EMPTY_BUFFER);
|
|
assertThrows(Http2Exception.class, new Executable() {
|
|
@Override
|
|
public void execute() throws Throwable {
|
|
server.local().createStream(3, true);
|
|
}
|
|
});
|
|
}
|
|
|
|
@Test
|
|
public void goAwayReceivedShouldAllowRemoteCreation() throws Http2Exception {
|
|
server.goAwayReceived(0, 1L, Unpooled.EMPTY_BUFFER);
|
|
server.remote().createStream(3, true);
|
|
}
|
|
|
|
@Test
|
|
public void goAwaySentShouldDisallowRemoteCreation() throws Http2Exception {
|
|
server.goAwaySent(0, 1L, Unpooled.EMPTY_BUFFER);
|
|
|
|
assertThrows(Http2Exception.class, new Executable() {
|
|
@Override
|
|
public void execute() throws Throwable {
|
|
server.remote().createStream(2, true);
|
|
}
|
|
});
|
|
}
|
|
|
|
@Test
|
|
public void goAwaySentShouldAllowLocalCreation() throws Http2Exception {
|
|
server.goAwaySent(0, 1L, Unpooled.EMPTY_BUFFER);
|
|
server.local().createStream(2, true);
|
|
}
|
|
|
|
@Test
|
|
public void closeShouldSucceed() throws Http2Exception {
|
|
Http2Stream stream = server.remote().createStream(3, true);
|
|
stream.close();
|
|
assertEquals(State.CLOSED, stream.state());
|
|
assertEquals(0, server.numActiveStreams());
|
|
}
|
|
|
|
@Test
|
|
public void closeLocalWhenOpenShouldSucceed() throws Http2Exception {
|
|
Http2Stream stream = server.remote().createStream(3, false);
|
|
stream.closeLocalSide();
|
|
assertEquals(State.HALF_CLOSED_LOCAL, stream.state());
|
|
assertEquals(1, server.numActiveStreams());
|
|
}
|
|
|
|
@Test
|
|
public void closeRemoteWhenOpenShouldSucceed() throws Http2Exception {
|
|
Http2Stream stream = server.remote().createStream(3, false);
|
|
stream.closeRemoteSide();
|
|
assertEquals(State.HALF_CLOSED_REMOTE, stream.state());
|
|
assertEquals(1, server.numActiveStreams());
|
|
}
|
|
|
|
@Test
|
|
public void closeOnlyOpenSideShouldClose() throws Http2Exception {
|
|
Http2Stream stream = server.remote().createStream(3, true);
|
|
stream.closeLocalSide();
|
|
assertEquals(State.CLOSED, stream.state());
|
|
assertEquals(0, server.numActiveStreams());
|
|
}
|
|
|
|
@SuppressWarnings("NumericOverflow")
|
|
@Test
|
|
public void localStreamInvalidStreamIdShouldThrow() {
|
|
assertThrows(Http2Exception.class, new Executable() {
|
|
@Override
|
|
public void execute() throws Throwable {
|
|
client.local().createStream(MAX_VALUE + 2, false);
|
|
}
|
|
});
|
|
}
|
|
|
|
@SuppressWarnings("NumericOverflow")
|
|
@Test
|
|
public void remoteStreamInvalidStreamIdShouldThrow() {
|
|
assertThrows(Http2Exception.class, new Executable() {
|
|
@Override
|
|
public void execute() throws Throwable {
|
|
client.remote().createStream(MAX_VALUE + 1, false);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* We force {@link #clientListener} methods to all throw a {@link RuntimeException} and verify the following:
|
|
* <ol>
|
|
* <li>all listener methods are called for both {@link #clientListener} and {@link #clientListener2}</li>
|
|
* <li>{@link #clientListener2} is notified after {@link #clientListener}</li>
|
|
* <li>{@link #clientListener2} methods are all still called despite {@link #clientListener}'s
|
|
* method throwing a {@link RuntimeException}</li>
|
|
* </ol>
|
|
*/
|
|
@Test
|
|
public void listenerThrowShouldNotPreventOtherListenersFromBeingNotified() throws Http2Exception {
|
|
final boolean[] calledArray = new boolean[128];
|
|
// The following setup will ensure that clientListener throws exceptions, and marks a value in an array
|
|
// such that clientListener2 will verify that is is set or fail the test.
|
|
int methodIndex = 0;
|
|
doAnswer(new ListenerExceptionThrower(calledArray, methodIndex))
|
|
.when(clientListener).onStreamAdded(any(Http2Stream.class));
|
|
doAnswer(new ListenerVerifyCallAnswer(calledArray, methodIndex++))
|
|
.when(clientListener2).onStreamAdded(any(Http2Stream.class));
|
|
|
|
doAnswer(new ListenerExceptionThrower(calledArray, methodIndex))
|
|
.when(clientListener).onStreamActive(any(Http2Stream.class));
|
|
doAnswer(new ListenerVerifyCallAnswer(calledArray, methodIndex++))
|
|
.when(clientListener2).onStreamActive(any(Http2Stream.class));
|
|
|
|
doAnswer(new ListenerExceptionThrower(calledArray, methodIndex))
|
|
.when(clientListener).onStreamHalfClosed(any(Http2Stream.class));
|
|
doAnswer(new ListenerVerifyCallAnswer(calledArray, methodIndex++))
|
|
.when(clientListener2).onStreamHalfClosed(any(Http2Stream.class));
|
|
|
|
doAnswer(new ListenerExceptionThrower(calledArray, methodIndex))
|
|
.when(clientListener).onStreamClosed(any(Http2Stream.class));
|
|
doAnswer(new ListenerVerifyCallAnswer(calledArray, methodIndex++))
|
|
.when(clientListener2).onStreamClosed(any(Http2Stream.class));
|
|
|
|
doAnswer(new ListenerExceptionThrower(calledArray, methodIndex))
|
|
.when(clientListener).onStreamRemoved(any(Http2Stream.class));
|
|
doAnswer(new ListenerVerifyCallAnswer(calledArray, methodIndex++))
|
|
.when(clientListener2).onStreamRemoved(any(Http2Stream.class));
|
|
|
|
doAnswer(new ListenerExceptionThrower(calledArray, methodIndex))
|
|
.when(clientListener).onGoAwaySent(anyInt(), anyLong(), any(ByteBuf.class));
|
|
doAnswer(new ListenerVerifyCallAnswer(calledArray, methodIndex++))
|
|
.when(clientListener2).onGoAwaySent(anyInt(), anyLong(), any(ByteBuf.class));
|
|
|
|
doAnswer(new ListenerExceptionThrower(calledArray, methodIndex))
|
|
.when(clientListener).onGoAwayReceived(anyInt(), anyLong(), any(ByteBuf.class));
|
|
doAnswer(new ListenerVerifyCallAnswer(calledArray, methodIndex++))
|
|
.when(clientListener2).onGoAwayReceived(anyInt(), anyLong(), any(ByteBuf.class));
|
|
|
|
doAnswer(new ListenerExceptionThrower(calledArray, methodIndex))
|
|
.when(clientListener).onStreamAdded(any(Http2Stream.class));
|
|
doAnswer(new ListenerVerifyCallAnswer(calledArray, methodIndex++))
|
|
.when(clientListener2).onStreamAdded(any(Http2Stream.class));
|
|
|
|
// Now we add clientListener2 and exercise all listener functionality
|
|
try {
|
|
client.addListener(clientListener2);
|
|
Http2Stream stream = client.local().createStream(3, false);
|
|
verify(clientListener).onStreamAdded(any(Http2Stream.class));
|
|
verify(clientListener2).onStreamAdded(any(Http2Stream.class));
|
|
verify(clientListener).onStreamActive(any(Http2Stream.class));
|
|
verify(clientListener2).onStreamActive(any(Http2Stream.class));
|
|
|
|
Http2Stream reservedStream = client.remote().reservePushStream(2, stream);
|
|
verify(clientListener, never()).onStreamActive(streamEq(reservedStream));
|
|
verify(clientListener2, never()).onStreamActive(streamEq(reservedStream));
|
|
|
|
reservedStream.open(false);
|
|
verify(clientListener).onStreamActive(streamEq(reservedStream));
|
|
verify(clientListener2).onStreamActive(streamEq(reservedStream));
|
|
|
|
stream.closeLocalSide();
|
|
verify(clientListener).onStreamHalfClosed(any(Http2Stream.class));
|
|
verify(clientListener2).onStreamHalfClosed(any(Http2Stream.class));
|
|
|
|
stream.close();
|
|
verify(clientListener).onStreamClosed(any(Http2Stream.class));
|
|
verify(clientListener2).onStreamClosed(any(Http2Stream.class));
|
|
verify(clientListener).onStreamRemoved(any(Http2Stream.class));
|
|
verify(clientListener2).onStreamRemoved(any(Http2Stream.class));
|
|
|
|
client.goAwaySent(client.connectionStream().id(), Http2Error.INTERNAL_ERROR.code(), Unpooled.EMPTY_BUFFER);
|
|
verify(clientListener).onGoAwaySent(anyInt(), anyLong(), any(ByteBuf.class));
|
|
verify(clientListener2).onGoAwaySent(anyInt(), anyLong(), any(ByteBuf.class));
|
|
|
|
client.goAwayReceived(client.connectionStream().id(),
|
|
Http2Error.INTERNAL_ERROR.code(), Unpooled.EMPTY_BUFFER);
|
|
verify(clientListener).onGoAwayReceived(anyInt(), anyLong(), any(ByteBuf.class));
|
|
verify(clientListener2).onGoAwayReceived(anyInt(), anyLong(), any(ByteBuf.class));
|
|
} finally {
|
|
client.removeListener(clientListener2);
|
|
}
|
|
}
|
|
|
|
private void testRemoveAllStreams() throws InterruptedException {
|
|
final CountDownLatch latch = new CountDownLatch(1);
|
|
final Promise<Void> promise = group.next().newPromise();
|
|
promise.asFuture().addListener(future -> {
|
|
latch.countDown();
|
|
});
|
|
client.close(promise);
|
|
assertTrue(latch.await(5, TimeUnit.SECONDS));
|
|
}
|
|
|
|
private static void incrementAndGetStreamShouldRespectOverflow(final Endpoint<?> endpoint, int streamId) {
|
|
assertTrue(streamId > 0);
|
|
try {
|
|
endpoint.createStream(streamId, true);
|
|
streamId = endpoint.incrementAndGetNextStreamId();
|
|
} catch (Throwable t) {
|
|
fail(t);
|
|
}
|
|
assertTrue(streamId < 0);
|
|
final int finalStreamId = streamId;
|
|
assertThrows(Http2NoMoreStreamIdsException.class, new Executable() {
|
|
@Override
|
|
public void execute() throws Throwable {
|
|
endpoint.createStream(finalStreamId, true);
|
|
}
|
|
});
|
|
}
|
|
|
|
private static void incrementAndGetStreamShouldSucceed(Endpoint<?> endpoint) throws Http2Exception {
|
|
Http2Stream streamA = endpoint.createStream(endpoint.incrementAndGetNextStreamId(), true);
|
|
Http2Stream streamB = endpoint.createStream(streamA.id() + 2, true);
|
|
Http2Stream streamC = endpoint.createStream(endpoint.incrementAndGetNextStreamId(), true);
|
|
assertEquals(streamB.id() + 2, streamC.id());
|
|
endpoint.createStream(streamC.id() + 2, true);
|
|
}
|
|
|
|
private static final class ListenerExceptionThrower implements Answer<Void> {
|
|
private static final RuntimeException FAKE_EXCEPTION = new RuntimeException("Fake Exception");
|
|
private final boolean[] array;
|
|
private final int index;
|
|
|
|
ListenerExceptionThrower(boolean[] array, int index) {
|
|
this.array = array;
|
|
this.index = index;
|
|
}
|
|
|
|
@Override
|
|
public Void answer(InvocationOnMock invocation) throws Throwable {
|
|
array[index] = true;
|
|
throw FAKE_EXCEPTION;
|
|
}
|
|
}
|
|
|
|
private static final class ListenerVerifyCallAnswer implements Answer<Void> {
|
|
private final boolean[] array;
|
|
private final int index;
|
|
|
|
ListenerVerifyCallAnswer(boolean[] array, int index) {
|
|
this.array = array;
|
|
this.index = index;
|
|
}
|
|
|
|
@Override
|
|
public Void answer(InvocationOnMock invocation) throws Throwable {
|
|
assertTrue(array[index]);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
private static <T> T streamEq(T stream) {
|
|
return (T) (stream == null ? ArgumentMatchers.<Http2Stream>isNull() : eq(stream));
|
|
}
|
|
}
|