Merge remote-tracking branch 'origin/jetty-9.4.x'

This commit is contained in:
Joakim Erdfelt 2017-10-10 13:44:22 -07:00
commit c3c3881c9f
13 changed files with 580 additions and 471 deletions

View File

@ -15,7 +15,7 @@
// ======================================================================== // ========================================================================
[[configuring-security-authentication]] [[configuring-security-authentication]]
=== Authentication === Authentication and Authorization
There are two aspects to securing a web application(or context) within the Jetty server: There are two aspects to securing a web application(or context) within the Jetty server:
@ -459,3 +459,58 @@ You can then define roles that should be able to perform these protected methods
---- ----
In the above example, only users with an `admin` role will be able to perform `DELETE` or `POST` methods. In the above example, only users with an `admin` role will be able to perform `DELETE` or `POST` methods.
===== Configuring Authorization with Context XML Files
While the examples above show configuration of Authorization in a `web.xml` file, they can also be configured as part of the link#link:#deployable-descriptor-file[context xml file] for a web application.
This is especially helpful if authorization needs change over time and need updated without re-packaging the whole web app.
To do this, we add a section for security constraints into the context xml file for our web app as part of the `securityHandler`.
In the example below, a `HashLoginService` is defined with authorization being granted too `foo/*` paths to users with the `admin` and `manager` roles.
[source, xml, subs="{sub-order}"]
----
<Configure id="testWebapp" class="org.eclipse.jetty.webapp.WebAppContext">
<Get name="securityHandler">
<Set name="realmName">Test Realm</Set>
<Set name="authMethod">BASIC</Set>
<Call name="addConstraintMapping">
<Arg>
<New class="org.eclipse.jetty.security.ConstraintMapping">
<Set name="pathSpec">/foo/*</Set>
<Set name="constraint">
<New class="org.eclipse.jetty.util.security.Constraint">
<Set name="name">Foo Auth</Set>
<Set name="authenticate">true</Set>
<Set name="roles">
<Array type="java.lang.String">
<Item>admin</Item>
<Item>manager</Item>
</Array>
</Set>
</New>
</Set>
</New>
</Arg>
</Call>
<Set name="loginService">
<New class="org.eclipse.jetty.security.HashLoginService">
<Set name="name">Test Realm</Set>
<Set name="config">/src/tmp/small-security-test/realm.properties</Set>
</New>
</Set>
</Get>
</Configure>
----
If roles changed in the future, administrators could easily change this context xml file without having to edit the contents of the web app at all.
==== Authentication and Authorization with Embedded Jetty
In addition to the distribution, security can be defined as part of an embedded implementation as well.
Below is an example which, like the one above, sets up a server with a `HashLoginService` and adds security constraints to restrict access based on roles.
[source, java, subs="{sub-order}"]
----
include::{SRCDIR}/examples/embedded/src/main/java/org/eclipse/jetty/embedded/SecuredHelloHandler.java[]
----

View File

@ -23,6 +23,7 @@ import java.security.KeyStore;
import java.security.cert.CRL; import java.security.cert.CRL;
import java.security.cert.CertificateFactory; import java.security.cert.CertificateFactory;
import java.util.Collection; import java.util.Collection;
import java.util.Objects;
import org.eclipse.jetty.util.resource.Resource; import org.eclipse.jetty.util.resource.Resource;
@ -35,6 +36,7 @@ public class CertificateUtils
if (store != null) if (store != null)
{ {
Objects.requireNonNull(storeType, "storeType cannot be null");
if (storeProvider != null) if (storeProvider != null)
{ {
keystore = KeyStore.getInstance(storeType, storeProvider); keystore = KeyStore.getInstance(storeType, storeProvider);

View File

@ -665,7 +665,7 @@ public class SslContextFactory extends AbstractLifeCycle implements Dumpable
} }
/** /**
* @return The type of the trust store (default "JKS") * @return The type of the trust store
*/ */
@ManagedAttribute("The trustStore type") @ManagedAttribute("The trustStore type")
public String getTrustStoreType() public String getTrustStoreType()
@ -674,7 +674,7 @@ public class SslContextFactory extends AbstractLifeCycle implements Dumpable
} }
/** /**
* @param trustStoreType The type of the trust store (default "JKS") * @param trustStoreType The type of the trust store
*/ */
public void setTrustStoreType(String trustStoreType) public void setTrustStoreType(String trustStoreType)
{ {
@ -1046,19 +1046,11 @@ public class SslContextFactory extends AbstractLifeCycle implements Dumpable
*/ */
protected KeyStore loadTrustStore(Resource resource) throws Exception protected KeyStore loadTrustStore(Resource resource) throws Exception
{ {
String type = getTrustStoreType(); String type = Objects.toString(getTrustStoreType(), getKeyStoreType());
String provider = getTrustStoreProvider(); String provider = Objects.toString(getTrustStoreProvider(), getKeyStoreProvider());
String passwd = Objects.toString(_trustStorePassword, null); String passwd = Objects.toString(_trustStorePassword, Objects.toString(_keyStorePassword, null));
if (resource == null || resource.equals(_keyStoreResource)) if (resource == null)
{
resource = _keyStoreResource; resource = _keyStoreResource;
if (type == null)
type = _keyStoreType;
if (provider == null)
provider = _keyStoreProvider;
if (passwd == null)
passwd = Objects.toString(_keyStorePassword, null);
}
return CertificateUtils.getKeyStore(resource, type, provider, passwd); return CertificateUtils.getKeyStore(resource, type, provider, passwd);
} }

View File

@ -18,6 +18,15 @@
package org.eclipse.jetty.util.ssl; package org.eclipse.jetty.util.ssl;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.greaterThan;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.security.KeyStore; import java.security.KeyStore;
@ -27,21 +36,16 @@ import javax.net.ssl.SSLEngine;
import org.eclipse.jetty.util.component.AbstractLifeCycle; import org.eclipse.jetty.util.component.AbstractLifeCycle;
import org.eclipse.jetty.util.log.StacklessLogging; import org.eclipse.jetty.util.log.StacklessLogging;
import org.eclipse.jetty.util.resource.Resource; import org.eclipse.jetty.util.resource.Resource;
import org.hamcrest.Matchers;
import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.ExpectedException;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.greaterThan;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
public class SslContextFactoryTest public class SslContextFactoryTest
{ {
@Rule
public ExpectedException expectedException = ExpectedException.none();
private SslContextFactory cf; private SslContextFactory cf;
@Before @Before
@ -155,14 +159,11 @@ public class SslContextFactoryTest
cf.setKeyManagerPassword("wrong_keypwd"); cf.setKeyManagerPassword("wrong_keypwd");
cf.setTrustStorePassword("storepwd"); cf.setTrustStorePassword("storepwd");
try (StacklessLogging stackless = new StacklessLogging(AbstractLifeCycle.class)) expectedException.expect(java.security.UnrecoverableKeyException.class);
expectedException.expectMessage(containsString("Cannot recover key"));
try (StacklessLogging ignore = new StacklessLogging(AbstractLifeCycle.class))
{ {
cf.start(); cf.start();
Assert.fail();
}
catch (java.security.UnrecoverableKeyException e)
{
Assert.assertThat(e.toString(), Matchers.containsString("UnrecoverableKeyException"));
} }
} }
@ -178,29 +179,23 @@ public class SslContextFactoryTest
cf.setKeyManagerPassword("keypwd"); cf.setKeyManagerPassword("keypwd");
cf.setTrustStorePassword("wrong_storepwd"); cf.setTrustStorePassword("wrong_storepwd");
try (StacklessLogging stackless = new StacklessLogging(AbstractLifeCycle.class)) expectedException.expect(IOException.class);
expectedException.expectMessage(containsString("Keystore was tampered with, or password was incorrect"));
try (StacklessLogging ignore = new StacklessLogging(AbstractLifeCycle.class))
{ {
cf.start(); cf.start();
Assert.fail();
}
catch (IOException e)
{
Assert.assertThat(e.toString(), Matchers.containsString("java.io.IOException: Keystore was tampered with, or password was incorrect"));
} }
} }
@Test @Test
public void testNoKeyConfig() throws Exception public void testNoKeyConfig() throws Exception
{ {
try (StacklessLogging stackless = new StacklessLogging(AbstractLifeCycle.class)) expectedException.expect(IllegalStateException.class);
expectedException.expectMessage(containsString("no valid keystore"));
try (StacklessLogging ignore = new StacklessLogging(AbstractLifeCycle.class))
{ {
cf.setTrustStorePath("/foo"); cf.setTrustStorePath("/foo");
cf.start(); cf.start();
Assert.fail();
}
catch (IllegalStateException e)
{
Assert.assertThat(e.toString(), Matchers.containsString("IllegalStateException: no valid keystore"));
} }
} }

