use of org.eclipse.jetty.http2.frames.DataFrame in project jetty.project by eclipse.
the class FlowControlStrategyTest method testServerTwoDataFramesWithStalledStream.
// TODO
// Since we changed the API to disallow consecutive data() calls without waiting
// for the callback, it is now not possible to have DATA1, DATA2 in the queue for
// the same stream. Perhaps this test should just be deleted.
@Ignore
@Test
public void testServerTwoDataFramesWithStalledStream() throws Exception {
// Frames in queue = DATA1, DATA2.
// Server writes part of DATA1, then stalls.
// A window update unstalls the session, verify that the data is correctly sent.
Random random = new Random();
final byte[] chunk1 = new byte[1024];
random.nextBytes(chunk1);
final byte[] chunk2 = new byte[2048];
random.nextBytes(chunk2);
// Two SETTINGS frames: the initial after the preface,
// and the explicit where we set the stream window size to zero.
final AtomicReference<CountDownLatch> settingsLatch = new AtomicReference<>(new CountDownLatch(2));
final CountDownLatch dataLatch = new CountDownLatch(1);
start(new ServerSessionListener.Adapter() {
@Override
public void onSettings(Session session, SettingsFrame frame) {
settingsLatch.get().countDown();
}
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) {
stream.data(new DataFrame(stream.getId(), ByteBuffer.wrap(chunk1), false), Callback.NOOP);
stream.data(new DataFrame(stream.getId(), ByteBuffer.wrap(chunk2), true), Callback.NOOP);
dataLatch.countDown();
return null;
}
});
Session session = newClient(new Session.Listener.Adapter());
Map<Integer, Integer> settings = new HashMap<>();
settings.put(SettingsFrame.INITIAL_WINDOW_SIZE, 0);
session.settings(new SettingsFrame(settings, false), Callback.NOOP);
Assert.assertTrue(settingsLatch.get().await(5, TimeUnit.SECONDS));
byte[] content = new byte[chunk1.length + chunk2.length];
final ByteBuffer buffer = ByteBuffer.wrap(content);
MetaData.Request metaData = newRequest("GET", new HttpFields());
HeadersFrame requestFrame = new HeadersFrame(metaData, null, true);
final CountDownLatch responseLatch = new CountDownLatch(1);
session.newStream(requestFrame, new Promise.Adapter<>(), new Stream.Listener.Adapter() {
@Override
public void onData(Stream stream, DataFrame frame, Callback callback) {
buffer.put(frame.getData());
callback.succeeded();
if (frame.isEndStream())
responseLatch.countDown();
}
});
Assert.assertTrue(dataLatch.await(5, TimeUnit.SECONDS));
// Now we have the 2 DATA frames queued in the server.
// Unstall the stream window.
settingsLatch.set(new CountDownLatch(1));
settings.clear();
settings.put(SettingsFrame.INITIAL_WINDOW_SIZE, chunk1.length / 2);
session.settings(new SettingsFrame(settings, false), Callback.NOOP);
Assert.assertTrue(settingsLatch.get().await(5, TimeUnit.SECONDS));
Assert.assertTrue(responseLatch.await(5, TimeUnit.SECONDS));
// Check that the data is sent correctly.
byte[] expected = new byte[content.length];
System.arraycopy(chunk1, 0, expected, 0, chunk1.length);
System.arraycopy(chunk2, 0, expected, chunk1.length, chunk2.length);
Assert.assertArrayEquals(expected, content);
}
use of org.eclipse.jetty.http2.frames.DataFrame in project jetty.project by eclipse.
the class FlowControlStrategyTest method testServerFlowControlOneBigWrite.
@Test
public void testServerFlowControlOneBigWrite() throws Exception {
final int windowSize = 1536;
final int length = 5 * windowSize;
final CountDownLatch settingsLatch = new CountDownLatch(1);
start(new ServerSessionListener.Adapter() {
@Override
public void onSettings(Session session, SettingsFrame frame) {
settingsLatch.countDown();
}
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame requestFrame) {
MetaData.Response metaData = new MetaData.Response(HttpVersion.HTTP_2, 200, new HttpFields());
HeadersFrame responseFrame = new HeadersFrame(stream.getId(), metaData, null, false);
CompletableFuture<Void> completable = new CompletableFuture<>();
stream.headers(responseFrame, Callback.from(completable));
completable.thenRun(() -> {
DataFrame dataFrame = new DataFrame(stream.getId(), ByteBuffer.allocate(length), true);
stream.data(dataFrame, Callback.NOOP);
});
return null;
}
});
Session session = newClient(new Session.Listener.Adapter());
Map<Integer, Integer> settings = new HashMap<>();
settings.put(SettingsFrame.INITIAL_WINDOW_SIZE, windowSize);
session.settings(new SettingsFrame(settings, false), Callback.NOOP);
Assert.assertTrue(settingsLatch.await(5, TimeUnit.SECONDS));
final CountDownLatch dataLatch = new CountDownLatch(1);
final Exchanger<Callback> exchanger = new Exchanger<>();
MetaData.Request metaData = newRequest("GET", new HttpFields());
HeadersFrame requestFrame = new HeadersFrame(metaData, null, true);
session.newStream(requestFrame, new Promise.Adapter<>(), new Stream.Listener.Adapter() {
private AtomicInteger dataFrames = new AtomicInteger();
@Override
public void onData(Stream stream, DataFrame frame, Callback callback) {
try {
int dataFrames = this.dataFrames.incrementAndGet();
if (dataFrames == 1 || dataFrames == 2) {
// Do not consume the data frame.
// We should then be flow-control stalled.
exchanger.exchange(callback);
} else if (dataFrames == 3 || dataFrames == 4 || dataFrames == 5) {
// Consume totally.
callback.succeeded();
if (frame.isEndStream())
dataLatch.countDown();
} else {
Assert.fail();
}
} catch (InterruptedException x) {
callback.failed(x);
}
}
});
Callback callback = exchanger.exchange(null, 5, TimeUnit.SECONDS);
checkThatWeAreFlowControlStalled(exchanger);
// Consume the first chunk.
callback.succeeded();
callback = exchanger.exchange(null, 5, TimeUnit.SECONDS);
checkThatWeAreFlowControlStalled(exchanger);
// Consume the second chunk.
callback.succeeded();
Assert.assertTrue(dataLatch.await(5, TimeUnit.SECONDS));
}
use of org.eclipse.jetty.http2.frames.DataFrame in project jetty.project by eclipse.
the class FlowControlStrategyTest method testClientFlowControlOneBigWrite.
@Test
public void testClientFlowControlOneBigWrite() throws Exception {
final int windowSize = 1536;
final Exchanger<Callback> exchanger = new Exchanger<>();
final CountDownLatch settingsLatch = new CountDownLatch(1);
final CountDownLatch dataLatch = new CountDownLatch(1);
start(new ServerSessionListener.Adapter() {
@Override
public Map<Integer, Integer> onPreface(Session session) {
Map<Integer, Integer> settings = new HashMap<>();
settings.put(SettingsFrame.INITIAL_WINDOW_SIZE, windowSize);
return settings;
}
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame requestFrame) {
MetaData.Response metaData = new MetaData.Response(HttpVersion.HTTP_2, 200, new HttpFields());
HeadersFrame responseFrame = new HeadersFrame(stream.getId(), metaData, null, true);
stream.headers(responseFrame, Callback.NOOP);
return new Stream.Listener.Adapter() {
private AtomicInteger dataFrames = new AtomicInteger();
@Override
public void onData(Stream stream, DataFrame frame, Callback callback) {
try {
int dataFrames = this.dataFrames.incrementAndGet();
if (dataFrames == 1 || dataFrames == 2) {
// Do not consume the data frame.
// We should then be flow-control stalled.
exchanger.exchange(callback);
} else if (dataFrames == 3 || dataFrames == 4 || dataFrames == 5) {
// Consume totally.
callback.succeeded();
if (frame.isEndStream())
dataLatch.countDown();
} else {
Assert.fail();
}
} catch (InterruptedException x) {
callback.failed(x);
}
}
};
}
});
Session session = newClient(new Session.Listener.Adapter() {
@Override
public void onSettings(Session session, SettingsFrame frame) {
settingsLatch.countDown();
}
});
Assert.assertTrue(settingsLatch.await(5, TimeUnit.SECONDS));
MetaData.Request metaData = newRequest("GET", new HttpFields());
HeadersFrame requestFrame = new HeadersFrame(metaData, null, false);
FuturePromise<Stream> streamPromise = new FuturePromise<>();
session.newStream(requestFrame, streamPromise, null);
Stream stream = streamPromise.get(5, TimeUnit.SECONDS);
final int length = 5 * windowSize;
DataFrame dataFrame = new DataFrame(stream.getId(), ByteBuffer.allocate(length), true);
stream.data(dataFrame, Callback.NOOP);
Callback callback = exchanger.exchange(null, 5, TimeUnit.SECONDS);
checkThatWeAreFlowControlStalled(exchanger);
// Consume the first chunk.
callback.succeeded();
callback = exchanger.exchange(null, 5, TimeUnit.SECONDS);
checkThatWeAreFlowControlStalled(exchanger);
// Consume the second chunk.
callback.succeeded();
Assert.assertTrue(dataLatch.await(5, TimeUnit.SECONDS));
}
use of org.eclipse.jetty.http2.frames.DataFrame in project jetty.project by eclipse.
the class FlowControlStrategyTest method testSessionStalledStallsNewStreams.
@Test
public void testSessionStalledStallsNewStreams() throws Exception {
final int windowSize = 1024;
start(new ServerSessionListener.Adapter() {
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame requestFrame) {
MetaData.Request request = (MetaData.Request) requestFrame.getMetaData();
if ("POST".equalsIgnoreCase(request.getMethod())) {
// Send data to consume most of the session window.
ByteBuffer data = ByteBuffer.allocate(FlowControlStrategy.DEFAULT_WINDOW_SIZE - windowSize);
DataFrame dataFrame = new DataFrame(stream.getId(), data, true);
stream.data(dataFrame, Callback.NOOP);
return null;
} else {
// For every stream, send down half the window size of data.
MetaData.Response metaData = new MetaData.Response(HttpVersion.HTTP_2, 200, new HttpFields());
HeadersFrame responseFrame = new HeadersFrame(stream.getId(), metaData, null, false);
Callback.Completable completable = new Callback.Completable();
stream.headers(responseFrame, completable);
completable.thenRun(() -> {
DataFrame dataFrame = new DataFrame(stream.getId(), ByteBuffer.allocate(windowSize / 2), true);
stream.data(dataFrame, Callback.NOOP);
});
return null;
}
}
});
Session session = newClient(new Session.Listener.Adapter());
// First request is just to consume most of the session window.
final List<Callback> callbacks1 = new ArrayList<>();
final CountDownLatch prepareLatch = new CountDownLatch(1);
MetaData.Request request1 = newRequest("POST", new HttpFields());
session.newStream(new HeadersFrame(request1, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() {
@Override
public void onData(Stream stream, DataFrame frame, Callback callback) {
// Do not consume the data to reduce the session window.
callbacks1.add(callback);
if (frame.isEndStream())
prepareLatch.countDown();
}
});
Assert.assertTrue(prepareLatch.await(5, TimeUnit.SECONDS));
// Second request will consume half of the remaining the session window.
MetaData.Request request2 = newRequest("GET", new HttpFields());
session.newStream(new HeadersFrame(request2, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() {
@Override
public void onData(Stream stream, DataFrame frame, Callback callback) {
// Do not consume it to stall flow control.
}
});
// Third request will consume the whole session window, which is now stalled.
// A fourth request will not be able to receive data.
MetaData.Request request3 = newRequest("GET", new HttpFields());
session.newStream(new HeadersFrame(request3, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() {
@Override
public void onData(Stream stream, DataFrame frame, Callback callback) {
// Do not consume it to stall flow control.
}
});
// Fourth request is now stalled.
final CountDownLatch latch = new CountDownLatch(1);
MetaData.Request request4 = newRequest("GET", new HttpFields());
session.newStream(new HeadersFrame(request4, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() {
@Override
public void onData(Stream stream, DataFrame frame, Callback callback) {
callback.succeeded();
if (frame.isEndStream())
latch.countDown();
}
});
// Verify that the data does not arrive because the server session is stalled.
Assert.assertFalse(latch.await(1, TimeUnit.SECONDS));
// This will open up the session window, allowing the fourth stream to send data.
for (Callback callback : callbacks1) callback.succeeded();
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
}
use of org.eclipse.jetty.http2.frames.DataFrame in project jetty.project by eclipse.
the class FlowControlStrategyTest method testClientSendingInitialSmallWindow.
@Test
public void testClientSendingInitialSmallWindow() throws Exception {
start(new ServerSessionListener.Adapter() {
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) {
MetaData metaData = new MetaData.Response(HttpVersion.HTTP_2, 200, new HttpFields());
HeadersFrame responseFrame = new HeadersFrame(stream.getId(), metaData, null, false);
Callback.Completable completable = new Callback.Completable();
stream.headers(responseFrame, completable);
return new Stream.Listener.Adapter() {
@Override
public void onData(Stream stream, DataFrame frame, Callback callback) {
// Since we echo back the data
// asynchronously we must copy it.
ByteBuffer data = frame.getData();
ByteBuffer copy = ByteBuffer.allocateDirect(data.remaining());
copy.put(data).flip();
completable.thenRun(() -> stream.data(new DataFrame(stream.getId(), copy, frame.isEndStream()), callback));
}
};
}
});
final int initialWindow = 16;
Session session = newClient(new Session.Listener.Adapter() {
@Override
public Map<Integer, Integer> onPreface(Session session) {
Map<Integer, Integer> settings = new HashMap<>();
settings.put(SettingsFrame.INITIAL_WINDOW_SIZE, initialWindow);
return settings;
}
});
byte[] requestData = new byte[initialWindow * 4];
new Random().nextBytes(requestData);
byte[] responseData = new byte[requestData.length];
final ByteBuffer responseContent = ByteBuffer.wrap(responseData);
MetaData.Request metaData = newRequest("GET", new HttpFields());
HeadersFrame requestFrame = new HeadersFrame(metaData, null, false);
Promise.Completable<Stream> completable = new Promise.Completable<>();
final CountDownLatch latch = new CountDownLatch(1);
session.newStream(requestFrame, completable, new Stream.Listener.Adapter() {
@Override
public void onData(Stream stream, DataFrame frame, Callback callback) {
responseContent.put(frame.getData());
callback.succeeded();
if (frame.isEndStream())
latch.countDown();
}
});
completable.thenAccept(stream -> {
ByteBuffer requestContent = ByteBuffer.wrap(requestData);
DataFrame dataFrame = new DataFrame(stream.getId(), requestContent, true);
stream.data(dataFrame, Callback.NOOP);
});
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
responseContent.flip();
Assert.assertArrayEquals(requestData, responseData);
}
Aggregations