diff --git a/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/IdleTimeoutTest.java b/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/IdleTimeoutTest.java index b33d93a4095..927d7b2c714 100644 --- a/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/IdleTimeoutTest.java +++ b/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/IdleTimeoutTest.java @@ -467,7 +467,7 @@ public class IdleTimeoutTest extends AbstractTest final Stream stream = promise.get(5, TimeUnit.SECONDS); sleep(idleTimeout / 2); - final CountDownLatch latch = new CountDownLatch(1); + final CountDownLatch dataLatch = new CountDownLatch(1); stream.data(new DataFrame(stream.getId(), ByteBuffer.allocate(1), false), new Callback.Adapter() { private int sends; @@ -482,16 +482,62 @@ public class IdleTimeoutTest extends AbstractTest @Override public void succeeded() { - latch.countDown(); + dataLatch.countDown(); } }); } }); - Assert.assertTrue(latch.await(2 * idleTimeout, TimeUnit.MILLISECONDS)); + Assert.assertTrue(dataLatch.await(2 * idleTimeout, TimeUnit.MILLISECONDS)); Assert.assertFalse(timeoutLatch.await(0, TimeUnit.SECONDS)); } + @Test + public void testStreamIdleTimeoutIsNotEnforcedWhenSending() throws Exception + { + final CountDownLatch resetLatch = new CountDownLatch(1); + startServer(new ServerSessionListener.Adapter() + { + @Override + public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) + { + MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_2, 200, new HttpFields()); + stream.headers(new HeadersFrame(stream.getId(), response, null, true), Callback.Adapter.INSTANCE); + return null; + } + + @Override + public void onReset(Session session, ResetFrame frame) + { + resetLatch.countDown(); + } + }); + + Session session = newClient(new Session.Listener.Adapter()); + MetaData.Request metaData = newRequest("GET", new HttpFields()); + HeadersFrame requestFrame = new HeadersFrame(0, metaData, null, false); + FuturePromise promise = new FuturePromise() + { + @Override + public void succeeded(Stream stream) + { + stream.setIdleTimeout(idleTimeout); + super.succeeded(stream); + } + }; + session.newStream(requestFrame, promise, new Stream.Listener.Adapter()); + final Stream stream = promise.get(5, TimeUnit.SECONDS); + + sleep(idleTimeout / 2); + stream.data(new DataFrame(stream.getId(), ByteBuffer.allocate(1), false), Callback.Adapter.INSTANCE); + sleep(idleTimeout / 2); + stream.data(new DataFrame(stream.getId(), ByteBuffer.allocate(1), false), Callback.Adapter.INSTANCE); + sleep(idleTimeout / 2); + stream.data(new DataFrame(stream.getId(), ByteBuffer.allocate(1), true), Callback.Adapter.INSTANCE); + + Assert.assertFalse(resetLatch.await(0, TimeUnit.SECONDS)); + } + private void sleep(long value) { try diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Stream.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Stream.java index 20b8f36f0fe..cb8f6658015 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Stream.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Stream.java @@ -72,24 +72,28 @@ public class HTTP2Stream extends IdleTimeout implements IStream @Override public void headers(HeadersFrame frame, Callback callback) { + notIdle(); session.control(this, callback, frame, Frame.EMPTY_ARRAY); } @Override public void push(PushPromiseFrame frame, Promise promise) { + notIdle(); session.push(this, promise, frame); } @Override public void data(DataFrame frame, Callback callback) { + notIdle(); session.data(this, callback, frame); } @Override public void reset(ResetFrame frame, Callback callback) { + notIdle(); session.control(this, callback, frame, Frame.EMPTY_ARRAY); } @@ -176,39 +180,23 @@ public class HTTP2Stream extends IdleTimeout implements IStream public boolean process(Frame frame, Callback callback) { notIdle(); - switch (frame.getType()) { - case DATA: - { - // TODO: handle cases where: - // TODO: A) stream already remotely close. - // TODO: B) DATA before HEADERS. - - if (getRecvWindow() < 0) - { - // It's a bad client, it does not deserve to be - // treated gently by just resetting the stream. - session.close(ErrorCodes.FLOW_CONTROL_ERROR, "stream_window_exceeded", callback); - return false; - } - - notifyData(this, (DataFrame)frame, callback); - return false; - } case HEADERS: { - // TODO: handle case where HEADERS after DATA. - return false; + return onHeaders((HeadersFrame)frame, callback); + } + case DATA: + { + return onData((DataFrame)frame, callback); } case RST_STREAM: { - reset = true; - return false; + return onReset((ResetFrame)frame, callback); } case PUSH_PROMISE: { - return false; + return onPush((PushPromiseFrame)frame, callback); } default: { @@ -217,6 +205,46 @@ public class HTTP2Stream extends IdleTimeout implements IStream } } + private boolean onHeaders(HeadersFrame frame, Callback callback) + { + // TODO: handle case where HEADERS after DATA. + callback.succeeded(); + return false; + } + + private boolean onData(DataFrame frame, Callback callback) + { + // TODO: handle cases where: + // TODO: A) stream already remotely close. + // TODO: B) DATA before HEADERS. + + if (getRecvWindow() < 0) + { + // It's a bad client, it does not deserve to be + // treated gently by just resetting the stream. + session.close(ErrorCodes.FLOW_CONTROL_ERROR, "stream_window_exceeded", callback); + return true; + } + else + { + notifyData(this, frame, callback); + return false; + } + } + + private boolean onReset(ResetFrame frame, Callback callback) + { + reset = true; + callback.succeeded(); + return false; + } + + private boolean onPush(PushPromiseFrame frame, Callback callback) + { + callback.succeeded(); + return false; + } + @Override public void updateClose(boolean update, boolean local) {