diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/AbstractWebSocketConnection.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/AbstractWebSocketConnection.java index 5786164ac4a..ffbb16409ba 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/AbstractWebSocketConnection.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/AbstractWebSocketConnection.java @@ -135,10 +135,8 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp // close FrameFlusher, we cannot write anymore at this point. flusher.close(); - // We need to gently close first, to allow - // SSL close alerts to be sent by Jetty - getEndPoint().close(); closed.set(true); + close(); } @Override diff --git a/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ClientCloseHandshakeTest.java b/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ClientCloseHandshakeTest.java new file mode 100644 index 00000000000..a1d51cc2265 --- /dev/null +++ b/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ClientCloseHandshakeTest.java @@ -0,0 +1,719 @@ +// +// ======================================================================== +// 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.websocket.tests.client; + +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.net.URI; +import java.nio.ByteBuffer; +import java.nio.channels.SelectableChannel; +import java.nio.channels.SelectionKey; +import java.nio.channels.SocketChannel; +import java.util.Arrays; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.io.EofException; +import org.eclipse.jetty.io.ManagedSelector; +import org.eclipse.jetty.io.SelectorManager; +import org.eclipse.jetty.io.SocketChannelEndPoint; +import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.log.StacklessLogging; +import org.eclipse.jetty.util.thread.Scheduler; +import org.eclipse.jetty.websocket.api.BatchMode; +import org.eclipse.jetty.websocket.api.ProtocolException; +import org.eclipse.jetty.websocket.api.Session; +import org.eclipse.jetty.websocket.api.StatusCode; +import org.eclipse.jetty.websocket.client.WebSocketClient; +import org.eclipse.jetty.websocket.common.OpCode; +import org.eclipse.jetty.websocket.common.Parser; +import org.eclipse.jetty.websocket.tests.Defaults; +import org.eclipse.jetty.websocket.tests.RawFrameBuilder; +import org.eclipse.jetty.websocket.tests.TrackingEndpoint; +import org.eclipse.jetty.websocket.tests.UntrustedWSServer; +import org.eclipse.jetty.websocket.tests.UntrustedWSSession; +import org.junit.After; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestName; + +public class ClientCloseHandshakeTest +{ + private static final Logger LOG = Log.getLogger(ClientCloseHandshakeTest.class); + + @Rule + public TestName testname = new TestName(); + + private UntrustedWSServer server; + private WebSocketClient client; + + public static class TestClientTransportOverHTTP extends HttpClientTransportOverHTTP + { + @Override + protected SelectorManager newSelectorManager(HttpClient client) + { + return new ClientSelectorManager(client, 1) + { + @Override + protected EndPoint newEndPoint(SelectableChannel channel, ManagedSelector selector, SelectionKey key) + { + ClientCloseTest.TestEndPoint endPoint = new ClientCloseTest.TestEndPoint(channel, selector, key, getScheduler()); + endPoint.setIdleTimeout(client.getIdleTimeout()); + return endPoint; + } + }; + } + } + + public static class TestEndPoint extends SocketChannelEndPoint + { + public AtomicBoolean congestedFlush = new AtomicBoolean(false); + + public TestEndPoint(SelectableChannel channel, ManagedSelector selector, SelectionKey key, Scheduler scheduler) + { + super((SocketChannel) channel, selector, key, scheduler); + } + + @Override + public boolean flush(ByteBuffer... buffers) throws IOException + { + boolean flushed = super.flush(buffers); + congestedFlush.set(!flushed); + // TODO: if true, toss exception (different use case) + return flushed; + } + } + + @Before + public void startClient() throws Exception + { + HttpClient httpClient = new HttpClient(new TestClientTransportOverHTTP(), null); + client = new WebSocketClient(httpClient); + client.addBean(httpClient); + client.start(); + } + + @Before + public void startServer() throws Exception + { + server = new UntrustedWSServer(); + server.start(); + } + + @After + public void stopClient() throws Exception + { + client.stop(); + } + + @After + public void stopServer() throws Exception + { + server.stop(); + } + + + /** + * Client Initiated - no data + *
+ * Client Server + * ---------- ---------- + * TCP Connect TCP Accept + * WS Handshake Request > + * < WS Handshake Response + * OnOpen() OnOpen() + * close(Normal) + * send:Close/Normal > + * OnFrame(Close) + * OnClose(normal) + * exit onClose() + * < send:Close/Normal + * OnFrame(Close) close.success(disconnect()) + * OnClose(normal) + * disconnect() + *+ */ + @Test + public void testClientInitiated_NoData() + { + + } + + /** + * Client Initiated - no data - alternate close code + *
+ * Client Server + * ---------- ---------- + * TCP Connect TCP Accept + * WS Handshake Request > + * < WS Handshake Response + * OnOpen() OnOpen() + * close(Normal) + * send:Close/Normal > + * OnFrame(Close) + * OnClose(normal) + * close(Shutdown) + * < send:Close/Shutdown (queue) + * cb.success() -> disconnect() + * exit onClose() + * OnFrame(Close) + * OnClose(Shutdown) + * disconnect() + *+ */ + @Test + public void testClientInitiated_NoData_ChangeClose() + { + + } + + /** + * Client Initiated - async send (complete message) during onClose + *
+ * Client Server + * ---------- ---------- + * TCP Connect TCP Accept + * WS Handshake Request > + * < WS Handshake Response + * OnOpen() OnOpen() + * close(Normal) + * send:Close/Normal > + * OnFrame(Close) + * OnClose(normal) + * sendAsync:Text (queued to extension stack) + * exit onClose() + * < send:Close/Normal (queued) + * OnFrame(Close) disconnect() + * OnClose(normal) + * disconnect() + *+ */ + @Test + public void testClientInitiated_AsyncDataDuringClose() + { + + } + + /** + * Client Initiated - partial send data during on close + *
+ * Client Server + * ---------- ---------- + * TCP Connect TCP Accept + * WS Handshake Request > + * < WS Handshake Response + * OnOpen() OnOpen() + * close(Normal) + * send:Close/Normal > + * OnFrame(Close) + * OnClose(normal) + * session.getRemote().writePartial(msg, fin=false) + * exit onClose() + * < send:Close/Normal (queued) + * OnFrame(Close) disconnect() + * OnClose(normal) + * disconnect() + *+ */ + @Test + public void testClientInitiated_PartialDataDuringClose() + { + + } + + /** + * Client Initiated - async streaming during on close + *
+ * Client Server + * ---------- ---------- + * TCP Connect TCP Accept + * WS Handshake Request > + * < WS Handshake Response + * OnOpen() OnOpen() + * close(Normal) + * send:Close/Normal > + * OnFrame(Close) + * OnClose(normal) + * session.getRemote().getOutputStream() + * new Thread() + * send movie to client + * exit onClose() + * < send:Close/Normal + * OnFrame(Close) disconnect() + * OnClose(normal) + * disconnect() + *+ */ + @Test + public void testClientInitiated_AsyncStreamingDuringClose() + { + // TODO: dubious + } + + /** + * Client Initiated - server is streaming data + *
+ * Client Server Server Thread + * ---------- ---------- -------------------- + * Connect Accept + * Handshake Request > + * < Handshake Response + * OnOpen OnOpen + * new Thread() send(Text/!fin) + * send(Continuation/!fin) + * close(Normal) + * send(Close/Normal) > + * OnFrame(Close) + * OnClose(normal) + * exit onClose() + * send(Continuation/!fin) + * < send(Close/Normal) + * send(Continuation/fin) - FAIL + * OnFrame(Close) disconnect() + * disconnect() + *+ */ + @Test + public void testClientInitiated_ServerStreamingData() + { + + } + + /** + * Client Initiated - client is streaming data + *
+ * Client Client Thread Server + * ---------- ------------- ---------- + * Connect Accept + * Handshake Request > + * < Handshake Response + * OnOpen OnOpen + * new Thread() send(Text/!fin) - (streaming here) + * send(Continuation/!fin) + * close(Normal) + * send:Close/Normal + * send(Continuation/!fin) FAIL + * send(Continuation/fin) FAIL - (whole here) + * OnFrame(Close) + * + * OnClose(normal) + * exit onClose() + * send:Close/Normal + * OnFrame(Close) + * OnClose(normal) disconnect() + * disconnect() + *+ */ + @Test + public void testClientInitiated_ClientStreamingData() + { + + } + + /** + * Server Initiated - no data + *
+ * Client Server + * ---------- ---------- + * Connect Accept + * Handshake Request > + * < Handshake Response + * OnOpen OnOpen + * close(Normal) + * < send(Close/Normal) + * OnFrame(Close) + * OnClose(normal) + * exit onClose() + * send:Close/Normal) > + * OnFrame(Close) + * disconnect() OnClose(normal) + * disconnect() + *+ */ + @Test + public void testServerInitiated_NoData() + { + + } + + /** + * Server Initiated - server is streaming data + *
+ * Client Server Server Thread + * ---------- ---------- -------------------- + * Connect Accept + * Handshake Request > + * < Handshake Response + * OnOpen OnOpen + * new Thread() send(Text/!fin) + * send(Continuation/!fin) + * close(Normal) + * send(Continuation/!fin) + * send(Continuation/fin) + * < send(Close/Normal) + * OnFrame(Close) + * send(Close/Normal) > + * OnClose(normal) OnFrame(Close) + * disconnect() OnClose(normal) + * disconnect() + *+ */ + @Test + public void testServerInitiated_ServerStreamingData() + { + + } + + /** + * Server Initiated - client is streaming data + *
+ * Client Client Thread Server + * ---------- ------------- ---------- + * Connect Accept + * Handshake Request > + * < Handshake Response + * OnOpen OnOpen + * new Thread() send(Text/!fin) + * send(Continuation/!fin) + * close(Normal) + * send(Close/Normal) + * OnFrame(Close) + * send(Continuation/!fin) + * send(Continuation/fin) + * send(Close/Normal) + * OnClose(normal) OnFrame(Close) + * disconnect() OnClose(normal) + * disconnect() + *+ */ + @Test + public void testServerInitiated_ClientStreamingData() + { + + } + + /** + * Client Read IOException + *
+ * Client Server + * ---------- ---------- + * Connect Accept + * Handshake Request > + * < Handshake Response + * OnOpen OnOpen + * .... + * disconnect - (accidental) + * conn.onError(EOF) + * OnClose(ABNORMAL) + * disconnect() + * read -> IOException + * conn.onError(IOException) + * OnClose(ABNORMAL) + * disconnect() + *+ */ + @Test + public void testClient_Read_IOException() + { + + } + + /** + * Client Reads -1 + *
+ * Client Server + * ---------- ---------- + * Connect Accept + * Handshake Request > + * < Handshake Response + * OnOpen OnOpen + * ...(some time later)... + * fillAndParse() disconnect - (no-close-handshake) + * read = -1 + * // no close frame received? + * OnClose(ABNORMAL) + * disconnect() + *+ */ + @Test + @Ignore("Needs work") + public void testClient_Read_Minus1() throws Exception + { + // Set client timeout + final int timeout = 1000; + client.setMaxIdleTimeout(timeout); + + URI wsUri = server.getUntrustedWsUri(this.getClass(), testname); + CompletableFuture
+ * Client Server + * ---------- ---------- + * Connect Accept + * Handshake Request > + * < Handshake Response + * OnOpen OnOpen + * ...(some time later)... + * onIdleTimeout() + * conn.onError(TimeoutException) + * send:Close/Shutdown > + * OnFrame(Close) + * OnClose(Shutdown) + * exit onClose() + * < send(Close/Shutdown) + * OnFrame(Close) + * OnClose(Shutdown) disconnect() + * disconnect() + *+ */ + @Test + public void testClient_IdleTimeout() + { + + } + + /** + * Client Idle Timeout + *
+ * Client Server + * ---------- ---------- + * Connect Accept + * Handshake Request > + * < Handshake Response + * OnOpen OnOpen + * ...(some time later)... + * close(Normal) + * send:Close/Normal > + * (state unknown) + * ...(some time later)... + * onIdleTimeout() + * conn.onError(TimeoutException) + * OnClose(ABNORMAL/IdleTimeout) + * disconnect() + *+ */ + @Test + public void testClient_IdleTimeout_Alt() + { + + } + + /** + * Client ProtocolViolation + *
+ * Bad Client Server + * ---------- ---------- + * Connect Accept + * Handshake Request > + * < Handshake Response + * OnOpen OnOpen + * .... + * send:Text(BadFormat) + * close(ProtocolViolation) + * send(Close/ProtocolViolation) + * OnFrame(Close) disconnect() + * OnClose(ProtocolViolation) + * send(Close/ProtocolViolation) > FAILS + * disconnect() + *+ */ + @Test + @Ignore("Needs work") + public void testClient_ProtocolViolation_Received() throws Exception + { + // Set client timeout + final int timeout = 1000; + client.setMaxIdleTimeout(timeout); + + URI wsUri = server.getUntrustedWsUri(this.getClass(), testname); + CompletableFuture
+ * Bad Client Server + * ---------- ---------- + * Connect Accept + * Handshake Request > + * < Handshake Response + * OnOpen OnOpen + * .... + * send:Text() + * write -> IOException + * conn.onError(IOException) + * OnClose(Shutdown) + * disconnect() + * read -> IOException + * conn.onError(IOException) + * OnClose(ABNORMAL) + * disconnect() + *+ */ + @Test + public void testWriteException() throws Exception + { + // Set client timeout + final int timeout = 1000; + client.setMaxIdleTimeout(timeout); + + TrackingEndpoint clientSocket = new TrackingEndpoint(testname.getMethodName()); + URI wsUri = server.getUntrustedWsUri(this.getClass(), testname); + CompletableFuture