diff --git a/README.markdown b/README.markdown
index 8265a6fec..68c1fac7a 100644
--- a/README.markdown
+++ b/README.markdown
@@ -49,13 +49,6 @@ Then you can just add the latest version to your build.
compile "org.java-websocket:Java-WebSocket:1.3.6"
```
-
-### Leiningen
-
-``` bash
-[org.java-websocket/java-websocket "1.3.6"]
-```
-
Running the Examples
-------------------
@@ -79,10 +72,7 @@ The chat client is a simple Swing GUI application that allows you to send
messages to all other connected clients, and receive messages from others in a
text box.
-In the example folder is also a simple HTML file chat client `chat.html`, which can be opened by any browser. If the browser natively supports the WebSocket API, then it's
-implementation will be used, otherwise it will fall back to a
-[Flash-based WebSocket Implementation](https://github.com/gimite/web-socket-js).
-
+In the example folder is also a simple HTML file chat client `chat.html`, which can be opened by any browser.
Writing your own WebSocket Server
---------------------------------
@@ -99,7 +89,7 @@ Writing your own WebSocket Client
The `org.java_websocket.client.WebSocketClient` abstract class can connect to
valid WebSocket servers. The constructor expects a valid `ws://` URI to
-connect to. Important events `onOpen`, `onClose`, `onMessage` and `onIOError`
+connect to. Important events `onOpen`, `onClose`, `onMessage` and `onError`
get fired throughout the life of the WebSocketClient, and must be implemented
in **your** subclass.
@@ -129,8 +119,8 @@ Minimum Required JDK
`Java-WebSocket` is known to work with:
- * Java 1.5 (aka SE 6)
- * Android 1.6 (API 4)
+ * Java 1.6 and higher
+ * Android 4.0 and higher
Other JRE implementations may work as well, but haven't been tested.
diff --git a/build.xml b/build.xml
index b68bb41b2..5658bfffa 100644
--- a/build.xml
+++ b/build.xml
@@ -1,31 +1,52 @@
-
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/java/org/java_websocket/WebSocketImpl.java b/src/main/java/org/java_websocket/WebSocketImpl.java
index 6d88fcd09..cd803a0c5 100644
--- a/src/main/java/org/java_websocket/WebSocketImpl.java
+++ b/src/main/java/org/java_websocket/WebSocketImpl.java
@@ -206,7 +206,7 @@ public void decode( ByteBuffer socketBuffer ) {
decodeFrames( socketBuffer );
}
} else {
- if( decodeHandshake( socketBuffer ) ) {
+ if( decodeHandshake( socketBuffer ) && (!isClosing() && !isClosed())) {
assert ( tmpHandshakeBytes.hasRemaining() != socketBuffer.hasRemaining() || !socketBuffer.hasRemaining() ); // the buffers will never have remaining bytes at the same time
if( socketBuffer.hasRemaining() ) {
diff --git a/src/test/java/org/java_websocket/issues/AllIssueTests.java b/src/test/java/org/java_websocket/issues/AllIssueTests.java
index d41a32405..1daf59d2d 100644
--- a/src/test/java/org/java_websocket/issues/AllIssueTests.java
+++ b/src/test/java/org/java_websocket/issues/AllIssueTests.java
@@ -30,7 +30,9 @@
@RunWith(Suite.class)
@Suite.SuiteClasses({
- org.java_websocket.issues.Issue609Test.class
+ org.java_websocket.issues.Issue609Test.class,
+ org.java_websocket.issues.Issue621Test.class,
+ org.java_websocket.issues.Issue580Test.class
})
/**
* Start all tests for issues
diff --git a/src/test/java/org/java_websocket/issues/Issue580Test.java b/src/test/java/org/java_websocket/issues/Issue580Test.java
new file mode 100644
index 000000000..368beb056
--- /dev/null
+++ b/src/test/java/org/java_websocket/issues/Issue580Test.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (c) 2010-2017 Nathan Rajlich
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+package org.java_websocket.issues;
+
+import org.java_websocket.WebSocket;
+import org.java_websocket.client.WebSocketClient;
+import org.java_websocket.framing.CloseFrame;
+import org.java_websocket.handshake.ClientHandshake;
+import org.java_websocket.handshake.ServerHandshake;
+import org.java_websocket.server.WebSocketServer;
+import org.java_websocket.util.SocketUtil;
+import org.java_websocket.util.ThreadCheck;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.net.InetSocketAddress;
+import java.net.URI;
+import java.util.concurrent.CountDownLatch;
+
+public class Issue580Test {
+
+ @Rule
+ public ThreadCheck zombies = new ThreadCheck();
+
+ private void runTestScenario(boolean closeBlocking) throws Exception {
+ final CountDownLatch countServerDownLatch = new CountDownLatch( 1 );
+ int port = SocketUtil.getAvailablePort();
+ WebSocketServer ws = new WebSocketServer( new InetSocketAddress( port ) ) {
+ @Override
+ public void onOpen( WebSocket conn, ClientHandshake handshake ) {
+
+ }
+
+ @Override
+ public void onClose( WebSocket conn, int code, String reason, boolean remote ) {
+
+ }
+
+ @Override
+ public void onMessage( WebSocket conn, String message ) {
+
+ }
+
+ @Override
+ public void onError( WebSocket conn, Exception ex ) {
+
+ }
+
+ @Override
+ public void onStart() {
+ countServerDownLatch.countDown();
+ }
+ };
+ ws.start();
+ countServerDownLatch.await();
+ WebSocketClient clt = new WebSocketClient( new URI( "ws://localhost:" + port ) ) {
+ @Override
+ public void onOpen( ServerHandshake handshakedata ) {
+
+ }
+
+ @Override
+ public void onMessage( String message ) {
+
+ }
+
+ @Override
+ public void onClose( int code, String reason, boolean remote ) {
+
+ }
+
+ @Override
+ public void onError( Exception ex ) {
+
+ }
+ };
+ clt.connectBlocking();
+ clt.send("test");
+ if (closeBlocking) {
+ clt.closeBlocking();
+ }
+ ws.stop();
+ Thread.sleep( 100 );
+ }
+
+ @Test
+ public void runNoCloseBlockingTestScenario0() throws Exception {
+ runTestScenario(false);
+ }
+ @Test
+ public void runNoCloseBlockingTestScenario1() throws Exception {
+ runTestScenario(false);
+ }
+ @Test
+ public void runNoCloseBlockingTestScenario2() throws Exception {
+ runTestScenario(false);
+ }
+ @Test
+ public void runNoCloseBlockingTestScenario3() throws Exception {
+ runTestScenario(false);
+ }
+ @Test
+ public void runNoCloseBlockingTestScenario4() throws Exception {
+ runTestScenario(false);
+ }
+ @Test
+ public void runNoCloseBlockingTestScenario5() throws Exception {
+ runTestScenario(false);
+ }
+ @Test
+ public void runNoCloseBlockingTestScenario6() throws Exception {
+ runTestScenario(false);
+ }
+ @Test
+ public void runNoCloseBlockingTestScenario7() throws Exception {
+ runTestScenario(false);
+ }
+ @Test
+ public void runNoCloseBlockingTestScenario8() throws Exception {
+ runTestScenario(false);
+ }
+ @Test
+ public void runNoCloseBlockingTestScenario9() throws Exception {
+ runTestScenario(false);
+ }
+
+ @Test
+ public void runCloseBlockingTestScenario0() throws Exception {
+ runTestScenario(true);
+ }
+ @Test
+ public void runCloseBlockingTestScenario1() throws Exception {
+ runTestScenario(true);
+ }
+ @Test
+ public void runCloseBlockingTestScenario2() throws Exception {
+ runTestScenario(true);
+ }
+ @Test
+ public void runCloseBlockingTestScenario3() throws Exception {
+ runTestScenario(true);
+ }
+ @Test
+ public void runCloseBlockingTestScenario4() throws Exception {
+ runTestScenario(true);
+ }
+ @Test
+ public void runCloseBlockingTestScenario5() throws Exception {
+ runTestScenario(true);
+ }
+ @Test
+ public void runCloseBlockingTestScenario6() throws Exception {
+ runTestScenario(true);
+ }
+ @Test
+ public void runCloseBlockingTestScenario7() throws Exception {
+ runTestScenario(true);
+ }
+ @Test
+ public void runCloseBlockingTestScenario8() throws Exception {
+ runTestScenario(true);
+ }
+ @Test
+ public void runCloseBlockingTestScenario9() throws Exception {
+ runTestScenario(true);
+ }
+
+}
+
diff --git a/src/test/java/org/java_websocket/issues/Issue621Test.java b/src/test/java/org/java_websocket/issues/Issue621Test.java
new file mode 100644
index 000000000..5af214bd1
--- /dev/null
+++ b/src/test/java/org/java_websocket/issues/Issue621Test.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2010-2017 Nathan Rajlich
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+package org.java_websocket.issues;
+
+import org.java_websocket.WebSocket;
+import org.java_websocket.client.WebSocketClient;
+import org.java_websocket.handshake.ClientHandshake;
+import org.java_websocket.handshake.ServerHandshake;
+import org.java_websocket.server.WebSocketServer;
+import org.java_websocket.util.SocketUtil;
+import org.junit.Test;
+
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.net.InetSocketAddress;
+import java.net.URI;
+import java.util.concurrent.CountDownLatch;
+
+import static org.junit.Assert.assertTrue;
+
+public class Issue621Test {
+
+ CountDownLatch countDownLatch = new CountDownLatch( 1 );
+ CountDownLatch countServerDownLatch = new CountDownLatch( 1 );
+
+ boolean wasError = false;
+
+ class TestPrintStream extends PrintStream {
+ public TestPrintStream( OutputStream out ) {
+ super( out );
+ }
+
+ @Override
+ public void println( Object o ) {
+ wasError = true;
+ super.println( o );
+ }
+ }
+
+ @Test
+ public void testIssue() throws Exception {
+ System.setErr( new TestPrintStream( System.err ) );
+ int port = SocketUtil.getAvailablePort();
+ WebSocketClient webSocket = new WebSocketClient( new URI( "ws://localhost:" + port ) ) {
+ @Override
+ public void onOpen( ServerHandshake handshakedata ) {
+
+ }
+
+ @Override
+ public void onMessage( String message ) {
+
+ }
+
+ @Override
+ public void onClose( int code, String reason, boolean remote ) {
+ countDownLatch.countDown();
+ }
+
+ @Override
+ public void onError( Exception ex ) {
+
+ }
+ };
+ WebSocketServer server = new WebSocketServer( new InetSocketAddress( port ) ) {
+ @Override
+ public void onOpen( WebSocket conn, ClientHandshake handshake ) {
+ conn.close();
+ }
+
+ @Override
+ public void onClose( WebSocket conn, int code, String reason, boolean remote ) {
+ }
+
+ @Override
+ public void onMessage( WebSocket conn, String message ) {
+
+ }
+
+ @Override
+ public void onError( WebSocket conn, Exception ex ) {
+
+ }
+
+ @Override
+ public void onStart() {
+ countServerDownLatch.countDown();
+ }
+ };
+ server.start();
+ countServerDownLatch.await();
+ webSocket.connectBlocking();
+ countDownLatch.await();
+ assertTrue( "There was an error using System.err", !wasError );
+ server.stop();
+ }
+}
diff --git a/src/test/java/org/java_websocket/util/ThreadCheck.java b/src/test/java/org/java_websocket/util/ThreadCheck.java
new file mode 100644
index 000000000..a63bc3026
--- /dev/null
+++ b/src/test/java/org/java_websocket/util/ThreadCheck.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2010-2017 Nathan Rajlich
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+package org.java_websocket.util;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.locks.LockSupport;
+
+import org.junit.Assert;
+import org.junit.rules.ExternalResource;
+/**
+ * Makes test fail if new threads are still alive after tear-down.
+ */
+public class ThreadCheck extends ExternalResource {
+ private Map map = new HashMap();
+
+ @Override protected void before() throws Throwable {
+ map = getThreadMap();
+ }
+
+ @Override protected void after() {
+ long time = System.currentTimeMillis();
+ do {
+ LockSupport.parkNanos( 10000000 );
+ } while ( checkZombies( true ) && System.currentTimeMillis() - time < 1000 );
+
+ checkZombies( false );
+ }
+
+ private boolean checkZombies( boolean testOnly ) {
+ Map newMap = getThreadMap();
+
+ int zombies = 0;
+ for( Thread t : newMap.values() ) {
+ Thread prev = map.get( t.getId() );
+ if( prev == null ) {
+ zombies++;
+ if( testOnly )
+ return true;
+
+ StringBuilder b = new StringBuilder( 4096 );
+ appendStack( t, b.append( "\n" ).append( t.getName() ) );
+ System.err.println( b );
+ }
+ }
+ if( zombies > 0 && ! testOnly )
+ Assert.fail( "Found " + zombies + " zombie thread(s) " );
+
+ return zombies > 0;
+ }
+
+ private Map getThreadMap() {
+ Map map = new HashMap();
+ Thread[] threads = new Thread[ Thread.activeCount() * 2 ];
+ int actualNb = Thread.enumerate( threads );
+ for( int i = 0; i < actualNb; i++ ) {
+ map.put( threads[ i ].getId(), threads[ i ] );
+ }
+ return map;
+ }
+
+ private static void appendStack( Thread th, StringBuilder s ) {
+ StackTraceElement[] st = th.getStackTrace();
+ for( int i = 0; i < st.length; i++ ) {
+ s.append( "\n at " );
+ s.append( st[ i ] );
+ }
+ }
+}