View File

@ -27,7 +27,6 @@ import org.eclipse.jetty.websocket.api.FrameCallback;
import org.eclipse.jetty.websocket.api.SuspendToken; import org.eclipse.jetty.websocket.api.SuspendToken;
import org.eclipse.jetty.websocket.api.WebSocketPolicy; import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.api.extensions.Frame; import org.eclipse.jetty.websocket.api.extensions.Frame;
import org.eclipse.jetty.websocket.common.LogicalConnection;
public class DummyConnection implements LogicalConnection public class DummyConnection implements LogicalConnection
{ {
@ -44,6 +43,17 @@ public class DummyConnection implements LogicalConnection
this.policy = policy; this.policy = policy;
} }
@Override
public void setSession(WebSocketSession session)
{
}
@Override
public void onLocalClose(CloseInfo close)
{
}
@Override @Override
public void disconnect() public void disconnect()
{ {

View File

@ -39,6 +39,12 @@ public interface LogicalConnection extends OutgoingFrames, SuspendToken
void onError(Throwable cause); void onError(Throwable cause);
} }
/**
* Called to indicate a close frame was successfully sent to the remote.
* @param close the close details
*/
void onLocalClose(CloseInfo close);
/** /**
* Terminate the connection (no close frame sent) * Terminate the connection (no close frame sent)
*/ */
@ -117,6 +123,13 @@ public interface LogicalConnection extends OutgoingFrames, SuspendToken
*/ */
void setMaxIdleTimeout(long ms); void setMaxIdleTimeout(long ms);
/**
* Associate the Active Session with the connection.
*
* @param session the session for this connection
*/
void setSession(WebSocketSession session);
/** /**
* Suspend a the incoming read events on the connection. * Suspend a the incoming read events on the connection.
* @return the suspend token * @return the suspend token

View File

@ -82,6 +82,7 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem
private final WebSocketContainerScope containerScope; private final WebSocketContainerScope containerScope;
private final WebSocketPolicy policy; private final WebSocketPolicy policy;
private final AtomicBoolean closed = new AtomicBoolean();
private final URI requestURI; private final URI requestURI;
private final LogicalConnection connection; private final LogicalConnection connection;
private final Executor executor; private final Executor executor;
@ -95,19 +96,6 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem
private Object endpoint; private Object endpoint;
// Callbacks // Callbacks
private FrameCallback onDisconnectCallback = new CompletionCallback()
{
@Override
public void complete()
{
if (connectionState.onClosed())
{
if (LOG.isDebugEnabled())
LOG.debug("ConnectionState: Transition to CLOSED");
connection.disconnect();
}
}
};
// Endpoint Functions and MessageSinks // Endpoint Functions and MessageSinks
protected EndpointFunctions endpointFunctions; protected EndpointFunctions endpointFunctions;
@ -142,7 +130,10 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem
this.outgoingHandler = connection; this.outgoingHandler = connection;
this.policy = connection.getPolicy(); this.policy = connection.getPolicy();
this.connection.setSession(this);
addBean(this.connection); addBean(this.connection);
addBean(endpoint);
} }
public EndpointFunctions newEndpointFunctions(Object endpoint) public EndpointFunctions newEndpointFunctions(Object endpoint)
@ -155,46 +146,52 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem
connectionState.onConnecting(); connectionState.onConnecting();
} }
/**
* Aborts the active session abruptly.
*/
public void abort(int statusCode, String reason)
{
close(new CloseInfo(statusCode, reason), new DisconnectCallback());
}
@Override @Override
public void close() public void close()
{ {
/* This is assumed to always be a NORMAL closure, no reason phrase */ /* This is assumed to always be a NORMAL closure, no reason phrase */
close(StatusCode.NORMAL, null); close(new CloseInfo(StatusCode.NORMAL), null);
} }
@Override @Override
public void close(CloseStatus closeStatus) public void close(CloseStatus closeStatus)
{ {
close(closeStatus.getCode(), closeStatus.getPhrase()); close(new CloseInfo(closeStatus.getCode(),closeStatus.getPhrase()), null);
} }
@Override @Override
public void close(int statusCode, String reason) public void close(int statusCode, String reason)
{ {
close(statusCode, reason, EMPTY); close(new CloseInfo(statusCode, reason), null);
}
private void close(int statusCode, String reason, FrameCallback callback)
{
close(new CloseInfo(statusCode, reason), callback);
} }
/**
* CLOSE Primary Entry Point.
*
* <ul>
* <li>atomically enqueue CLOSE frame + flip flag to reject more frames</li>
* <li>setup CLOSE frame callback: must close flusher</li>
* </ul>
*
* @param closeInfo the close details
*/
private void close(CloseInfo closeInfo, FrameCallback callback) private void close(CloseInfo closeInfo, FrameCallback callback)
{ {
connectionState.onClosing(); // always move to (at least) the CLOSING state (might already be past it, which is ok) if (LOG.isDebugEnabled())
LOG.debug("close({})", closeInfo);
if (closeSent.compareAndSet(false, true)) if (closed.compareAndSet(false, true))
{ {
if (LOG.isDebugEnabled()) CloseFrame frame = closeInfo.asFrame();
LOG.debug("Sending Close Frame"); connection.outgoingFrame(frame, new OnCloseLocalCallback(callback, connection, closeInfo), BatchMode.OFF);
CloseFrame closeFrame = closeInfo.asFrame();
outgoingHandler.outgoingFrame(closeFrame, callback, BatchMode.OFF);
}
else
{
if (LOG.isDebugEnabled())
LOG.debug("Close Frame Previously Sent: ignoring: {} [{}]", closeInfo, callback);
callback.fail(new WebSocketException("Already closed"));
} }
} }
@ -510,7 +507,7 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem
CloseFrame closeframe = (CloseFrame) frame; CloseFrame closeframe = (CloseFrame) frame;
CloseInfo closeInfo = new CloseInfo(closeframe, true); CloseInfo closeInfo = new CloseInfo(closeframe, true);
notifyClose(closeInfo); notifyClose(closeInfo);
close(closeInfo, onDisconnectCallback); close(closeInfo, new DisconnectCallback());
} }
else if (connectionState.onClosed()) else if (connectionState.onClosed())
{ {
@ -616,7 +613,7 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem
@Override @Override
public boolean isOpen() public boolean isOpen()
{ {
return this.connectionState.get() == AtomicConnectionState.State.OPEN; return !closed.get() && (this.connectionState.get() == AtomicConnectionState.State.OPEN);
} }
@Override @Override
@ -677,21 +674,21 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem
if (cause instanceof NotUtf8Exception) if (cause instanceof NotUtf8Exception)
{ {
close(StatusCode.BAD_PAYLOAD, cause.getMessage(), onDisconnectCallback); close(new CloseInfo(StatusCode.BAD_PAYLOAD, cause.getMessage()), new DisconnectCallback());
} }
else if (cause instanceof SocketTimeoutException) else if (cause instanceof SocketTimeoutException)
{ {
// A path often seen in Windows // A path often seen in Windows
close(StatusCode.SHUTDOWN, cause.getMessage(), onDisconnectCallback); close(new CloseInfo(StatusCode.SHUTDOWN, cause.getMessage()), new DisconnectCallback());
} }
else if (cause instanceof IOException) else if (cause instanceof IOException)
{ {
close(StatusCode.PROTOCOL, cause.getMessage(), onDisconnectCallback); close(new CloseInfo(StatusCode.PROTOCOL, cause.getMessage()), new DisconnectCallback());
} }
else if (cause instanceof SocketException) else if (cause instanceof SocketException)
{ {
// A path unique to Unix // A path unique to Unix
close(StatusCode.SHUTDOWN, cause.getMessage(), onDisconnectCallback); close(new CloseInfo(StatusCode.SHUTDOWN, cause.getMessage()), new DisconnectCallback());
} }
else if (cause instanceof CloseException) else if (cause instanceof CloseException)
{ {
@ -708,15 +705,15 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem
case StatusCode.POLICY_VIOLATION: case StatusCode.POLICY_VIOLATION:
case StatusCode.SERVER_ERROR: case StatusCode.SERVER_ERROR:
{ {
callback = onDisconnectCallback; callback = new DisconnectCallback();
} }
} }
close(ce.getStatusCode(), ce.getMessage(), callback); close(new CloseInfo(ce.getStatusCode(), ce.getMessage()), callback);
} }
else if (cause instanceof WebSocketTimeoutException) else if (cause instanceof WebSocketTimeoutException)
{ {
close(StatusCode.SHUTDOWN, cause.getMessage(), onDisconnectCallback); close(new CloseInfo(StatusCode.SHUTDOWN, cause.getMessage()), new DisconnectCallback());
} }
else else
{ {
@ -986,4 +983,60 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem
void onClosed(WebSocketSession session); void onClosed(WebSocketSession session);
} }
public static class OnCloseLocalCallback implements FrameCallback
{
private final FrameCallback wrapped;
private final LogicalConnection connection;
private final CloseInfo close;
public OnCloseLocalCallback(FrameCallback callback, LogicalConnection connection, CloseInfo close)
{
this.wrapped = callback;
this.connection = connection;
this.close = close;
}
@Override
public void succeed()
{
try
{
if (wrapped != null)
{
wrapped.succeed();
}
}
finally
{
connection.onLocalClose(close);
}
}
@Override
public void fail(Throwable cause)
{
try
{
if (wrapped != null)
{
wrapped.fail(cause);
}
}
finally
{
connection.onLocalClose(close);
}
}
}
public class DisconnectCallback extends CompletionCallback
{
@Override
public void complete()
{
connectionState.onClosed();
disconnect();
}
}
} }

