blob: c2cc52572391fd5e9188395706067665f11ec2e9 [file] [log] [blame]
//
// ========================================================================
// Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.spdy.server;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jetty.spdy.api.DataInfo;
import org.eclipse.jetty.spdy.api.GoAwayInfo;
import org.eclipse.jetty.spdy.api.GoAwayResultInfo;
import org.eclipse.jetty.spdy.api.ReplyInfo;
import org.eclipse.jetty.spdy.api.Session;
import org.eclipse.jetty.spdy.api.SessionFrameListener;
import org.eclipse.jetty.spdy.api.SessionStatus;
import org.eclipse.jetty.spdy.api.Stream;
import org.eclipse.jetty.spdy.api.StreamFrameListener;
import org.eclipse.jetty.spdy.api.StringDataInfo;
import org.eclipse.jetty.spdy.api.SynInfo;
import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.Fields;
import org.eclipse.jetty.util.FutureCallback;
import org.eclipse.jetty.util.FuturePromise;
import org.junit.Assert;
import org.junit.Test;
public class GoAwayTest extends AbstractTest
{
@Test
public void testServerReceivesGoAwayOnClientGoAway() throws Exception
{
final CountDownLatch latch = new CountDownLatch(1);
ServerSessionFrameListener serverSessionFrameListener = new ServerSessionFrameListener.Adapter()
{
@Override
public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
{
stream.reply(new ReplyInfo(true), new Callback.Adapter());
return null;
}
@Override
public void onGoAway(Session session, GoAwayResultInfo goAwayInfo)
{
Assert.assertEquals(0, goAwayInfo.getLastStreamId());
Assert.assertSame(SessionStatus.OK, goAwayInfo.getSessionStatus());
latch.countDown();
}
};
Session session = startClient(startServer(serverSessionFrameListener), null);
session.syn(new SynInfo(new Fields(), true), null);
session.goAway(new GoAwayInfo());
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
}
@Test
public void testClientReceivesGoAwayOnServerGoAway() throws Exception
{
ServerSessionFrameListener serverSessionFrameListener = new ServerSessionFrameListener.Adapter()
{
@Override
public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
{
stream.reply(new ReplyInfo(true), new Callback.Adapter());
stream.getSession().goAway(new GoAwayInfo(), new FutureCallback());
return null;
}
};
final AtomicReference<GoAwayResultInfo> ref = new AtomicReference<>();
final CountDownLatch latch = new CountDownLatch(1);
SessionFrameListener clientSessionFrameListener = new SessionFrameListener.Adapter()
{
@Override
public void onGoAway(Session session, GoAwayResultInfo goAwayInfo)
{
ref.set(goAwayInfo);
latch.countDown();
}
};
Session session = startClient(startServer(serverSessionFrameListener), clientSessionFrameListener);
Stream stream1 = session.syn(new SynInfo(5, TimeUnit.SECONDS, new Fields(), true, (byte)0), null);
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
GoAwayResultInfo goAwayResultInfo = ref.get();
Assert.assertNotNull(goAwayResultInfo);
Assert.assertEquals(stream1.getId(), goAwayResultInfo.getLastStreamId());
Assert.assertSame(SessionStatus.OK, goAwayResultInfo.getSessionStatus());
}
@Test
public void testSynStreamIgnoredAfterGoAway() throws Exception
{
final CountDownLatch latch = new CountDownLatch(1);
ServerSessionFrameListener serverSessionFrameListener = new ServerSessionFrameListener.Adapter()
{
private final AtomicInteger syns = new AtomicInteger();
@Override
public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
{
int synCount = syns.incrementAndGet();
if (synCount == 1)
{
stream.reply(new ReplyInfo(true), new Callback.Adapter());
stream.getSession().goAway(new GoAwayInfo(), new FutureCallback());
}
else
{
latch.countDown();
}
return null;
}
};
SessionFrameListener clientSessionFrameListener = new SessionFrameListener.Adapter()
{
@Override
public void onGoAway(Session session, GoAwayResultInfo goAwayInfo)
{
session.syn(new SynInfo(new Fields(), true), null, new FuturePromise<Stream>());
}
};
Session session = startClient(startServer(serverSessionFrameListener), clientSessionFrameListener);
session.syn(new SynInfo(new Fields(), true), null);
Assert.assertFalse(latch.await(1, TimeUnit.SECONDS));
}
@Test
public void testDataNotProcessedAfterGoAway() throws Exception
{
final CountDownLatch closeLatch = new CountDownLatch(1);
final CountDownLatch dataLatch = new CountDownLatch(1);
ServerSessionFrameListener serverSessionFrameListener = new ServerSessionFrameListener.Adapter()
{
private AtomicInteger syns = new AtomicInteger();
@Override
public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
{
stream.reply(new ReplyInfo(true), new Callback.Adapter());
int synCount = syns.incrementAndGet();
if (synCount == 1)
{
return null;
}
else
{
stream.getSession().goAway(new GoAwayInfo(), new FutureCallback());
closeLatch.countDown();
return new StreamFrameListener.Adapter()
{
@Override
public void onData(Stream stream, DataInfo dataInfo)
{
dataLatch.countDown();
}
};
}
}
};
final AtomicReference<GoAwayResultInfo> goAwayRef = new AtomicReference<>();
final CountDownLatch goAwayLatch = new CountDownLatch(1);
SessionFrameListener clientSessionFrameListener = new SessionFrameListener.Adapter()
{
@Override
public void onGoAway(Session session, GoAwayResultInfo goAwayInfo)
{
goAwayRef.set(goAwayInfo);
goAwayLatch.countDown();
}
};
Session session = startClient(startServer(serverSessionFrameListener), clientSessionFrameListener);
// First stream is processed ok
final CountDownLatch reply1Latch = new CountDownLatch(1);
session.syn(new SynInfo(5, TimeUnit.SECONDS, new Fields(), true, (byte)0), new StreamFrameListener.Adapter()
{
@Override
public void onReply(Stream stream, ReplyInfo replyInfo)
{
reply1Latch.countDown();
}
});
Assert.assertTrue(reply1Latch.await(5, TimeUnit.SECONDS));
// Second stream is closed in the middle
Stream stream2 = session.syn(new SynInfo(5, TimeUnit.SECONDS, new Fields(), false, (byte)0), null);
Assert.assertTrue(closeLatch.await(5, TimeUnit.SECONDS));
// There is a race between the data we want to send, and the client
// closing the connection because the server closed it after the
// go_away, so we guard with a try/catch to have the test pass cleanly
try
{
stream2.data(new StringDataInfo("foo", true));
Assert.assertFalse(dataLatch.await(1, TimeUnit.SECONDS));
}
catch (ExecutionException x)
{
// doesn't matter which exception we get, it's important that the data is not been written and the
// previous assertion is true
}
// The last good stream is the second, because it was received by the server
Assert.assertTrue(goAwayLatch.await(5, TimeUnit.SECONDS));
GoAwayResultInfo goAway = goAwayRef.get();
Assert.assertNotNull(goAway);
Assert.assertEquals(stream2.getId(), goAway.getLastStreamId());
}
}