Merge branch 'master' into release-9

This commit is contained in:
Jesse McConnell 2013-06-21 15:18:46 -05:00
commit 018a51a04e
12 changed files with 187 additions and 129 deletions

View File

@ -43,12 +43,12 @@
<td>
<h2>information ...</h2>
<ul>
<li><a href="http://www.eclipse.org/jetty/">Jetty @ Eclipse Home</a></li>
<li><a href="http://wiki.eclipse.org/Jetty">Jetty @ Eclipse Doco</a></li>
<li><a href="http://www.eclipse.org/jetty/">Jetty Homepage</a></li>
<li><a href="http://www.eclipse.org/jetty/documentation/current">Jetty Documentation</a></li>
<li><a href="/proxy/apidocs/">Javadoc</a> (via transparent proxy)</li>
<li><a href="/proxy/xref/">Xref</a> (via transparent proxy)</li>
<li><a
href="http://docs.codehaus.org/display/JETTY/Jetty+Powered">Jetty Powered</a></li>
href="http://www.eclipse.org/jetty/powered">Jetty Powered</a></li>
</ul>
</td>
<td>

View File

@ -141,6 +141,21 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp
return String.format("%s@%x",FlushInvoker.class.getSimpleName(),hashCode());
}
}
public class OnDisconnectCallback implements WriteCallback
{
@Override
public void writeFailed(Throwable x)
{
disconnect();
}
@Override
public void writeSuccess()
{
disconnect();
}
}
public static class Stats
{
@ -233,6 +248,7 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp
@Override
public void disconnect()
{
LOG.debug("{} disconnect()", policy.getBehavior());
synchronized (writeBytes)
{
if (!writeBytes.isClosed())
@ -426,7 +442,7 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp
@Override
public void onConnectionStateChange(ConnectionState state)
{
LOG.debug("Connection State Change: {}",state);
LOG.debug("{} Connection State Change: {}",policy.getBehavior(),state);
switch (state)
{
case OPEN:
@ -439,7 +455,7 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp
case CLOSING:
CloseInfo close = ioState.getCloseInfo();
// append close frame
outgoingFrame(close.asFrame(),null);
outgoingFrame(close.asFrame(),new OnDisconnectCallback());
default:
break;
}

View File

@ -182,11 +182,13 @@ public class IOState
*/
public void onCloseLocal(CloseInfo close)
{
LOG.debug("onCloseLocal({})",close);
ConnectionState event = null;
ConnectionState initialState = this.state;
if (initialState == ConnectionState.CLOSED)
{
// already closed
LOG.debug("already closed");
return;
}
@ -224,15 +226,19 @@ public class IOState
event = this.state;
}
}
LOG.debug("event = {}",event);
// Only notify on state change events
if (event != null)
{
LOG.debug("notifying state listeners: {}",event);
notifyStateListeners(event);
// if SHUTDOWN, we don't expect an answer.
if (close.getStatusCode() == StatusCode.SHUTDOWN)
// if harsh, we don't expect an answer.
if (close.isHarsh())
{
LOG.debug("Harsh close, disconnecting");
synchronized (this.state)
{
this.state = ConnectionState.CLOSED;
@ -253,6 +259,7 @@ public class IOState
*/
public void onCloseRemote(CloseInfo close)
{
LOG.debug("onCloseRemote({})",close);
ConnectionState event = null;
synchronized (this.state)
{

View File

@ -27,6 +27,7 @@ import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.util.thread.Scheduler;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.api.extensions.IncomingFrames;
import org.eclipse.jetty.websocket.common.ConnectionState;
import org.eclipse.jetty.websocket.common.io.AbstractWebSocketConnection;
public class WebSocketServerConnection extends AbstractWebSocketConnection
@ -74,7 +75,7 @@ public class WebSocketServerConnection extends AbstractWebSocketConnection
}
super.onOpen();
}
@Override
public void setNextIncomingFrames(IncomingFrames incoming)
{

View File

@ -23,8 +23,12 @@ import static org.hamcrest.Matchers.*;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.common.CloseInfo;
import org.eclipse.jetty.websocket.common.OpCode;
import org.eclipse.jetty.websocket.common.WebSocketFrame;
import org.eclipse.jetty.websocket.server.blockhead.BlockheadClient;
import org.eclipse.jetty.websocket.server.helper.IncomingFramesCapture;
import org.eclipse.jetty.websocket.server.helper.RFCSocket;
import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
@ -69,7 +73,7 @@ public class IdleTimeoutTest
{
BlockheadClient client = new BlockheadClient(server.getServerUri());
client.setProtocols("onConnect");
client.setTimeout(TimeUnit.MILLISECONDS,1500);
client.setTimeout(TimeUnit.MILLISECONDS,2500);
try
{
client.connect();
@ -83,13 +87,15 @@ public class IdleTimeoutTest
// Write to server (the server should be timed out and disconnect now)
client.write(WebSocketFrame.text("Hello"));
// now attempt to read 2 echoed frames from server (shouldn't work)
client.readFrames(2,TimeUnit.MILLISECONDS,1500);
Assert.fail("Should have resulted in IOException");
}
catch (IOException e)
{
Assert.assertThat("IOException",e.getMessage(),anyOf(containsString("closed"),containsString("disconnected")));
// now read 1 frame from server (should be close frame)
IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,1500);
WebSocketFrame frame = capture.getFrames().poll();
Assert.assertThat("Was close frame", frame.getOpCode(), is(OpCode.CLOSE));
CloseInfo close = new CloseInfo(frame);
Assert.assertThat("Close.code", close.getStatusCode(), is(StatusCode.SHUTDOWN));
Assert.assertThat("Close.reason", close.getReason(), containsString("Idle Timeout"));
client.expectServerDisconnect();
}
finally
{

View File

@ -65,7 +65,9 @@ public class WebSocketOverSSLTest
client.start();
CaptureSocket clientSocket = new CaptureSocket();
Future<Session> fut = client.connect(clientSocket,server.getServerUri());
URI requestUri = server.getServerUri();
System.err.printf("Request URI: %s%n",requestUri.toASCIIString());
Future<Session> fut = client.connect(clientSocket,requestUri);
// wait for connect
Session session = fut.get(3,TimeUnit.SECONDS);

View File

@ -62,13 +62,18 @@ public class Fuzzer
PER_FRAME,
SLOW
}
public static enum DisconnectMode
{
/** Disconnect occurred after a proper close handshake */
CLEAN,
/** Disconnect occurred in a harsh manner, without a close handshake */
UNCLEAN
}
private static final int KBYTE = 1024;
private static final int MBYTE = KBYTE * KBYTE;
public static final boolean CLEAN_CLOSE = true;
public static final boolean NOT_CLEAN_CLOSE = false;
private static final Logger LOG = Log.getLogger(Fuzzer.class);
// Client side framing mask
@ -88,6 +93,7 @@ public class Fuzzer
int bigMessageSize = 20 * MBYTE;
policy.setMaxMessageSize(bigMessageSize);
policy.setIdleTimeout(5000);
this.client = new BlockheadClient(policy,testcase.getServer().getServerUri());
this.generator = testcase.getLaxGenerator();
@ -183,35 +189,21 @@ public class Fuzzer
// TODO Should test for no more frames. success if connection closed.
}
public void expectServerClose(boolean wasClean) throws IOException, InterruptedException
public void expectServerDisconnect(DisconnectMode mode)
{
// we expect that the close handshake to have occurred and the server should have closed the connection
try
{
ByteBuffer buf = ByteBuffer.wrap(new byte[]
{ 0x00 });
BufferUtil.flipToFill(buf);
int len = client.read(buf);
Assert.assertThat("Server has not closed socket",len,lessThanOrEqualTo(0));
}
catch (IOException e)
{
// valid path
}
client.expectServerDisconnect();
IOState ios = client.getIOState();
if (wasClean)
switch (mode)
{
Assert.assertTrue(ios.wasRemoteCloseInitiated());
Assert.assertTrue(ios.wasCleanClose());
case CLEAN:
Assert.assertTrue(ios.wasRemoteCloseInitiated());
Assert.assertTrue(ios.wasCleanClose());
break;
case UNCLEAN:
Assert.assertTrue(ios.wasRemoteCloseInitiated());
break;
}
else
{
Assert.assertTrue(ios.wasRemoteCloseInitiated());
}
}
public CloseState getCloseState()
@ -342,20 +334,6 @@ public class Fuzzer
}
}
public void sendExpectingIOException(ByteBuffer part3)
{
try
{
send(part3);
Assert.fail("Expected a IOException on this send");
}
catch (IOException ignore)
{
// Send, but expect the send to fail with a IOException.
// Usually, this is a SocketException("Socket Closed") condition.
}
}
private void setClientMask(WebSocketFrame f)
{
if (LOG.isDebugEnabled())

View File

@ -51,7 +51,7 @@ public class TestABCase1 extends AbstractABCase
fuzzer.setSendMode(SendMode.BULK);
fuzzer.send(send);
fuzzer.expect(expect);
fuzzer.expectServerClose(Fuzzer.CLEAN_CLOSE);
fuzzer.expectServerDisconnect(Fuzzer.DisconnectMode.CLEAN);
}
finally
{
@ -83,6 +83,7 @@ public class TestABCase1 extends AbstractABCase
fuzzer.setSendMode(SendMode.BULK);
fuzzer.send(send);
fuzzer.expect(expect);
fuzzer.expectServerDisconnect(Fuzzer.DisconnectMode.CLEAN);
}
finally
{
@ -114,6 +115,7 @@ public class TestABCase1 extends AbstractABCase
fuzzer.setSendMode(SendMode.BULK);
fuzzer.send(send);
fuzzer.expect(expect);
fuzzer.expectServerDisconnect(Fuzzer.DisconnectMode.CLEAN);
}
finally
{
@ -145,6 +147,7 @@ public class TestABCase1 extends AbstractABCase
fuzzer.setSendMode(SendMode.BULK);
fuzzer.send(send);
fuzzer.expect(expect);
fuzzer.expectServerDisconnect(Fuzzer.DisconnectMode.CLEAN);
}
finally
{
@ -176,6 +179,7 @@ public class TestABCase1 extends AbstractABCase
fuzzer.setSendMode(SendMode.BULK);
fuzzer.send(send);
fuzzer.expect(expect);
fuzzer.expectServerDisconnect(Fuzzer.DisconnectMode.CLEAN);
}
finally
{
@ -207,6 +211,7 @@ public class TestABCase1 extends AbstractABCase
fuzzer.setSendMode(SendMode.BULK);
fuzzer.send(send);
fuzzer.expect(expect);
fuzzer.expectServerDisconnect(Fuzzer.DisconnectMode.CLEAN);
}
finally
{
@ -238,6 +243,7 @@ public class TestABCase1 extends AbstractABCase
fuzzer.setSendMode(SendMode.BULK);
fuzzer.send(send);
fuzzer.expect(expect);
fuzzer.expectServerDisconnect(Fuzzer.DisconnectMode.CLEAN);
}
finally
{
@ -275,6 +281,7 @@ public class TestABCase1 extends AbstractABCase
fuzzer.setSlowSendSegmentSize(segmentSize);
fuzzer.send(send);
fuzzer.expect(expect);
fuzzer.expectServerDisconnect(Fuzzer.DisconnectMode.CLEAN);
}
finally
{
@ -303,6 +310,7 @@ public class TestABCase1 extends AbstractABCase
fuzzer.setSendMode(SendMode.BULK);
fuzzer.send(send);
fuzzer.expect(expect);
fuzzer.expectServerDisconnect(Fuzzer.DisconnectMode.CLEAN);
}
finally
{
@ -334,6 +342,7 @@ public class TestABCase1 extends AbstractABCase
fuzzer.setSendMode(SendMode.BULK);
fuzzer.send(send);
fuzzer.expect(expect);
fuzzer.expectServerDisconnect(Fuzzer.DisconnectMode.CLEAN);
}
finally
{
@ -365,6 +374,7 @@ public class TestABCase1 extends AbstractABCase
fuzzer.setSendMode(SendMode.BULK);
fuzzer.send(send);
fuzzer.expect(expect);
fuzzer.expectServerDisconnect(Fuzzer.DisconnectMode.CLEAN);
}
finally
{
@ -396,6 +406,7 @@ public class TestABCase1 extends AbstractABCase
fuzzer.setSendMode(SendMode.BULK);
fuzzer.send(send);
fuzzer.expect(expect);
fuzzer.expectServerDisconnect(Fuzzer.DisconnectMode.CLEAN);
}
finally
{
@ -427,6 +438,7 @@ public class TestABCase1 extends AbstractABCase
fuzzer.setSendMode(SendMode.BULK);
fuzzer.send(send);
fuzzer.expect(expect);
fuzzer.expectServerDisconnect(Fuzzer.DisconnectMode.CLEAN);
}
finally
{
@ -458,6 +470,7 @@ public class TestABCase1 extends AbstractABCase
fuzzer.setSendMode(SendMode.BULK);
fuzzer.send(send);
fuzzer.expect(expect);
fuzzer.expectServerDisconnect(Fuzzer.DisconnectMode.CLEAN);
}
finally
{
@ -489,6 +502,7 @@ public class TestABCase1 extends AbstractABCase
fuzzer.setSendMode(SendMode.BULK);
fuzzer.send(send);
fuzzer.expect(expect);
fuzzer.expectServerDisconnect(Fuzzer.DisconnectMode.CLEAN);
}
finally
{
@ -526,6 +540,7 @@ public class TestABCase1 extends AbstractABCase
fuzzer.setSlowSendSegmentSize(segmentSize);
fuzzer.send(send);
fuzzer.expect(expect);
fuzzer.expectServerDisconnect(Fuzzer.DisconnectMode.CLEAN);
}
finally
{

View File

@ -24,6 +24,7 @@ import java.util.List;
import org.eclipse.jetty.toolchain.test.AdvancedRunner;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.log.StacklessLogging;
import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.common.CloseInfo;
import org.eclipse.jetty.websocket.common.OpCode;
@ -236,32 +237,32 @@ public class TestABCase2 extends AbstractABCase
@Test
public void testCase2_5() throws Exception
{
// Disable Long Stacks from Parser (we know this test will throw an exception)
enableStacks(Parser.class,false);
byte payload[] = new byte[126]; // intentionally too big
Arrays.fill(payload,(byte)'5');
List<WebSocketFrame> send = new ArrayList<>();
// trick websocket frame into making extra large payload for ping
send.add(WebSocketFrame.binary(payload).setOpCode(OpCode.PING));
send.add(new CloseInfo(StatusCode.NORMAL,"Test 2.5").asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
try
try(StacklessLogging scope = new StacklessLogging(Parser.class))
{
fuzzer.connect();
fuzzer.setSendMode(Fuzzer.SendMode.BULK);
fuzzer.send(send);
fuzzer.expect(expect);
}
finally
{
enableStacks(Parser.class,true);
fuzzer.close();
byte payload[] = new byte[126]; // intentionally too big
Arrays.fill(payload,(byte)'5');
List<WebSocketFrame> send = new ArrayList<>();
// trick websocket frame into making extra large payload for ping
send.add(WebSocketFrame.binary(payload).setOpCode(OpCode.PING));
send.add(new CloseInfo(StatusCode.NORMAL,"Test 2.5").asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
try
{
fuzzer.connect();
fuzzer.setSendMode(Fuzzer.SendMode.BULK);
fuzzer.send(send);
fuzzer.expect(expect);
fuzzer.expectServerDisconnect(Fuzzer.DisconnectMode.CLEAN);
}
finally
{
fuzzer.close();
}
}
}

View File

@ -28,6 +28,7 @@ import org.eclipse.jetty.toolchain.test.annotation.Slow;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.log.StacklessLogging;
import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.common.CloseInfo;
import org.eclipse.jetty.websocket.common.OpCode;
@ -358,54 +359,55 @@ public class TestABCase6 extends AbstractABCase
public void testCase6_4_3() throws Exception
{
// Disable Long Stacks from Parser (we know this test will throw an exception)
enableStacks(Parser.class,false);
ByteBuffer payload = ByteBuffer.allocate(64);
BufferUtil.clearToFill(payload);
payload.put(TypeUtil.fromHexString("cebae1bdb9cf83cebcceb5")); // good
payload.put(TypeUtil.fromHexString("f4908080")); // INVALID
payload.put(TypeUtil.fromHexString("656469746564")); // good
BufferUtil.flipToFlush(payload,0);
List<WebSocketFrame> send = new ArrayList<>();
send.add(new WebSocketFrame(OpCode.TEXT).setPayload(payload));
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
List<WebSocketFrame> expect = new ArrayList<>();
expect.add(new CloseInfo(StatusCode.BAD_PAYLOAD).asFrame());
Fuzzer fuzzer = new Fuzzer(this);
try
try(StacklessLogging scope = new StacklessLogging(Parser.class))
{
fuzzer.connect();
ByteBuffer payload = ByteBuffer.allocate(64);
BufferUtil.clearToFill(payload);
payload.put(TypeUtil.fromHexString("cebae1bdb9cf83cebcceb5")); // good
payload.put(TypeUtil.fromHexString("f4908080")); // INVALID
payload.put(TypeUtil.fromHexString("656469746564")); // good
BufferUtil.flipToFlush(payload,0);
ByteBuffer net = fuzzer.asNetworkBuffer(send);
List<WebSocketFrame> send = new ArrayList<>();
send.add(new WebSocketFrame(OpCode.TEXT).setPayload(payload));
send.add(new CloseInfo(StatusCode.NORMAL).asFrame());
int splits[] =
{ 17, 21, net.limit() };
List<WebSocketFrame> expect = new ArrayList<>();
expect.add(new CloseInfo(StatusCode.BAD_PAYLOAD).asFrame());
ByteBuffer part1 = net.slice(); // Header + good UTF
part1.limit(splits[0]);
ByteBuffer part2 = net.slice(); // invalid UTF
part2.position(splits[0]);
part2.limit(splits[1]);
ByteBuffer part3 = net.slice(); // good UTF
part3.position(splits[1]);
part3.limit(splits[2]);
Fuzzer fuzzer = new Fuzzer(this);
try
{
fuzzer.connect();
fuzzer.send(part1); // the header + good utf
TimeUnit.SECONDS.sleep(1);
fuzzer.send(part2); // the bad UTF
ByteBuffer net = fuzzer.asNetworkBuffer(send);
fuzzer.expect(expect);
int splits[] =
{ 17, 21, net.limit() };
TimeUnit.SECONDS.sleep(1);
fuzzer.sendExpectingIOException(part3); // the rest (shouldn't work)
}
finally
{
enableStacks(Parser.class,true);
fuzzer.close();
ByteBuffer part1 = net.slice(); // Header + good UTF
part1.limit(splits[0]);
ByteBuffer part2 = net.slice(); // invalid UTF
part2.position(splits[0]);
part2.limit(splits[1]);
ByteBuffer part3 = net.slice(); // good UTF
part3.position(splits[1]);
part3.limit(splits[2]);
fuzzer.send(part1); // the header + good utf
TimeUnit.SECONDS.sleep(1);
fuzzer.send(part2); // the bad UTF
fuzzer.expect(expect);
TimeUnit.SECONDS.sleep(1);
fuzzer.send(part3); // the rest (shouldn't work)
fuzzer.expectServerDisconnect(Fuzzer.DisconnectMode.UNCLEAN);
}
finally
{
fuzzer.close();
}
}
}

View File

@ -168,7 +168,7 @@ public class TestABCase6_BadUTF extends AbstractABCase
fuzzer.setSendMode(Fuzzer.SendMode.BULK);
fuzzer.send(send);
fuzzer.expect(expect);
fuzzer.expectServerClose(Fuzzer.NOT_CLEAN_CLOSE);
fuzzer.expectServerDisconnect(Fuzzer.DisconnectMode.UNCLEAN);
}
finally
{

View File

@ -29,6 +29,7 @@ import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketTimeoutException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.ByteBuffer;
@ -125,10 +126,8 @@ public class BlockheadClient implements IncomingFrames, OutgoingFrames, Connecti
{
Assert.assertThat("Websocket URI scheme",destWebsocketURI.getScheme(),anyOf(is("ws"),is("wss")));
this.destWebsocketURI = destWebsocketURI;
String scheme = "http";
if (destWebsocketURI.getScheme().equals("wss"))
{
scheme = "https";
throw new RuntimeException("Sorry, BlockheadClient does not support SSL");
}
this.destHttpURI = WSURI.toHttp(destWebsocketURI);
@ -415,7 +414,8 @@ public class BlockheadClient implements IncomingFrames, OutgoingFrames, Connecti
switch (state)
{
case CLOSED:
this.disconnect();
// Per Spec, client should not initiate disconnect on its own
// this.disconnect();
break;
case CLOSING:
if (ioState.wasRemoteCloseInitiated())
@ -465,6 +465,36 @@ public class BlockheadClient implements IncomingFrames, OutgoingFrames, Connecti
}
}
public void expectServerDisconnect()
{
if (eof)
{
return;
}
try
{
int len = in.read();
if (len == (-1))
{
// we are disconnected
eof = true;
return;
}
Assert.assertThat("Expecting no data and proper socket disconnect (issued from server)",len,is(-1));
}
catch (SocketTimeoutException e)
{
LOG.warn(e);
Assert.fail("Expected a server initiated disconnect, instead the read timed out");
}
catch (IOException e)
{
// acceptable path
}
}
public int read(ByteBuffer buf) throws IOException
{
if (eof)