use of org.eclipse.jetty.http2.frames.DataFrame in project jetty.project by eclipse.
the class FlowControlStrategyTest method testClientExceedingStreamWindow.
@Test
public void testClientExceedingStreamWindow() throws Exception {
// On server, we don't consume the data.
start(new ServerSessionListener.Adapter() {
@Override
public Map<Integer, Integer> onPreface(Session session) {
// Enlarge the session window.
((ISession) session).updateRecvWindow(FlowControlStrategy.DEFAULT_WINDOW_SIZE);
return super.onPreface(session);
}
});
final CountDownLatch closeLatch = new CountDownLatch(1);
Session session = newClient(new Session.Listener.Adapter() {
@Override
public void onClose(Session session, GoAwayFrame frame) {
if (frame.getError() == ErrorCode.FLOW_CONTROL_ERROR.code)
closeLatch.countDown();
}
});
// Consume the whole stream window.
MetaData.Request metaData = newRequest("POST", new HttpFields());
HeadersFrame requestFrame = new HeadersFrame(metaData, null, false);
FuturePromise<Stream> streamPromise = new FuturePromise<>();
session.newStream(requestFrame, streamPromise, new Stream.Listener.Adapter());
Stream stream = streamPromise.get(5, TimeUnit.SECONDS);
ByteBuffer data = ByteBuffer.allocate(FlowControlStrategy.DEFAULT_WINDOW_SIZE);
final CountDownLatch dataLatch = new CountDownLatch(1);
stream.data(new DataFrame(stream.getId(), data, false), new Callback() {
@Override
public InvocationType getInvocationType() {
return InvocationType.NON_BLOCKING;
}
@Override
public void succeeded() {
dataLatch.countDown();
}
});
Assert.assertTrue(dataLatch.await(5, TimeUnit.SECONDS));
// Wait for a while before doing the "sneaky" write
// below, see comments in the previous test case.
Thread.sleep(1000);
// Now the client is supposed to not send more frames.
// If it does, the connection must be closed.
HTTP2Session http2Session = (HTTP2Session) session;
ByteBufferPool.Lease lease = new ByteBufferPool.Lease(connector.getByteBufferPool());
ByteBuffer extraData = ByteBuffer.allocate(1024);
http2Session.getGenerator().data(lease, new DataFrame(stream.getId(), extraData, true), extraData.remaining());
List<ByteBuffer> buffers = lease.getByteBuffers();
http2Session.getEndPoint().write(Callback.NOOP, buffers.toArray(new ByteBuffer[buffers.size()]));
// Expect the connection to be closed.
Assert.assertTrue(closeLatch.await(5, TimeUnit.SECONDS));
}
use of org.eclipse.jetty.http2.frames.DataFrame in project jetty.project by eclipse.
the class HTTP2Test method testInvalidAPIUsageOnServer.
@Test
public void testInvalidAPIUsageOnServer() throws Exception {
long sleep = 1000;
CountDownLatch completeLatch = new CountDownLatch(2);
start(new ServerSessionListener.Adapter() {
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) {
MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, new HttpFields());
DataFrame dataFrame = new DataFrame(stream.getId(), BufferUtil.EMPTY_BUFFER, true);
// The call to headers() is legal, but slow.
new Thread(() -> {
stream.headers(new HeadersFrame(stream.getId(), response, null, false) {
@Override
public MetaData getMetaData() {
sleep(2 * sleep);
return super.getMetaData();
}
}, new Callback() {
@Override
public void succeeded() {
stream.data(dataFrame, NOOP);
}
});
}).start();
// Wait for the headers() call to happen.
sleep(sleep);
// This data call is illegal because it does not
// wait for the previous callback to complete.
stream.data(dataFrame, new Callback() {
@Override
public void failed(Throwable x) {
if (x instanceof WritePendingException) {
// Expected.
completeLatch.countDown();
}
}
});
return null;
}
});
Session session = newClient(new Session.Listener.Adapter());
MetaData.Request metaData = newRequest("GET", new HttpFields());
HeadersFrame frame = new HeadersFrame(metaData, null, true);
session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener.Adapter() {
@Override
public void onData(Stream stream, DataFrame frame, Callback callback) {
callback.succeeded();
if (frame.isEndStream())
completeLatch.countDown();
}
});
Assert.assertTrue(completeLatch.await(5, TimeUnit.SECONDS));
}
use of org.eclipse.jetty.http2.frames.DataFrame in project jetty.project by eclipse.
the class IdleTimeoutTest method testBufferedReadsResetStreamIdleTimeout.
@Test
public void testBufferedReadsResetStreamIdleTimeout() throws Exception {
int bufferSize = 8192;
long delay = 1000;
start(new HttpServlet() {
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
ServletInputStream input = request.getInputStream();
byte[] buffer = new byte[bufferSize];
while (true) {
int read = input.read(buffer);
Log.getLogger(IdleTimeoutTest.class).info("Read {} bytes", read);
if (read < 0)
break;
sleep(delay);
}
}
});
connector.setIdleTimeout(2 * delay);
Session session = newClient(new Session.Listener.Adapter());
MetaData.Request metaData = newRequest("POST", new HttpFields());
HeadersFrame requestFrame = new HeadersFrame(metaData, null, false);
FuturePromise<Stream> promise = new FuturePromise<>();
CountDownLatch latch = new CountDownLatch(1);
session.newStream(requestFrame, promise, new Stream.Listener.Adapter() {
@Override
public void onHeaders(Stream stream, HeadersFrame frame) {
if (frame.isEndStream())
latch.countDown();
}
});
Stream stream = promise.get(5, TimeUnit.SECONDS);
// Send data larger than the flow control window.
// The client will send bytes up to the flow control window immediately
// and they will be buffered by the server; the Servlet will consume them slowly.
// Servlet reads should reset the idle timeout.
int contentLength = FlowControlStrategy.DEFAULT_WINDOW_SIZE + 1;
ByteBuffer data = ByteBuffer.allocate(contentLength);
stream.data(new DataFrame(stream.getId(), data, true), Callback.NOOP);
Assert.assertTrue(latch.await(2 * (contentLength / bufferSize + 1) * delay, TimeUnit.MILLISECONDS));
}
use of org.eclipse.jetty.http2.frames.DataFrame in project jetty.project by eclipse.
the class IdleTimeoutTest method testClientEnforcingStreamIdleTimeout.
@Test
public void testClientEnforcingStreamIdleTimeout() throws Exception {
final int idleTimeout = 1000;
start(new HttpServlet() {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
sleep(2 * idleTimeout);
}
});
Session session = newClient(new Session.Listener.Adapter());
final CountDownLatch dataLatch = new CountDownLatch(1);
final CountDownLatch timeoutLatch = new CountDownLatch(1);
MetaData.Request metaData = newRequest("GET", new HttpFields());
HeadersFrame requestFrame = new HeadersFrame(metaData, null, true);
session.newStream(requestFrame, new Promise.Adapter<Stream>() {
@Override
public void succeeded(Stream stream) {
stream.setIdleTimeout(idleTimeout);
}
}, new Stream.Listener.Adapter() {
@Override
public void onData(Stream stream, DataFrame frame, Callback callback) {
callback.succeeded();
dataLatch.countDown();
}
@Override
public boolean onIdleTimeout(Stream stream, Throwable x) {
Assert.assertThat(x, Matchers.instanceOf(TimeoutException.class));
timeoutLatch.countDown();
return true;
}
});
Assert.assertTrue(timeoutLatch.await(5, TimeUnit.SECONDS));
// We must not receive any DATA frame.
Assert.assertFalse(dataLatch.await(2 * idleTimeout, TimeUnit.MILLISECONDS));
// Stream must be gone.
Assert.assertTrue(session.getStreams().isEmpty());
// Session must not be closed, nor disconnected.
Assert.assertFalse(session.isClosed());
Assert.assertFalse(((HTTP2Session) session).isDisconnected());
}
use of org.eclipse.jetty.http2.frames.DataFrame in project jetty.project by eclipse.
the class InterleavingTest method testInterleaving.
@Test
public void testInterleaving() throws Exception {
CountDownLatch serverStreamsLatch = new CountDownLatch(2);
List<Stream> serverStreams = new ArrayList<>();
start(new ServerSessionListener.Adapter() {
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) {
serverStreams.add(stream);
serverStreamsLatch.countDown();
return null;
}
});
int maxFrameSize = Frame.DEFAULT_MAX_LENGTH + 1;
Session session = newClient(new Session.Listener.Adapter() {
@Override
public Map<Integer, Integer> onPreface(Session session) {
Map<Integer, Integer> settings = new HashMap<>();
settings.put(SettingsFrame.MAX_FRAME_SIZE, maxFrameSize);
return settings;
}
});
BlockingQueue<FrameBytesCallback> dataFrames = new LinkedBlockingDeque<>();
Stream.Listener streamListener = new Stream.Listener.Adapter() {
@Override
public void onData(Stream stream, DataFrame frame, Callback callback) {
ByteBuffer data = frame.getData();
byte[] bytes = new byte[data.remaining()];
data.get(bytes);
dataFrames.offer(new FrameBytesCallback(frame, bytes, callback));
}
};
HeadersFrame headersFrame1 = new HeadersFrame(newRequest("GET", new HttpFields()), null, true);
FuturePromise<Stream> streamPromise1 = new FuturePromise<>();
session.newStream(headersFrame1, streamPromise1, streamListener);
streamPromise1.get(5, TimeUnit.SECONDS);
HeadersFrame headersFrame2 = new HeadersFrame(newRequest("GET", new HttpFields()), null, true);
FuturePromise<Stream> streamPromise2 = new FuturePromise<>();
session.newStream(headersFrame2, streamPromise2, streamListener);
streamPromise2.get(5, TimeUnit.SECONDS);
Assert.assertTrue(serverStreamsLatch.await(5, TimeUnit.SECONDS));
Thread.sleep(1000);
Stream serverStream1 = serverStreams.get(0);
Stream serverStream2 = serverStreams.get(1);
MetaData.Response response1 = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, new HttpFields(), 0);
serverStream1.headers(new HeadersFrame(serverStream1.getId(), response1, null, false), Callback.NOOP);
Random random = new Random();
byte[] content1 = new byte[2 * ((ISession) serverStream1.getSession()).updateSendWindow(0)];
random.nextBytes(content1);
byte[] content2 = new byte[2 * ((ISession) serverStream2.getSession()).updateSendWindow(0)];
random.nextBytes(content2);
MetaData.Response response2 = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, new HttpFields(), 0);
serverStream2.headers(new HeadersFrame(serverStream2.getId(), response2, null, false), new Callback() {
@Override
public void succeeded() {
// Write data for both streams from within the callback so that they get queued together.
ByteBuffer buffer1 = ByteBuffer.wrap(content1);
serverStream1.data(new DataFrame(serverStream1.getId(), buffer1, true), NOOP);
ByteBuffer buffer2 = ByteBuffer.wrap(content2);
serverStream2.data(new DataFrame(serverStream2.getId(), buffer2, true), NOOP);
}
});
// The client reads with a buffer size that is different from the
// frame size and synthesizes DATA frames, so expect N frames for
// stream1 up to maxFrameSize of data, then M frames for stream2
// up to maxFrameSize of data, and so forth, interleaved.
Map<Integer, ByteArrayOutputStream> contents = new HashMap<>();
contents.put(serverStream1.getId(), new ByteArrayOutputStream());
contents.put(serverStream2.getId(), new ByteArrayOutputStream());
List<StreamLength> streamLengths = new ArrayList<>();
int finished = 0;
while (finished < 2) {
FrameBytesCallback frameBytesCallback = dataFrames.poll(5, TimeUnit.SECONDS);
if (frameBytesCallback == null)
Assert.fail();
DataFrame dataFrame = frameBytesCallback.frame;
int streamId = dataFrame.getStreamId();
int length = dataFrame.remaining();
streamLengths.add(new StreamLength(streamId, length));
if (dataFrame.isEndStream())
++finished;
contents.get(streamId).write(frameBytesCallback.bytes);
frameBytesCallback.callback.succeeded();
}
// Verify that the content has been sent properly.
Assert.assertArrayEquals(content1, contents.get(serverStream1.getId()).toByteArray());
Assert.assertArrayEquals(content2, contents.get(serverStream2.getId()).toByteArray());
// Verify that the interleaving is correct.
Map<Integer, List<Integer>> groups = new HashMap<>();
groups.put(serverStream1.getId(), new ArrayList<>());
groups.put(serverStream2.getId(), new ArrayList<>());
int currentStream = 0;
int currentLength = 0;
for (StreamLength streamLength : streamLengths) {
if (currentStream == 0)
currentStream = streamLength.stream;
if (currentStream != streamLength.stream) {
groups.get(currentStream).add(currentLength);
currentStream = streamLength.stream;
currentLength = 0;
}
currentLength += streamLength.length;
}
groups.get(currentStream).add(currentLength);
Logger logger = Log.getLogger(getClass());
logger.debug("frame lengths = {}", streamLengths);
groups.forEach((stream, lengths) -> {
logger.debug("stream {} interleaved lengths = {}", stream, lengths);
for (Integer length : lengths) Assert.assertThat(length, Matchers.lessThanOrEqualTo(maxFrameSize));
});
}
Aggregations