View File

@ -18,6 +18,7 @@
package org.eclipse.jetty.websocket.common.io; package org.eclipse.jetty.websocket.common.io;
import java.io.EOFException;
import java.io.IOException; import java.io.IOException;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.SocketTimeoutException; import java.net.SocketTimeoutException;
@ -28,6 +29,7 @@ import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import org.eclipse.jetty.io.AbstractConnection; import org.eclipse.jetty.io.AbstractConnection;
import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.ByteBufferPool;
@ -40,14 +42,16 @@ import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.api.BatchMode; import org.eclipse.jetty.websocket.api.BatchMode;
import org.eclipse.jetty.websocket.api.FrameCallback; import org.eclipse.jetty.websocket.api.FrameCallback;
import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.api.SuspendToken; import org.eclipse.jetty.websocket.api.SuspendToken;
import org.eclipse.jetty.websocket.api.WebSocketPolicy; import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.api.WebSocketTimeoutException;
import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig; import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
import org.eclipse.jetty.websocket.api.extensions.Frame; import org.eclipse.jetty.websocket.api.extensions.Frame;
import org.eclipse.jetty.websocket.common.CloseInfo;
import org.eclipse.jetty.websocket.common.Generator; import org.eclipse.jetty.websocket.common.Generator;
import org.eclipse.jetty.websocket.common.LogicalConnection; import org.eclipse.jetty.websocket.common.LogicalConnection;
import org.eclipse.jetty.websocket.common.Parser; import org.eclipse.jetty.websocket.common.Parser;
import org.eclipse.jetty.websocket.common.WebSocketSession;
import org.eclipse.jetty.websocket.common.extensions.ExtensionStack; import org.eclipse.jetty.websocket.common.extensions.ExtensionStack;
/** /**
@ -57,18 +61,25 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp
{ {
private class Flusher extends FrameFlusher private class Flusher extends FrameFlusher
{ {
private Flusher(int bufferSize, Generator generator, EndPoint endpoint) private Flusher(ByteBufferPool bufferPool, Generator generator, EndPoint endpoint)
{ {
super(generator,endpoint,bufferSize,8); super(bufferPool,generator,endpoint,getPolicy().getMaxBinaryMessageBufferSize(),8);
} }
@Override @Override
protected void onFailure(Throwable x) public void onCompleteFailure(Throwable failure)
{ {
notifyError(x); super.onCompleteFailure(failure);
notifyError(failure);
if (LOG.isDebugEnabled())
LOG.debug("Write flush failure", failure);
session.notifyClose(new CloseInfo(StatusCode.ABNORMAL, "Write Flush Failure"));
disconnect();
} }
} }
private static final AtomicLong ID_GEN = new AtomicLong(0);
/** /**
* Minimum size of a buffer is the determined to be what would be the maximum framing header size (not including payload) * Minimum size of a buffer is the determined to be what would be the maximum framing header size (not including payload)
*/ */
@ -83,8 +94,9 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp
private final AtomicBoolean closed = new AtomicBoolean(); private final AtomicBoolean closed = new AtomicBoolean();
private final FrameFlusher flusher; private final FrameFlusher flusher;
private final String id; private final String id;
private final ExtensionStack extensionStack; private WebSocketSession session;
private final List<LogicalConnection.Listener> listeners = new CopyOnWriteArrayList<>(); private ExtensionStack extensionStack;
private List<LogicalConnection.Listener> listeners = new CopyOnWriteArrayList<>();
private AtomicBoolean fillAndParseScope = new AtomicBoolean(false); private AtomicBoolean fillAndParseScope = new AtomicBoolean(false);
private List<ExtensionConfig> extensions; private List<ExtensionConfig> extensions;
private ByteBuffer networkBuffer; private ByteBuffer networkBuffer;
@ -99,12 +111,7 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp
Objects.requireNonNull(bufferPool, "ByteBufferPool"); Objects.requireNonNull(bufferPool, "ByteBufferPool");
LOG = Log.getLogger(AbstractWebSocketConnection.class.getName() + "." + policy.getBehavior()); LOG = Log.getLogger(AbstractWebSocketConnection.class.getName() + "." + policy.getBehavior());
this.id = Long.toString(ID_GEN.incrementAndGet());
this.id = String.format("%s:%d->%s:%d",
endp.getLocalAddress().getAddress().getHostAddress(),
endp.getLocalAddress().getPort(),
endp.getRemoteAddress().getAddress().getHostAddress(),
endp.getRemoteAddress().getPort());
this.policy = policy; this.policy = policy;
this.bufferPool = bufferPool; this.bufferPool = bufferPool;
this.extensionStack = extensionStack; this.extensionStack = extensionStack;
@ -113,7 +120,7 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp
this.parser = new Parser(policy,bufferPool,this); this.parser = new Parser(policy,bufferPool,this);
this.extensions = new ArrayList<>(); this.extensions = new ArrayList<>();
this.suspendToken = new AtomicBoolean(false); this.suspendToken = new AtomicBoolean(false);
this.flusher = new Flusher(policy.getOutputBufferSize(),generator,endp); this.flusher = new Flusher(bufferPool,generator,endp);
this.setInputBufferSize(policy.getInputBufferSize()); this.setInputBufferSize(policy.getInputBufferSize());
this.setMaxIdleTimeout(policy.getIdleTimeout()); this.setMaxIdleTimeout(policy.getIdleTimeout());
@ -128,17 +135,58 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp
return super.getExecutor(); return super.getExecutor();
} }
@Override
public void onLocalClose(CloseInfo close)
{
if (LOG.isDebugEnabled())
LOG.debug("Local Close Confirmed {}",close);
if (close.isAbnormal())
{
session.notifyClose(close);
disconnect();
}
else
{
// TODO: ugly - creates a new CloseInfo object later.
session.close(close.getStatusCode(), close.getReason());
}
}
@Override
public void setSession(WebSocketSession session)
{
this.session = session;
}
@Override
public boolean onIdleExpired()
{
// TODO: handle closing handshake (see HTTP2Connection).
return super.onIdleExpired();
}
/**
* Jetty Connection Close
*/
@Override
public void close()
{
session.close();
}
@Override @Override
public void disconnect() public void disconnect()
{ {
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
LOG.debug("disconnect()"); LOG.debug("{} disconnect()",policy.getBehavior());
// close FrameFlusher, we cannot write anymore at this point.
flusher.close();
closed.set(true); closed.set(true);
close(); flusher.terminate(new EOFException("Disconnected"), false);
EndPoint endPoint = getEndPoint();
// We need to gently close first, to allow
// SSL close alerts to be sent by Jetty
endPoint.shutdownOutput();
endPoint.close();
} }
@Override @Override
@ -218,21 +266,9 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp
LOG.debug("onClose()"); LOG.debug("onClose()");
closed.set(true); closed.set(true);
flusher.close();
super.onClose(); super.onClose();
} }
@Override
public boolean onIdleExpired()
{
if (LOG.isDebugEnabled())
LOG.debug("onIdleExpired()");
notifyError(new WebSocketTimeoutException("Connection Idle Timeout"));
return true;
}
@Override @Override
public boolean onFrame(Frame frame) public boolean onFrame(Frame frame)
{ {

View File

@ -18,21 +18,20 @@
package org.eclipse.jetty.websocket.common.io; package org.eclipse.jetty.websocket.common.io;
import java.io.EOFException;
import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.util.ArrayDeque; import java.util.ArrayDeque;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Deque; import java.util.Deque;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.IteratingCallback; import org.eclipse.jetty.util.IteratingCallback;
import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.thread.Locker;
import org.eclipse.jetty.websocket.api.BatchMode; import org.eclipse.jetty.websocket.api.BatchMode;
import org.eclipse.jetty.websocket.api.FrameCallback; import org.eclipse.jetty.websocket.api.FrameCallback;
import org.eclipse.jetty.websocket.api.extensions.Frame; import org.eclipse.jetty.websocket.api.extensions.Frame;
@ -40,166 +39,102 @@ import org.eclipse.jetty.websocket.common.Generator;
import org.eclipse.jetty.websocket.common.OpCode; import org.eclipse.jetty.websocket.common.OpCode;
import org.eclipse.jetty.websocket.common.frames.BinaryFrame; import org.eclipse.jetty.websocket.common.frames.BinaryFrame;
/** public class FrameFlusher extends IteratingCallback
* Interface for working with bytes destined for {@link EndPoint#write(org.eclipse.jetty.util.Callback, ByteBuffer...)}
*/
public class FrameFlusher
{
private class Flusher extends IteratingCallback
{ {
public static final BinaryFrame FLUSH_FRAME = new BinaryFrame();
private static final Logger LOG = Log.getLogger(FrameFlusher.class);
private final ByteBufferPool bufferPool;
private final EndPoint endPoint;
private final int bufferSize;
private final Generator generator;
private final int maxGather;
private final Deque<FrameEntry> queue = new ArrayDeque<>();
private final List<FrameEntry> entries; private final List<FrameEntry> entries;
private final List<ByteBuffer> buffers; private final List<ByteBuffer> buffers;
private boolean closed;
private Throwable terminated;
private ByteBuffer aggregate; private ByteBuffer aggregate;
private BatchMode batchMode; private BatchMode batchMode;
public Flusher(int maxGather) public FrameFlusher(ByteBufferPool bufferPool, Generator generator, EndPoint endPoint, int bufferSize, int maxGather)
{ {
entries = new ArrayList<>(maxGather); this.bufferPool = bufferPool;
buffers = new ArrayList<>((maxGather * 2) + 1); this.endPoint = endPoint;
this.bufferSize = bufferSize;
this.generator = Objects.requireNonNull(generator);
this.maxGather = maxGather;
this.entries = new ArrayList<>(maxGather);
this.buffers = new ArrayList<>((maxGather * 2) + 1);
} }
private Action batch() public void enqueue(Frame frame, FrameCallback callback, BatchMode batchMode)
{ {
if (aggregate == null) FrameEntry entry = new FrameEntry(frame, callback, batchMode);
Throwable closed;
synchronized (this)
{ {
aggregate = generator.getBufferPool().acquire(bufferSize,true); closed = terminated;
BufferUtil.clearToFill(aggregate); if (closed == null)
if (LOG.isDebugEnabled())
{ {
LOG.debug("{} acquired aggregate buffer {}",FrameFlusher.this,aggregate); byte opCode = frame.getOpCode();
if (opCode == OpCode.PING || opCode == OpCode.PONG)
queue.offerFirst(entry);
else
queue.offerLast(entry);
} }
} }
// Do not allocate the iterator here. if (closed == null)
for (int i = 0; i < entries.size(); ++i) iterate();
{ else
FrameEntry entry = entries.get(i); notifyCallbackFailure(callback, closed);
entry.generateHeaderBytes(aggregate);
ByteBuffer payload = entry.frame.getPayload();
if (BufferUtil.hasContent(payload))
{
BufferUtil.put(payload, aggregate);
}
}
if (LOG.isDebugEnabled())
{
LOG.debug("{} aggregated {} frames in {}: {}", FrameFlusher.this, entries.size(), aggregate, entries);
}
succeeded();
return Action.SCHEDULED;
} }
@Override @Override
protected void onCompleteSuccess() protected Action process() throws Throwable
{ {
// This IteratingCallback never completes.
}
@Override
public void onCompleteFailure(Throwable x)
{
for (FrameEntry entry : entries)
{
notifyCallbackFailure(entry.callback,x);
entry.release();
}
entries.clear();
failure = x;
onFailure(x);
}
private Action flush()
{
if (!BufferUtil.isEmpty(aggregate))
{
aggregate.flip();
buffers.add(aggregate);
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
{ LOG.debug("Flushing {}", this);
LOG.debug("{} flushing aggregate {}",FrameFlusher.this,aggregate);
}
}
// Do not allocate the iterator here.
for (int i = 0; i < entries.size(); ++i)
{
FrameEntry entry = entries.get(i);
// Skip the "synthetic" frame used for flushing.
if (entry.frame == FLUSH_FRAME)
{
continue;
}
buffers.add(entry.generateHeaderBytes());
ByteBuffer payload = entry.frame.getPayload();
if (BufferUtil.hasContent(payload))
{
buffers.add(payload);
}
}
if (LOG.isDebugEnabled())
{
LOG.debug("{} flushing {} frames: {}",FrameFlusher.this,entries.size(),entries);
}
if (buffers.isEmpty())
{
releaseAggregate();
// We may have the FLUSH_FRAME to notify.
succeedEntries();
return Action.IDLE;
}
endpoint.write(this,buffers.toArray(new ByteBuffer[buffers.size()]));
buffers.clear();
return Action.SCHEDULED;
}
@Override
protected Action process() throws Exception
{
BatchMode currentBatchMode = BatchMode.AUTO;
try (Locker.Lock l = lock.lock())
{
int space = aggregate == null ? bufferSize : BufferUtil.space(aggregate); int space = aggregate == null ? bufferSize : BufferUtil.space(aggregate);
while ((entries.size() <= maxGather) && !queue.isEmpty()) BatchMode currentBatchMode = BatchMode.AUTO;
synchronized (this)
{
if (closed)
return Action.SUCCEEDED;
if (terminated != null)
throw terminated;
while (!queue.isEmpty() && entries.size() <= maxGather)
{ {
FrameEntry entry = queue.poll(); FrameEntry entry = queue.poll();
currentBatchMode = BatchMode.max(currentBatchMode, entry.batchMode); currentBatchMode = BatchMode.max(currentBatchMode, entry.batchMode);
// Force flush if we need to. // Force flush if we need to.
if (entry.frame == FLUSH_FRAME) if (entry.frame == FLUSH_FRAME)
{
currentBatchMode = BatchMode.OFF; currentBatchMode = BatchMode.OFF;
}
int payloadLength = BufferUtil.length(entry.frame.getPayload()); int payloadLength = BufferUtil.length(entry.frame.getPayload());
int approxFrameLength = Generator.MAX_HEADER_LENGTH + payloadLength; int approxFrameLength = Generator.MAX_HEADER_LENGTH + payloadLength;
// If it is a "big" frame, avoid copying into the aggregate buffer. // If it is a "big" frame, avoid copying into the aggregate buffer.
if (approxFrameLength > (bufferSize >> 2)) if (approxFrameLength > (bufferSize >> 2))
{
currentBatchMode = BatchMode.OFF; currentBatchMode = BatchMode.OFF;
}
// If the aggregate buffer overflows, do not batch. // If the aggregate buffer overflows, do not batch.
space -= approxFrameLength; space -= approxFrameLength;
if (space <= 0) if (space <= 0)
{
currentBatchMode = BatchMode.OFF; currentBatchMode = BatchMode.OFF;
}
entries.add(entry); entries.add(entry);
} }
} }
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
{ LOG.debug("{} processing {} entries: {}", this, entries.size(), entries);
LOG.debug("{} processing {} entries: {}",FrameFlusher.this,entries.size(),entries);
}
if (entries.isEmpty()) if (entries.isEmpty())
{ {
@ -211,7 +146,9 @@ public class FrameFlusher
return Action.IDLE; return Action.IDLE;
} }
LOG.debug("{} auto flushing",FrameFlusher.this); if (LOG.isDebugEnabled())
LOG.debug("{} auto flushing", this);
return flush(); return flush();
} }
@ -220,12 +157,76 @@ public class FrameFlusher
return currentBatchMode == BatchMode.OFF ? flush() : batch(); return currentBatchMode == BatchMode.OFF ? flush() : batch();
} }
private void releaseAggregate() private Action batch()
{ {
if ((aggregate != null) && BufferUtil.isEmpty(aggregate)) if (aggregate == null)
{ {
generator.getBufferPool().release(aggregate); aggregate = bufferPool.acquire(bufferSize, true);
aggregate = null; BufferUtil.clearToFill(aggregate);
if (LOG.isDebugEnabled())
LOG.debug("{} acquired aggregate buffer {}", this, BufferUtil.toDetailString(aggregate));
}
for (FrameEntry entry : entries)
{
entry.generateHeaderBytes(aggregate);
ByteBuffer payload = entry.frame.getPayload();
if (BufferUtil.hasContent(payload))
BufferUtil.put(payload, aggregate);
}
if (LOG.isDebugEnabled())
LOG.debug("{} aggregated {} frames: {}", this, entries.size(), entries);
// We just aggregated the entries, so we need to succeed their callbacks.
succeeded();
return Action.SCHEDULED;
}
private Action flush()
{
if (!BufferUtil.isEmpty(aggregate))
{
aggregate.flip();
buffers.add(aggregate);
if (LOG.isDebugEnabled())
LOG.debug("{} flushing aggregate {}", this, aggregate);
}
for (FrameEntry entry : entries)
{
// Skip the "synthetic" frame used for flushing.
if (entry.frame == FLUSH_FRAME)
continue;
buffers.add(entry.generateHeaderBytes());
ByteBuffer payload = entry.frame.getPayload();
if (BufferUtil.hasContent(payload))
buffers.add(payload);
}
if (LOG.isDebugEnabled())
LOG.debug("{} flushing {} frames: {}", this, entries.size(), entries);
if (buffers.isEmpty())
{
releaseAggregate();
// We may have the FLUSH_FRAME to notify.
succeedEntries();
return Action.IDLE;
}
endPoint.write(this, buffers.toArray(new ByteBuffer[buffers.size()]));
buffers.clear();
return Action.SCHEDULED;
}
private int getQueueSize()
{
synchronized (this)
{
return queue.size();
} }
} }
@ -238,18 +239,108 @@ public class FrameFlusher
private void succeedEntries() private void succeedEntries()
{ {
if(LOG.isDebugEnabled()) for (FrameEntry entry : entries)
LOG.debug("succeedEntries()");
// Do not allocate the iterator here.
for (int i = 0; i < entries.size(); ++i)
{ {
FrameEntry entry = entries.get(i);
notifyCallbackSuccess(entry.callback); notifyCallbackSuccess(entry.callback);
entry.release(); entry.release();
if (entry.frame.getOpCode() == OpCode.CLOSE)
{
terminate(new ClosedChannelException(), true);
endPoint.shutdownOutput();
}
}
entries.clear();
}
@Override
public void onCompleteFailure(Throwable failure)
{
releaseAggregate();
Throwable closed;
synchronized (this)
{
closed = terminated;
if (closed == null)
terminated = failure;
entries.addAll(queue);
queue.clear();
}
for (FrameEntry entry : entries)
{
notifyCallbackFailure(entry.callback, failure);
entry.release();
} }
entries.clear(); entries.clear();
} }
private void releaseAggregate()
{
if (BufferUtil.isEmpty(aggregate))
{
bufferPool.release(aggregate);
aggregate = null;
}
}
void terminate(Throwable cause, boolean close)
{
Throwable reason;
synchronized (this)
{
closed = close;
reason = terminated;
if (reason == null)
terminated = cause;
}
if (LOG.isDebugEnabled())
LOG.debug("{} {}", reason == null ? "Terminating" : "Terminated", this);
if (reason == null && !close)
iterate();
}
protected void notifyCallbackSuccess(FrameCallback callback)
{
try
{
if (callback != null)
{
callback.succeed();
}
}
catch (Throwable x)
{
if (LOG.isDebugEnabled())
LOG.debug("Exception while notifying success of callback " + callback, x);
}
}
protected void notifyCallbackFailure(FrameCallback callback, Throwable failure)
{
try
{
if (callback != null)
{
callback.fail(failure);
}
}
catch (Throwable x)
{
if (LOG.isDebugEnabled())
LOG.debug("Exception while notifying failure of callback " + callback, x);
}
}
@Override
public String toString()
{
return String.format("%s@%x[queueSize=%d,aggregateSize=%d,terminated=%s]",
getClass().getSimpleName(),
hashCode(),
getQueueSize(),
aggregate == null ? 0 : aggregate.position(),
terminated);
} }
private class FrameEntry private class FrameEntry
@ -288,164 +379,7 @@ public class FrameFlusher
@Override @Override
public String toString() public String toString()
{ {
return String.format("%s[%s,%s,%s,%s]",getClass().getSimpleName(),frame,callback,batchMode,failure); return String.format("%s[%s,%s,%s,%s]", getClass().getSimpleName(), frame, callback, batchMode, terminated);
}
}
public static final BinaryFrame FLUSH_FRAME = new BinaryFrame();
private final Logger LOG;
private final EndPoint endpoint;
private final int bufferSize;
private final Generator generator;
private final int maxGather;
private final Locker lock = new Locker();
private final Deque<FrameEntry> queue = new ArrayDeque<>();
private final Flusher flusher;
private boolean closed = false;
private volatile Throwable failure;
public FrameFlusher(Generator generator, EndPoint endpoint, int bufferSize, int maxGather)
{
this.LOG = Log.getLogger(FrameFlusher.class.getName() + "." + generator.getBehavior().name());
this.endpoint = endpoint;
this.bufferSize = bufferSize;
this.generator = Objects.requireNonNull(generator);
this.maxGather = maxGather;
this.flusher = new Flusher(maxGather);
}
public void close()
{
List<FrameEntry> entries = null;
try(Locker.Lock l = lock.lock())
{
if (!closed)
{
closed = true;
LOG.debug("{} closing",this);
entries = new ArrayList<>();
entries.addAll(queue);
queue.clear();
}
}
// Notify outside sync block.
if (entries != null)
{
EOFException eof = new EOFException("Connection has been closed locally");
flusher.failed(eof);
for (FrameEntry entry : entries)
{
notifyCallbackFailure(entry.callback,eof);
} }
} }
} }
public void enqueue(Frame frame, FrameCallback callback, BatchMode batchMode)
{
FrameEntry entry = new FrameEntry(frame,callback,batchMode);
Throwable failed = null;
try (Locker.Lock l = lock.lock())
{
if (closed)
{
failed = new EOFException("Connection has been closed locally");
}
else if (flusher.isFailed())
{
failed = failure==null?new IOException():failure;
}
else
{
switch (frame.getOpCode())
{
case OpCode.PING:
{
// Prepend PINGs so they are processed first.
queue.offerFirst(entry);
break;
}
case OpCode.CLOSE:
{
// There may be a chance that other frames are
// added after this close frame, but we will
// fail them later to keep it simple here.
closed = true;
queue.offer(entry);
break;
}
default:
{
queue.offer(entry);
break;
}
}
}
}
if (failed!=null)
{
if (LOG.isDebugEnabled())
{
LOG.debug("{} failed {}",this,failed);
}
notifyCallbackFailure(callback,failed);
}
else
{
if (LOG.isDebugEnabled())
{
LOG.debug("{} queued {}",this,entry);
}
flusher.iterate();
}
}
protected void notifyCallbackFailure(FrameCallback callback, Throwable failure)
{
try
{
if (callback != null)
{
callback.fail(failure);
}
}
catch (Throwable x)
{
if (LOG.isDebugEnabled())
LOG.debug("Exception while notifying failure of callback " + callback,x);
}
}
protected void notifyCallbackSuccess(FrameCallback callback)
{
try
{
if (callback != null)
{
callback.succeed();
}
}
catch (Throwable x)
{
if (LOG.isDebugEnabled())
LOG.debug("Exception while notifying success of callback " + callback,x);
}
}
protected void onFailure(Throwable x)
{
LOG.warn(x);
}
@Override
public String toString()
{
ByteBuffer aggregate = flusher.aggregate;
return String.format("%s[queueSize=%d,aggregateSize=%d,failure=%s]",getClass().getSimpleName(),queue.size(),aggregate == null?0:aggregate.position(),
failure);
}
}

