/* * Copyright (c) 2012 the original author or authors. * * Licensed 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 org.eclipse.jetty.spdy; import java.io.ByteArrayOutputStream; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.util.Arrays; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import org.eclipse.jetty.spdy.api.BytesDataInfo; import org.eclipse.jetty.spdy.api.DataInfo; import org.eclipse.jetty.spdy.api.Handler; import org.eclipse.jetty.spdy.api.Headers; import org.eclipse.jetty.spdy.api.ReplyInfo; import org.eclipse.jetty.spdy.api.RstInfo; import org.eclipse.jetty.spdy.api.Session; import org.eclipse.jetty.spdy.api.SessionFrameListener; import org.eclipse.jetty.spdy.api.Stream; import org.eclipse.jetty.spdy.api.StreamFrameListener; import org.eclipse.jetty.spdy.api.StreamStatus; import org.eclipse.jetty.spdy.api.StringDataInfo; import org.eclipse.jetty.spdy.api.SynInfo; import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener; import org.junit.Assert; import org.junit.Test; public class SynReplyTest extends AbstractTest { @Test public void testSynReply() throws Exception { final AtomicReference<Session> sessionRef = new AtomicReference<>(); final CountDownLatch sessionLatch = new CountDownLatch(1); final CountDownLatch synLatch = new CountDownLatch(1); ServerSessionFrameListener serverSessionFrameListener = new ServerSessionFrameListener.Adapter() { @Override public void onConnect(Session session) { sessionRef.set(session); sessionLatch.countDown(); } @Override public StreamFrameListener onSyn(Stream stream, SynInfo synInfo) { Assert.assertTrue(stream.isHalfClosed()); stream.reply(new ReplyInfo(new Headers(), true)); synLatch.countDown(); return null; } }; Session session = startClient(startServer(serverSessionFrameListener), null); Assert.assertTrue(sessionLatch.await(5, TimeUnit.SECONDS)); Session serverSession = sessionRef.get(); Assert.assertNotNull(serverSession); final CountDownLatch streamCreatedLatch = new CountDownLatch(1); final CountDownLatch streamRemovedLatch = new CountDownLatch(1); session.addListener(new Session.StreamListener() { @Override public void onStreamCreated(Stream stream) { streamCreatedLatch.countDown(); } @Override public void onStreamClosed(Stream stream) { streamRemovedLatch.countDown(); } }); final CountDownLatch replyLatch = new CountDownLatch(1); Stream stream = session.syn(new SynInfo(new Headers(), true), new StreamFrameListener.Adapter() { @Override public void onReply(Stream stream, ReplyInfo replyInfo) { Assert.assertTrue(stream.isClosed()); replyLatch.countDown(); } }).get(5, TimeUnit.SECONDS); Assert.assertTrue(synLatch.await(5, TimeUnit.SECONDS)); Assert.assertTrue(streamCreatedLatch.await(5, TimeUnit.SECONDS)); Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS)); Assert.assertTrue(stream.isClosed()); Assert.assertTrue(streamRemovedLatch.await(5, TimeUnit.SECONDS)); Assert.assertEquals(0, session.getStreams().size()); } @Test public void testSynDataReply() throws Exception { final byte[] dataBytes = "foo".getBytes(Charset.forName("UTF-8")); final CountDownLatch synLatch = new CountDownLatch(1); final CountDownLatch dataLatch = new CountDownLatch(1); ServerSessionFrameListener serverSessionFrameListener = new ServerSessionFrameListener.Adapter() { @Override public StreamFrameListener onSyn(Stream stream, SynInfo synInfo) { Assert.assertFalse(stream.isHalfClosed()); Assert.assertFalse(stream.isClosed()); synLatch.countDown(); return new StreamFrameListener.Adapter() { @Override public void onData(Stream stream, DataInfo dataInfo) { ByteArrayOutputStream bytes = new ByteArrayOutputStream(); ByteBuffer buffer = ByteBuffer.allocate(2); while (dataInfo.available() > 0) { dataInfo.readInto(buffer); buffer.flip(); bytes.write(buffer.array(), buffer.arrayOffset(), buffer.remaining()); buffer.clear(); } Assert.assertTrue(Arrays.equals(dataBytes, bytes.toByteArray())); Assert.assertTrue(stream.isHalfClosed()); Assert.assertFalse(stream.isClosed()); stream.reply(new ReplyInfo(true)); Assert.assertTrue(stream.isClosed()); dataLatch.countDown(); } }; } }; Session session = startClient(startServer(serverSessionFrameListener), null); final CountDownLatch streamRemovedLatch = new CountDownLatch(1); session.addListener(new Session.StreamListener.Adapter() { @Override public void onStreamClosed(Stream stream) { streamRemovedLatch.countDown(); } }); final CountDownLatch replyLatch = new CountDownLatch(1); Stream stream = session.syn(new SynInfo(false), new StreamFrameListener.Adapter() { @Override public void onReply(Stream stream, ReplyInfo replyInfo) { replyLatch.countDown(); } }).get(5, TimeUnit.SECONDS); stream.data(new BytesDataInfo(dataBytes, true)); Assert.assertTrue(synLatch.await(5, TimeUnit.SECONDS)); Assert.assertTrue(dataLatch.await(5, TimeUnit.SECONDS)); Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS)); Assert.assertTrue(streamRemovedLatch.await(5, TimeUnit.SECONDS)); Assert.assertEquals(0, session.getStreams().size()); } @Test public void testSynReplyDataData() throws Exception { final String data1 = "foo"; final String data2 = "bar"; Session session = startClient(startServer(new ServerSessionFrameListener.Adapter() { @Override public StreamFrameListener onSyn(final Stream stream, SynInfo synInfo) { Assert.assertTrue(stream.isHalfClosed()); stream.reply(new ReplyInfo(false)); stream.data(new StringDataInfo(data1, false), 5, TimeUnit.SECONDS, new Handler.Adapter<Void>() { @Override public void completed(Void context) { stream.data(new StringDataInfo(data2, true)); } }); return null; } }), null); final CountDownLatch replyLatch = new CountDownLatch(1); final CountDownLatch dataLatch1 = new CountDownLatch(1); final CountDownLatch dataLatch2 = new CountDownLatch(1); session.syn(new SynInfo(true), new StreamFrameListener.Adapter() { private AtomicInteger dataCount = new AtomicInteger(); @Override public void onReply(Stream stream, ReplyInfo replyInfo) { Assert.assertFalse(replyInfo.isClose()); replyLatch.countDown(); } @Override public void onData(Stream stream, DataInfo dataInfo) { int dataCount = this.dataCount.incrementAndGet(); if (dataCount == 1) { String chunk1 = dataInfo.asString("UTF-8", true); Assert.assertEquals(data1, chunk1); dataLatch1.countDown(); } else if (dataCount == 2) { String chunk2 = dataInfo.asString("UTF-8", true); Assert.assertEquals(data2, chunk2); dataLatch2.countDown(); } } }); Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS)); Assert.assertTrue(dataLatch1.await(5, TimeUnit.SECONDS)); Assert.assertTrue(dataLatch2.await(5, TimeUnit.SECONDS)); } @Test public void testServerSynDataReplyData() throws Exception { final String serverData = "server"; final String clientData = "client"; final CountDownLatch replyLatch = new CountDownLatch(1); final CountDownLatch clientDataLatch = new CountDownLatch(1); ServerSessionFrameListener serverSessionFrameListener = new ServerSessionFrameListener.Adapter() { @Override public void onConnect(Session session) { session.syn(new SynInfo(false), new StreamFrameListener.Adapter() { @Override public void onReply(Stream stream, ReplyInfo replyInfo) { replyLatch.countDown(); } @Override public void onData(Stream stream, DataInfo dataInfo) { String data = dataInfo.asString("UTF-8", true); Assert.assertEquals(clientData, data); clientDataLatch.countDown(); } }, 0, TimeUnit.MILLISECONDS, new Handler.Adapter<Stream>() { @Override public void completed(Stream stream) { stream.data(new StringDataInfo(serverData, true)); } }); } }; final CountDownLatch synLatch = new CountDownLatch(1); final CountDownLatch serverDataLatch = new CountDownLatch(1); SessionFrameListener clientSessionFrameListener = new SessionFrameListener.Adapter() { @Override public StreamFrameListener onSyn(Stream stream, SynInfo synInfo) { Assert.assertEquals(0, stream.getId() % 2); stream.reply(new ReplyInfo(false)); stream.data(new StringDataInfo(clientData, true)); synLatch.countDown(); return new StreamFrameListener.Adapter() { @Override public void onData(Stream stream, DataInfo dataInfo) { ByteBuffer buffer = dataInfo.asByteBuffer(false); String data = Charset.forName("UTF-8").decode(buffer).toString(); Assert.assertEquals(serverData, data); serverDataLatch.countDown(); } }; } }; startClient(startServer(serverSessionFrameListener), clientSessionFrameListener); Assert.assertTrue(synLatch.await(5, TimeUnit.SECONDS)); Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS)); Assert.assertTrue(serverDataLatch.await(5, TimeUnit.SECONDS)); Assert.assertTrue(clientDataLatch.await(5, TimeUnit.SECONDS)); } @Test public void testSynDataRst() throws Exception { final AtomicReference<RstInfo> ref = new AtomicReference<>(); final CountDownLatch latch = new CountDownLatch(1); ServerSessionFrameListener serverSessionFrameListener = new ServerSessionFrameListener.Adapter() { @Override public StreamFrameListener onSyn(Stream stream, SynInfo synInfo) { // Do not send the reply, we expect a RST_STREAM stream.data(new StringDataInfo("foo", true)); return null; } @Override public void onRst(Session session, RstInfo rstInfo) { ref.set(rstInfo); latch.countDown(); } }; Session session = startClient(startServer(serverSessionFrameListener), null); Stream stream = session.syn(new SynInfo(true), null).get(5, TimeUnit.SECONDS); Assert.assertTrue(latch.await(5, TimeUnit.SECONDS)); RstInfo rstInfo = ref.get(); Assert.assertNotNull(rstInfo); Assert.assertEquals(stream.getId(), rstInfo.getStreamId()); Assert.assertSame(StreamStatus.PROTOCOL_ERROR, rstInfo.getStreamStatus()); } @Test public void testSynReplyDataSynReplyData() throws Exception { final String data = "foo"; ServerSessionFrameListener serverSessionFrameListener = new ServerSessionFrameListener.Adapter() { @Override public StreamFrameListener onSyn(Stream stream, SynInfo synInfo) { Assert.assertTrue(stream.isHalfClosed()); stream.reply(new ReplyInfo(false)); stream.data(new StringDataInfo(data, true)); return null; } }; Session session = startClient(startServer(serverSessionFrameListener), null); final CountDownLatch replyLatch = new CountDownLatch(2); final CountDownLatch dataLatch = new CountDownLatch(2); StreamFrameListener clientStreamFrameListener = new StreamFrameListener.Adapter() { @Override public void onReply(Stream stream, ReplyInfo replyInfo) { Assert.assertFalse(replyInfo.isClose()); replyLatch.countDown(); } @Override public void onData(Stream stream, DataInfo dataInfo) { String chunk = dataInfo.asString("UTF-8", true); Assert.assertEquals(data, chunk); dataLatch.countDown(); } }; session.syn(new SynInfo(true), clientStreamFrameListener); session.syn(new SynInfo(true), clientStreamFrameListener); Assert.assertTrue(replyLatch.await(5, TimeUnit.SECONDS)); Assert.assertTrue(dataLatch.await(5, TimeUnit.SECONDS)); } }