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 ] ); + } + } +}