View File

@ -31,7 +31,9 @@ import org.eclipse.jetty.websocket.api.FrameCallback;
import org.eclipse.jetty.websocket.api.SuspendToken; import org.eclipse.jetty.websocket.api.SuspendToken;
import org.eclipse.jetty.websocket.api.WebSocketPolicy; import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.api.extensions.Frame; import org.eclipse.jetty.websocket.api.extensions.Frame;
import org.eclipse.jetty.websocket.common.CloseInfo;
import org.eclipse.jetty.websocket.common.LogicalConnection; import org.eclipse.jetty.websocket.common.LogicalConnection;
import org.eclipse.jetty.websocket.common.WebSocketSession;
import org.eclipse.jetty.websocket.common.scopes.WebSocketContainerScope; import org.eclipse.jetty.websocket.common.scopes.WebSocketContainerScope;
import org.junit.rules.TestName; import org.junit.rules.TestName;
@ -73,6 +75,11 @@ public class LocalWebSocketConnection implements LogicalConnection
return executor; return executor;
} }
@Override
public void onLocalClose(CloseInfo close)
{
}
@Override @Override
public void disconnect() public void disconnect()
{ {
@ -148,6 +155,11 @@ public class LocalWebSocketConnection implements LogicalConnection
{ {
} }
@Override
public void setSession(WebSocketSession session)
{
}
public void setPolicy(WebSocketPolicy policy) public void setPolicy(WebSocketPolicy policy)
{ {
this.policy = policy; this.policy = policy;

View File

@ -48,6 +48,7 @@ import org.eclipse.jetty.websocket.tests.UntrustedWSServer;
import org.eclipse.jetty.websocket.tests.UntrustedWSSession; import org.eclipse.jetty.websocket.tests.UntrustedWSSession;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.TestName; import org.junit.rules.TestName;
@ -337,6 +338,7 @@ public class ClientCloseHandshakeTest
* </pre> * </pre>
*/ */
@Test @Test
@Ignore("Needs review")
public void testClient_IdleTimeout() throws Exception public void testClient_IdleTimeout() throws Exception
{ {
// Set client timeout // Set client timeout
@ -474,6 +476,7 @@ public class ClientCloseHandshakeTest
* </pre> * </pre>
*/ */
@Test @Test
@Ignore("Needs review")
public void testWriteException() throws Exception public void testWriteException() throws Exception
{ {
// Set client timeout // Set client timeout

View File

@ -25,6 +25,7 @@ import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
import java.io.IOException; import java.io.IOException;
import java.net.SocketTimeoutException;
import java.net.URI; import java.net.URI;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.channels.SelectableChannel; import java.nio.channels.SelectableChannel;
@ -49,7 +50,6 @@ import org.eclipse.jetty.util.thread.Scheduler;
import org.eclipse.jetty.websocket.api.BatchMode; import org.eclipse.jetty.websocket.api.BatchMode;
import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.StatusCode; import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.api.WebSocketTimeoutException;
import org.eclipse.jetty.websocket.client.WebSocketClient; import org.eclipse.jetty.websocket.client.WebSocketClient;
import org.eclipse.jetty.websocket.common.OpCode; import org.eclipse.jetty.websocket.common.OpCode;
import org.eclipse.jetty.websocket.common.WebSocketFrame; import org.eclipse.jetty.websocket.common.WebSocketFrame;
@ -223,7 +223,7 @@ public class ClientCloseTest
// Verify timeout error // Verify timeout error
clientSocket.awaitErrorEvent("Client"); clientSocket.awaitErrorEvent("Client");
clientSocket.assertErrorEvent("Client", instanceOf(WebSocketTimeoutException.class), containsString("Idle Timeout")); clientSocket.assertErrorEvent("Client", instanceOf(SocketTimeoutException.class), containsString("Timeout on Read"));
} }
finally finally
{ {

View File

@ -54,6 +54,7 @@ import org.eclipse.jetty.websocket.tests.SimpleServletServer;
import org.eclipse.jetty.websocket.tests.TrackingEndpoint; import org.eclipse.jetty.websocket.tests.TrackingEndpoint;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.ExpectedException; import org.junit.rules.ExpectedException;
@ -321,6 +322,7 @@ public class ClientDisconnectedTest
* @throws Exception on test failure * @throws Exception on test failure
*/ */
@Test @Test
@Ignore("Needs review")
public void messageDrop() throws Exception public void messageDrop() throws Exception
{ {
ClientUpgradeRequest upgradeRequest = new ClientUpgradeRequest(); ClientUpgradeRequest upgradeRequest = new ClientUpgradeRequest();
@ -362,6 +364,7 @@ public class ClientDisconnectedTest
* @throws Exception on test failure * @throws Exception on test failure
*/ */
@Test @Test
@Ignore("Needs review")
public void closeDrop() throws Exception public void closeDrop() throws Exception
{ {
ClientUpgradeRequest upgradeRequest = new ClientUpgradeRequest(); ClientUpgradeRequest upgradeRequest = new ClientUpgradeRequest();
@ -403,6 +406,7 @@ public class ClientDisconnectedTest
* @throws Exception on test failure * @throws Exception on test failure
*/ */
@Test @Test
@Ignore("Needs review")
public void closeNoReply() throws Exception public void closeNoReply() throws Exception
{ {
ClientUpgradeRequest upgradeRequest = new ClientUpgradeRequest(); ClientUpgradeRequest upgradeRequest = new ClientUpgradeRequest();