From c89323b6fba9ede3c4ff7fe9982a8dbd534ee447 Mon Sep 17 00:00:00 2001 From: Davidiusdadi Date: Wed, 2 Jan 2013 00:49:28 +0100 Subject: [PATCH 01/73] removed possible InterruptedException(#122), made sure that all created threads will be stopped when binding server socket fails, and minor changes --- .../org/java_websocket/server/WebSocketServer.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/java_websocket/server/WebSocketServer.java b/src/main/java/org/java_websocket/server/WebSocketServer.java index 210de36be..377fd1448 100644 --- a/src/main/java/org/java_websocket/server/WebSocketServer.java +++ b/src/main/java/org/java_websocket/server/WebSocketServer.java @@ -213,8 +213,10 @@ public void stop( int timeout ) throws IOException , InterruptedException { if( Thread.currentThread() != selectorthread ) { } - selectorthread.interrupt(); - selectorthread.join(); + if( selectorthread != Thread.currentThread() ) { + selectorthread.interrupt(); + selectorthread.join(); + } } if( decoders != null ) { for( WebSocketWorker w : decoders ) { @@ -283,7 +285,7 @@ public void run() { selector = Selector.open(); server.register( selector, server.validOps() ); } catch ( IOException ex ) { - onWebsocketError( null, ex ); + handleFatal( null, ex ); return; } try { @@ -315,7 +317,7 @@ public void run() { channel.configureBlocking( false ); WebSocketImpl w = wsf.createWebSocket( this, drafts, channel.socket() ); w.key = channel.register( selector, SelectionKey.OP_READ, w ); - w.channel = wsf.wrapChannel( w.key ); + w.channel = wsf.wrapChannel( channel, w.key ); i.remove(); allocateBuffers( w ); continue; @@ -430,7 +432,7 @@ private void handleIOException( WebSocket conn, IOException ex ) { } } - private void handleFatal( WebSocket conn, RuntimeException e ) { + private void handleFatal( WebSocket conn, Exception e ) { onError( conn, e ); try { stop(); @@ -655,6 +657,6 @@ public interface WebSocketServerFactory extends WebSocketFactory { * a SelectionKey of an open SocketChannel. * @return The channel on which the read and write operations will be performed.
*/ - public ByteChannel wrapChannel( SelectionKey key ) throws IOException; + public ByteChannel wrapChannel( SocketChannel channel, SelectionKey key ) throws IOException; } } From 3128285d89d1a80e6e8e286caf3d2b41edf23f65 Mon Sep 17 00:00:00 2001 From: Davidiusdadi Date: Wed, 2 Jan 2013 00:56:47 +0100 Subject: [PATCH 02/73] made SSLSocketChannel2 also work on SocketChannels in blocking mode and therefore"generalized" WebSocketServerFactory and WebSocketClientFactory --- .../org/java_websocket/SSLSocketChannel2.java | 60 +++++++++++++++---- .../java_websocket/WrappedByteChannel.java | 1 + .../DefaultSSLWebSocketClientFactory.java | 5 +- .../DefaultSSLWebSocketServerFactory.java | 5 +- .../server/DefaultWebSocketServerFactory.java | 4 +- 5 files changed, 57 insertions(+), 18 deletions(-) diff --git a/src/main/java/org/java_websocket/SSLSocketChannel2.java b/src/main/java/org/java_websocket/SSLSocketChannel2.java index 4cb3d2712..cc75280e9 100644 --- a/src/main/java/org/java_websocket/SSLSocketChannel2.java +++ b/src/main/java/org/java_websocket/SSLSocketChannel2.java @@ -16,6 +16,7 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; @@ -48,16 +49,20 @@ public class SSLSocketChannel2 implements ByteChannel, WrappedByteChannel { protected SSLEngineResult res; protected SSLEngine sslEngine; + protected final boolean isblocking; + + public SSLSocketChannel2( SocketChannel channel , SSLEngine sslEngine , ExecutorService exec , SelectionKey key ) throws IOException { + this.sc = channel; - public SSLSocketChannel2( SelectionKey key , SSLEngine sslEngine , ExecutorService exec ) throws IOException { - this.sc = (SocketChannel) key.channel(); - this.key = key; this.sslEngine = sslEngine; this.exec = exec; tasks = new ArrayList>( 3 ); - - this.key.interestOps( key.interestOps() | SelectionKey.OP_WRITE ); + if( key != null ) { + key.interestOps( key.interestOps() | SelectionKey.OP_WRITE ); + this.key = key; + } + isblocking = channel.isBlocking(); sslEngine.setEnableSessionCreation( true ); SSLSession session = sslEngine.getSession(); @@ -67,7 +72,25 @@ public SSLSocketChannel2( SelectionKey key , SSLEngine sslEngine , ExecutorServi processHandshake(); } - private void processHandshake() throws IOException { + private void consumeFutureUninterruptible( Future f ) { + try { + boolean interrupted = false; + while ( true ) { + try { + f.get(); + break; + } catch ( InterruptedException e ) { + interrupted = true; + } + } + if( interrupted ) + Thread.currentThread().interrupt(); + } catch ( ExecutionException e ) { + throw new RuntimeException( e ); + } + } + + private synchronized void processHandshake() throws IOException { if( !tasks.isEmpty() ) { Iterator> it = tasks.iterator(); while ( it.hasNext() ) { @@ -75,6 +98,8 @@ private void processHandshake() throws IOException { if( f.isDone() ) { it.remove(); } else { + if( isBlocking() ) + consumeFutureUninterruptible( f ); return; } } @@ -106,11 +131,11 @@ private synchronized ByteBuffer wrap( ByteBuffer b ) throws SSLException { private synchronized ByteBuffer unwrap() throws SSLException { int rem; - do{ + do { rem = inData.remaining(); res = sslEngine.unwrap( inCrypt, inData ); } while ( rem != inData.remaining() ); - + inData.flip(); return inData; } @@ -146,9 +171,17 @@ public int write( ByteBuffer src ) throws IOException { } public int read( ByteBuffer dst ) throws IOException { - if( !isHandShakeComplete() ) { - processHandshake(); + if( !dst.hasRemaining() ) return 0; + if( isBlocking() ) { + while ( !isHandShakeComplete() ) { + processHandshake(); + } + } else { + processHandshake(); + if( !isHandShakeComplete() ) { + return 0; + } } int purged = readRemaining( dst ); @@ -173,8 +206,6 @@ public int read( ByteBuffer dst ) throws IOException { } private int readRemaining( ByteBuffer dst ) throws SSLException { - assert ( dst.hasRemaining() ); - if( inData.hasRemaining() ) { return transfereTo( inData, dst ); } @@ -269,4 +300,9 @@ private int transfereTo( ByteBuffer from, ByteBuffer to ) { } + @Override + public boolean isBlocking() { + return isblocking; + } + } \ No newline at end of file diff --git a/src/main/java/org/java_websocket/WrappedByteChannel.java b/src/main/java/org/java_websocket/WrappedByteChannel.java index b7e110fa8..8f0f838ed 100644 --- a/src/main/java/org/java_websocket/WrappedByteChannel.java +++ b/src/main/java/org/java_websocket/WrappedByteChannel.java @@ -12,4 +12,5 @@ public interface WrappedByteChannel extends ByteChannel { public boolean isNeedRead(); public int readMore( ByteBuffer dst ) throws SSLException; + public boolean isBlocking(); } diff --git a/src/main/java/org/java_websocket/client/DefaultSSLWebSocketClientFactory.java b/src/main/java/org/java_websocket/client/DefaultSSLWebSocketClientFactory.java index 011ce655e..0646cea1a 100644 --- a/src/main/java/org/java_websocket/client/DefaultSSLWebSocketClientFactory.java +++ b/src/main/java/org/java_websocket/client/DefaultSSLWebSocketClientFactory.java @@ -3,6 +3,7 @@ import java.net.Socket; import java.nio.channels.ByteChannel; import java.nio.channels.SelectionKey; +import java.nio.channels.SocketChannel; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -33,10 +34,10 @@ public DefaultSSLWebSocketClientFactory( SSLContext sslContext , ExecutorService } @Override - public ByteChannel wrapChannel( SelectionKey c, String host, int port ) throws IOException { + public ByteChannel wrapChannel( SocketChannel channel, SelectionKey key, String host, int port ) throws IOException { SSLEngine e = sslcontext.createSSLEngine( host, port ); e.setUseClientMode( true ); - return new SSLSocketChannel2( c, e, exec ); + return new SSLSocketChannel2( channel, e, exec, key ); } @Override diff --git a/src/main/java/org/java_websocket/server/DefaultSSLWebSocketServerFactory.java b/src/main/java/org/java_websocket/server/DefaultSSLWebSocketServerFactory.java index 6841151fc..045e7b3a9 100644 --- a/src/main/java/org/java_websocket/server/DefaultSSLWebSocketServerFactory.java +++ b/src/main/java/org/java_websocket/server/DefaultSSLWebSocketServerFactory.java @@ -3,6 +3,7 @@ import java.net.Socket; import java.nio.channels.ByteChannel; import java.nio.channels.SelectionKey; +import java.nio.channels.SocketChannel; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -32,10 +33,10 @@ public DefaultSSLWebSocketServerFactory( SSLContext sslContext , ExecutorService } @Override - public ByteChannel wrapChannel( SelectionKey c ) throws IOException { + public ByteChannel wrapChannel( SocketChannel channel, SelectionKey key ) throws IOException { SSLEngine e = sslcontext.createSSLEngine(); e.setUseClientMode( false ); - return new SSLSocketChannel2( c, e, exec ); + return new SSLSocketChannel2( channel, e, exec, key ); } @Override diff --git a/src/main/java/org/java_websocket/server/DefaultWebSocketServerFactory.java b/src/main/java/org/java_websocket/server/DefaultWebSocketServerFactory.java index 70df6bc73..7556cb047 100644 --- a/src/main/java/org/java_websocket/server/DefaultWebSocketServerFactory.java +++ b/src/main/java/org/java_websocket/server/DefaultWebSocketServerFactory.java @@ -20,7 +20,7 @@ public WebSocketImpl createWebSocket( WebSocketAdapter a, List d, Socket return new WebSocketImpl( a, d, s ); } @Override - public SocketChannel wrapChannel( SelectionKey c ) { - return (SocketChannel) c.channel(); + public SocketChannel wrapChannel( SocketChannel channel, SelectionKey key ) { + return (SocketChannel) channel; } } \ No newline at end of file From c00c3d95627cb565b3d5e3d79dc8fdbd95f3ebad Mon Sep 17 00:00:00 2001 From: Davidiusdadi Date: Wed, 2 Jan 2013 01:01:01 +0100 Subject: [PATCH 03/73] fixed case in which onClose's would be CloseFrame.ABNORMAL_CLOSE wrongly and made WebSocketImpl work without SelectionKeys --- .../java/org/java_websocket/WebSocketImpl.java | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/java_websocket/WebSocketImpl.java b/src/main/java/org/java_websocket/WebSocketImpl.java index 44dabccdd..c49426b1c 100644 --- a/src/main/java/org/java_websocket/WebSocketImpl.java +++ b/src/main/java/org/java_websocket/WebSocketImpl.java @@ -431,11 +431,11 @@ protected synchronized void closeConnection( int code, String message, boolean r if( key != null ) { // key.attach( null ); //see issue #114 key.cancel(); - try { - channel.close(); - } catch ( IOException e ) { - wsl.onWebsocketError( this, e ); - } + } + try { + channel.close(); + } catch ( IOException e ) { + wsl.onWebsocketError( this, e ); } try { this.wsl.onWebsocketClose( this, code, message, remote ); @@ -489,9 +489,8 @@ protected synchronized void flushAndClose( int code, String message, boolean rem public void eot() { if( getReadyState() == READYSTATE.NOT_YET_CONNECTED ) { closeConnection( CloseFrame.NEVER_CONNECTED, true ); - } - if( draft == null ) { - closeConnection( CloseFrame.ABNORMAL_CLOSE, true ); + } else if( flushandclosestate ) { + closeConnection( closecode, closemessage, closedremotely ); } else if( draft.getCloseHandshakeType() == CloseHandshakeType.NONE ) { closeConnection( CloseFrame.NORMAL, true ); } else if( draft.getCloseHandshakeType() == CloseHandshakeType.ONEWAY ) { From 47256b16290db165a0e3206efa16613772435a50 Mon Sep 17 00:00:00 2001 From: Davidiusdadi Date: Wed, 2 Jan 2013 01:03:11 +0100 Subject: [PATCH 04/73] simplified WebSocketClient by using blocking instead of nonblocking channels --- .../java_websocket/SocketChannelIOHelper.java | 17 ++- .../client/WebSocketClient.java | 136 +++++++----------- 2 files changed, 66 insertions(+), 87 deletions(-) diff --git a/src/main/java/org/java_websocket/SocketChannelIOHelper.java b/src/main/java/org/java_websocket/SocketChannelIOHelper.java index fa865cbf1..1ec0d14f3 100644 --- a/src/main/java/org/java_websocket/SocketChannelIOHelper.java +++ b/src/main/java/org/java_websocket/SocketChannelIOHelper.java @@ -3,6 +3,7 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.ByteChannel; +import java.nio.channels.spi.AbstractSelectableChannel; public class SocketChannelIOHelper { @@ -33,10 +34,11 @@ public static boolean readMore( final ByteBuffer buf, WebSocketImpl ws, WrappedB /** Returns whether the whole outQueue has been flushed */ public static boolean batch( WebSocketImpl ws, ByteChannel sockchannel ) throws IOException { ByteBuffer buffer = ws.outQueue.peek(); + WrappedByteChannel c = null; if( buffer == null ) { if( sockchannel instanceof WrappedByteChannel ) { - WrappedByteChannel c = (WrappedByteChannel) sockchannel; + c = (WrappedByteChannel) sockchannel; if( c.isNeedWrite() ) { c.writeMore(); } @@ -53,12 +55,21 @@ public static boolean batch( WebSocketImpl ws, ByteChannel sockchannel ) throws } while ( buffer != null ); } - if( ws.outQueue.isEmpty() && ws.isFlushAndClose() ) { + if( ws.outQueue.isEmpty() && ws.isFlushAndClose() /*&& ( c == null || c.isNeedWrite() )*/) { synchronized ( ws ) { ws.closeConnection(); } } - return sockchannel instanceof WrappedByteChannel == true ? !( (WrappedByteChannel) sockchannel ).isNeedWrite() : true; + return c != null ? !( (WrappedByteChannel) sockchannel ).isNeedWrite() : true; + } + + public static void writeBlocking( WebSocketImpl ws, ByteChannel channel ) throws InterruptedException , IOException { + assert ( channel instanceof AbstractSelectableChannel == true ? ( (AbstractSelectableChannel) channel ).isBlocking() : true ); + assert ( channel instanceof WrappedByteChannel == true ? ( (WrappedByteChannel) channel ).isBlocking() : true ); + + ByteBuffer buf = ws.outQueue.take(); + while ( buf.hasRemaining() ) + channel.write( buf ); } } diff --git a/src/main/java/org/java_websocket/client/WebSocketClient.java b/src/main/java/org/java_websocket/client/WebSocketClient.java index fcfc36967..e3a39e617 100644 --- a/src/main/java/org/java_websocket/client/WebSocketClient.java +++ b/src/main/java/org/java_websocket/client/WebSocketClient.java @@ -10,12 +10,10 @@ import java.nio.channels.ClosedByInterruptException; import java.nio.channels.NotYetConnectedException; import java.nio.channels.SelectionKey; -import java.nio.channels.Selector; import java.nio.channels.SocketChannel; -import java.util.Iterator; +import java.nio.channels.spi.SelectorProvider; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.concurrent.CountDownLatch; import org.java_websocket.SocketChannelIOHelper; @@ -58,13 +56,9 @@ public abstract class WebSocketClient extends WebSocketAdapter implements Runnab private ByteChannel wrappedchannel = null; - private SelectionKey key = null; - /** - * The 'Selector' used to get event keys from the underlying socket. - */ - private Selector selector = null; + private Thread writethread; - private Thread thread; + private Thread readthread; private Draft draft; @@ -88,8 +82,10 @@ public WebSocket createWebSocket( WebSocketAdapter a, List d, Socket s ) } @Override - public ByteChannel wrapChannel( SelectionKey c, String host, int port ) { - return (ByteChannel) c.channel(); + public ByteChannel wrapChannel( SocketChannel channel, SelectionKey c, String host, int port ) { + if( c == null ) + return channel; + return channel; } }; @@ -117,6 +113,7 @@ public WebSocketClient( URI serverUri , Draft draft , Map headers this.draft = draft; this.headers = headers; this.timeout = connecttimeout; + } /** @@ -139,10 +136,10 @@ public Draft getDraft() { * setURI. */ public void connect() { - if( thread != null ) + if( writethread != null ) throw new IllegalStateException( "WebSocketClient objects are not reuseable" ); - thread = new Thread( this ); - thread.start(); + writethread = new Thread( this ); + writethread.start(); } /** @@ -156,7 +153,7 @@ public boolean connectBlocking() throws InterruptedException { } public void close() { - if( thread != null && conn != null ) { + if( writethread != null && conn != null ) { conn.close( CloseFrame.NORMAL ); } } @@ -190,34 +187,34 @@ public void send( byte[] data ) throws NotYetConnectedException { } } - private void tryToConnect( InetSocketAddress remote ) throws IOException { - channel = SocketChannel.open(); - channel.configureBlocking( false ); + private void tryToConnect( InetSocketAddress remote ) throws IOException , InvalidHandshakeException { + channel = SelectorProvider.provider().openSocketChannel(); + channel.configureBlocking( true ); channel.connect( remote ); - selector = Selector.open(); - key = channel.register( selector, SelectionKey.OP_CONNECT ); + } // Runnable IMPLEMENTATION ///////////////////////////////////////////////// public void run() { - if( thread == null ) - thread = Thread.currentThread(); + if( writethread == null ) + writethread = Thread.currentThread(); interruptableRun(); assert ( !channel.isOpen() ); - try { - if( selector != null ) // if the initialization in tryToConnect fails, it could be null - selector.close(); - } catch ( IOException e ) { - onError( e ); - } - } private final void interruptableRun() { try { - tryToConnect( new InetSocketAddress( uri.getHost(), getPort() ) ); + String host = uri.getHost(); + int port = getPort(); + tryToConnect( new InetSocketAddress( host, port ) ); + conn = (WebSocketImpl) wf.createWebSocket( this, draft, channel.socket() ); + conn.channel = wrappedchannel = wf.wrapChannel( channel, null, host, port ); + timeout = 0; // since connect is over + sendHandshake(); + readthread = new Thread( new WebsocketWriteThread() ); + readthread.start(); } catch ( ClosedByInterruptException e ) { onWebsocketError( null, e ); return; @@ -226,46 +223,16 @@ private final void interruptableRun() { conn.closeConnection( CloseFrame.NEVER_CONNECTED, e.getMessage() ); return; } - conn = (WebSocketImpl) wf.createWebSocket( this, draft, channel.socket() ); + ByteBuffer buff = ByteBuffer.allocate( WebSocket.RCVBUF ); try/*IO*/{ while ( channel.isOpen() ) { - SelectionKey key = null; - selector.select( timeout ); - Set keys = selector.selectedKeys(); - Iterator i = keys.iterator(); - if( conn.getReadyState() == READYSTATE.NOT_YET_CONNECTED && !i.hasNext() ) { - // Hack for issue #140: - // Android does simply return form select without closing the channel if address is not reachable(which seems to be a bug in the android nio proivder) - // TODO provide a way to fix this problem which does not require this hack - throw new IOException( "Host is not reachable(Android Hack)" ); - } - while ( i.hasNext() ) { - key = i.next(); - i.remove(); - if( !key.isValid() ) { - conn.eot(); - continue; - } - if( key.isReadable() && SocketChannelIOHelper.read( buff, this.conn, wrappedchannel ) ) { - conn.decode( buff ); - } - if( key.isConnectable() ) { - try { - finishConnect( key ); - } catch ( InvalidHandshakeException e ) { - conn.close( e ); // http error - } - } - if( key.isWritable() ) { - if( SocketChannelIOHelper.batch( conn, wrappedchannel ) ) { - if( key.isValid() ) - key.interestOps( SelectionKey.OP_READ ); - } else { - key.interestOps( SelectionKey.OP_READ | SelectionKey.OP_WRITE ); - } - } + if( SocketChannelIOHelper.read( buff, this.conn, wrappedchannel ) ) { + conn.decode( buff ); + } else { + conn.eot(); } + if( wrappedchannel instanceof WrappedByteChannel ) { WrappedByteChannel w = (WrappedByteChannel) wrappedchannel; if( w.isNeedRead() ) { @@ -302,17 +269,6 @@ private int getPort() { return port; } - private void finishConnect( SelectionKey key ) throws IOException , InvalidHandshakeException { - if( !channel.finishConnect() ) - return; - // Now that we're connected, re-register for only 'READ' keys. - conn.key = key.interestOps( SelectionKey.OP_READ | SelectionKey.OP_WRITE ); - - conn.channel = wrappedchannel = wf.wrapChannel( key, uri.getHost(), getPort() ); - timeout = 0; // since connect is over - sendHandshake(); - } - private void sendHandshake() throws InvalidHandshakeException { String path; String part1 = uri.getPath(); @@ -384,6 +340,7 @@ public final void onWebsocketOpen( WebSocket conn, Handshakedata handshake ) { public final void onWebsocketClose( WebSocket conn, int code, String reason, boolean remote ) { connectLatch.countDown(); closeLatch.countDown(); + readthread.interrupt(); onClose( code, reason, remote ); } @@ -399,12 +356,7 @@ public final void onWebsocketError( WebSocket conn, Exception ex ) { @Override public final void onWriteDemand( WebSocket conn ) { - try { - key.interestOps( SelectionKey.OP_READ | SelectionKey.OP_WRITE ); - selector.wakeup(); - } catch ( CancelledKeyException e ) { - // since such an exception/event will also occur on the selector there is no need to do anything herec - } + // nothing to do } @Override @@ -444,6 +396,22 @@ public void onMessage( ByteBuffer bytes ) { }; public interface WebSocketClientFactory extends WebSocketFactory { - public ByteChannel wrapChannel( SelectionKey key, String host, int port ) throws IOException; + public ByteChannel wrapChannel( SocketChannel channel, SelectionKey key, String host, int port ) throws IOException; + } + + private class WebsocketWriteThread implements Runnable { + @Override + public void run() { + Thread.currentThread().setName( "WebsocketWriteThread" ); + try { + while ( !Thread.interrupted() ) { + SocketChannelIOHelper.writeBlocking( conn, wrappedchannel ); + } + } catch ( IOException e ) { + conn.eot(); + } catch ( InterruptedException e ) { + // this thread is regulary terminated via an interrupt + } + } } } From d74858c0f77f0ba8c6503ee70549072de922f3b0 Mon Sep 17 00:00:00 2001 From: Davidiusdadi Date: Thu, 10 Jan 2013 22:42:10 +0100 Subject: [PATCH 05/73] thanks @jw-gadgetworks for pointing this error out!! (#147) --- .../org/java_websocket/server/WebSocketServer.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/java_websocket/server/WebSocketServer.java b/src/main/java/org/java_websocket/server/WebSocketServer.java index 377fd1448..7a3a63b6b 100644 --- a/src/main/java/org/java_websocket/server/WebSocketServer.java +++ b/src/main/java/org/java_websocket/server/WebSocketServer.java @@ -359,10 +359,14 @@ public void run() { conn = iqueue.remove( 0 ); WrappedByteChannel c = ( (WrappedByteChannel) conn.channel ); ByteBuffer buf = takeBuffer(); - if( SocketChannelIOHelper.readMore( buf, conn, c ) ) - iqueue.add( conn ); - conn.inQueue.put( buf ); - queue( conn ); + try { + if( SocketChannelIOHelper.readMore( buf, conn, c ) ) + iqueue.add( conn ); + conn.inQueue.put( buf ); + queue( conn ); + } finally { + pushBuffer( buf ); + } } } catch ( CancelledKeyException e ) { @@ -381,7 +385,6 @@ public void run() { handleFatal( null, e ); } } - protected void allocateBuffers( WebSocket c ) throws InterruptedException { if( queuesize.get() >= 2 * decoders.size() + 1 ) { return; From 51ec156b25bf535dce2770af1d74713d74a5d155 Mon Sep 17 00:00:00 2001 From: Davidiusdadi Date: Thu, 10 Jan 2013 23:10:15 +0100 Subject: [PATCH 06/73] made Websocket an interface (#147) --- src/main/example/ChatClient.java | 4 ++-- src/main/example/ChatServer.java | 3 ++- src/main/example/SSLClientExample.java | 4 ++-- src/main/example/SSLServerExample.java | 4 ++-- src/main/java/org/java_websocket/WebSocket.java | 13 +++---------- src/main/java/org/java_websocket/WebSocketImpl.java | 7 +++++-- .../org/java_websocket/client/WebSocketClient.java | 2 +- .../org/java_websocket/server/WebSocketServer.java | 4 ++-- src/test/java/AutobahnClientTest.java | 7 ++++--- src/test/java/AutobahnServerTest.java | 3 ++- 10 files changed, 25 insertions(+), 26 deletions(-) diff --git a/src/main/example/ChatClient.java b/src/main/example/ChatClient.java index ee2bdbe24..96a19100d 100644 --- a/src/main/example/ChatClient.java +++ b/src/main/example/ChatClient.java @@ -13,7 +13,7 @@ import javax.swing.JTextArea; import javax.swing.JTextField; -import org.java_websocket.WebSocket; +import org.java_websocket.WebSocketImpl; import org.java_websocket.client.WebSocketClient; import org.java_websocket.drafts.Draft; import org.java_websocket.drafts.Draft_10; @@ -148,7 +148,7 @@ public void onError( Exception ex ) { } public static void main( String[] args ) { - WebSocket.DEBUG = true; + WebSocketImpl.DEBUG = true; String location; if( args.length != 0 ) { location = args[ 0 ]; diff --git a/src/main/example/ChatServer.java b/src/main/example/ChatServer.java index 65fa5b36f..b5e570def 100644 --- a/src/main/example/ChatServer.java +++ b/src/main/example/ChatServer.java @@ -6,6 +6,7 @@ import java.util.Collection; import org.java_websocket.WebSocket; +import org.java_websocket.WebSocketImpl; import org.java_websocket.handshake.ClientHandshake; import org.java_websocket.server.WebSocketServer; @@ -41,7 +42,7 @@ public void onMessage( WebSocket conn, String message ) { } public static void main( String[] args ) throws InterruptedException , IOException { - WebSocket.DEBUG = true; + WebSocketImpl.DEBUG = true; int port = 8887; // 843 flash policy port try { port = Integer.parseInt( args[ 0 ] ); diff --git a/src/main/example/SSLClientExample.java b/src/main/example/SSLClientExample.java index 80ec555fa..2231e5475 100644 --- a/src/main/example/SSLClientExample.java +++ b/src/main/example/SSLClientExample.java @@ -9,7 +9,7 @@ import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManagerFactory; -import org.java_websocket.WebSocket; +import org.java_websocket.WebSocketImpl; import org.java_websocket.client.DefaultSSLWebSocketClientFactory; import org.java_websocket.client.WebSocketClient; import org.java_websocket.handshake.ServerHandshake; @@ -55,7 +55,7 @@ public class SSLClientExample { *keytool -genkey -validity 3650 -keystore "keystore.jks" -storepass "storepassword" -keypass "keypassword" -alias "default" -dname "CN=127.0.0.1, OU=MyOrgUnit, O=MyOrg, L=MyCity, S=MyRegion, C=MyCountry" */ public static void main( String[] args ) throws Exception { - WebSocket.DEBUG = true; + WebSocketImpl.DEBUG = true; WebSocketChatClient chatclient = new WebSocketChatClient( new URI( "wss://localhost:8887" ) ); diff --git a/src/main/example/SSLServerExample.java b/src/main/example/SSLServerExample.java index 9ffb6ce21..563395403 100644 --- a/src/main/example/SSLServerExample.java +++ b/src/main/example/SSLServerExample.java @@ -8,7 +8,7 @@ import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManagerFactory; -import org.java_websocket.WebSocket; +import org.java_websocket.WebSocketImpl; import org.java_websocket.server.DefaultSSLWebSocketServerFactory; public class SSLServerExample { @@ -19,7 +19,7 @@ public class SSLServerExample { *keytool -genkey -validity 3650 -keystore "keystore.jks" -storepass "storepassword" -keypass "keypassword" -alias "default" -dname "CN=127.0.0.1, OU=MyOrgUnit, O=MyOrg, L=MyCity, S=MyRegion, C=MyCountry" */ public static void main( String[] args ) throws Exception { - WebSocket.DEBUG = true; + WebSocketImpl.DEBUG = true; ChatServer chatserver = new ChatServer( 8887 ); // Firefox does allow multible ssl connection only via port 443 //tested on FF16 diff --git a/src/main/java/org/java_websocket/WebSocket.java b/src/main/java/org/java_websocket/WebSocket.java index 2f3ca4074..b21a3a78e 100644 --- a/src/main/java/org/java_websocket/WebSocket.java +++ b/src/main/java/org/java_websocket/WebSocket.java @@ -6,10 +6,9 @@ import java.nio.channels.NotYetConnectedException; import org.java_websocket.drafts.Draft; -import org.java_websocket.exceptions.InvalidDataException; import org.java_websocket.framing.Framedata; -public abstract class WebSocket { +public interface WebSocket { public enum Role { CLIENT, SERVER } @@ -18,10 +17,6 @@ public enum READYSTATE { NOT_YET_CONNECTED, CONNECTING, OPEN, CLOSING, CLOSED; } - public static int RCVBUF = 16384; - - public static/*final*/boolean DEBUG = false; // must be final in the future in order to take advantage of VM optimization - /** * The default port of WebSockets, as defined in the spec. If the nullary * constructor is used, DEFAULT_PORT will be the port the WebSocketServer @@ -35,9 +30,9 @@ public enum READYSTATE { * sends the closing handshake. * may be send in response to an other handshake. */ - public abstract void close( int code, String message ); + public void close( int code, String message ); - public abstract void close( int code ); + public void close( int code ); /** * This will close the connection immediately without a proper close handshake. @@ -45,8 +40,6 @@ public enum READYSTATE { **/ public abstract void closeConnection( int code, String message ); - protected abstract void close( InvalidDataException e ); - /** * Send Text data to the other end. * diff --git a/src/main/java/org/java_websocket/WebSocketImpl.java b/src/main/java/org/java_websocket/WebSocketImpl.java index c49426b1c..4582c7abd 100644 --- a/src/main/java/org/java_websocket/WebSocketImpl.java +++ b/src/main/java/org/java_websocket/WebSocketImpl.java @@ -42,7 +42,11 @@ * text frames, and receiving frames through an event-based model. * */ -public class WebSocketImpl extends WebSocket { +public class WebSocketImpl implements WebSocket { + + public static int RCVBUF = 16384; + + public static/*final*/boolean DEBUG = false; // must be final in the future in order to take advantage of VM optimization public static final List defaultdraftlist = new ArrayList( 4 ); static { @@ -508,7 +512,6 @@ public void close( int code ) { close( code, "", false ); } - @Override public void close( InvalidDataException e ) { close( e.getCloseCode(), e.getMessage(), false ); } diff --git a/src/main/java/org/java_websocket/client/WebSocketClient.java b/src/main/java/org/java_websocket/client/WebSocketClient.java index e3a39e617..08aace6bb 100644 --- a/src/main/java/org/java_websocket/client/WebSocketClient.java +++ b/src/main/java/org/java_websocket/client/WebSocketClient.java @@ -224,7 +224,7 @@ private final void interruptableRun() { return; } - ByteBuffer buff = ByteBuffer.allocate( WebSocket.RCVBUF ); + ByteBuffer buff = ByteBuffer.allocate( WebSocketImpl.RCVBUF ); try/*IO*/{ while ( channel.isOpen() ) { if( SocketChannelIOHelper.read( buff, this.conn, wrappedchannel ) ) { diff --git a/src/main/java/org/java_websocket/server/WebSocketServer.java b/src/main/java/org/java_websocket/server/WebSocketServer.java index 7a3a63b6b..32a655275 100644 --- a/src/main/java/org/java_websocket/server/WebSocketServer.java +++ b/src/main/java/org/java_websocket/server/WebSocketServer.java @@ -280,7 +280,7 @@ public void run() { server = ServerSocketChannel.open(); server.configureBlocking( false ); ServerSocket socket = server.socket(); - socket.setReceiveBufferSize( WebSocket.RCVBUF ); + socket.setReceiveBufferSize( WebSocketImpl.RCVBUF ); socket.bind( address ); selector = Selector.open(); server.register( selector, server.validOps() ); @@ -399,7 +399,7 @@ protected void releaseBuffers( WebSocket c ) throws InterruptedException { } public ByteBuffer createBuffer() { - return ByteBuffer.allocate( WebSocket.RCVBUF ); + return ByteBuffer.allocate( WebSocketImpl.RCVBUF ); } private void queue( WebSocketImpl ws ) throws InterruptedException { diff --git a/src/test/java/AutobahnClientTest.java b/src/test/java/AutobahnClientTest.java index bd345186a..a567fd1b4 100644 --- a/src/test/java/AutobahnClientTest.java +++ b/src/test/java/AutobahnClientTest.java @@ -5,6 +5,7 @@ import java.nio.ByteBuffer; import org.java_websocket.WebSocket; +import org.java_websocket.WebSocketImpl; import org.java_websocket.client.WebSocketClient; import org.java_websocket.drafts.Draft; import org.java_websocket.drafts.Draft_17; @@ -55,11 +56,11 @@ public static void main( String[] args ) { if( nextline != null ) { line = nextline; nextline = null; - WebSocket.DEBUG = false; + WebSocketImpl.DEBUG = false; } else { System.out.print( ">" ); line = sysin.readLine(); - WebSocket.DEBUG = true; + WebSocketImpl.DEBUG = true; } if( line.equals( "l" ) ) { line = perviousline; @@ -84,7 +85,7 @@ public static void main( String[] args ) { uri = URI.create( serverlocation + "/runCase?case=" + spl[ 1 ] + "&agent=" + clientname ); } else if( line.startsWith( "u" ) ) { - WebSocket.DEBUG = false; + WebSocketImpl.DEBUG = false; uri = URI.create( serverlocation + "/updateReports?agent=" + clientname ); } else if( line.startsWith( "d" ) ) { try { diff --git a/src/test/java/AutobahnServerTest.java b/src/test/java/AutobahnServerTest.java index dcf707b4c..ed7d05c47 100644 --- a/src/test/java/AutobahnServerTest.java +++ b/src/test/java/AutobahnServerTest.java @@ -4,6 +4,7 @@ import java.util.Collections; import org.java_websocket.WebSocket; +import org.java_websocket.WebSocketImpl; import org.java_websocket.drafts.Draft; import org.java_websocket.drafts.Draft_17; import org.java_websocket.framing.FrameBuilder; @@ -57,7 +58,7 @@ public void onWebsocketMessageFragment( WebSocket conn, Framedata frame ) { } public static void main( String[] args ) throws UnknownHostException { - WebSocket.DEBUG = false; + WebSocketImpl.DEBUG = false; int port; try { port = new Integer( args[ 0 ] ); From 774bc894392e415bd8ccff953f110186dbdc997f Mon Sep 17 00:00:00 2001 From: Davidiusdadi Date: Sun, 13 Jan 2013 23:36:53 +0100 Subject: [PATCH 07/73] made WebSocket.getLocalSocketAddress and WebSocket.getRemoteSocketAddress more reliable by making them never return null unlike their counterparts Socket.getLocalSocketAddress and Socket.getRemoteSocketAddress (#149) --- .../java/org/java_websocket/WebSocket.java | 7 ++----- .../org/java_websocket/WebSocketImpl.java | 19 ++++++++++++++----- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/java_websocket/WebSocket.java b/src/main/java/org/java_websocket/WebSocket.java index b21a3a78e..69443554d 100644 --- a/src/main/java/org/java_websocket/WebSocket.java +++ b/src/main/java/org/java_websocket/WebSocket.java @@ -1,7 +1,6 @@ package org.java_websocket; import java.net.InetSocketAddress; -import java.net.Socket; import java.nio.ByteBuffer; import java.nio.channels.NotYetConnectedException; @@ -63,14 +62,12 @@ public enum READYSTATE { public abstract boolean hasBufferedData(); /** - * @returns null when connections is closed - * @see Socket#getRemoteSocketAddress() + * @returns never returns null */ public abstract InetSocketAddress getRemoteSocketAddress(); /** - * @returns null when connections is closed - * @see Socket#getLocalSocketAddress() + * @returns never returns null */ public abstract InetSocketAddress getLocalSocketAddress(); diff --git a/src/main/java/org/java_websocket/WebSocketImpl.java b/src/main/java/org/java_websocket/WebSocketImpl.java index 4582c7abd..e516f0fae 100644 --- a/src/main/java/org/java_websocket/WebSocketImpl.java +++ b/src/main/java/org/java_websocket/WebSocketImpl.java @@ -58,8 +58,8 @@ public class WebSocketImpl implements WebSocket { public SelectionKey key; - /* only used to obtain the socket addresses*/ - public final Socket socket; + private final InetSocketAddress localSocketAddress; + private final InetSocketAddress remoteSocketAddress; /** the possibly wrapped channel object whose selection is controlled by {@link #key} */ public ByteChannel channel; /** @@ -122,13 +122,22 @@ public WebSocketImpl( WebSocketListener listener , List drafts , Socket s * crates a websocket with client role */ public WebSocketImpl( WebSocketListener listener , Draft draft , Socket sock ) { + if( listener == null || sock == null || ( draft == null && role == Role.SERVER ) ) + throw new IllegalArgumentException( "parameters must not be null" ); + if( !sock.isBound() ) { + throw new IllegalArgumentException( "socket has to be bound" ); + } this.outQueue = new LinkedBlockingQueue(); inQueue = new LinkedBlockingQueue(); this.wsl = listener; this.role = Role.CLIENT; if( draft != null ) this.draft = draft.copyInstance(); - this.socket = sock; + + localSocketAddress = (InetSocketAddress) sock.getLocalSocketAddress(); + remoteSocketAddress = (InetSocketAddress) sock.getRemoteSocketAddress(); + assert ( localSocketAddress != null ); + assert ( remoteSocketAddress != null ); } /** @@ -682,12 +691,12 @@ public String toString() { @Override public InetSocketAddress getRemoteSocketAddress() { - return (InetSocketAddress) socket.getRemoteSocketAddress(); + return remoteSocketAddress; } @Override public InetSocketAddress getLocalSocketAddress() { - return (InetSocketAddress) socket.getLocalSocketAddress(); + return localSocketAddress; } @Override From 7d4d69a98ade45fe98e5fe37f08bb449bec54891 Mon Sep 17 00:00:00 2001 From: Davidiusdadi Date: Tue, 15 Jan 2013 01:34:45 +0100 Subject: [PATCH 08/73] removed unnecessary code witch caused a racing condition (#145) --- .../java_websocket/server/WebSocketServer.java | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/src/main/java/org/java_websocket/server/WebSocketServer.java b/src/main/java/org/java_websocket/server/WebSocketServer.java index 32a655275..e8decb8ca 100644 --- a/src/main/java/org/java_websocket/server/WebSocketServer.java +++ b/src/main/java/org/java_websocket/server/WebSocketServer.java @@ -76,7 +76,6 @@ public abstract class WebSocketServer extends WebSocketAdapter implements Runnab private List decoders; - private BlockingQueue oqueue; private List iqueue; private BlockingQueue buffers; private int queueinvokes = 0; @@ -157,7 +156,6 @@ public WebSocketServer( InetSocketAddress address , int decodercount , List(); iqueue = new LinkedList(); decoders = new ArrayList( decodercount ); @@ -294,8 +292,6 @@ public void run() { WebSocketImpl conn = null; try { selector.select(); - registerWrite(); - Set keys = selector.selectedKeys(); Iterator i = keys.iterator(); @@ -420,14 +416,6 @@ private void pushBuffer( ByteBuffer buf ) throws InterruptedException { buffers.put( buf ); } - private void registerWrite() throws CancelledKeyException { - int size = oqueue.size(); - for( int i = 0 ; i < size ; i++ ) { - WebSocketImpl conn = oqueue.remove(); - conn.key.interestOps( SelectionKey.OP_READ | SelectionKey.OP_WRITE ); - } - } - private void handleIOException( WebSocket conn, IOException ex ) { onWebsocketError( conn, ex );// conn may be null here if( conn != null ) { @@ -483,7 +471,6 @@ public final void onWebsocketOpen( WebSocket conn, Handshakedata handshake ) { @Override public final void onWebsocketClose( WebSocket conn, int code, String reason, boolean remote ) { - oqueue.add( (WebSocketImpl) conn );// because the ostream will close the channel selector.wakeup(); try { if( removeConnection( conn ) ) { @@ -530,7 +517,7 @@ public final void onWebsocketError( WebSocket conn, Exception ex ) { @Override public final void onWriteDemand( WebSocket w ) { WebSocketImpl conn = (WebSocketImpl) w; - oqueue.add( conn ); + conn.key.interestOps( SelectionKey.OP_READ | SelectionKey.OP_WRITE ); selector.wakeup(); } From 84f343cefe68ece0dae56ec3c66c1d1a077882a0 Mon Sep 17 00:00:00 2001 From: Davidiusdadi Date: Wed, 30 Jan 2013 02:45:11 +0100 Subject: [PATCH 09/73] added function Websocket::close(void) for convenience so that it is not necessary to pass a closecode (#151) --- src/main/java/org/java_websocket/WebSocket.java | 3 +++ src/main/java/org/java_websocket/WebSocketImpl.java | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/src/main/java/org/java_websocket/WebSocket.java b/src/main/java/org/java_websocket/WebSocket.java index 69443554d..af82ab03f 100644 --- a/src/main/java/org/java_websocket/WebSocket.java +++ b/src/main/java/org/java_websocket/WebSocket.java @@ -33,6 +33,9 @@ public enum READYSTATE { public void close( int code ); + /** Convenience function which behaves like close(CloseFrame.NORMAL) */ + public void close(); + /** * This will close the connection immediately without a proper close handshake. * The code and the message therefore won't be transfered over the wire also they will be forwarded to onClose/onWebsocketClose. diff --git a/src/main/java/org/java_websocket/WebSocketImpl.java b/src/main/java/org/java_websocket/WebSocketImpl.java index e516f0fae..5f972cf74 100644 --- a/src/main/java/org/java_websocket/WebSocketImpl.java +++ b/src/main/java/org/java_websocket/WebSocketImpl.java @@ -704,4 +704,9 @@ public Draft getDraft() { return draft; } + @Override + public void close() { + close( CloseFrame.NORMAL ); + } + } From 390c4039e31c8068b72e44e5ea583ec0824d1748 Mon Sep 17 00:00:00 2001 From: Davidiusdadi Date: Wed, 30 Jan 2013 02:45:11 +0100 Subject: [PATCH 10/73] added function Websocket::close(void) for convenience so that it is not necessary to pass a closecode (#151) --- src/main/java/org/java_websocket/WebSocket.java | 3 +++ src/main/java/org/java_websocket/WebSocketImpl.java | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/src/main/java/org/java_websocket/WebSocket.java b/src/main/java/org/java_websocket/WebSocket.java index 69443554d..af82ab03f 100644 --- a/src/main/java/org/java_websocket/WebSocket.java +++ b/src/main/java/org/java_websocket/WebSocket.java @@ -33,6 +33,9 @@ public enum READYSTATE { public void close( int code ); + /** Convenience function which behaves like close(CloseFrame.NORMAL) */ + public void close(); + /** * This will close the connection immediately without a proper close handshake. * The code and the message therefore won't be transfered over the wire also they will be forwarded to onClose/onWebsocketClose. diff --git a/src/main/java/org/java_websocket/WebSocketImpl.java b/src/main/java/org/java_websocket/WebSocketImpl.java index e516f0fae..5f972cf74 100644 --- a/src/main/java/org/java_websocket/WebSocketImpl.java +++ b/src/main/java/org/java_websocket/WebSocketImpl.java @@ -704,4 +704,9 @@ public Draft getDraft() { return draft; } + @Override + public void close() { + close( CloseFrame.NORMAL ); + } + } From 9d9407e7c619690d3443a9927d8740bb1af5382f Mon Sep 17 00:00:00 2001 From: Adam Patacchiola Date: Sun, 3 Feb 2013 08:56:15 -0700 Subject: [PATCH 11/73] merged SSLSocketChannel2 fix (#153) --- src/main/java/org/java_websocket/SSLSocketChannel2.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/java_websocket/SSLSocketChannel2.java b/src/main/java/org/java_websocket/SSLSocketChannel2.java index cc75280e9..f19075bfc 100644 --- a/src/main/java/org/java_websocket/SSLSocketChannel2.java +++ b/src/main/java/org/java_websocket/SSLSocketChannel2.java @@ -134,7 +134,7 @@ private synchronized ByteBuffer unwrap() throws SSLException { do { rem = inData.remaining(); res = sslEngine.unwrap( inCrypt, inData ); - } while ( rem != inData.remaining() ); + } while ( res.getStatus() == SSLEngineResult.Status.OK && (rem != inData.remaining() || res.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP)); inData.flip(); return inData; From c2e7493453bb410ea4bd95ddac435f3609640f2d Mon Sep 17 00:00:00 2001 From: Eli Ezeugoh Date: Wed, 13 Feb 2013 02:05:11 +0000 Subject: [PATCH 12/73] Ignore idea and gradle outputs --- .gitignore | 6 + build.gradle | 44 ++++++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 45336 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 164 +++++++++++++++++++++++ gradlew.bat | 90 +++++++++++++ 6 files changed, 310 insertions(+) create mode 100644 build.gradle create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat diff --git a/.gitignore b/.gitignore index 3f7974706..d7e41aae5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,13 @@ *.class +.git +.gradle +build *.svn *~ target +*.ipr +*.iml +*.iws .settings .project .classpath diff --git a/build.gradle b/build.gradle new file mode 100644 index 000000000..a87d144f7 --- /dev/null +++ b/build.gradle @@ -0,0 +1,44 @@ +apply plugin: 'java' +apply plugin: 'idea' +apply plugin: 'maven' + + +repositories { + mavenLocal() + mavenCentral() +} + +group = 'org.java_websocket' +version = '1.2.1-SNAPSHOT' +sourceCompatibility = 1.6 +targetCompatibility = 1.6 + +configurations { + deployerJars +} + +configure(install.repositories.mavenInstaller) { + pom.version = project.version + pom.groupId = "org.java_websocket" + pom.artifactId = 'Java-WebSocket' +} + +dependencies { + deployerJars "org.apache.maven.wagon:wagon-webdav:1.0-beta-2" +} + + +//deploy to maven repository +//uploadArchives { +// repositories.mavenDeployer { +// configuration = configurations.deployerJars +// repository(url: repositoryUrl) { +// authentication(userName: repositoryUsername, password: repositoryPassword) +// } +// } +//} + +task wrapper(type: Wrapper) { + gradleVersion = '1.4' +} + diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..cf8ca39e94d65e8ecb49956e8e2e0643308f00e6 GIT binary patch literal 45336 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 literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..3d698f898 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Wed Feb 13 00:13:35 GMT 2013 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=http\://services.gradle.org/distributions/gradle-1.4-bin.zip diff --git a/gradlew b/gradlew new file mode 100755 index 000000000..ae91ed902 --- /dev/null +++ b/gradlew @@ -0,0 +1,164 @@ +#!/bin/bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" +APP_HOME="`pwd -P`" +cd "$SAVED" + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query businessSystem maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + JAVA_OPTS="$JAVA_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 000000000..8a0b282aa --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega From bfcb018b66807aaaa2e5859dde145ebf8228d749 Mon Sep 17 00:00:00 2001 From: Eli Ezeugoh Date: Wed, 13 Feb 2013 02:05:41 +0000 Subject: [PATCH 13/73] Enable travis --- .travis.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..067c53e42 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,5 @@ +language: java +jdk: + - oraclejdk7 + +script: "./gradlew test" From 62379ce103370e42d9a2afba7a12e1bd05e5e3fe Mon Sep 17 00:00:00 2001 From: Eli Ezeugoh Date: Wed, 13 Feb 2013 02:23:03 +0000 Subject: [PATCH 14/73] Added travis status image to README. --- README.markdown | 1 + 1 file changed, 1 insertion(+) diff --git a/README.markdown b/README.markdown index 414ec32b6..438302fad 100644 --- a/README.markdown +++ b/README.markdown @@ -1,3 +1,4 @@ +[![Build Status](https://travis-ci.org/ck1125/Java-WebSocket.png?branch=master)](https://travis-ci.org/ck1125/Java-WebSocket) Java WebSockets =============== From b6ce8878d7c144f4c1eca28abe6b9da9b7d0476a Mon Sep 17 00:00:00 2001 From: Davidiusdadi Date: Fri, 15 Feb 2013 21:51:23 +0100 Subject: [PATCH 15/73] fixed #152 --- src/main/java/org/java_websocket/framing/CloseFrameBuilder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/java_websocket/framing/CloseFrameBuilder.java b/src/main/java/org/java_websocket/framing/CloseFrameBuilder.java index 3922cabfa..fee1b540d 100644 --- a/src/main/java/org/java_websocket/framing/CloseFrameBuilder.java +++ b/src/main/java/org/java_websocket/framing/CloseFrameBuilder.java @@ -40,7 +40,7 @@ private void setCodeAndMessage( int code, String m ) throws InvalidDataException m = ""; } if( code == CloseFrame.NOCODE ) { - if( !m.isEmpty() ) { + if( 0 < m.length() ) { throw new InvalidDataException( PROTOCOL_ERROR, "A close frame must have a closecode if it has a reason" ); } return;// empty payload From 2c1085265d0fed552c57095fa77b6914964cab0c Mon Sep 17 00:00:00 2001 From: Davidiusdadi Date: Tue, 19 Feb 2013 23:33:41 +0100 Subject: [PATCH 16/73] made SSLSocketChannel2.java work both in blocking and nonblocking mode (#154) --- .../org/java_websocket/SSLSocketChannel2.java | 44 +++++++++++-------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/src/main/java/org/java_websocket/SSLSocketChannel2.java b/src/main/java/org/java_websocket/SSLSocketChannel2.java index f19075bfc..5cd0ca36d 100644 --- a/src/main/java/org/java_websocket/SSLSocketChannel2.java +++ b/src/main/java/org/java_websocket/SSLSocketChannel2.java @@ -51,6 +51,8 @@ public class SSLSocketChannel2 implements ByteChannel, WrappedByteChannel { protected SSLEngine sslEngine; protected final boolean isblocking; + private Status status = Status.BUFFER_UNDERFLOW; + public SSLSocketChannel2( SocketChannel channel , SSLEngine sslEngine , ExecutorService exec , SelectionKey key ) throws IOException { this.sc = channel; @@ -106,12 +108,14 @@ private synchronized void processHandshake() throws IOException { } if( res.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_UNWRAP ) { - inCrypt.compact(); - int read = sc.read( inCrypt ); - if( read == -1 ) { - throw new IOException( "connection closed unexpectedly by peer" ); + if( !isblocking || status == Status.BUFFER_UNDERFLOW ) { + inCrypt.compact(); + int read = sc.read( inCrypt ); + if( read == -1 ) { + throw new IOException( "connection closed unexpectedly by peer" ); + } + inCrypt.flip(); } - inCrypt.flip(); inData.compact(); unwrap(); } @@ -134,7 +138,8 @@ private synchronized ByteBuffer unwrap() throws SSLException { do { rem = inData.remaining(); res = sslEngine.unwrap( inCrypt, inData ); - } while ( res.getStatus() == SSLEngineResult.Status.OK && (rem != inData.remaining() || res.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP)); + status = res.getStatus(); + } while ( status == SSLEngineResult.Status.OK && ( rem != inData.remaining() || res.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP ) ); inData.flip(); return inData; @@ -173,14 +178,16 @@ public int write( ByteBuffer src ) throws IOException { public int read( ByteBuffer dst ) throws IOException { if( !dst.hasRemaining() ) return 0; - if( isBlocking() ) { - while ( !isHandShakeComplete() ) { + if( !isHandShakeComplete() ) { + if( isBlocking() ) { + while ( !isHandShakeComplete() ) { + processHandshake(); + } + } else { processHandshake(); - } - } else { - processHandshake(); - if( !isHandShakeComplete() ) { - return 0; + if( !isHandShakeComplete() ) { + return 0; + } } } @@ -196,9 +203,10 @@ public int read( ByteBuffer dst ) throws IOException { else inCrypt.compact(); - if( sc.read( inCrypt ) == -1 ) { - return -1; - } + if( ( isblocking && inCrypt.position() == 0 ) || status == Status.BUFFER_UNDERFLOW ) + if( sc.read( inCrypt ) == -1 ) { + return -1; + } inCrypt.flip(); unwrap(); return transfereTo( inData, dst ); @@ -209,8 +217,8 @@ private int readRemaining( ByteBuffer dst ) throws SSLException { if( inData.hasRemaining() ) { return transfereTo( inData, dst ); } - assert ( !inData.hasRemaining() ); - inData.clear(); + if( !inData.hasRemaining() ) + inData.clear(); // test if some bytes left from last read (e.g. BUFFER_UNDERFLOW) if( inCrypt.hasRemaining() ) { unwrap(); From d7b546762170c9ab2ec76b1423c5316de7880eac Mon Sep 17 00:00:00 2001 From: Davidiusdadi Date: Wed, 20 Feb 2013 02:13:49 +0100 Subject: [PATCH 17/73] improved buffer allocation, renamed variables, documented code and fixed potential threading issue in SSLSocketChannel2#processHandshake (#154) --- .../org/java_websocket/SSLSocketChannel2.java | 106 +++++++++++------- .../java_websocket/SocketChannelIOHelper.java | 4 + .../java_websocket/WrappedByteChannel.java | 10 ++ 3 files changed, 78 insertions(+), 42 deletions(-) diff --git a/src/main/java/org/java_websocket/SSLSocketChannel2.java b/src/main/java/org/java_websocket/SSLSocketChannel2.java index 5cd0ca36d..5402e2dbe 100644 --- a/src/main/java/org/java_websocket/SSLSocketChannel2.java +++ b/src/main/java/org/java_websocket/SSLSocketChannel2.java @@ -44,33 +44,34 @@ public class SSLSocketChannel2 implements ByteChannel, WrappedByteChannel { /** encrypted data incoming */ protected ByteBuffer inCrypt; - protected SocketChannel sc; - protected SelectionKey key; + /** the underlying channel */ + protected SocketChannel socketChannel; + /** used to set interestOP SelectionKey.OP_WRITE for the underlying channel */ + protected SelectionKey selectionKey; - protected SSLEngineResult res; + + protected SSLEngineResult engineResult; protected SSLEngine sslEngine; - protected final boolean isblocking; - private Status status = Status.BUFFER_UNDERFLOW; + + private Status engineStatus = Status.BUFFER_UNDERFLOW; public SSLSocketChannel2( SocketChannel channel , SSLEngine sslEngine , ExecutorService exec , SelectionKey key ) throws IOException { - this.sc = channel; + if( channel == null || sslEngine == null || exec == null ) + throw new IllegalArgumentException( "parameter must not be null" ); + this.socketChannel = channel; this.sslEngine = sslEngine; this.exec = exec; tasks = new ArrayList>( 3 ); if( key != null ) { key.interestOps( key.interestOps() | SelectionKey.OP_WRITE ); - this.key = key; + this.selectionKey = key; } - isblocking = channel.isBlocking(); - - sslEngine.setEnableSessionCreation( true ); - SSLSession session = sslEngine.getSession(); - createBuffers( session ); - - sc.write( wrap( emptybuffer ) );// initializes res + createBuffers( sslEngine.getSession() ); + // kick off handshake + socketChannel.write( wrap( emptybuffer ) );// initializes res processHandshake(); } @@ -93,6 +94,8 @@ private void consumeFutureUninterruptible( Future f ) { } private synchronized void processHandshake() throws IOException { + if( engineResult.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING ) + return; // since this may be called either from a reading or a writing thread and because this method is synchronized it is necessary to double check if we are still handshaking. if( !tasks.isEmpty() ) { Iterator> it = tasks.iterator(); while ( it.hasNext() ) { @@ -107,10 +110,10 @@ private synchronized void processHandshake() throws IOException { } } - if( res.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_UNWRAP ) { - if( !isblocking || status == Status.BUFFER_UNDERFLOW ) { + if( engineResult.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_UNWRAP ) { + if( !isBlocking() || engineStatus == Status.BUFFER_UNDERFLOW ) { inCrypt.compact(); - int read = sc.read( inCrypt ); + int read = socketChannel.read( inCrypt ); if( read == -1 ) { throw new IOException( "connection closed unexpectedly by peer" ); } @@ -118,17 +121,24 @@ private synchronized void processHandshake() throws IOException { } inData.compact(); unwrap(); + if( engineResult.getHandshakeStatus() == HandshakeStatus.FINISHED ) { + createBuffers( sslEngine.getSession() ); + return; + } } consumeDelegatedTasks(); - if( tasks.isEmpty() || res.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_WRAP ) { - sc.write( wrap( emptybuffer ) ); + assert ( engineResult.getHandshakeStatus() != HandshakeStatus.NOT_HANDSHAKING ); + if( tasks.isEmpty() || engineResult.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_WRAP ) { + socketChannel.write( wrap( emptybuffer ) ); + if( engineResult.getHandshakeStatus() == HandshakeStatus.FINISHED ) { + createBuffers( sslEngine.getSession() ); + } } - } private synchronized ByteBuffer wrap( ByteBuffer b ) throws SSLException { outCrypt.compact(); - res = sslEngine.wrap( b, outCrypt ); + engineResult = sslEngine.wrap( b, outCrypt ); outCrypt.flip(); return outCrypt; } @@ -137,9 +147,9 @@ private synchronized ByteBuffer unwrap() throws SSLException { int rem; do { rem = inData.remaining(); - res = sslEngine.unwrap( inCrypt, inData ); - status = res.getStatus(); - } while ( status == SSLEngineResult.Status.OK && ( rem != inData.remaining() || res.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP ) ); + engineResult = sslEngine.unwrap( inCrypt, inData ); + engineStatus = engineResult.getStatus(); + } while ( engineStatus == SSLEngineResult.Status.OK && ( rem != inData.remaining() || engineResult.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP ) ); inData.flip(); return inData; @@ -157,11 +167,23 @@ protected void createBuffers( SSLSession session ) { int appBufferMax = session.getApplicationBufferSize(); int netBufferMax = session.getPacketBufferSize(); - inData = ByteBuffer.allocate( appBufferMax ); - outCrypt = ByteBuffer.allocate( netBufferMax ); - inCrypt = ByteBuffer.allocate( netBufferMax ); + if( inData == null ) { + inData = ByteBuffer.allocate( appBufferMax ); + outCrypt = ByteBuffer.allocate( netBufferMax ); + inCrypt = ByteBuffer.allocate( netBufferMax ); + } else { + if( inData.capacity() != appBufferMax ) + inData = ByteBuffer.allocate( appBufferMax ); + if( outCrypt.capacity() != netBufferMax ) + outCrypt = ByteBuffer.allocate( netBufferMax ); + if( inCrypt.capacity() != netBufferMax ) + inCrypt = ByteBuffer.allocate( netBufferMax ); + } + inData.rewind(); inData.flip(); + inCrypt.rewind(); inCrypt.flip(); + outCrypt.rewind(); outCrypt.flip(); } @@ -170,7 +192,7 @@ public int write( ByteBuffer src ) throws IOException { processHandshake(); return 0; } - int num = sc.write( wrap( src ) ); + int num = socketChannel.write( wrap( src ) ); return num; } @@ -203,8 +225,8 @@ public int read( ByteBuffer dst ) throws IOException { else inCrypt.compact(); - if( ( isblocking && inCrypt.position() == 0 ) || status == Status.BUFFER_UNDERFLOW ) - if( sc.read( inCrypt ) == -1 ) { + if( ( isBlocking() && inCrypt.position() == 0 ) || engineStatus == Status.BUFFER_UNDERFLOW ) + if( socketChannel.read( inCrypt ) == -1 ) { return -1; } inCrypt.flip(); @@ -230,36 +252,36 @@ private int readRemaining( ByteBuffer dst ) throws SSLException { } public boolean isConnected() { - return sc.isConnected(); + return socketChannel.isConnected(); } public void close() throws IOException { sslEngine.closeOutbound(); sslEngine.getSession().invalidate(); - if( sc.isOpen() ) - sc.write( wrap( emptybuffer ) );// FIXME what if not all bytes can be written - sc.close(); + if( socketChannel.isOpen() ) + socketChannel.write( wrap( emptybuffer ) );// FIXME what if not all bytes can be written + socketChannel.close(); } private boolean isHandShakeComplete() { - HandshakeStatus status = res.getHandshakeStatus(); + HandshakeStatus status = engineResult.getHandshakeStatus(); return status == SSLEngineResult.HandshakeStatus.FINISHED || status == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING; } public SelectableChannel configureBlocking( boolean b ) throws IOException { - return sc.configureBlocking( b ); + return socketChannel.configureBlocking( b ); } public boolean connect( SocketAddress remote ) throws IOException { - return sc.connect( remote ); + return socketChannel.connect( remote ); } public boolean finishConnect() throws IOException { - return sc.finishConnect(); + return socketChannel.finishConnect(); } public Socket socket() { - return sc.socket(); + return socketChannel.socket(); } public boolean isInboundDone() { @@ -268,7 +290,7 @@ public boolean isInboundDone() { @Override public boolean isOpen() { - return sc.isOpen(); + return socketChannel.isOpen(); } @Override @@ -283,7 +305,7 @@ public void writeMore() throws IOException { @Override public boolean isNeedRead() { - return inData.hasRemaining() || ( inCrypt.hasRemaining() && res.getStatus() != Status.BUFFER_UNDERFLOW ); + return inData.hasRemaining() || ( inCrypt.hasRemaining() && engineResult.getStatus() != Status.BUFFER_UNDERFLOW ); } @Override @@ -310,7 +332,7 @@ private int transfereTo( ByteBuffer from, ByteBuffer to ) { @Override public boolean isBlocking() { - return isblocking; + return socketChannel.isBlocking(); } } \ No newline at end of file diff --git a/src/main/java/org/java_websocket/SocketChannelIOHelper.java b/src/main/java/org/java_websocket/SocketChannelIOHelper.java index 1ec0d14f3..9752098c4 100644 --- a/src/main/java/org/java_websocket/SocketChannelIOHelper.java +++ b/src/main/java/org/java_websocket/SocketChannelIOHelper.java @@ -19,6 +19,10 @@ public static boolean read( final ByteBuffer buf, WebSocketImpl ws, ByteChannel return read != 0; } + /** + * @see WrappedByteChannel#readMore(ByteBuffer) + * @return returns whether there is more data left which can be obtained via {@link #readMore(ByteBuffer, WebSocketImpl, WrappedByteChannel)} + **/ public static boolean readMore( final ByteBuffer buf, WebSocketImpl ws, WrappedByteChannel channel ) throws IOException { buf.clear(); int read = channel.readMore( buf ); diff --git a/src/main/java/org/java_websocket/WrappedByteChannel.java b/src/main/java/org/java_websocket/WrappedByteChannel.java index 8f0f838ed..83a3290b3 100644 --- a/src/main/java/org/java_websocket/WrappedByteChannel.java +++ b/src/main/java/org/java_websocket/WrappedByteChannel.java @@ -10,7 +10,17 @@ public interface WrappedByteChannel extends ByteChannel { public boolean isNeedWrite(); public void writeMore() throws IOException; + /** + * returns whether readMore should be called to fetch data which has been decoded but not yet been returned. + * + * @see #read(ByteBuffer) + * @see #readMore(ByteBuffer) + **/ public boolean isNeedRead(); + /** + * This function does not read data from the underlying channel at all. It is just a way to fetch data which has already be received or decoded but was but was not yet returned to the user. + * This could be the case when the decoded data did not fit into the buffer the user passed to {@link #read(ByteBuffer)}. + **/ public int readMore( ByteBuffer dst ) throws SSLException; public boolean isBlocking(); } From f01e7851ff4790205f122e46e70bd8b2cf7ddbac Mon Sep 17 00:00:00 2001 From: Tamer Saadeh Date: Sat, 23 Feb 2013 13:59:00 +0100 Subject: [PATCH 18/73] Fixed a minor typo htts:// -> https:// --- README.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.markdown b/README.markdown index 414ec32b6..a987c05a7 100644 --- a/README.markdown +++ b/README.markdown @@ -90,7 +90,7 @@ To see how to use wss please take a look at the examples.
If you do not have a valid **certificate** in place then you will have to create a self signed one. Browsers will simply refuse the connection in case of a bad certificate and will not ask the user to accept it. So the first step will be to make a browser to accept your self signed certificate. ( https://bugzilla.mozilla.org/show_bug.cgi?id=594502 ).
-If the websocket server url is `wss://localhost:8000` visit the url `htts://localhost:8000` with your browser. The browser will recognize the handshake and allow you to accept the certificate. +If the websocket server url is `wss://localhost:8000` visit the url `https://localhost:8000` with your browser. The browser will recognize the handshake and allow you to accept the certificate. The vm option `-Djavax.net.debug=all` can help to find out if there is a problem with the certificate. From adb20a55231e152f6f9064838b8d82334b11a45c Mon Sep 17 00:00:00 2001 From: Davidiusdadi Date: Sat, 23 Feb 2013 19:14:10 +0100 Subject: [PATCH 19/73] improved WebSocketClient: made member initialization more comprehensible --- .../org/java_websocket/WebSocketImpl.java | 19 ++++++-------- .../client/WebSocketClient.java | 26 +++++++++---------- 2 files changed, 21 insertions(+), 24 deletions(-) diff --git a/src/main/java/org/java_websocket/WebSocketImpl.java b/src/main/java/org/java_websocket/WebSocketImpl.java index 5f972cf74..d5924acbb 100644 --- a/src/main/java/org/java_websocket/WebSocketImpl.java +++ b/src/main/java/org/java_websocket/WebSocketImpl.java @@ -58,8 +58,8 @@ public class WebSocketImpl implements WebSocket { public SelectionKey key; - private final InetSocketAddress localSocketAddress; - private final InetSocketAddress remoteSocketAddress; + private final Socket socket; + /** the possibly wrapped channel object whose selection is controlled by {@link #key} */ public ByteChannel channel; /** @@ -120,13 +120,13 @@ public WebSocketImpl( WebSocketListener listener , List drafts , Socket s /** * crates a websocket with client role + * + * @param sock + * may be unbound */ public WebSocketImpl( WebSocketListener listener , Draft draft , Socket sock ) { if( listener == null || sock == null || ( draft == null && role == Role.SERVER ) ) throw new IllegalArgumentException( "parameters must not be null" ); - if( !sock.isBound() ) { - throw new IllegalArgumentException( "socket has to be bound" ); - } this.outQueue = new LinkedBlockingQueue(); inQueue = new LinkedBlockingQueue(); this.wsl = listener; @@ -134,10 +134,7 @@ public WebSocketImpl( WebSocketListener listener , Draft draft , Socket sock ) { if( draft != null ) this.draft = draft.copyInstance(); - localSocketAddress = (InetSocketAddress) sock.getLocalSocketAddress(); - remoteSocketAddress = (InetSocketAddress) sock.getRemoteSocketAddress(); - assert ( localSocketAddress != null ); - assert ( remoteSocketAddress != null ); + socket = sock; } /** @@ -691,12 +688,12 @@ public String toString() { @Override public InetSocketAddress getRemoteSocketAddress() { - return remoteSocketAddress; + return (InetSocketAddress) socket.getRemoteSocketAddress(); } @Override public InetSocketAddress getLocalSocketAddress() { - return localSocketAddress; + return (InetSocketAddress) socket.getLocalSocketAddress(); } @Override diff --git a/src/main/java/org/java_websocket/client/WebSocketClient.java b/src/main/java/org/java_websocket/client/WebSocketClient.java index 08aace6bb..ae6a3709c 100644 --- a/src/main/java/org/java_websocket/client/WebSocketClient.java +++ b/src/main/java/org/java_websocket/client/WebSocketClient.java @@ -114,6 +114,15 @@ public WebSocketClient( URI serverUri , Draft draft , Map headers this.headers = headers; this.timeout = connecttimeout; + try { + channel = SelectorProvider.provider().openSocketChannel(); + channel.configureBlocking( true ); + conn = (WebSocketImpl) wf.createWebSocket( this, draft, channel.socket() ); + } catch ( IOException e ) { + onWebsocketError( null, e ); + conn.closeConnection( CloseFrame.NEVER_CONNECTED, e.getMessage() ); + return; + } } /** @@ -153,7 +162,7 @@ public boolean connectBlocking() throws InterruptedException { } public void close() { - if( writethread != null && conn != null ) { + if( writethread != null ) { conn.close( CloseFrame.NORMAL ); } } @@ -170,9 +179,7 @@ public void closeBlocking() throws InterruptedException { * The String to send to the WebSocket server. */ public void send( String text ) throws NotYetConnectedException { - if( conn != null ) { - conn.send( text ); - } + conn.send( text ); } /** @@ -182,14 +189,11 @@ public void send( String text ) throws NotYetConnectedException { * The Byte-Array of data to send to the WebSocket server. */ public void send( byte[] data ) throws NotYetConnectedException { - if( conn != null ) { conn.send( data ); - } } private void tryToConnect( InetSocketAddress remote ) throws IOException , InvalidHandshakeException { - channel = SelectorProvider.provider().openSocketChannel(); - channel.configureBlocking( true ); + channel.connect( remote ); } @@ -209,7 +213,6 @@ private final void interruptableRun() { String host = uri.getHost(); int port = getPort(); tryToConnect( new InetSocketAddress( host, port ) ); - conn = (WebSocketImpl) wf.createWebSocket( this, draft, channel.socket() ); conn.channel = wrappedchannel = wf.wrapChannel( channel, null, host, port ); timeout = 0; // since connect is over sendHandshake(); @@ -298,9 +301,6 @@ private void sendHandshake() throws InvalidHandshakeException { * You can use this method instead of */ public READYSTATE getReadyState() { - if( conn == null ) { - return READYSTATE.NOT_YET_CONNECTED; - } return conn.getReadyState(); } @@ -410,7 +410,7 @@ public void run() { } catch ( IOException e ) { conn.eot(); } catch ( InterruptedException e ) { - // this thread is regulary terminated via an interrupt + // this thread is regularly terminated via an interrupt } } } From b6cc899715148dfeafd36e195f82e86e1ae786ee Mon Sep 17 00:00:00 2001 From: Davidiusdadi Date: Sat, 23 Feb 2013 19:18:28 +0100 Subject: [PATCH 20/73] improved ssl handling --- src/main/java/org/java_websocket/client/WebSocketClient.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/java_websocket/client/WebSocketClient.java b/src/main/java/org/java_websocket/client/WebSocketClient.java index ae6a3709c..77c9699b6 100644 --- a/src/main/java/org/java_websocket/client/WebSocketClient.java +++ b/src/main/java/org/java_websocket/client/WebSocketClient.java @@ -239,9 +239,10 @@ private final void interruptableRun() { if( wrappedchannel instanceof WrappedByteChannel ) { WrappedByteChannel w = (WrappedByteChannel) wrappedchannel; if( w.isNeedRead() ) { - while ( SocketChannelIOHelper.read( buff, conn, w ) ) { + while ( SocketChannelIOHelper.readMore( buff, conn, w ) ) { conn.decode( buff ); } + conn.decode( buff ); } } } From 85efa0a17105482b22b2399ca4aa60e3737ebafb Mon Sep 17 00:00:00 2001 From: Davidiusdadi Date: Tue, 26 Feb 2013 00:37:07 +0100 Subject: [PATCH 21/73] fix for #154 --- .../java/org/java_websocket/SSLSocketChannel2.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/java_websocket/SSLSocketChannel2.java b/src/main/java/org/java_websocket/SSLSocketChannel2.java index 5402e2dbe..83de34292 100644 --- a/src/main/java/org/java_websocket/SSLSocketChannel2.java +++ b/src/main/java/org/java_websocket/SSLSocketChannel2.java @@ -197,6 +197,10 @@ public int write( ByteBuffer src ) throws IOException { } + /** + * Blocks when in blocking mode until at least one byte has been decoded.
+ * When not in blocking mode 0 may be returned. + **/ public int read( ByteBuffer dst ) throws IOException { if( !dst.hasRemaining() ) return 0; @@ -231,8 +235,12 @@ public int read( ByteBuffer dst ) throws IOException { } inCrypt.flip(); unwrap(); - return transfereTo( inData, dst ); + int transfered = transfereTo( inData, dst ); + if( transfered == 0 && isBlocking() ) { + return read( dst ); // "transfered" may be 0 when not enough bytes were received or during rehandshaking + } + return transfered; } private int readRemaining( ByteBuffer dst ) throws SSLException { From a18d0f707a8baf061be7f0eeb5c1444f34bdd347 Mon Sep 17 00:00:00 2001 From: wuzixiu Date: Tue, 26 Feb 2013 17:40:59 +0800 Subject: [PATCH 22/73] fix NullPointerException in WebSocketClient --- .../org/java_websocket/client/WebSocketClient.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/java_websocket/client/WebSocketClient.java b/src/main/java/org/java_websocket/client/WebSocketClient.java index 77c9699b6..5e595ced6 100644 --- a/src/main/java/org/java_websocket/client/WebSocketClient.java +++ b/src/main/java/org/java_websocket/client/WebSocketClient.java @@ -120,7 +120,11 @@ public WebSocketClient( URI serverUri , Draft draft , Map headers conn = (WebSocketImpl) wf.createWebSocket( this, draft, channel.socket() ); } catch ( IOException e ) { onWebsocketError( null, e ); - conn.closeConnection( CloseFrame.NEVER_CONNECTED, e.getMessage() ); + + if(conn != null) { + conn.closeConnection( CloseFrame.NEVER_CONNECTED, e.getMessage() ); + } + return; } } @@ -223,7 +227,11 @@ private final void interruptableRun() { return; } catch ( /*IOException | SecurityException | UnresolvedAddressException*/Exception e ) {// onWebsocketError( conn, e ); - conn.closeConnection( CloseFrame.NEVER_CONNECTED, e.getMessage() ); + + if(conn != null) { + conn.closeConnection( CloseFrame.NEVER_CONNECTED, e.getMessage() ); + } + return; } From a0ebef9a4396e37ec3b505861ec3fcc84de230dd Mon Sep 17 00:00:00 2001 From: Davidiusdadi Date: Wed, 27 Feb 2013 22:17:41 +0100 Subject: [PATCH 23/73] did a little refactoring to get rid of WebSocketImpl.socket and because of #159 --- .../org/java_websocket/WebSocketImpl.java | 26 ++++++---- .../org/java_websocket/WebSocketListener.java | 4 ++ .../client/WebSocketClient.java | 52 ++++++++++++------- .../DefaultSSLWebSocketServerFactory.java | 4 +- .../server/DefaultWebSocketServerFactory.java | 4 +- .../server/WebSocketServer.java | 15 ++++++ 6 files changed, 73 insertions(+), 32 deletions(-) diff --git a/src/main/java/org/java_websocket/WebSocketImpl.java b/src/main/java/org/java_websocket/WebSocketImpl.java index d5924acbb..f8f849fc5 100644 --- a/src/main/java/org/java_websocket/WebSocketImpl.java +++ b/src/main/java/org/java_websocket/WebSocketImpl.java @@ -58,8 +58,6 @@ public class WebSocketImpl implements WebSocket { public SelectionKey key; - private final Socket socket; - /** the possibly wrapped channel object whose selection is controlled by {@link #key} */ public ByteChannel channel; /** @@ -107,8 +105,8 @@ public class WebSocketImpl implements WebSocket { /** * crates a websocket with server role */ - public WebSocketImpl( WebSocketListener listener , List drafts , Socket sock ) { - this( listener, (Draft) null, sock ); + public WebSocketImpl( WebSocketListener listener , List drafts ) { + this( listener, (Draft) null ); this.role = Role.SERVER; // draft.copyInstance will be called when the draft is first needed if( drafts == null || drafts.isEmpty() ) { @@ -121,11 +119,11 @@ public WebSocketImpl( WebSocketListener listener , List drafts , Socket s /** * crates a websocket with client role * - * @param sock + * @param socket * may be unbound */ - public WebSocketImpl( WebSocketListener listener , Draft draft , Socket sock ) { - if( listener == null || sock == null || ( draft == null && role == Role.SERVER ) ) + public WebSocketImpl( WebSocketListener listener , Draft draft ) { + if( listener == null || ( draft == null && role == Role.SERVER ) )// socket can be null because we want do be able to create the object without already having a bound channel throw new IllegalArgumentException( "parameters must not be null" ); this.outQueue = new LinkedBlockingQueue(); inQueue = new LinkedBlockingQueue(); @@ -133,8 +131,16 @@ public WebSocketImpl( WebSocketListener listener , Draft draft , Socket sock ) { this.role = Role.CLIENT; if( draft != null ) this.draft = draft.copyInstance(); + } + + @Deprecated + public WebSocketImpl( WebSocketListener listener , Draft draft , Socket socket ) { + this( listener, draft ); + } - socket = sock; + @Deprecated + public WebSocketImpl( WebSocketListener listener , List drafts , Socket socket ) { + this( listener, drafts ); } /** @@ -688,12 +694,12 @@ public String toString() { @Override public InetSocketAddress getRemoteSocketAddress() { - return (InetSocketAddress) socket.getRemoteSocketAddress(); + return wsl.getRemoteSocketAddress( this ); } @Override public InetSocketAddress getLocalSocketAddress() { - return (InetSocketAddress) socket.getLocalSocketAddress(); + return wsl.getLocalSocketAddress( this ); } @Override diff --git a/src/main/java/org/java_websocket/WebSocketListener.java b/src/main/java/org/java_websocket/WebSocketListener.java index d7d4145b3..4b35f245c 100644 --- a/src/main/java/org/java_websocket/WebSocketListener.java +++ b/src/main/java/org/java_websocket/WebSocketListener.java @@ -1,5 +1,6 @@ package org.java_websocket; +import java.net.InetSocketAddress; import java.nio.ByteBuffer; import org.java_websocket.drafts.Draft; @@ -143,4 +144,7 @@ public interface WebSocketListener { /** This method is used to inform the selector thread that there is data queued to be written to the socket. */ public void onWriteDemand( WebSocket conn ); + + public InetSocketAddress getLocalSocketAddress( WebSocket conn ); + public InetSocketAddress getRemoteSocketAddress( WebSocket conn ); } diff --git a/src/main/java/org/java_websocket/client/WebSocketClient.java b/src/main/java/org/java_websocket/client/WebSocketClient.java index 5e595ced6..bdf6dfbc5 100644 --- a/src/main/java/org/java_websocket/client/WebSocketClient.java +++ b/src/main/java/org/java_websocket/client/WebSocketClient.java @@ -70,15 +70,15 @@ public abstract class WebSocketClient extends WebSocketAdapter implements Runnab private int timeout = 0; - WebSocketClientFactory wf = new WebSocketClientFactory() { + WebSocketClientFactory wsfactory = new WebSocketClientFactory() { @Override public WebSocket createWebSocket( WebSocketAdapter a, Draft d, Socket s ) { - return new WebSocketImpl( WebSocketClient.this, d, s ); + return new WebSocketImpl( WebSocketClient.this, d ); } @Override public WebSocket createWebSocket( WebSocketAdapter a, List d, Socket s ) { - return new WebSocketImpl( WebSocketClient.this, d, s ); + return new WebSocketImpl( WebSocketClient.this, d ); } @Override @@ -117,16 +117,18 @@ public WebSocketClient( URI serverUri , Draft draft , Map headers try { channel = SelectorProvider.provider().openSocketChannel(); channel.configureBlocking( true ); - conn = (WebSocketImpl) wf.createWebSocket( this, draft, channel.socket() ); } catch ( IOException e ) { + channel = null; onWebsocketError( null, e ); - - if(conn != null) { - conn.closeConnection( CloseFrame.NEVER_CONNECTED, e.getMessage() ); - } - - return; } + if(channel == null){ + conn = (WebSocketImpl) wsfactory.createWebSocket( this, draft, null ); + conn.close( CloseFrame.NEVER_CONNECTED, "Failed to create or configure SocketChannel." ); + } + else{ + conn = (WebSocketImpl) wsfactory.createWebSocket( this, draft, channel.socket() ); + } + } /** @@ -213,11 +215,15 @@ public void run() { } private final void interruptableRun() { + if( channel == null ) { + return;// channel will be initialized in the constructor and only be null if no socket channel could be created or if blocking mode could be established + } + try { String host = uri.getHost(); int port = getPort(); tryToConnect( new InetSocketAddress( host, port ) ); - conn.channel = wrappedchannel = wf.wrapChannel( channel, null, host, port ); + conn.channel = wrappedchannel = wsfactory.wrapChannel( channel, null, host, port ); timeout = 0; // since connect is over sendHandshake(); readthread = new Thread( new WebsocketWriteThread() ); @@ -227,11 +233,7 @@ private final void interruptableRun() { return; } catch ( /*IOException | SecurityException | UnresolvedAddressException*/Exception e ) {// onWebsocketError( conn, e ); - - if(conn != null) { - conn.closeConnection( CloseFrame.NEVER_CONNECTED, e.getMessage() ); - } - + conn.closeConnection( CloseFrame.NEVER_CONNECTED, e.getMessage() ); return; } @@ -389,11 +391,25 @@ public WebSocket getConnection() { } public final void setWebSocketFactory( WebSocketClientFactory wsf ) { - this.wf = wsf; + this.wsfactory = wsf; } public final WebSocketFactory getWebSocketFactory() { - return wf; + return wsfactory; + } + + @Override + public InetSocketAddress getLocalSocketAddress( WebSocket conn ) { + if( channel != null ) + return (InetSocketAddress) channel.socket().getLocalSocketAddress(); + return null; + } + + @Override + public InetSocketAddress getRemoteSocketAddress( WebSocket conn ) { + if( channel != null ) + return (InetSocketAddress) channel.socket().getLocalSocketAddress(); + return null; } // ABTRACT METHODS ///////////////////////////////////////////////////////// diff --git a/src/main/java/org/java_websocket/server/DefaultSSLWebSocketServerFactory.java b/src/main/java/org/java_websocket/server/DefaultSSLWebSocketServerFactory.java index 045e7b3a9..b871260f8 100644 --- a/src/main/java/org/java_websocket/server/DefaultSSLWebSocketServerFactory.java +++ b/src/main/java/org/java_websocket/server/DefaultSSLWebSocketServerFactory.java @@ -41,11 +41,11 @@ public ByteChannel wrapChannel( SocketChannel channel, SelectionKey key ) throws @Override public WebSocketImpl createWebSocket( WebSocketAdapter a, Draft d, Socket c ) { - return new WebSocketImpl( a, d, c ); + return new WebSocketImpl( a, d ); } @Override public WebSocketImpl createWebSocket( WebSocketAdapter a, List d, Socket s ) { - return new WebSocketImpl( a, d, s ); + return new WebSocketImpl( a, d ); } } \ No newline at end of file diff --git a/src/main/java/org/java_websocket/server/DefaultWebSocketServerFactory.java b/src/main/java/org/java_websocket/server/DefaultWebSocketServerFactory.java index 7556cb047..3b89cdc2f 100644 --- a/src/main/java/org/java_websocket/server/DefaultWebSocketServerFactory.java +++ b/src/main/java/org/java_websocket/server/DefaultWebSocketServerFactory.java @@ -13,11 +13,11 @@ public class DefaultWebSocketServerFactory implements WebSocketServerFactory { @Override public WebSocketImpl createWebSocket( WebSocketAdapter a, Draft d, Socket s ) { - return new WebSocketImpl( a, d, s ); + return new WebSocketImpl( a, d ); } @Override public WebSocketImpl createWebSocket( WebSocketAdapter a, List d, Socket s ) { - return new WebSocketImpl( a, d, s ); + return new WebSocketImpl( a, d ); } @Override public SocketChannel wrapChannel( SocketChannel channel, SelectionKey key ) { diff --git a/src/main/java/org/java_websocket/server/WebSocketServer.java b/src/main/java/org/java_websocket/server/WebSocketServer.java index e8decb8ca..ef2a32978 100644 --- a/src/main/java/org/java_websocket/server/WebSocketServer.java +++ b/src/main/java/org/java_websocket/server/WebSocketServer.java @@ -557,6 +557,21 @@ protected boolean onConnect( SelectionKey key ) { return true; } + private Socket getSocket( WebSocket conn ) { + WebSocketImpl impl = (WebSocketImpl) conn; + return ( (SocketChannel) impl.key.channel() ).socket(); + } + + @Override + public InetSocketAddress getLocalSocketAddress( WebSocket conn ) { + return (InetSocketAddress) getSocket( conn ).getLocalSocketAddress(); + } + + @Override + public InetSocketAddress getRemoteSocketAddress( WebSocket conn ) { + return (InetSocketAddress) getSocket( conn ).getLocalSocketAddress(); + } + /** Called after an opening handshake has been performed and the given websocket is ready to be written on. */ public abstract void onOpen( WebSocket conn, ClientHandshake handshake ); /** From 0ddee5fd22ab56e4ee78a849979ec1f921089136 Mon Sep 17 00:00:00 2001 From: Graham Mueller Date: Tue, 12 Mar 2013 20:18:09 -0500 Subject: [PATCH 24/73] Removing toLowerCase Is there a reason all of the header fields are being lower cased? This actually caused requests I was making to a server I don't have control over to fail. --- .../org/java_websocket/handshake/HandshakedataImpl1.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/java_websocket/handshake/HandshakedataImpl1.java b/src/main/java/org/java_websocket/handshake/HandshakedataImpl1.java index 39e7d4c9c..3ec19cf84 100644 --- a/src/main/java/org/java_websocket/handshake/HandshakedataImpl1.java +++ b/src/main/java/org/java_websocket/handshake/HandshakedataImpl1.java @@ -32,7 +32,7 @@ public Iterator iterateHttpFields() { @Override public String getFieldValue( String name ) { - String s = map.get( name.toLowerCase( Locale.ENGLISH ) ); + String s = map.get( name ); if( s == null ) { return ""; } @@ -51,11 +51,11 @@ public void setContent( byte[] content ) { @Override public void put( String name, String value ) { - map.put( name.toLowerCase( Locale.ENGLISH ), value ); + map.put( name, value ); } @Override public boolean hasFieldValue( String name ) { - return map.containsKey( name.toLowerCase( Locale.ENGLISH ) ); + return map.containsKey( name ); } } From c7223154704828e6aea91ca3dcb3ad8ecd1ff2ff Mon Sep 17 00:00:00 2001 From: Graham Mueller Date: Tue, 12 Mar 2013 22:45:03 -0500 Subject: [PATCH 25/73] Fixes #161 Changed header map to use a TreeMap with a Case Insensitive comparator, which keeps the framework in compliance with RFC2616 standard, section 4.2. --- .../org/java_websocket/handshake/HandshakedataImpl1.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/java_websocket/handshake/HandshakedataImpl1.java b/src/main/java/org/java_websocket/handshake/HandshakedataImpl1.java index 3ec19cf84..d4d9555c6 100644 --- a/src/main/java/org/java_websocket/handshake/HandshakedataImpl1.java +++ b/src/main/java/org/java_websocket/handshake/HandshakedataImpl1.java @@ -2,15 +2,14 @@ import java.util.Collections; import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.Locale; +import java.util.TreeMap; public class HandshakedataImpl1 implements HandshakeBuilder { private byte[] content; - private LinkedHashMap map; + private TreeMap map; public HandshakedataImpl1() { - map = new LinkedHashMap(); + map = new TreeMap( String.CASE_INSENSITIVE_ORDER ); } /*public HandshakedataImpl1( Handshakedata h ) { @@ -33,7 +32,7 @@ public Iterator iterateHttpFields() { @Override public String getFieldValue( String name ) { String s = map.get( name ); - if( s == null ) { + if ( s == null ) { return ""; } return s; From 1c655b4dc8917f0d37b58d029643fe1a7c649354 Mon Sep 17 00:00:00 2001 From: Davidiusdadi Date: Sun, 17 Mar 2013 22:35:46 +0100 Subject: [PATCH 26/73] Fixed situation in which a connection would not be closed by the server if the client connects via ws also the server expects wss --- .../java_websocket/server/WebSocketServer.java | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/java_websocket/server/WebSocketServer.java b/src/main/java/org/java_websocket/server/WebSocketServer.java index ef2a32978..4e652b659 100644 --- a/src/main/java/org/java_websocket/server/WebSocketServer.java +++ b/src/main/java/org/java_websocket/server/WebSocketServer.java @@ -8,6 +8,7 @@ import java.nio.ByteBuffer; import java.nio.channels.ByteChannel; import java.nio.channels.CancelledKeyException; +import java.nio.channels.SelectableChannel; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; @@ -370,7 +371,7 @@ public void run() { } catch ( IOException ex ) { if( key != null ) key.cancel(); - handleIOException( conn, ex ); + handleIOException( key, conn, ex ); } catch ( InterruptedException e ) { return;// FIXME controlled shutdown } @@ -416,10 +417,21 @@ private void pushBuffer( ByteBuffer buf ) throws InterruptedException { buffers.put( buf ); } - private void handleIOException( WebSocket conn, IOException ex ) { + private void handleIOException( SelectionKey key, WebSocket conn, IOException ex ) { onWebsocketError( conn, ex );// conn may be null here if( conn != null ) { conn.closeConnection( CloseFrame.ABNORMAL_CLOSE, ex.getMessage() ); + } else if( key != null ) { + SelectableChannel channel = key.channel(); + if( channel != null && channel.isOpen() ) { // this could be the case if the IOException ex is a SSLException + try { + channel.close(); + } catch ( IOException e ) { + // there is nothing that must be done here + } + if( WebSocketImpl.DEBUG ) + System.out.println( "Connection closed because of" + ex ); + } } } From 11bc43f73b201caeeed01d3f4de7e831921993d5 Mon Sep 17 00:00:00 2001 From: Davidiusdadi Date: Sun, 17 Mar 2013 22:44:07 +0100 Subject: [PATCH 27/73] updated wss section of the readme --- README.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.markdown b/README.markdown index a764f599a..eeb522e98 100644 --- a/README.markdown +++ b/README.markdown @@ -91,7 +91,7 @@ To see how to use wss please take a look at the examples.
If you do not have a valid **certificate** in place then you will have to create a self signed one. Browsers will simply refuse the connection in case of a bad certificate and will not ask the user to accept it. So the first step will be to make a browser to accept your self signed certificate. ( https://bugzilla.mozilla.org/show_bug.cgi?id=594502 ).
-If the websocket server url is `wss://localhost:8000` visit the url `https://localhost:8000` with your browser. The browser will recognize the handshake and allow you to accept the certificate. +If the websocket server url is `wss://localhost:8000` visit the url `https://localhost:8000` with your browser. The browser will recognize the handshake and allow you to accept the certificate. This technique is also demonstrated in this [video](http://www.youtube.com/watch?v=F8lBdfAZPkU). The vm option `-Djavax.net.debug=all` can help to find out if there is a problem with the certificate. From ab3d0ad649e6f6a0111ec6505b13b1fc0c5137a0 Mon Sep 17 00:00:00 2001 From: Davidiusdadi Date: Sun, 17 Mar 2013 23:51:47 +0100 Subject: [PATCH 28/73] configured maven pom.xml for for snapshot deploy (#118) --- pom.xml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 2d4f42b32..cc4618c01 100644 --- a/pom.xml +++ b/pom.xml @@ -1,8 +1,18 @@ + + org.sonatype.oss + oss-parent + 7 + + + scm:git:git@github.com/TooTallNate/Java-WebSocket.git + scm:git:git@github.com/TooTallNate/Java-WebSocket.git + git@github.com/TooTallNate/Java-WebSocket.git + 4.0.0 - org.java_websocket + org.java-websocket Java-WebSocket 1.0.0-SNAPSHOT jar From de4507ed68c46744088cbcbc50433bcfca1528c3 Mon Sep 17 00:00:00 2001 From: Davidiusdadi Date: Mon, 18 Mar 2013 20:03:42 +0100 Subject: [PATCH 29/73] configured maven pom.xml for release --- pom.xml | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 55 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index cc4618c01..dc1b7302e 100644 --- a/pom.xml +++ b/pom.xml @@ -3,10 +3,10 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> org.sonatype.oss - oss-parent - 7 + oss-parent + 7 - + scm:git:git@github.com/TooTallNate/Java-WebSocket.git scm:git:git@github.com/TooTallNate/Java-WebSocket.git git@github.com/TooTallNate/Java-WebSocket.git @@ -14,10 +14,11 @@ 4.0.0 org.java-websocket Java-WebSocket - 1.0.0-SNAPSHOT + 1.3.0 jar Java WebSocket http://java-websocket.org/ + A barebones WebSocket client and server implementation written in 100% Java UTF-8 1.6 @@ -33,6 +34,56 @@ ${java.version} + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + + attach-javadocs + + jar + + + + + + + TooTallNate + Nathan Rajlich + nathan@tootallnate.net + https://github.com/TooTallNate + + founder + + + + Davidiusdadi + David Rohmer + rohmer.david@gmail.com + https://github.com/Davidiusdadi + + maintainer + + + + + + MIT License + http://github.com/TooTallNate/Java-WebSocket/blob/master/LICENSE + + \ No newline at end of file From bd36c52fc23b8e52a460ed949c02100c3294a66a Mon Sep 17 00:00:00 2001 From: Davidiusdadi Date: Sun, 24 Mar 2013 14:32:06 +0100 Subject: [PATCH 30/73] fixed #164 --- src/main/java/org/java_websocket/server/WebSocketServer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/java_websocket/server/WebSocketServer.java b/src/main/java/org/java_websocket/server/WebSocketServer.java index 4e652b659..10579ed7c 100644 --- a/src/main/java/org/java_websocket/server/WebSocketServer.java +++ b/src/main/java/org/java_websocket/server/WebSocketServer.java @@ -581,7 +581,7 @@ public InetSocketAddress getLocalSocketAddress( WebSocket conn ) { @Override public InetSocketAddress getRemoteSocketAddress( WebSocket conn ) { - return (InetSocketAddress) getSocket( conn ).getLocalSocketAddress(); + return (InetSocketAddress) getSocket( conn ).getRemoteSocketAddress(); } /** Called after an opening handshake has been performed and the given websocket is ready to be written on. */ From 787acc853f774d2a357682d1f298a34c26072f1d Mon Sep 17 00:00:00 2001 From: Davidiusdadi Date: Sun, 24 Mar 2013 15:47:08 +0100 Subject: [PATCH 31/73] fixed #165 --- src/main/java/org/java_websocket/WebSocketImpl.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/java_websocket/WebSocketImpl.java b/src/main/java/org/java_websocket/WebSocketImpl.java index f8f849fc5..059540141 100644 --- a/src/main/java/org/java_websocket/WebSocketImpl.java +++ b/src/main/java/org/java_websocket/WebSocketImpl.java @@ -448,10 +448,12 @@ protected synchronized void closeConnection( int code, String message, boolean r // key.attach( null ); //see issue #114 key.cancel(); } - try { - channel.close(); - } catch ( IOException e ) { - wsl.onWebsocketError( this, e ); + if( channel != null ) { + try { + channel.close(); + } catch ( IOException e ) { + wsl.onWebsocketError( this, e ); + } } try { this.wsl.onWebsocketClose( this, code, message, remote ); From 76d1206067918d8ea1ae6e2400fadb0d3147139f Mon Sep 17 00:00:00 2001 From: Davidiusdadi Date: Sun, 24 Mar 2013 23:37:15 +0100 Subject: [PATCH 32/73] worked on proxy support #88 --- src/main/example/ProxyClientExample.java | 26 +++++++ .../AbstractWrappedByteChannel.java | 75 ++++++++++++++++++ .../client/AbstractClientProxyChannel.java | 38 +++++++++ .../client/WebSocketClient.java | 77 +++++++++++-------- 4 files changed, 186 insertions(+), 30 deletions(-) create mode 100644 src/main/example/ProxyClientExample.java create mode 100644 src/main/java/org/java_websocket/AbstractWrappedByteChannel.java create mode 100644 src/main/java/org/java_websocket/client/AbstractClientProxyChannel.java diff --git a/src/main/example/ProxyClientExample.java b/src/main/example/ProxyClientExample.java new file mode 100644 index 000000000..133756fd0 --- /dev/null +++ b/src/main/example/ProxyClientExample.java @@ -0,0 +1,26 @@ +import java.net.InetSocketAddress; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.channels.ByteChannel; + +public class ProxyClientExample extends ExampleClient { + + public ProxyClientExample( URI serverURI , InetSocketAddress proxy ) { + super( serverURI ); + setProxy( proxy ); + } + + @Override + public ByteChannel createProxyChannel( ByteChannel towrap ) { + /* + * You can create custom proxy handshake here. + * For more infos see: WebSocketClient.DefaultClientProxyChannel and http://tools.ietf.org/html/rfc6455#section-4.1 + */ + return super.createProxyChannel( towrap ); + } + + public static void main( String[] args ) throws URISyntaxException { + ProxyClientExample c = new ProxyClientExample( new URI( "ws://echo.websocket.org" ), new InetSocketAddress( "proxyaddress", 80 ) );// don't forget to change "proxyaddress" + c.connect(); + } +} diff --git a/src/main/java/org/java_websocket/AbstractWrappedByteChannel.java b/src/main/java/org/java_websocket/AbstractWrappedByteChannel.java new file mode 100644 index 000000000..0481a6d4a --- /dev/null +++ b/src/main/java/org/java_websocket/AbstractWrappedByteChannel.java @@ -0,0 +1,75 @@ +package org.java_websocket; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.ByteChannel; +import java.nio.channels.SocketChannel; + +import javax.net.ssl.SSLException; + + +public class AbstractWrappedByteChannel implements WrappedByteChannel { + + private final ByteChannel channel; + + public AbstractWrappedByteChannel( ByteChannel towrap ) { + this.channel = towrap; + } + + public AbstractWrappedByteChannel( WrappedByteChannel towrap ) { + this.channel = towrap; + } + + @Override + public int read( ByteBuffer dst ) throws IOException { + return channel.read( dst ); + } + + @Override + public boolean isOpen() { + return channel.isOpen(); + } + + @Override + public void close() throws IOException { + channel.close(); + } + + @Override + public int write( ByteBuffer src ) throws IOException { + return channel.write( src ); + } + + @Override + public boolean isNeedWrite() { + return channel instanceof WrappedByteChannel ? ( (WrappedByteChannel) channel ).isNeedWrite() : false; + } + + @Override + public void writeMore() throws IOException { + if( channel instanceof WrappedByteChannel ) + ( (WrappedByteChannel) channel ).writeMore(); + + } + + @Override + public boolean isNeedRead() { + return channel instanceof WrappedByteChannel ? ( (WrappedByteChannel) channel ).isNeedRead() : false; + + } + + @Override + public int readMore( ByteBuffer dst ) throws SSLException { + return channel instanceof WrappedByteChannel ? ( (WrappedByteChannel) channel ).readMore( dst ) : 0; + } + + @Override + public boolean isBlocking() { + if( channel instanceof SocketChannel ) + return ( (SocketChannel) channel ).isBlocking(); + else if( channel instanceof WrappedByteChannel ) + return ( (WrappedByteChannel) channel ).isBlocking(); + return false; + } + +} diff --git a/src/main/java/org/java_websocket/client/AbstractClientProxyChannel.java b/src/main/java/org/java_websocket/client/AbstractClientProxyChannel.java new file mode 100644 index 000000000..bbac67258 --- /dev/null +++ b/src/main/java/org/java_websocket/client/AbstractClientProxyChannel.java @@ -0,0 +1,38 @@ +package org.java_websocket.client; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.nio.ByteBuffer; +import java.nio.channels.ByteChannel; + +import org.java_websocket.AbstractWrappedByteChannel; + +public abstract class AbstractClientProxyChannel extends AbstractWrappedByteChannel { + protected final ByteBuffer proxyHandshake; + + + /** + * @param towrap + * The channel to the proxy server + **/ + public AbstractClientProxyChannel( ByteChannel towrap ) { + super( towrap ); + try { + proxyHandshake = ByteBuffer.wrap( buildHandShake().getBytes( "ASCII" ) ); + } catch ( UnsupportedEncodingException e ) { + throw new RuntimeException( e ); + } + } + + @Override + public int write( ByteBuffer src ) throws IOException { + if( !proxyHandshake.hasRemaining() ) { + return super.write( src ); + } else { + return super.write( proxyHandshake ); + } + } + + public abstract String buildHandShake(); + +} diff --git a/src/main/java/org/java_websocket/client/WebSocketClient.java b/src/main/java/org/java_websocket/client/WebSocketClient.java index bdf6dfbc5..6a099914d 100644 --- a/src/main/java/org/java_websocket/client/WebSocketClient.java +++ b/src/main/java/org/java_websocket/client/WebSocketClient.java @@ -2,7 +2,6 @@ import java.io.IOException; import java.net.InetSocketAddress; -import java.net.Socket; import java.net.URI; import java.nio.ByteBuffer; import java.nio.channels.ByteChannel; @@ -12,7 +11,6 @@ import java.nio.channels.SelectionKey; import java.nio.channels.SocketChannel; import java.nio.channels.spi.SelectorProvider; -import java.util.List; import java.util.Map; import java.util.concurrent.CountDownLatch; @@ -46,7 +44,7 @@ public abstract class WebSocketClient extends WebSocketAdapter implements Runnab /** * The URI this channel is supposed to connect to. */ - private URI uri = null; + protected URI uri = null; private WebSocketImpl conn = null; /** @@ -70,24 +68,9 @@ public abstract class WebSocketClient extends WebSocketAdapter implements Runnab private int timeout = 0; - WebSocketClientFactory wsfactory = new WebSocketClientFactory() { - @Override - public WebSocket createWebSocket( WebSocketAdapter a, Draft d, Socket s ) { - return new WebSocketImpl( WebSocketClient.this, d ); - } + private WebSocketClientFactory wsfactory = new DefaultWebSocketClientFactory( this ); - @Override - public WebSocket createWebSocket( WebSocketAdapter a, List d, Socket s ) { - return new WebSocketImpl( WebSocketClient.this, d ); - } - - @Override - public ByteChannel wrapChannel( SocketChannel channel, SelectionKey c, String host, int port ) { - if( c == null ) - return channel; - return channel; - } - }; + private InetSocketAddress proxyAddress = null; public WebSocketClient( URI serverURI ) { this( serverURI, new Draft_10() ); @@ -198,12 +181,6 @@ public void send( byte[] data ) throws NotYetConnectedException { conn.send( data ); } - private void tryToConnect( InetSocketAddress remote ) throws IOException , InvalidHandshakeException { - - channel.connect( remote ); - - } - // Runnable IMPLEMENTATION ///////////////////////////////////////////////// public void run() { if( writethread == null ) @@ -220,10 +197,19 @@ private final void interruptableRun() { } try { - String host = uri.getHost(); - int port = getPort(); - tryToConnect( new InetSocketAddress( host, port ) ); - conn.channel = wrappedchannel = wsfactory.wrapChannel( channel, null, host, port ); + String host; + int port ; + + if( proxyAddress != null ) { + host = proxyAddress.getHostName(); + port = proxyAddress.getPort(); + } else { + host = uri.getHost(); + port = getPort(); + } + channel.connect( new InetSocketAddress( host, port ) ); + conn.channel = wrappedchannel = createProxyChannel( wsfactory.wrapChannel( channel, null, host, port ) ); + timeout = 0; // since connect is over sendHandshake(); readthread = new Thread( new WebsocketWriteThread() ); @@ -420,6 +406,26 @@ public InetSocketAddress getRemoteSocketAddress( WebSocket conn ) { public void onMessage( ByteBuffer bytes ) { }; + public class DefaultClientProxyChannel extends AbstractClientProxyChannel { + public DefaultClientProxyChannel( ByteChannel towrap ) { + super( towrap ); + } + @Override + public String buildHandShake() { + StringBuilder b = new StringBuilder(); + String host = uri.getHost(); + b.append( "CONNECT " ); + b.append( host ); + b.append( ":" ); + b.append( getPort() ); + b.append( " HTTP/1.1\n" ); + b.append( "Host: " ); + b.append( host ); + b.append( "\n" ); + return b.toString(); + } + } + public interface WebSocketClientFactory extends WebSocketFactory { public ByteChannel wrapChannel( SocketChannel channel, SelectionKey key, String host, int port ) throws IOException; } @@ -439,4 +445,15 @@ public void run() { } } } + + public ByteChannel createProxyChannel( ByteChannel towrap ) { + if( proxyAddress != null ){ + return new DefaultClientProxyChannel( towrap ); + } + return towrap;//no proxy in use + } + + public void setProxy( InetSocketAddress proxyaddress ) { + proxyAddress = proxyaddress; + } } From 8e74a29586e56ab73b9765bbf101ae42a2cce07d Mon Sep 17 00:00:00 2001 From: Davidiusdadi Date: Tue, 26 Mar 2013 09:25:02 +0100 Subject: [PATCH 33/73] added missing file DefaultWebSocketClientFactory.java #88 --- .../client/DefaultWebSocketClientFactory.java | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 src/main/java/org/java_websocket/client/DefaultWebSocketClientFactory.java diff --git a/src/main/java/org/java_websocket/client/DefaultWebSocketClientFactory.java b/src/main/java/org/java_websocket/client/DefaultWebSocketClientFactory.java new file mode 100644 index 000000000..005f8f038 --- /dev/null +++ b/src/main/java/org/java_websocket/client/DefaultWebSocketClientFactory.java @@ -0,0 +1,39 @@ +package org.java_websocket.client; + +import java.net.Socket; +import java.nio.channels.ByteChannel; +import java.nio.channels.SelectionKey; +import java.nio.channels.SocketChannel; +import java.util.List; + +import org.java_websocket.WebSocket; +import org.java_websocket.WebSocketAdapter; +import org.java_websocket.WebSocketImpl; +import org.java_websocket.drafts.Draft; + +public class DefaultWebSocketClientFactory implements WebSocketClient.WebSocketClientFactory { + /** + * + */ + private final WebSocketClient webSocketClient; + /** + * @param webSocketClient + */ + public DefaultWebSocketClientFactory( WebSocketClient webSocketClient ) { + this.webSocketClient = webSocketClient; + } + @Override + public WebSocket createWebSocket( WebSocketAdapter a, Draft d, Socket s ) { + return new WebSocketImpl( this.webSocketClient, d ); + } + @Override + public WebSocket createWebSocket( WebSocketAdapter a, List d, Socket s ) { + return new WebSocketImpl( this.webSocketClient, d ); + } + @Override + public ByteChannel wrapChannel( SocketChannel channel, SelectionKey c, String host, int port ) { + if( c == null ) + return channel; + return channel; + } +} \ No newline at end of file From f0e9a15d71eca67539cb8ff1a9724e69b3849160 Mon Sep 17 00:00:00 2001 From: Davidiusdadi Date: Tue, 26 Mar 2013 22:31:35 +0100 Subject: [PATCH 34/73] fixed #165 --- src/main/java/org/java_websocket/client/WebSocketClient.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/java_websocket/client/WebSocketClient.java b/src/main/java/org/java_websocket/client/WebSocketClient.java index 6a099914d..d885fcda2 100644 --- a/src/main/java/org/java_websocket/client/WebSocketClient.java +++ b/src/main/java/org/java_websocket/client/WebSocketClient.java @@ -337,7 +337,8 @@ public final void onWebsocketOpen( WebSocket conn, Handshakedata handshake ) { public final void onWebsocketClose( WebSocket conn, int code, String reason, boolean remote ) { connectLatch.countDown(); closeLatch.countDown(); - readthread.interrupt(); + if( readthread != null ) + readthread.interrupt(); onClose( code, reason, remote ); } From 5d4deaddf44423f2347ccdd9565a62c8ecb3db24 Mon Sep 17 00:00:00 2001 From: Davidiusdadi Date: Wed, 3 Apr 2013 01:14:25 +0200 Subject: [PATCH 35/73] quickfix for #168 --- src/main/java/org/java_websocket/WebSocketImpl.java | 2 +- .../java/org/java_websocket/server/WebSocketServer.java | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/java_websocket/WebSocketImpl.java b/src/main/java/org/java_websocket/WebSocketImpl.java index 059540141..0b9caeb4c 100644 --- a/src/main/java/org/java_websocket/WebSocketImpl.java +++ b/src/main/java/org/java_websocket/WebSocketImpl.java @@ -465,7 +465,7 @@ protected synchronized void closeConnection( int code, String message, boolean r handshakerequest = null; readystate = READYSTATE.CLOSED; - + this.outQueue.clear(); } protected void closeConnection( int code, boolean remote ) { diff --git a/src/main/java/org/java_websocket/server/WebSocketServer.java b/src/main/java/org/java_websocket/server/WebSocketServer.java index 10579ed7c..26d3e51fc 100644 --- a/src/main/java/org/java_websocket/server/WebSocketServer.java +++ b/src/main/java/org/java_websocket/server/WebSocketServer.java @@ -529,7 +529,12 @@ public final void onWebsocketError( WebSocket conn, Exception ex ) { @Override public final void onWriteDemand( WebSocket w ) { WebSocketImpl conn = (WebSocketImpl) w; - conn.key.interestOps( SelectionKey.OP_READ | SelectionKey.OP_WRITE ); + try { + conn.key.interestOps( SelectionKey.OP_READ | SelectionKey.OP_WRITE ); + } catch ( CancelledKeyException e ) { + // the thread which cancels key is responsible for possible cleanup + conn.outQueue.clear(); + } selector.wakeup(); } From 6930555ddf99b4b475d2c605eb224f1f3ee25170 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 23 Apr 2013 23:18:53 +0200 Subject: [PATCH 36/73] Revert "quickfix for #168" This reverts commit 5d4deaddf44423f2347ccdd9565a62c8ecb3db24. --- src/main/java/org/java_websocket/WebSocketImpl.java | 2 +- .../java/org/java_websocket/server/WebSocketServer.java | 7 +------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/java_websocket/WebSocketImpl.java b/src/main/java/org/java_websocket/WebSocketImpl.java index 0b9caeb4c..059540141 100644 --- a/src/main/java/org/java_websocket/WebSocketImpl.java +++ b/src/main/java/org/java_websocket/WebSocketImpl.java @@ -465,7 +465,7 @@ protected synchronized void closeConnection( int code, String message, boolean r handshakerequest = null; readystate = READYSTATE.CLOSED; - this.outQueue.clear(); + } protected void closeConnection( int code, boolean remote ) { diff --git a/src/main/java/org/java_websocket/server/WebSocketServer.java b/src/main/java/org/java_websocket/server/WebSocketServer.java index 26d3e51fc..10579ed7c 100644 --- a/src/main/java/org/java_websocket/server/WebSocketServer.java +++ b/src/main/java/org/java_websocket/server/WebSocketServer.java @@ -529,12 +529,7 @@ public final void onWebsocketError( WebSocket conn, Exception ex ) { @Override public final void onWriteDemand( WebSocket w ) { WebSocketImpl conn = (WebSocketImpl) w; - try { - conn.key.interestOps( SelectionKey.OP_READ | SelectionKey.OP_WRITE ); - } catch ( CancelledKeyException e ) { - // the thread which cancels key is responsible for possible cleanup - conn.outQueue.clear(); - } + conn.key.interestOps( SelectionKey.OP_READ | SelectionKey.OP_WRITE ); selector.wakeup(); } From a0ad953af00127b40e3c41e668ecad6355e29440 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 23 Apr 2013 23:20:56 +0200 Subject: [PATCH 37/73] Revert "Revert "quickfix for #168"" This reverts commit 6930555ddf99b4b475d2c605eb224f1f3ee25170. --- src/main/java/org/java_websocket/WebSocketImpl.java | 2 +- .../java/org/java_websocket/server/WebSocketServer.java | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/java_websocket/WebSocketImpl.java b/src/main/java/org/java_websocket/WebSocketImpl.java index 059540141..0b9caeb4c 100644 --- a/src/main/java/org/java_websocket/WebSocketImpl.java +++ b/src/main/java/org/java_websocket/WebSocketImpl.java @@ -465,7 +465,7 @@ protected synchronized void closeConnection( int code, String message, boolean r handshakerequest = null; readystate = READYSTATE.CLOSED; - + this.outQueue.clear(); } protected void closeConnection( int code, boolean remote ) { diff --git a/src/main/java/org/java_websocket/server/WebSocketServer.java b/src/main/java/org/java_websocket/server/WebSocketServer.java index 10579ed7c..26d3e51fc 100644 --- a/src/main/java/org/java_websocket/server/WebSocketServer.java +++ b/src/main/java/org/java_websocket/server/WebSocketServer.java @@ -529,7 +529,12 @@ public final void onWebsocketError( WebSocket conn, Exception ex ) { @Override public final void onWriteDemand( WebSocket w ) { WebSocketImpl conn = (WebSocketImpl) w; - conn.key.interestOps( SelectionKey.OP_READ | SelectionKey.OP_WRITE ); + try { + conn.key.interestOps( SelectionKey.OP_READ | SelectionKey.OP_WRITE ); + } catch ( CancelledKeyException e ) { + // the thread which cancels key is responsible for possible cleanup + conn.outQueue.clear(); + } selector.wakeup(); } From a90957144d6ae9f397f9aaf09cc7f7c99a614497 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 23 Apr 2013 23:57:31 +0200 Subject: [PATCH 38/73] update pom.xml --- pom.xml | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index dc1b7302e..1bea0f6c3 100644 --- a/pom.xml +++ b/pom.xml @@ -86,4 +86,33 @@ http://github.com/TooTallNate/Java-WebSocket/blob/master/LICENSE - \ No newline at end of file + + + release-sign-artifacts + + performReleasetrue + + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.1 + + + sign-artifacts + verify + + sign + + + + + rohmer.david@gmail.com> + + + + + + + From 8c4e27174e83ed33181f489818265c56aef5c333 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 24 Apr 2013 00:14:50 +0200 Subject: [PATCH 39/73] updated pom.xml --- pom.xml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1bea0f6c3..a11d97e5f 100644 --- a/pom.xml +++ b/pom.xml @@ -14,7 +14,7 @@ 4.0.0 org.java-websocket Java-WebSocket - 1.3.0 + 1.3.0-SNAPSHOT jar Java WebSocket http://java-websocket.org/ @@ -37,6 +37,7 @@ org.apache.maven.plugins maven-source-plugin + 2.2.1 attach-sources @@ -49,6 +50,7 @@ org.apache.maven.plugins maven-javadoc-plugin + 2.9 attach-javadocs From bd67f3bd2a332134a0f506fd18d0ed8cd0257e22 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 24 Apr 2013 01:37:32 +0200 Subject: [PATCH 40/73] updated pom.xml scm --- pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index a11d97e5f..d7e857764 100644 --- a/pom.xml +++ b/pom.xml @@ -7,9 +7,9 @@ 7 - scm:git:git@github.com/TooTallNate/Java-WebSocket.git - scm:git:git@github.com/TooTallNate/Java-WebSocket.git - git@github.com/TooTallNate/Java-WebSocket.git + scm:git:git@github.com:TooTallNate/Java-WebSocket.git + scm:git:git@github.com:TooTallNate/Java-WebSocket.git + git@github.com:TooTallNate/Java-WebSocket.git 4.0.0 org.java-websocket From 700bfc4335a4f04ce357f3e22c422a8022e599ff Mon Sep 17 00:00:00 2001 From: David Date: Wed, 24 Apr 2013 01:47:15 +0200 Subject: [PATCH 41/73] [maven-release-plugin] prepare release Java-WebSocket-1.3.0 --- pom.xml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index d7e857764..9e99d3e13 100644 --- a/pom.xml +++ b/pom.xml @@ -1,6 +1,5 @@ - + org.sonatype.oss oss-parent @@ -14,7 +13,7 @@ 4.0.0 org.java-websocket Java-WebSocket - 1.3.0-SNAPSHOT + 1.3.0 jar Java WebSocket http://java-websocket.org/ @@ -110,7 +109,7 @@ - rohmer.david@gmail.com> + rohmer.david@gmail.com> From aa163e569d9f70971f90e873637bda1290ce46f8 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 24 Apr 2013 01:48:59 +0200 Subject: [PATCH 42/73] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 9e99d3e13..46ca696b8 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ 4.0.0 org.java-websocket Java-WebSocket - 1.3.0 + 1.3.1-SNAPSHOT jar Java WebSocket http://java-websocket.org/ From 35984c519b52e37408f6c549ce76b25120a76673 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 24 Apr 2013 03:26:51 +0200 Subject: [PATCH 43/73] updated maven section in the readme --- README.markdown | 10 ++++++++-- pom.xml | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/README.markdown b/README.markdown index eeb522e98..0aaab7a2f 100644 --- a/README.markdown +++ b/README.markdown @@ -33,8 +33,14 @@ The ant targets are: ```compile```, ```jar```, ```doc``` and ```clean``` ###Maven -Maven is supported. More documentation in that is yet to come... - +To use maven just add this dependency to your pom.xml: +```xml + + org.java-websocket + Java-WebSocket + 1.3.0 + +``` Running the Examples ------------------- diff --git a/pom.xml b/pom.xml index 46ca696b8..9189fba8d 100644 --- a/pom.xml +++ b/pom.xml @@ -109,7 +109,7 @@ - rohmer.david@gmail.com> + rohmer.david@gmail.com From cadb9c19a878701c0997fac9237d09893f3ae011 Mon Sep 17 00:00:00 2001 From: temka1234 Date: Wed, 24 Apr 2013 13:00:54 +0400 Subject: [PATCH 44/73] If connection closed isNeedRead() always returns true. We need to check the status of SSLResult for it. --- src/main/java/org/java_websocket/SSLSocketChannel2.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/java_websocket/SSLSocketChannel2.java b/src/main/java/org/java_websocket/SSLSocketChannel2.java index 83de34292..286b9ddc0 100644 --- a/src/main/java/org/java_websocket/SSLSocketChannel2.java +++ b/src/main/java/org/java_websocket/SSLSocketChannel2.java @@ -313,7 +313,8 @@ public void writeMore() throws IOException { @Override public boolean isNeedRead() { - return inData.hasRemaining() || ( inCrypt.hasRemaining() && engineResult.getStatus() != Status.BUFFER_UNDERFLOW ); + return inData.hasRemaining() || ( inCrypt.hasRemaining() && engineResult.getStatus() != Status.BUFFER_UNDERFLOW && + engineResult.getStatus() != Status.CLOSED ); } @Override From 1c43128daa25c3315d1c876956370915c5464d23 Mon Sep 17 00:00:00 2001 From: David Date: Thu, 16 May 2013 23:16:29 +0200 Subject: [PATCH 45/73] added missing shutdown statement for the websocket clients executorservice (#171) --- src/main/java/org/java_websocket/SSLSocketChannel2.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/java_websocket/SSLSocketChannel2.java b/src/main/java/org/java_websocket/SSLSocketChannel2.java index 83de34292..49823cd60 100644 --- a/src/main/java/org/java_websocket/SSLSocketChannel2.java +++ b/src/main/java/org/java_websocket/SSLSocketChannel2.java @@ -269,6 +269,7 @@ public void close() throws IOException { if( socketChannel.isOpen() ) socketChannel.write( wrap( emptybuffer ) );// FIXME what if not all bytes can be written socketChannel.close(); + exec.shutdownNow(); } private boolean isHandShakeComplete() { From c46279dbc62c4afbf0280e226e90efa499fe449e Mon Sep 17 00:00:00 2001 From: David Date: Fri, 17 May 2013 00:34:18 +0200 Subject: [PATCH 46/73] Made Draft_75 only deliver complete frames and no incomplete frame fragments. (#144) These changes also fix the NullPointerException in #178 --- .../org/java_websocket/drafts/Draft_75.java | 35 +++++++++---------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/src/main/java/org/java_websocket/drafts/Draft_75.java b/src/main/java/org/java_websocket/drafts/Draft_75.java index f6e67f8ab..d380eba25 100644 --- a/src/main/java/org/java_websocket/drafts/Draft_75.java +++ b/src/main/java/org/java_websocket/drafts/Draft_75.java @@ -7,7 +7,9 @@ import java.util.Random; import org.java_websocket.exceptions.InvalidDataException; +import org.java_websocket.exceptions.InvalidFrameException; import org.java_websocket.exceptions.InvalidHandshakeException; +import org.java_websocket.exceptions.LimitExedeedException; import org.java_websocket.exceptions.NotSendableException; import org.java_websocket.framing.CloseFrame; import org.java_websocket.framing.FrameBuilder; @@ -40,14 +42,13 @@ public class Draft_75 extends Draft { */ public static final byte END_OF_FRAME = (byte) 0xFF; + /** Is only used to detect protocol violations */ protected boolean readingState = false; - private boolean inframe = false; + protected List readyframes = new LinkedList(); protected ByteBuffer currentFrame; - - + private final Random reuseableRandom = new Random(); - @Override public HandshakeState acceptHandshakeAsClient( ClientHandshake request, ServerHandshake response ) { @@ -122,15 +123,16 @@ public HandshakeBuilder postProcessHandshakeResponseAsServer( ClientHandshake re } protected List translateRegularFrame( ByteBuffer buffer ) throws InvalidDataException { + while ( buffer.hasRemaining() ) { byte newestByte = buffer.get(); if( newestByte == START_OF_FRAME ) { // Beginning of Frame if( readingState ) - return null; + throw new InvalidFrameException( "unexpected START_OF_FRAME" ); readingState = true; } else if( newestByte == END_OF_FRAME ) { // End of Frame if( !readingState ) - return null; + throw new InvalidFrameException( "unexpected END_OF_FRAME" ); // currentFrame will be null if END_OF_FRAME was send directly after // START_OF_FRAME, thus we will send 'null' as the sent message. if( this.currentFrame != null ) { @@ -138,13 +140,12 @@ protected List translateRegularFrame( ByteBuffer buffer ) throws Inva FramedataImpl1 curframe = new FramedataImpl1(); curframe.setPayload( currentFrame ); curframe.setFin( true ); - curframe.setOptcode( inframe ? Opcode.CONTINUOUS : Opcode.TEXT ); + curframe.setOptcode( Opcode.TEXT ); readyframes.add( curframe ); this.currentFrame = null; buffer.mark(); } readingState = false; - inframe = false; } else if( readingState ) { // Regular frame data, add to current frame buffer //TODO This code is very expensive and slow if( currentFrame == null ) { currentFrame = createBuffer(); @@ -156,15 +157,11 @@ protected List translateRegularFrame( ByteBuffer buffer ) throws Inva return null; } } - if( readingState ) { - FramedataImpl1 curframe = new FramedataImpl1(); - currentFrame.flip(); - curframe.setPayload( currentFrame ); - curframe.setFin( false ); - curframe.setOptcode( inframe ? Opcode.CONTINUOUS : Opcode.TEXT ); - inframe = true; - readyframes.add( curframe ); - } + + // if no error occurred this block will be reached + /*if( readingState ) { + checkAlloc(currentFrame.position()+1); + }*/ List frames = readyframes; readyframes = new LinkedList(); @@ -196,9 +193,9 @@ public ByteBuffer createBuffer() { return ByteBuffer.allocate( INITIAL_FAMESIZE ); } - public ByteBuffer increaseBuffer( ByteBuffer full ) { + public ByteBuffer increaseBuffer( ByteBuffer full ) throws LimitExedeedException , InvalidDataException { full.flip(); - ByteBuffer newbuffer = ByteBuffer.allocate( full.capacity() * 2 ); + ByteBuffer newbuffer = ByteBuffer.allocate( checkAlloc( full.capacity() * 2 ) ); newbuffer.put( full ); return newbuffer; } From 5d7a1feb99fd3d7858756d8072f465fc38e61419 Mon Sep 17 00:00:00 2001 From: David Date: Fri, 17 May 2013 00:46:59 +0200 Subject: [PATCH 47/73] finally muted completely unnecessary forwarding of already handeled IOExceptions to the user via onError --- src/main/java/org/java_websocket/server/WebSocketServer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/java_websocket/server/WebSocketServer.java b/src/main/java/org/java_websocket/server/WebSocketServer.java index 26d3e51fc..bf0b85c50 100644 --- a/src/main/java/org/java_websocket/server/WebSocketServer.java +++ b/src/main/java/org/java_websocket/server/WebSocketServer.java @@ -418,7 +418,7 @@ private void pushBuffer( ByteBuffer buf ) throws InterruptedException { } private void handleIOException( SelectionKey key, WebSocket conn, IOException ex ) { - onWebsocketError( conn, ex );// conn may be null here + //onWebsocketError( conn, ex );// conn may be null here if( conn != null ) { conn.closeConnection( CloseFrame.ABNORMAL_CLOSE, ex.getMessage() ); } else if( key != null ) { From 53fced8cf271c61c9645c744d6a4d1d1eb43eb4c Mon Sep 17 00:00:00 2001 From: Davidiusdadi Date: Wed, 5 Jun 2013 01:03:34 +0200 Subject: [PATCH 48/73] simplified obtaining the http-get-resource-descriptor by adding method getResourceDescriptor do the WebSocket interface. #182 --- src/main/java/org/java_websocket/WebSocket.java | 6 ++++++ src/main/java/org/java_websocket/WebSocketImpl.java | 11 +++++++++++ .../org/java_websocket/handshake/ClientHandshake.java | 1 + .../handshake/ClientHandshakeBuilder.java | 2 +- .../handshake/HandshakeImpl1Client.java | 10 ++++++---- 5 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/java_websocket/WebSocket.java b/src/main/java/org/java_websocket/WebSocket.java index af82ab03f..aaba4b139 100644 --- a/src/main/java/org/java_websocket/WebSocket.java +++ b/src/main/java/org/java_websocket/WebSocket.java @@ -99,4 +99,10 @@ public enum READYSTATE { * @return Returns '0 = CONNECTING', '1 = OPEN', '2 = CLOSING' or '3 = CLOSED' */ public abstract READYSTATE getReadyState(); + + /** + * Returns the HTTP Request-URI as defined by http://tools.ietf.org/html/rfc2616#section-5.1.2
+ * If the opening handshake has not yet happened it will return null. + **/ + public abstract String getResourceDescriptor(); } \ No newline at end of file diff --git a/src/main/java/org/java_websocket/WebSocketImpl.java b/src/main/java/org/java_websocket/WebSocketImpl.java index 0b9caeb4c..4d4e0f838 100644 --- a/src/main/java/org/java_websocket/WebSocketImpl.java +++ b/src/main/java/org/java_websocket/WebSocketImpl.java @@ -101,6 +101,8 @@ public class WebSocketImpl implements WebSocket { private String closemessage = null; private Integer closecode = null; private Boolean closedremotely = null; + + private String resourceDescriptor = null; /** * crates a websocket with server role @@ -211,6 +213,7 @@ private boolean decodeHandshake( ByteBuffer socketBufferNew ) { ClientHandshake handshake = (ClientHandshake) tmphandshake; handshakestate = d.acceptHandshakeAsServer( handshake ); if( handshakestate == HandshakeState.MATCHED ) { + resourceDescriptor = handshake.getResourceDescriptor(); ServerHandshakeBuilder response; try { response = wsl.onWebsocketHandshakeReceivedAsServer( this, d, handshake ); @@ -605,6 +608,9 @@ public void startHandshake( ClientHandshakeBuilder handshakedata ) throws Invali // Store the Handshake Request we are about to send this.handshakerequest = draft.postProcessHandshakeRequestAsClient( handshakedata ); + resourceDescriptor = handshakedata.getResourceDescriptor(); + assert( resourceDescriptor != null ); + // Notify Listener try { wsl.onWebsocketHandshakeSentAsClient( this, this.handshakerequest ); @@ -714,4 +720,9 @@ public void close() { close( CloseFrame.NORMAL ); } + @Override + public String getResourceDescriptor() { + return resourceDescriptor; + } + } diff --git a/src/main/java/org/java_websocket/handshake/ClientHandshake.java b/src/main/java/org/java_websocket/handshake/ClientHandshake.java index 4ab28475e..918d22184 100644 --- a/src/main/java/org/java_websocket/handshake/ClientHandshake.java +++ b/src/main/java/org/java_websocket/handshake/ClientHandshake.java @@ -1,5 +1,6 @@ package org.java_websocket.handshake; public interface ClientHandshake extends Handshakedata { + /**returns the HTTP Request-URI as defined by http://tools.ietf.org/html/rfc2616#section-5.1.2*/ public String getResourceDescriptor(); } diff --git a/src/main/java/org/java_websocket/handshake/ClientHandshakeBuilder.java b/src/main/java/org/java_websocket/handshake/ClientHandshakeBuilder.java index 07d17309b..88ac4f27b 100644 --- a/src/main/java/org/java_websocket/handshake/ClientHandshakeBuilder.java +++ b/src/main/java/org/java_websocket/handshake/ClientHandshakeBuilder.java @@ -1,5 +1,5 @@ package org.java_websocket.handshake; public interface ClientHandshakeBuilder extends HandshakeBuilder, ClientHandshake { - public void setResourceDescriptor( String resourcedescriptor ); + public void setResourceDescriptor( String resourceDescriptor ); } diff --git a/src/main/java/org/java_websocket/handshake/HandshakeImpl1Client.java b/src/main/java/org/java_websocket/handshake/HandshakeImpl1Client.java index 789cb087a..15715e37e 100644 --- a/src/main/java/org/java_websocket/handshake/HandshakeImpl1Client.java +++ b/src/main/java/org/java_websocket/handshake/HandshakeImpl1Client.java @@ -1,16 +1,18 @@ package org.java_websocket.handshake; public class HandshakeImpl1Client extends HandshakedataImpl1 implements ClientHandshakeBuilder { - private String resourcedescriptor; + private String resourceDescriptor = "*"; public HandshakeImpl1Client() { } - public void setResourceDescriptor( String resourcedescriptor ) throws IllegalArgumentException { - this.resourcedescriptor = resourcedescriptor; + public void setResourceDescriptor( String resourceDescriptor ) throws IllegalArgumentException { + if(resourceDescriptor==null) + throw new IllegalArgumentException( "http resource descriptor must not be null" ); + this.resourceDescriptor = resourceDescriptor; } public String getResourceDescriptor() { - return resourcedescriptor; + return resourceDescriptor; } } From ea2b57014be3a5355f3c9b093462edd598c0d136 Mon Sep 17 00:00:00 2001 From: Davidiusdadi Date: Wed, 24 Jul 2013 22:27:07 +0200 Subject: [PATCH 49/73] SSLSocketChannel2 refactoring: made read and write operations not work on the same SSLEngineResult and use sslEngine directly to obtain HandshakeStatus --- .../org/java_websocket/SSLSocketChannel2.java | 55 +++++++++++-------- 1 file changed, 33 insertions(+), 22 deletions(-) diff --git a/src/main/java/org/java_websocket/SSLSocketChannel2.java b/src/main/java/org/java_websocket/SSLSocketChannel2.java index 1d2e67ecd..d8d2b5167 100644 --- a/src/main/java/org/java_websocket/SSLSocketChannel2.java +++ b/src/main/java/org/java_websocket/SSLSocketChannel2.java @@ -31,6 +31,9 @@ * Implements the relevant portions of the SocketChannel interface with the SSLEngine wrapper. */ public class SSLSocketChannel2 implements ByteChannel, WrappedByteChannel { + /** + * This object is used to feed the {@link SSLEngine}'s wrap and unwrap methods during the handshake phase. + **/ protected static ByteBuffer emptybuffer = ByteBuffer.allocate( 0 ); protected ExecutorService exec; @@ -49,12 +52,9 @@ public class SSLSocketChannel2 implements ByteChannel, WrappedByteChannel { /** used to set interestOP SelectionKey.OP_WRITE for the underlying channel */ protected SelectionKey selectionKey; - - protected SSLEngineResult engineResult; protected SSLEngine sslEngine; - - - private Status engineStatus = Status.BUFFER_UNDERFLOW; + protected SSLEngineResult readEngineResult; + protected SSLEngineResult writeEngineResult; public SSLSocketChannel2( SocketChannel channel , SSLEngine sslEngine , ExecutorService exec , SelectionKey key ) throws IOException { if( channel == null || sslEngine == null || exec == null ) @@ -64,6 +64,8 @@ public SSLSocketChannel2( SocketChannel channel , SSLEngine sslEngine , Executor this.sslEngine = sslEngine; this.exec = exec; + readEngineResult = writeEngineResult = new SSLEngineResult( Status.BUFFER_UNDERFLOW, sslEngine.getHandshakeStatus(), 0, 0 ); // init to prevent NPEs + tasks = new ArrayList>( 3 ); if( key != null ) { key.interestOps( key.interestOps() | SelectionKey.OP_WRITE ); @@ -93,8 +95,12 @@ private void consumeFutureUninterruptible( Future f ) { } } + /** + * This method will do whatever necessary to process the sslengine handshake. + * Thats why it's called both from the {@link #read(ByteBuffer)} and {@link #write(ByteBuffer)} + **/ private synchronized void processHandshake() throws IOException { - if( engineResult.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING ) + if( sslEngine.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING ) return; // since this may be called either from a reading or a writing thread and because this method is synchronized it is necessary to double check if we are still handshaking. if( !tasks.isEmpty() ) { Iterator> it = tasks.iterator(); @@ -110,8 +116,8 @@ private synchronized void processHandshake() throws IOException { } } - if( engineResult.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_UNWRAP ) { - if( !isBlocking() || engineStatus == Status.BUFFER_UNDERFLOW ) { + if( sslEngine.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_UNWRAP ) { + if( !isBlocking() || readEngineResult.getStatus() == Status.BUFFER_UNDERFLOW ) { inCrypt.compact(); int read = socketChannel.read( inCrypt ); if( read == -1 ) { @@ -121,16 +127,16 @@ private synchronized void processHandshake() throws IOException { } inData.compact(); unwrap(); - if( engineResult.getHandshakeStatus() == HandshakeStatus.FINISHED ) { + if( sslEngine.getHandshakeStatus() == HandshakeStatus.FINISHED ) { createBuffers( sslEngine.getSession() ); return; } } consumeDelegatedTasks(); - assert ( engineResult.getHandshakeStatus() != HandshakeStatus.NOT_HANDSHAKING ); - if( tasks.isEmpty() || engineResult.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_WRAP ) { + assert ( sslEngine.getHandshakeStatus() != HandshakeStatus.NOT_HANDSHAKING ); + if( tasks.isEmpty() || sslEngine.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_WRAP ) { socketChannel.write( wrap( emptybuffer ) ); - if( engineResult.getHandshakeStatus() == HandshakeStatus.FINISHED ) { + if( sslEngine.getHandshakeStatus() == HandshakeStatus.FINISHED ) { createBuffers( sslEngine.getSession() ); } } @@ -138,18 +144,20 @@ private synchronized void processHandshake() throws IOException { private synchronized ByteBuffer wrap( ByteBuffer b ) throws SSLException { outCrypt.compact(); - engineResult = sslEngine.wrap( b, outCrypt ); + writeEngineResult = sslEngine.wrap( b, outCrypt ); outCrypt.flip(); return outCrypt; } + /** + * performs the unwrap operation by unwrapping from {@link #inCrypt} to {@link #inData} + **/ private synchronized ByteBuffer unwrap() throws SSLException { int rem; do { rem = inData.remaining(); - engineResult = sslEngine.unwrap( inCrypt, inData ); - engineStatus = engineResult.getStatus(); - } while ( engineStatus == SSLEngineResult.Status.OK && ( rem != inData.remaining() || engineResult.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP ) ); + readEngineResult = sslEngine.unwrap( inCrypt, inData ); + } while ( readEngineResult.getStatus() == SSLEngineResult.Status.OK && ( rem != inData.remaining() || sslEngine.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP ) ); inData.flip(); return inData; @@ -200,6 +208,8 @@ public int write( ByteBuffer src ) throws IOException { /** * Blocks when in blocking mode until at least one byte has been decoded.
* When not in blocking mode 0 may be returned. + * + * @return the number of bytes read. **/ public int read( ByteBuffer dst ) throws IOException { if( !dst.hasRemaining() ) @@ -216,7 +226,6 @@ public int read( ByteBuffer dst ) throws IOException { } } } - int purged = readRemaining( dst ); if( purged != 0 ) return purged; @@ -229,7 +238,7 @@ public int read( ByteBuffer dst ) throws IOException { else inCrypt.compact(); - if( ( isBlocking() && inCrypt.position() == 0 ) || engineStatus == Status.BUFFER_UNDERFLOW ) + if( ( isBlocking() && inCrypt.position() == 0 ) || readEngineResult.getStatus() == Status.BUFFER_UNDERFLOW ) if( socketChannel.read( inCrypt ) == -1 ) { return -1; } @@ -243,6 +252,9 @@ public int read( ByteBuffer dst ) throws IOException { return transfered; } + /** + * {@link #read(ByteBuffer)} may not be to leave all buffers(inData, inCrypt) + **/ private int readRemaining( ByteBuffer dst ) throws SSLException { if( inData.hasRemaining() ) { return transfereTo( inData, dst ); @@ -273,7 +285,7 @@ public void close() throws IOException { } private boolean isHandShakeComplete() { - HandshakeStatus status = engineResult.getHandshakeStatus(); + HandshakeStatus status = sslEngine.getHandshakeStatus(); return status == SSLEngineResult.HandshakeStatus.FINISHED || status == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING; } @@ -304,7 +316,7 @@ public boolean isOpen() { @Override public boolean isNeedWrite() { - return outCrypt.hasRemaining() || !isHandShakeComplete(); + return outCrypt.hasRemaining() || !isHandShakeComplete(); // FIXME this condition can cause high cpu load during handshaking when network is slow } @Override @@ -314,8 +326,7 @@ public void writeMore() throws IOException { @Override public boolean isNeedRead() { - return inData.hasRemaining() || ( inCrypt.hasRemaining() && engineResult.getStatus() != Status.BUFFER_UNDERFLOW && - engineResult.getStatus() != Status.CLOSED ); + return inData.hasRemaining() || ( inCrypt.hasRemaining() && readEngineResult.getStatus() != Status.BUFFER_UNDERFLOW && readEngineResult.getStatus() != Status.CLOSED ); } @Override From 8471a35420296fb259a5517bdf3484ec6f970c33 Mon Sep 17 00:00:00 2001 From: Davidiusdadi Date: Thu, 25 Jul 2013 00:23:43 +0200 Subject: [PATCH 50/73] fixed rare fatal racing condition in SSLSocketChannel2 ( #190 ) --- src/main/java/org/java_websocket/SSLSocketChannel2.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/java_websocket/SSLSocketChannel2.java b/src/main/java/org/java_websocket/SSLSocketChannel2.java index d8d2b5167..d72532545 100644 --- a/src/main/java/org/java_websocket/SSLSocketChannel2.java +++ b/src/main/java/org/java_websocket/SSLSocketChannel2.java @@ -158,7 +158,6 @@ private synchronized ByteBuffer unwrap() throws SSLException { rem = inData.remaining(); readEngineResult = sslEngine.unwrap( inCrypt, inData ); } while ( readEngineResult.getStatus() == SSLEngineResult.Status.OK && ( rem != inData.remaining() || sslEngine.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP ) ); - inData.flip(); return inData; } @@ -226,10 +225,16 @@ public int read( ByteBuffer dst ) throws IOException { } } } + /* 1. When "dst" is smaller than "inData" readRemaining will fill "dst" with data decoded in a previous read call. + * 2. When "inCrypt" contains more data than "inData" has remaining space, unwrap has to be called on more time(readRemaining) + */ int purged = readRemaining( dst ); if( purged != 0 ) return purged; + /* We only continue when we really need more data from the network. + * Thats the case if inData is empty or inCrypt holds to less data than necessary for decryption + */ assert ( inData.position() == 0 ); inData.clear(); @@ -238,7 +243,7 @@ public int read( ByteBuffer dst ) throws IOException { else inCrypt.compact(); - if( ( isBlocking() && inCrypt.position() == 0 ) || readEngineResult.getStatus() == Status.BUFFER_UNDERFLOW ) + if( isBlocking() || readEngineResult.getStatus() == Status.BUFFER_UNDERFLOW ) if( socketChannel.read( inCrypt ) == -1 ) { return -1; } From 62306de87f4468c26de84bb135307a51d3606b00 Mon Sep 17 00:00:00 2001 From: Davidiusdadi Date: Thu, 25 Jul 2013 00:35:53 +0200 Subject: [PATCH 51/73] Fixed fatal error in buffer management which could cause buffers to be used more than once at the time resulting in strange protocol errors. --- .../org/java_websocket/server/WebSocketServer.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/java_websocket/server/WebSocketServer.java b/src/main/java/org/java_websocket/server/WebSocketServer.java index bf0b85c50..d0620bf6c 100644 --- a/src/main/java/org/java_websocket/server/WebSocketServer.java +++ b/src/main/java/org/java_websocket/server/WebSocketServer.java @@ -325,6 +325,7 @@ public void run() { ByteBuffer buf = takeBuffer(); try { if( SocketChannelIOHelper.read( buf, conn, (ByteChannel) conn.channel ) ) { + assert ( buf.hasRemaining() ); conn.inQueue.put( buf ); queue( conn ); i.remove(); @@ -339,9 +340,6 @@ public void run() { } catch ( IOException e ) { pushBuffer( buf ); throw e; - } catch ( RuntimeException e ) { - pushBuffer( buf ); - throw e; } } if( key.isWritable() ) { @@ -359,10 +357,12 @@ public void run() { try { if( SocketChannelIOHelper.readMore( buf, conn, c ) ) iqueue.add( conn ); + assert ( buf.hasRemaining() ); conn.inQueue.put( buf ); queue( conn ); - } finally { + } catch ( IOException e ) { pushBuffer( buf ); + throw e; } } @@ -373,7 +373,7 @@ public void run() { key.cancel(); handleIOException( key, conn, ex ); } catch ( InterruptedException e ) { - return;// FIXME controlled shutdown + return;// FIXME controlled shutdown (e.g. take care of buffermanagement) } } @@ -418,7 +418,7 @@ private void pushBuffer( ByteBuffer buf ) throws InterruptedException { } private void handleIOException( SelectionKey key, WebSocket conn, IOException ex ) { - //onWebsocketError( conn, ex );// conn may be null here + // onWebsocketError( conn, ex );// conn may be null here if( conn != null ) { conn.closeConnection( CloseFrame.ABNORMAL_CLOSE, ex.getMessage() ); } else if( key != null ) { From a2e09f113e19bd4207812ebedf0f6448befb71e9 Mon Sep 17 00:00:00 2001 From: Davidiusdadi Date: Wed, 31 Jul 2013 23:27:04 +0200 Subject: [PATCH 52/73] fixed case in which a the bytes could be skipped when the handshake was received in multiple parts and the last part was including nonpayload data. --- .../org/java_websocket/WebSocketImpl.java | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/java_websocket/WebSocketImpl.java b/src/main/java/org/java_websocket/WebSocketImpl.java index 0b9caeb4c..de7797ca8 100644 --- a/src/main/java/org/java_websocket/WebSocketImpl.java +++ b/src/main/java/org/java_websocket/WebSocketImpl.java @@ -93,7 +93,7 @@ public class WebSocketImpl implements WebSocket { private Opcode current_continuous_frame_opcode = null; /** the bytes of an incomplete received handshake */ - private ByteBuffer tmpHandshakeBytes; + private ByteBuffer tmpHandshakeBytes = ByteBuffer.allocate( 0 ); /** stores the handshake sent by this websocket ( Role.CLIENT only ) */ private ClientHandshake handshakerequest = null; @@ -147,8 +147,11 @@ public WebSocketImpl( WebSocketListener listener , List drafts , Socket s * */ public void decode( ByteBuffer socketBuffer ) { - if( !socketBuffer.hasRemaining() || flushandclosestate ) + assert ( socketBuffer.hasRemaining() ); + + if( flushandclosestate ) { return; + } if( DEBUG ) System.out.println( "process(" + socketBuffer.remaining() + "): {" + ( socketBuffer.remaining() > 1000 ? "too big to display" : new String( socketBuffer.array(), socketBuffer.position(), socketBuffer.remaining() ) ) + "}" ); @@ -157,19 +160,24 @@ public void decode( ByteBuffer socketBuffer ) { decodeFrames( socketBuffer ); } else { if( decodeHandshake( socketBuffer ) ) { - decodeFrames( socketBuffer ); + assert ( tmpHandshakeBytes.hasRemaining() != socketBuffer.hasRemaining() || !socketBuffer.hasRemaining() ); // the buffers will never have remaining bytes at the same time + + if( socketBuffer.hasRemaining() ) { + decodeFrames( socketBuffer ); + } else if( tmpHandshakeBytes.hasRemaining() ) { + decodeFrames( tmpHandshakeBytes ); + } } } assert ( isClosing() || isFlushAndClose() || !socketBuffer.hasRemaining() ); } - /** * Returns whether the handshake phase has is completed. * In case of a broken handshake this will be never the case. **/ private boolean decodeHandshake( ByteBuffer socketBufferNew ) { ByteBuffer socketBuffer; - if( tmpHandshakeBytes == null ) { + if( tmpHandshakeBytes.capacity() == 0 ) { socketBuffer = socketBufferNew; } else { if( tmpHandshakeBytes.remaining() < socketBufferNew.remaining() ) { @@ -257,7 +265,7 @@ private boolean decodeHandshake( ByteBuffer socketBufferNew ) { draft.setParseMode( role ); Handshakedata tmphandshake = draft.translateHandshake( socketBuffer ); if( tmphandshake instanceof ServerHandshake == false ) { - flushAndClose( CloseFrame.PROTOCOL_ERROR, "Wwrong http function", false ); + flushAndClose( CloseFrame.PROTOCOL_ERROR, "wrong http function", false ); return false; } ServerHandshake handshake = (ServerHandshake) tmphandshake; @@ -283,7 +291,7 @@ private boolean decodeHandshake( ByteBuffer socketBufferNew ) { close( e ); } } catch ( IncompleteHandshakeException e ) { - if( tmpHandshakeBytes == null ) { + if( tmpHandshakeBytes.capacity() == 0 ) { socketBuffer.reset(); int newsize = e.getPreferedSize(); if( newsize == 0 ) { From 158b9bc8ff059075b0ddb680c7f74ddd9be07c2d Mon Sep 17 00:00:00 2001 From: Davidiusdadi Date: Mon, 5 Aug 2013 22:21:08 +0200 Subject: [PATCH 53/73] applied fix submitted by @DeHecht ( #191 ) --- src/main/java/org/java_websocket/server/WebSocketServer.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/org/java_websocket/server/WebSocketServer.java b/src/main/java/org/java_websocket/server/WebSocketServer.java index d0620bf6c..eb0cfdf7a 100644 --- a/src/main/java/org/java_websocket/server/WebSocketServer.java +++ b/src/main/java/org/java_websocket/server/WebSocketServer.java @@ -8,6 +8,7 @@ import java.nio.ByteBuffer; import java.nio.channels.ByteChannel; import java.nio.channels.CancelledKeyException; +import java.nio.channels.ClosedByInterruptException; import java.nio.channels.SelectableChannel; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; @@ -368,6 +369,8 @@ public void run() { } } catch ( CancelledKeyException e ) { // an other thread may cancel the key + } catch ( ClosedByInterruptException e ) { + return; // do the same stuff as when InterruptedException is thrown } catch ( IOException ex ) { if( key != null ) key.cancel(); From c17693e474b7f90f117a9cedd31ab77f5d1c31fa Mon Sep 17 00:00:00 2001 From: Davidiusdadi Date: Tue, 6 Aug 2013 19:37:20 +0200 Subject: [PATCH 54/73] fix for #190 Create buffers according to maximum net and application data. Added a few asserts for better understanding. --- .../java/org/java_websocket/SSLSocketChannel2.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/java_websocket/SSLSocketChannel2.java b/src/main/java/org/java_websocket/SSLSocketChannel2.java index d72532545..6f6e637dc 100644 --- a/src/main/java/org/java_websocket/SSLSocketChannel2.java +++ b/src/main/java/org/java_websocket/SSLSocketChannel2.java @@ -56,6 +56,8 @@ public class SSLSocketChannel2 implements ByteChannel, WrappedByteChannel { protected SSLEngineResult readEngineResult; protected SSLEngineResult writeEngineResult; + protected int bufferallocations = 0; + public SSLSocketChannel2( SocketChannel channel , SSLEngine sslEngine , ExecutorService exec , SelectionKey key ) throws IOException { if( channel == null || sslEngine == null || exec == null ) throw new IllegalArgumentException( "parameter must not be null" ); @@ -127,21 +129,21 @@ private synchronized void processHandshake() throws IOException { } inData.compact(); unwrap(); - if( sslEngine.getHandshakeStatus() == HandshakeStatus.FINISHED ) { + if( readEngineResult.getHandshakeStatus() == HandshakeStatus.FINISHED ) { createBuffers( sslEngine.getSession() ); return; } } consumeDelegatedTasks(); - assert ( sslEngine.getHandshakeStatus() != HandshakeStatus.NOT_HANDSHAKING ); if( tasks.isEmpty() || sslEngine.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_WRAP ) { socketChannel.write( wrap( emptybuffer ) ); - if( sslEngine.getHandshakeStatus() == HandshakeStatus.FINISHED ) { + if( writeEngineResult.getHandshakeStatus() == HandshakeStatus.FINISHED ) { createBuffers( sslEngine.getSession() ); + return; } } + assert ( sslEngine.getHandshakeStatus() != HandshakeStatus.NOT_HANDSHAKING );// this function could only leave NOT_HANDSHAKING after createBuffers was called } - private synchronized ByteBuffer wrap( ByteBuffer b ) throws SSLException { outCrypt.compact(); writeEngineResult = sslEngine.wrap( b, outCrypt ); @@ -192,6 +194,7 @@ protected void createBuffers( SSLSession session ) { inCrypt.flip(); outCrypt.rewind(); outCrypt.flip(); + bufferallocations++; } public int write( ByteBuffer src ) throws IOException { @@ -199,6 +202,7 @@ public int write( ByteBuffer src ) throws IOException { processHandshake(); return 0; } + assert ( bufferallocations > 1 ); int num = socketChannel.write( wrap( src ) ); return num; @@ -225,6 +229,7 @@ public int read( ByteBuffer dst ) throws IOException { } } } + assert ( bufferallocations > 1 ); /* 1. When "dst" is smaller than "inData" readRemaining will fill "dst" with data decoded in a previous read call. * 2. When "inCrypt" contains more data than "inData" has remaining space, unwrap has to be called on more time(readRemaining) */ @@ -256,7 +261,6 @@ public int read( ByteBuffer dst ) throws IOException { } return transfered; } - /** * {@link #read(ByteBuffer)} may not be to leave all buffers(inData, inCrypt) **/ From 4e27fa8c13e631dc76dbf6152fb1a43c230a99c3 Mon Sep 17 00:00:00 2001 From: Davidiusdadi Date: Thu, 8 Aug 2013 22:32:59 +0200 Subject: [PATCH 55/73] Fixed minor issue with sending binary/fragmented frames when buffer position is not 0. --- src/main/java/org/java_websocket/drafts/Draft_10.java | 9 ++++----- .../java/org/java_websocket/framing/FramedataImpl1.java | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/java_websocket/drafts/Draft_10.java b/src/main/java/org/java_websocket/drafts/Draft_10.java index 608a78f90..305460a52 100644 --- a/src/main/java/org/java_websocket/drafts/Draft_10.java +++ b/src/main/java/org/java_websocket/drafts/Draft_10.java @@ -31,13 +31,12 @@ public class Draft_10 extends Draft { private class IncompleteException extends Throwable { - + /** * It's Serializable. */ private static final long serialVersionUID = 7330519489840500997L; - - + private int preferedsize; public IncompleteException( int preferedsize ) { this.preferedsize = preferedsize; @@ -63,7 +62,7 @@ public static int readVersion( Handshakedata handshakedata ) { private ByteBuffer incompleteframe; private Framedata fragmentedframe = null; - + private final Random reuseableRandom = new Random(); @Override @@ -117,7 +116,7 @@ public ByteBuffer createBinaryFrame( Framedata framedata ) { ByteBuffer maskkey = ByteBuffer.allocate( 4 ); maskkey.putInt( reuseableRandom.nextInt() ); buf.put( maskkey.array() ); - for( int i = 0 ; i < mes.limit() ; i++ ) { + for( int i = 0 ; mes.hasRemaining() ; i++ ) { buf.put( (byte) ( mes.get() ^ maskkey.get( i % 4 ) ) ); } } else diff --git a/src/main/java/org/java_websocket/framing/FramedataImpl1.java b/src/main/java/org/java_websocket/framing/FramedataImpl1.java index 5a1a5574f..5fba075b4 100644 --- a/src/main/java/org/java_websocket/framing/FramedataImpl1.java +++ b/src/main/java/org/java_websocket/framing/FramedataImpl1.java @@ -104,7 +104,7 @@ public void append( Framedata nextframe ) throws InvalidFrameException { @Override public String toString() { - return "Framedata{ optcode:" + getOpcode() + ", fin:" + isFin() + ", payloadlength:" + unmaskedpayload.limit() + ", payload:" + Arrays.toString( Charsetfunctions.utf8Bytes( new String( unmaskedpayload.array() ) ) ) + "}"; + return "Framedata{ optcode:" + getOpcode() + ", fin:" + isFin() + ", payloadlength:[pos:" + unmaskedpayload.position() + ", len:" + unmaskedpayload.remaining() + "], payload:" + Arrays.toString( Charsetfunctions.utf8Bytes( new String( unmaskedpayload.array() ) ) ) + "}"; } } From d4d99ed6dde4c325d7df3ec3c9e44469fcb823c3 Mon Sep 17 00:00:00 2001 From: Davidiusdadi Date: Thu, 8 Aug 2013 22:36:48 +0200 Subject: [PATCH 56/73] Introduced method sendFragmentedFrame() to send fragmented frames more convenient. --- .../java/org/java_websocket/WebSocket.java | 16 +++++++++ .../org/java_websocket/WebSocketImpl.java | 5 +++ .../java/org/java_websocket/drafts/Draft.java | 33 +++++++++++++++++++ 3 files changed, 54 insertions(+) diff --git a/src/main/java/org/java_websocket/WebSocket.java b/src/main/java/org/java_websocket/WebSocket.java index af82ab03f..218f5a862 100644 --- a/src/main/java/org/java_websocket/WebSocket.java +++ b/src/main/java/org/java_websocket/WebSocket.java @@ -6,6 +6,7 @@ import org.java_websocket.drafts.Draft; import org.java_websocket.framing.Framedata; +import org.java_websocket.framing.Framedata.Opcode; public interface WebSocket { public enum Role { @@ -62,6 +63,21 @@ public enum READYSTATE { public abstract void sendFrame( Framedata framedata ); + /** + * Allows to send continuous/fragmented frames conveniently.
+ * For more into on this frame type see http://tools.ietf.org/html/rfc6455#section-5.4
+ * + * If the first frame you send is also the last then it is not a fragmented frame and will received via onMessage instead of onFragmented even though it was send by this method. + * + * @param op + * This is only important for the first frame in the sequence. Opcode.TEXT, Opcode.BINARY are allowed. + * @param buffer + * The buffer which contains the payload. It may have no bytes remaining. + * @param fin + * true means the current frame is the last in the sequence. + **/ + public abstract void sendFragmentedFrame( Opcode op, ByteBuffer buffer, boolean fin ); + public abstract boolean hasBufferedData(); /** diff --git a/src/main/java/org/java_websocket/WebSocketImpl.java b/src/main/java/org/java_websocket/WebSocketImpl.java index de7797ca8..7bb7e8891 100644 --- a/src/main/java/org/java_websocket/WebSocketImpl.java +++ b/src/main/java/org/java_websocket/WebSocketImpl.java @@ -577,6 +577,11 @@ private void send( Collection frames ) { } } + @Override + public void sendFragmentedFrame( Opcode op, ByteBuffer buffer, boolean fin ) { + send( draft.continuousFrame( op, buffer, fin ) ); + } + @Override public void sendFrame( Framedata framedata ) { if( DEBUG ) diff --git a/src/main/java/org/java_websocket/drafts/Draft.java b/src/main/java/org/java_websocket/drafts/Draft.java index fdeafc933..529ed110b 100644 --- a/src/main/java/org/java_websocket/drafts/Draft.java +++ b/src/main/java/org/java_websocket/drafts/Draft.java @@ -12,7 +12,10 @@ import org.java_websocket.exceptions.InvalidHandshakeException; import org.java_websocket.exceptions.LimitExedeedException; import org.java_websocket.framing.CloseFrame; +import org.java_websocket.framing.FrameBuilder; import org.java_websocket.framing.Framedata; +import org.java_websocket.framing.Framedata.Opcode; +import org.java_websocket.framing.FramedataImpl1; import org.java_websocket.handshake.ClientHandshake; import org.java_websocket.handshake.ClientHandshakeBuilder; import org.java_websocket.handshake.HandshakeBuilder; @@ -46,6 +49,8 @@ public enum CloseHandshakeType { /** In some cases the handshake will be parsed different depending on whether */ protected Role role = null; + protected Opcode continuousFrameType = null; + public static ByteBuffer readLine( ByteBuffer buf ) { ByteBuffer sbuf = ByteBuffer.allocate( buf.remaining() ); byte prev = '0'; @@ -123,6 +128,34 @@ protected boolean basicAccept( Handshakedata handshakedata ) { public abstract List createFrames( String text, boolean mask ); + public List continuousFrame( Opcode op, ByteBuffer buffer, boolean fin ) { + if( op != Opcode.BINARY && op != Opcode.TEXT && op != Opcode.TEXT ) { + throw new IllegalArgumentException( "Only Opcode.BINARY or Opcode.TEXT are allowed" ); + } + + if( continuousFrameType != null ) { + continuousFrameType = Opcode.CONTINUOUS; + } else if( fin ) { + throw new IllegalArgumentException( "There is no continious frame to continue" ); + } else { + continuousFrameType = op; + } + + FrameBuilder bui = new FramedataImpl1( continuousFrameType ); + try { + bui.setPayload( buffer ); + } catch ( InvalidDataException e ) { + throw new RuntimeException( e ); // can only happen when one builds close frames(Opcode.Close) + } + bui.setFin( fin ); + if( fin ) { + continuousFrameType = null; + } else { + continuousFrameType = op; + } + return Collections.singletonList( (Framedata) bui ); + } + public abstract void reset(); public List createHandshake( Handshakedata handshakedata, Role ownrole ) { From 7ea8526b456f72ca9e8a5eacb64f76b75007bbf5 Mon Sep 17 00:00:00 2001 From: Davidiusdadi Date: Thu, 8 Aug 2013 22:41:50 +0200 Subject: [PATCH 57/73] Made WebSocketClient implement the WebSocket interface. Reformatted code. --- .../client/WebSocketClient.java | 104 ++++++++++++++++-- 1 file changed, 92 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/java_websocket/client/WebSocketClient.java b/src/main/java/org/java_websocket/client/WebSocketClient.java index d885fcda2..1aeb64688 100644 --- a/src/main/java/org/java_websocket/client/WebSocketClient.java +++ b/src/main/java/org/java_websocket/client/WebSocketClient.java @@ -16,7 +16,6 @@ import org.java_websocket.SocketChannelIOHelper; import org.java_websocket.WebSocket; -import org.java_websocket.WebSocket.READYSTATE; import org.java_websocket.WebSocketAdapter; import org.java_websocket.WebSocketFactory; import org.java_websocket.WebSocketImpl; @@ -25,6 +24,8 @@ import org.java_websocket.drafts.Draft_10; import org.java_websocket.exceptions.InvalidHandshakeException; import org.java_websocket.framing.CloseFrame; +import org.java_websocket.framing.Framedata; +import org.java_websocket.framing.Framedata.Opcode; import org.java_websocket.handshake.HandshakeImpl1Client; import org.java_websocket.handshake.Handshakedata; import org.java_websocket.handshake.ServerHandshake; @@ -39,7 +40,7 @@ * * @author Nathan Rajlich */ -public abstract class WebSocketClient extends WebSocketAdapter implements Runnable { +public abstract class WebSocketClient extends WebSocketAdapter implements Runnable, WebSocket { /** * The URI this channel is supposed to connect to. @@ -104,14 +105,13 @@ public WebSocketClient( URI serverUri , Draft draft , Map headers channel = null; onWebsocketError( null, e ); } - if(channel == null){ + if( channel == null ) { conn = (WebSocketImpl) wsfactory.createWebSocket( this, draft, null ); conn.close( CloseFrame.NEVER_CONNECTED, "Failed to create or configure SocketChannel." ); - } - else{ + } else { conn = (WebSocketImpl) wsfactory.createWebSocket( this, draft, channel.socket() ); } - + } /** @@ -171,6 +171,10 @@ public void send( String text ) throws NotYetConnectedException { conn.send( text ); } + public void sendFragment( Framedata f ) { + conn.sendFrame( f ); + } + /** * Sends data to the connected WebSocket server. * @@ -178,7 +182,7 @@ public void send( String text ) throws NotYetConnectedException { * The Byte-Array of data to send to the WebSocket server. */ public void send( byte[] data ) throws NotYetConnectedException { - conn.send( data ); + conn.send( data ); } // Runnable IMPLEMENTATION ///////////////////////////////////////////////// @@ -198,7 +202,7 @@ private final void interruptableRun() { try { String host; - int port ; + int port; if( proxyAddress != null ) { host = proxyAddress.getHostName(); @@ -317,6 +321,11 @@ public final void onWebsocketMessage( WebSocket conn, ByteBuffer blob ) { onMessage( blob ); } + @Override + public void onWebsocketMessageFragment( WebSocket conn, Framedata frame ) { + onFragment( frame ); + } + /** * Calls subclass' implementation of onOpen. * @@ -405,7 +414,9 @@ public InetSocketAddress getRemoteSocketAddress( WebSocket conn ) { public abstract void onClose( int code, String reason, boolean remote ); public abstract void onError( Exception ex ); public void onMessage( ByteBuffer bytes ) { - }; + } + public void onFragment( Framedata frame ) { + } public class DefaultClientProxyChannel extends AbstractClientProxyChannel { public DefaultClientProxyChannel( ByteChannel towrap ) { @@ -446,15 +457,84 @@ public void run() { } } } - + public ByteChannel createProxyChannel( ByteChannel towrap ) { - if( proxyAddress != null ){ + if( proxyAddress != null ) { return new DefaultClientProxyChannel( towrap ); } - return towrap;//no proxy in use + return towrap;// no proxy in use } public void setProxy( InetSocketAddress proxyaddress ) { proxyAddress = proxyaddress; } + + @Override + public void sendFragmentedFrame( Opcode op, ByteBuffer buffer, boolean fin ) { + conn.sendFragmentedFrame( op, buffer, fin ); + } + + @Override + public boolean isOpen() { + return conn.isOpen(); + } + + @Override + public boolean isFlushAndClose() { + return conn.isFlushAndClose(); + } + + @Override + public boolean isClosed() { + return conn.isClosed(); + } + + @Override + public boolean isClosing() { + return conn.isClosing(); + } + + @Override + public boolean isConnecting() { + return conn.isConnecting(); + } + + @Override + public boolean hasBufferedData() { + return conn.hasBufferedData(); + } + + @Override + public void close( int code ) { + conn.close(); + } + + @Override + public void close( int code, String message ) { + conn.close( code, message ); + } + + @Override + public void closeConnection( int code, String message ) { + conn.closeConnection( code, message ); + } + + @Override + public void send( ByteBuffer bytes ) throws IllegalArgumentException , NotYetConnectedException { + conn.send( bytes ); + } + + @Override + public void sendFrame( Framedata framedata ) { + conn.sendFrame( framedata ); + } + + @Override + public InetSocketAddress getLocalSocketAddress() { + return conn.getLocalSocketAddress(); + } + @Override + public InetSocketAddress getRemoteSocketAddress() { + return conn.getRemoteSocketAddress(); + } } From e1676f53c88507cebcddc24f2f4e2dad51db666d Mon Sep 17 00:00:00 2001 From: Davidiusdadi Date: Thu, 8 Aug 2013 22:49:52 +0200 Subject: [PATCH 58/73] Added method onFragment to replace onWebsocketMessageFragment and let the main examples overload it. --- src/main/example/ChatServer.java | 15 ++++++++++++++- src/main/example/ExampleClient.java | 9 +++++++-- .../java_websocket/server/WebSocketServer.java | 15 ++++++++++++++- 3 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/main/example/ChatServer.java b/src/main/example/ChatServer.java index b5e570def..a27cb0ee0 100644 --- a/src/main/example/ChatServer.java +++ b/src/main/example/ChatServer.java @@ -7,6 +7,7 @@ import org.java_websocket.WebSocket; import org.java_websocket.WebSocketImpl; +import org.java_websocket.framing.Framedata; import org.java_websocket.handshake.ClientHandshake; import org.java_websocket.server.WebSocketServer; @@ -41,6 +42,11 @@ public void onMessage( WebSocket conn, String message ) { System.out.println( conn + ": " + message ); } + @Override + public void onFragment( WebSocket conn, Framedata fragment ) { + System.out.println( "received fragment: " + fragment ); + } + public static void main( String[] args ) throws InterruptedException , IOException { WebSocketImpl.DEBUG = true; int port = 8887; // 843 flash policy port @@ -56,9 +62,16 @@ public static void main( String[] args ) throws InterruptedException , IOExcepti while ( true ) { String in = sysin.readLine(); s.sendToAll( in ); + if( in.equals( "exit" ) ) { + s.stop(); + break; + } else if( in.equals( "restart" ) ) { + s.stop(); + s.start(); + break; + } } } - @Override public void onError( WebSocket conn, Exception ex ) { ex.printStackTrace(); diff --git a/src/main/example/ExampleClient.java b/src/main/example/ExampleClient.java index 531784528..ec4aac813 100644 --- a/src/main/example/ExampleClient.java +++ b/src/main/example/ExampleClient.java @@ -4,6 +4,7 @@ import org.java_websocket.client.WebSocketClient; import org.java_websocket.drafts.Draft; import org.java_websocket.drafts.Draft_10; +import org.java_websocket.framing.Framedata; import org.java_websocket.handshake.ServerHandshake; /** This example demonstrates how to create a websocket connection to a server. Only the most important callbacks are overloaded. */ @@ -20,13 +21,17 @@ public ExampleClient( URI serverURI ) { @Override public void onOpen( ServerHandshake handshakedata ) { System.out.println( "opened connection" ); - // if you pan to refuse connection based on ip or httpfields overload: onWebsocketHandshakeReceivedAsClient + // if you plan to refuse connection based on ip or httpfields overload: onWebsocketHandshakeReceivedAsClient } @Override public void onMessage( String message ) { System.out.println( "received: " + message ); - // send( "you said: " + message ); + } + + @Override + public void onFragment( Framedata fragment ) { + System.out.println( "received fragment: " + fragment ); } @Override diff --git a/src/main/java/org/java_websocket/server/WebSocketServer.java b/src/main/java/org/java_websocket/server/WebSocketServer.java index eb0cfdf7a..6257d7d81 100644 --- a/src/main/java/org/java_websocket/server/WebSocketServer.java +++ b/src/main/java/org/java_websocket/server/WebSocketServer.java @@ -36,6 +36,7 @@ import org.java_websocket.WrappedByteChannel; import org.java_websocket.drafts.Draft; import org.java_websocket.framing.CloseFrame; +import org.java_websocket.framing.Framedata; import org.java_websocket.handshake.ClientHandshake; import org.java_websocket.handshake.Handshakedata; @@ -472,6 +473,12 @@ public final void onWebsocketMessage( WebSocket conn, String message ) { onMessage( conn, message ); } + @Override + @Deprecated + public/*final*/void onWebsocketMessageFragment( WebSocket conn, Framedata frame ) {// onFragment should be overloaded instead + onFragment( conn, frame ); + } + @Override public final void onWebsocketMessage( WebSocket conn, ByteBuffer blob ) { onMessage( conn, blob ); @@ -626,7 +633,13 @@ public InetSocketAddress getRemoteSocketAddress( WebSocket conn ) { * @see #onMessage(WebSocket, String) **/ public void onMessage( WebSocket conn, ByteBuffer message ) { - }; + } + + /** + * @see WebSocket#sendFragmentedFrame(org.java_websocket.framing.Framedata.Opcode, ByteBuffer, boolean) + */ + public void onFragment( WebSocket conn, Framedata fragment ) { + } public class WebSocketWorker extends Thread { From fe11114f00b613bcfd4e528ada11fbd612174a1d Mon Sep 17 00:00:00 2001 From: Davidiusdadi Date: Thu, 8 Aug 2013 23:16:21 +0200 Subject: [PATCH 59/73] Added example on how send fragmented frames. --- src/main/example/FragmentedFramesExample.java | 61 +++++++++++++++++++ .../java/org/java_websocket/drafts/Draft.java | 2 - 2 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 src/main/example/FragmentedFramesExample.java diff --git a/src/main/example/FragmentedFramesExample.java b/src/main/example/FragmentedFramesExample.java new file mode 100644 index 000000000..9e102a92b --- /dev/null +++ b/src/main/example/FragmentedFramesExample.java @@ -0,0 +1,61 @@ +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.ByteBuffer; + +import org.java_websocket.WebSocket; +import org.java_websocket.client.WebSocketClient; +import org.java_websocket.drafts.Draft_17; +import org.java_websocket.framing.Framedata.Opcode; + +/** + * This example show how to send fragmented frames.
+ * It also shows that one can mix fragmented and normal frames at will.
+ * Of course one has to finish with a fragmented frame sequence before continuing with the next. + * + * @see WebSocket#sendFragmentedFrame(Opcode, ByteBuffer, boolean) + **/ +public class FragmentedFramesExample { + public static void main( String[] args ) throws URISyntaxException , IOException , InterruptedException { + // WebSocketImpl.DEBUG = true; // will give extra output + + WebSocketClient websocket = new ExampleClient( new URI( "ws://localhost:8887" ), new Draft_17() ); // Draft_17 is implementation of rfc6455 + if( !websocket.connectBlocking() ) { + System.err.println( "Could not connect to the server." ); + return; + } + + System.out.println( "This example shows how to send fragmented(continuous) messages.\n It also shows that fragments can be intercepted by normal messages." ); + + BufferedReader stdin = new BufferedReader( new InputStreamReader( System.in ) ); + while ( websocket.isOpen() ) { + System.out.println( "Please type in a loooooong line(which will be send in multible parts):" ); + String longline = stdin.readLine(); + ByteBuffer longelinebuffer = ByteBuffer.wrap( longline.getBytes() ); + longelinebuffer.rewind(); + + System.out.println( "The long message you just typed in will be fragmented in messages of 2bytes payload each.\nPress enter so send the next fragemnt or make some other input to send text messages inbetween." ); + for( int position = 2 ; ; position += 2 ) { + + String sendInOnePiece = stdin.readLine(); + if( !sendInOnePiece.isEmpty() ) { + websocket.send( sendInOnePiece ); + } + + if( position < longelinebuffer.capacity() ) { + longelinebuffer.limit( position ); + websocket.sendFragmentedFrame( Opcode.TEXT, longelinebuffer, false );// when sending binary data use Opcode.BINARY + } else { + longelinebuffer.limit( longelinebuffer.capacity() ); + websocket.sendFragmentedFrame( Opcode.TEXT, longelinebuffer, true ); + break; + } + + } + System.out.println( "You can not type in the next long message or press Ctr-C to exit." ); + } + System.out.println( "FragmentedFramesExample terminated" ); + } +} diff --git a/src/main/java/org/java_websocket/drafts/Draft.java b/src/main/java/org/java_websocket/drafts/Draft.java index 529ed110b..1bad2ad7b 100644 --- a/src/main/java/org/java_websocket/drafts/Draft.java +++ b/src/main/java/org/java_websocket/drafts/Draft.java @@ -135,8 +135,6 @@ public List continuousFrame( Opcode op, ByteBuffer buffer, boolean fi if( continuousFrameType != null ) { continuousFrameType = Opcode.CONTINUOUS; - } else if( fin ) { - throw new IllegalArgumentException( "There is no continious frame to continue" ); } else { continuousFrameType = op; } From effeb32b4cc5e7f31243d7776d90393723c0367d Mon Sep 17 00:00:00 2001 From: Davidiusdadi Date: Mon, 12 Aug 2013 20:18:19 +0200 Subject: [PATCH 60/73] pragmatic fix for #190 --- .../org/java_websocket/SSLSocketChannel2.java | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/java_websocket/SSLSocketChannel2.java b/src/main/java/org/java_websocket/SSLSocketChannel2.java index 6f6e637dc..110cf3805 100644 --- a/src/main/java/org/java_websocket/SSLSocketChannel2.java +++ b/src/main/java/org/java_websocket/SSLSocketChannel2.java @@ -56,6 +56,10 @@ public class SSLSocketChannel2 implements ByteChannel, WrappedByteChannel { protected SSLEngineResult readEngineResult; protected SSLEngineResult writeEngineResult; + /** + * Should be used to count the buffer allocations. + * But because of #190 where HandshakeStatus.FINISHED is not properly returned by nio wrap/unwrap this variable is used to check whether {@link #createBuffers(SSLSession)} needs to be called. + **/ protected int bufferallocations = 0; public SSLSocketChannel2( SocketChannel channel , SSLEngine sslEngine , ExecutorService exec , SelectionKey key ) throws IOException { @@ -142,7 +146,9 @@ private synchronized void processHandshake() throws IOException { return; } } - assert ( sslEngine.getHandshakeStatus() != HandshakeStatus.NOT_HANDSHAKING );// this function could only leave NOT_HANDSHAKING after createBuffers was called + assert ( sslEngine.getHandshakeStatus() != HandshakeStatus.NOT_HANDSHAKING );// this function could only leave NOT_HANDSHAKING after createBuffers was called unless #190 occurs which means that nio wrap/unwrap never return HandshakeStatus.FINISHED + + bufferallocations = 1; // look at variable declaration why this line exists and #190. Without this line buffers would not be be recreated when #190 AND a rehandshake occur. } private synchronized ByteBuffer wrap( ByteBuffer b ) throws SSLException { outCrypt.compact(); @@ -202,7 +208,10 @@ public int write( ByteBuffer src ) throws IOException { processHandshake(); return 0; } - assert ( bufferallocations > 1 ); + // assert ( bufferallocations > 1 ); //see #190 + if( bufferallocations <= 1 ) { + createBuffers( sslEngine.getSession() ); + } int num = socketChannel.write( wrap( src ) ); return num; @@ -229,7 +238,10 @@ public int read( ByteBuffer dst ) throws IOException { } } } - assert ( bufferallocations > 1 ); + // assert ( bufferallocations > 1 ); //see #190 + if( bufferallocations <= 1 ) { + createBuffers( sslEngine.getSession() ); + } /* 1. When "dst" is smaller than "inData" readRemaining will fill "dst" with data decoded in a previous read call. * 2. When "inCrypt" contains more data than "inData" has remaining space, unwrap has to be called on more time(readRemaining) */ From f34bc4d17f6bb86ff2e97f175e8b2ff1957e9543 Mon Sep 17 00:00:00 2001 From: Davidiusdadi Date: Mon, 12 Aug 2013 21:17:16 +0200 Subject: [PATCH 61/73] corrected logical errors in FragmentedFramesExample --- src/main/example/ExampleClient.java | 2 +- src/main/example/FragmentedFramesExample.java | 24 ++++++++----------- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/src/main/example/ExampleClient.java b/src/main/example/ExampleClient.java index ec4aac813..3cf68377e 100644 --- a/src/main/example/ExampleClient.java +++ b/src/main/example/ExampleClient.java @@ -31,7 +31,7 @@ public void onMessage( String message ) { @Override public void onFragment( Framedata fragment ) { - System.out.println( "received fragment: " + fragment ); + System.out.println( "received fragment: " + new String( fragment.getPayloadData().array() ) ); } @Override diff --git a/src/main/example/FragmentedFramesExample.java b/src/main/example/FragmentedFramesExample.java index 9e102a92b..b5a03e1aa 100644 --- a/src/main/example/FragmentedFramesExample.java +++ b/src/main/example/FragmentedFramesExample.java @@ -11,9 +11,10 @@ import org.java_websocket.framing.Framedata.Opcode; /** - * This example show how to send fragmented frames.
- * It also shows that one can mix fragmented and normal frames at will.
- * Of course one has to finish with a fragmented frame sequence before continuing with the next. + * This example shows how to send fragmented frames.
+ * For information on when to used fragmented frames see http://tools.ietf.org/html/rfc6455#section-5.4
+ * Fragmented and normal messages can not be mixed. + * One is however allowed to mix them with control messages like ping/pong. * * @see WebSocket#sendFragmentedFrame(Opcode, ByteBuffer, boolean) **/ @@ -27,29 +28,24 @@ public static void main( String[] args ) throws URISyntaxException , IOException return; } - System.out.println( "This example shows how to send fragmented(continuous) messages.\n It also shows that fragments can be intercepted by normal messages." ); + System.out.println( "This example shows how to send fragmented(continuous) messages." ); BufferedReader stdin = new BufferedReader( new InputStreamReader( System.in ) ); while ( websocket.isOpen() ) { - System.out.println( "Please type in a loooooong line(which will be send in multible parts):" ); + System.out.println( "Please type in a loooooong line(which then will be send in 2 byte fragments):" ); String longline = stdin.readLine(); ByteBuffer longelinebuffer = ByteBuffer.wrap( longline.getBytes() ); longelinebuffer.rewind(); - System.out.println( "The long message you just typed in will be fragmented in messages of 2bytes payload each.\nPress enter so send the next fragemnt or make some other input to send text messages inbetween." ); for( int position = 2 ; ; position += 2 ) { - - String sendInOnePiece = stdin.readLine(); - if( !sendInOnePiece.isEmpty() ) { - websocket.send( sendInOnePiece ); - } - if( position < longelinebuffer.capacity() ) { longelinebuffer.limit( position ); - websocket.sendFragmentedFrame( Opcode.TEXT, longelinebuffer, false );// when sending binary data use Opcode.BINARY + websocket.sendFragmentedFrame( Opcode.TEXT, longelinebuffer, false );// when sending binary data one should use Opcode.BINARY + assert ( longelinebuffer.remaining() == 0 ); + // after calling sendFragmentedFrame one may reuse the buffer given to the method immediately } else { longelinebuffer.limit( longelinebuffer.capacity() ); - websocket.sendFragmentedFrame( Opcode.TEXT, longelinebuffer, true ); + websocket.sendFragmentedFrame( Opcode.TEXT, longelinebuffer, true );// sending the last frame break; } From 6ea3658f67415fed2e413518b80e4ef69211112b Mon Sep 17 00:00:00 2001 From: Davidiusdadi Date: Tue, 13 Aug 2013 22:29:28 +0200 Subject: [PATCH 62/73] fixed #199 --- src/main/java/org/java_websocket/drafts/Draft_75.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/org/java_websocket/drafts/Draft_75.java b/src/main/java/org/java_websocket/drafts/Draft_75.java index d380eba25..947a35ece 100644 --- a/src/main/java/org/java_websocket/drafts/Draft_75.java +++ b/src/main/java/org/java_websocket/drafts/Draft_75.java @@ -165,7 +165,6 @@ protected List translateRegularFrame( ByteBuffer buffer ) throws Inva List frames = readyframes; readyframes = new LinkedList(); - this.currentFrame = null; return frames; } From 9b0a4b8c182bbe3f384d8ee8ede54eb754900bf5 Mon Sep 17 00:00:00 2001 From: Davidiusdadi Date: Tue, 3 Sep 2013 22:47:33 +0200 Subject: [PATCH 63/73] made websocket client use java.net instead of java.nio (addresses #88, #155, #167, #175, #177, #187) (changes not yet fully tested) --- src/main/example/ProxyClientExample.java | 22 +- src/main/example/SSLClientExample.java | 6 +- .../DefaultSSLWebSocketClientFactory.java | 52 --- .../client/DefaultWebSocketClientFactory.java | 39 --- .../client/WebSocketClient.java | 322 ++++++------------ 5 files changed, 120 insertions(+), 321 deletions(-) delete mode 100644 src/main/java/org/java_websocket/client/DefaultSSLWebSocketClientFactory.java delete mode 100644 src/main/java/org/java_websocket/client/DefaultWebSocketClientFactory.java diff --git a/src/main/example/ProxyClientExample.java b/src/main/example/ProxyClientExample.java index 133756fd0..ddff0ea0d 100644 --- a/src/main/example/ProxyClientExample.java +++ b/src/main/example/ProxyClientExample.java @@ -1,26 +1,12 @@ import java.net.InetSocketAddress; +import java.net.Proxy; import java.net.URI; import java.net.URISyntaxException; -import java.nio.channels.ByteChannel; - -public class ProxyClientExample extends ExampleClient { - - public ProxyClientExample( URI serverURI , InetSocketAddress proxy ) { - super( serverURI ); - setProxy( proxy ); - } - - @Override - public ByteChannel createProxyChannel( ByteChannel towrap ) { - /* - * You can create custom proxy handshake here. - * For more infos see: WebSocketClient.DefaultClientProxyChannel and http://tools.ietf.org/html/rfc6455#section-4.1 - */ - return super.createProxyChannel( towrap ); - } +public class ProxyClientExample { public static void main( String[] args ) throws URISyntaxException { - ProxyClientExample c = new ProxyClientExample( new URI( "ws://echo.websocket.org" ), new InetSocketAddress( "proxyaddress", 80 ) );// don't forget to change "proxyaddress" + ExampleClient c = new ExampleClient( new URI( "ws://echo.websocket.org" ) ); + c.setProxy( new Proxy( Proxy.Type.HTTP, new InetSocketAddress( "proxyaddress", 80 ) ) ); c.connect(); } } diff --git a/src/main/example/SSLClientExample.java b/src/main/example/SSLClientExample.java index 2231e5475..e740c9c54 100644 --- a/src/main/example/SSLClientExample.java +++ b/src/main/example/SSLClientExample.java @@ -7,10 +7,10 @@ import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManagerFactory; import org.java_websocket.WebSocketImpl; -import org.java_websocket.client.DefaultSSLWebSocketClientFactory; import org.java_websocket.client.WebSocketClient; import org.java_websocket.handshake.ServerHandshake; @@ -79,7 +79,9 @@ public static void main( String[] args ) throws Exception { sslContext.init( kmf.getKeyManagers(), tmf.getTrustManagers(), null ); // sslContext.init( null, null, null ); // will use java's default key and trust store which is sufficient unless you deal with self-signed certificates - chatclient.setWebSocketFactory( new DefaultSSLWebSocketClientFactory( sslContext ) ); + SSLSocketFactory factory = sslContext.getSocketFactory();// (SSLSocketFactory) SSLSocketFactory.getDefault(); + + chatclient.setSocket( factory.createSocket() ); chatclient.connectBlocking(); diff --git a/src/main/java/org/java_websocket/client/DefaultSSLWebSocketClientFactory.java b/src/main/java/org/java_websocket/client/DefaultSSLWebSocketClientFactory.java deleted file mode 100644 index 0646cea1a..000000000 --- a/src/main/java/org/java_websocket/client/DefaultSSLWebSocketClientFactory.java +++ /dev/null @@ -1,52 +0,0 @@ -package org.java_websocket.client; -import java.io.IOException; -import java.net.Socket; -import java.nio.channels.ByteChannel; -import java.nio.channels.SelectionKey; -import java.nio.channels.SocketChannel; -import java.util.List; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLEngine; - -import org.java_websocket.SSLSocketChannel2; -import org.java_websocket.WebSocketAdapter; -import org.java_websocket.WebSocketImpl; -import org.java_websocket.client.WebSocketClient.WebSocketClientFactory; -import org.java_websocket.drafts.Draft; - - -public class DefaultSSLWebSocketClientFactory implements WebSocketClientFactory { - protected SSLContext sslcontext; - protected ExecutorService exec; - - public DefaultSSLWebSocketClientFactory( SSLContext sslContext ) { - this( sslContext, Executors.newSingleThreadScheduledExecutor() ); - } - - public DefaultSSLWebSocketClientFactory( SSLContext sslContext , ExecutorService exec ) { - if( sslContext == null || exec == null ) - throw new IllegalArgumentException(); - this.sslcontext = sslContext; - this.exec = exec; - } - - @Override - public ByteChannel wrapChannel( SocketChannel channel, SelectionKey key, String host, int port ) throws IOException { - SSLEngine e = sslcontext.createSSLEngine( host, port ); - e.setUseClientMode( true ); - return new SSLSocketChannel2( channel, e, exec, key ); - } - - @Override - public WebSocketImpl createWebSocket( WebSocketAdapter a, Draft d, Socket c ) { - return new WebSocketImpl( a, d, c ); - } - - @Override - public WebSocketImpl createWebSocket( WebSocketAdapter a, List d, Socket s ) { - return new WebSocketImpl( a, d, s ); - } -} \ No newline at end of file diff --git a/src/main/java/org/java_websocket/client/DefaultWebSocketClientFactory.java b/src/main/java/org/java_websocket/client/DefaultWebSocketClientFactory.java deleted file mode 100644 index 005f8f038..000000000 --- a/src/main/java/org/java_websocket/client/DefaultWebSocketClientFactory.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.java_websocket.client; - -import java.net.Socket; -import java.nio.channels.ByteChannel; -import java.nio.channels.SelectionKey; -import java.nio.channels.SocketChannel; -import java.util.List; - -import org.java_websocket.WebSocket; -import org.java_websocket.WebSocketAdapter; -import org.java_websocket.WebSocketImpl; -import org.java_websocket.drafts.Draft; - -public class DefaultWebSocketClientFactory implements WebSocketClient.WebSocketClientFactory { - /** - * - */ - private final WebSocketClient webSocketClient; - /** - * @param webSocketClient - */ - public DefaultWebSocketClientFactory( WebSocketClient webSocketClient ) { - this.webSocketClient = webSocketClient; - } - @Override - public WebSocket createWebSocket( WebSocketAdapter a, Draft d, Socket s ) { - return new WebSocketImpl( this.webSocketClient, d ); - } - @Override - public WebSocket createWebSocket( WebSocketAdapter a, List d, Socket s ) { - return new WebSocketImpl( this.webSocketClient, d ); - } - @Override - public ByteChannel wrapChannel( SocketChannel channel, SelectionKey c, String host, int port ) { - if( c == null ) - return channel; - return channel; - } -} \ No newline at end of file diff --git a/src/main/java/org/java_websocket/client/WebSocketClient.java b/src/main/java/org/java_websocket/client/WebSocketClient.java index 1aeb64688..635cb17f8 100644 --- a/src/main/java/org/java_websocket/client/WebSocketClient.java +++ b/src/main/java/org/java_websocket/client/WebSocketClient.java @@ -1,27 +1,22 @@ package org.java_websocket.client; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.net.InetSocketAddress; +import java.net.Proxy; +import java.net.Socket; import java.net.URI; import java.nio.ByteBuffer; -import java.nio.channels.ByteChannel; -import java.nio.channels.CancelledKeyException; -import java.nio.channels.ClosedByInterruptException; import java.nio.channels.NotYetConnectedException; -import java.nio.channels.SelectionKey; -import java.nio.channels.SocketChannel; -import java.nio.channels.spi.SelectorProvider; import java.util.Map; import java.util.concurrent.CountDownLatch; -import org.java_websocket.SocketChannelIOHelper; import org.java_websocket.WebSocket; import org.java_websocket.WebSocketAdapter; -import org.java_websocket.WebSocketFactory; import org.java_websocket.WebSocketImpl; -import org.java_websocket.WrappedByteChannel; import org.java_websocket.drafts.Draft; -import org.java_websocket.drafts.Draft_10; +import org.java_websocket.drafts.Draft_17; import org.java_websocket.exceptions.InvalidHandshakeException; import org.java_websocket.framing.CloseFrame; import org.java_websocket.framing.Framedata; @@ -31,14 +26,8 @@ import org.java_websocket.handshake.ServerHandshake; /** - * The WebSocketClient is an abstract class that expects a valid - * "ws://" URI to connect to. When connected, an instance recieves important - * events related to the life of the connection. A subclass must implement - * onOpen, onClose, and onMessage to be - * useful. An instance can send messages to it's connected server via the - * send method. - * - * @author Nathan Rajlich + * A subclass must implement at least onOpen, onClose, and onMessage to be + * useful. At runtime the user is expected to establish a connection via {@link #connect()}, then receive events like {@link #onMessage(String)} via the overloaded methods and to {@link #send(String)} data to the server. */ public abstract class WebSocketClient extends WebSocketAdapter implements Runnable, WebSocket { @@ -47,17 +36,17 @@ public abstract class WebSocketClient extends WebSocketAdapter implements Runnab */ protected URI uri = null; - private WebSocketImpl conn = null; - /** - * The SocketChannel instance this channel uses. - */ - private SocketChannel channel = null; + private WebSocketImpl engine = null; + + private Socket socket = null; + + private InputStream istream; - private ByteChannel wrappedchannel = null; + private OutputStream ostream; - private Thread writethread; + private Proxy proxy = Proxy.NO_PROXY; - private Thread readthread; + private Thread writeThread; private Draft draft; @@ -67,92 +56,77 @@ public abstract class WebSocketClient extends WebSocketAdapter implements Runnab private CountDownLatch closeLatch = new CountDownLatch( 1 ); - private int timeout = 0; - - private WebSocketClientFactory wsfactory = new DefaultWebSocketClientFactory( this ); - - private InetSocketAddress proxyAddress = null; + private int connectTimeout = 0; + /** This open a websocket connection as specified by rfc6455 */ public WebSocketClient( URI serverURI ) { - this( serverURI, new Draft_10() ); + this( serverURI, new Draft_17() ); } /** * Constructs a WebSocketClient instance and sets it to the connect to the - * specified URI. The channel does not attampt to connect automatically. You - * must call connect first to initiate the socket connection. + * specified URI. The channel does not attampt to connect automatically. The connection + * will be established once you call connect. */ public WebSocketClient( URI serverUri , Draft draft ) { this( serverUri, draft, null, 0 ); } - public WebSocketClient( URI serverUri , Draft draft , Map headers , int connecttimeout ) { + public WebSocketClient( URI serverUri , Draft protocolDraft , Map httpHeaders , int connectTimeout ) { if( serverUri == null ) { throw new IllegalArgumentException(); - } - if( draft == null ) { + } else if( protocolDraft == null ) { throw new IllegalArgumentException( "null as draft is permitted for `WebSocketServer` only!" ); } this.uri = serverUri; - this.draft = draft; - this.headers = headers; - this.timeout = connecttimeout; - - try { - channel = SelectorProvider.provider().openSocketChannel(); - channel.configureBlocking( true ); - } catch ( IOException e ) { - channel = null; - onWebsocketError( null, e ); - } - if( channel == null ) { - conn = (WebSocketImpl) wsfactory.createWebSocket( this, draft, null ); - conn.close( CloseFrame.NEVER_CONNECTED, "Failed to create or configure SocketChannel." ); - } else { - conn = (WebSocketImpl) wsfactory.createWebSocket( this, draft, channel.socket() ); - } - + this.draft = protocolDraft; + this.headers = httpHeaders; + this.connectTimeout = connectTimeout; + this.engine = new WebSocketImpl( this, protocolDraft ); } /** - * Gets the URI that this WebSocketClient is connected to. - * - * @return The URI for this WebSocketClient. + * Returns the URI that this WebSocketClient is connected to. */ public URI getURI() { return uri; } - /** Returns the protocol version this channel uses. */ + /** + * Returns the protocol version this channel uses.
+ * For more infos see https://github.com/TooTallNate/Java-WebSocket/wiki/Drafts + */ public Draft getDraft() { return draft; } /** - * Starts a background thread that attempts and maintains a WebSocket - * connection to the URI specified in the constructor or via setURI. - * setURI. + * Initiates the websocket connection. This method does not block. */ public void connect() { - if( writethread != null ) + if( writeThread != null ) throw new IllegalStateException( "WebSocketClient objects are not reuseable" ); - writethread = new Thread( this ); - writethread.start(); + writeThread = new Thread( this ); + writeThread.start(); } /** - * Same as connect but blocks until the websocket connected or failed to do so.
+ * Same as connect but blocks until the websocket connected or failed to do so.
* Returns whether it succeeded or not. **/ public boolean connectBlocking() throws InterruptedException { connect(); connectLatch.await(); - return conn.isOpen(); + return engine.isOpen(); } + /** + * Initiates the websocket close handshake. This method does not block
+ * In oder to make sure the connection is closed use closeBlocking + */ public void close() { - if( writethread != null ) { - conn.close( CloseFrame.NORMAL ); + if( writeThread != null ) { + engine.close( CloseFrame.NORMAL ); } } @@ -162,102 +136,64 @@ public void closeBlocking() throws InterruptedException { } /** - * Sends text to the connected WebSocket server. + * Sends text to the connected websocket server. * * @param text - * The String to send to the WebSocket server. + * The string which will be transmitted. */ public void send( String text ) throws NotYetConnectedException { - conn.send( text ); - } - - public void sendFragment( Framedata f ) { - conn.sendFrame( f ); + engine.send( text ); } /** - * Sends data to the connected WebSocket server. + * Sends binary data to the connected webSocket server. * * @param data - * The Byte-Array of data to send to the WebSocket server. + * The byte-Array of data to send to the WebSocket server. */ public void send( byte[] data ) throws NotYetConnectedException { - conn.send( data ); + engine.send( data ); } - // Runnable IMPLEMENTATION ///////////////////////////////////////////////// public void run() { - if( writethread == null ) - writethread = Thread.currentThread(); - interruptableRun(); - - assert ( !channel.isOpen() ); - - } - - private final void interruptableRun() { - if( channel == null ) { - return;// channel will be initialized in the constructor and only be null if no socket channel could be created or if blocking mode could be established - } - try { - String host; - int port; - - if( proxyAddress != null ) { - host = proxyAddress.getHostName(); - port = proxyAddress.getPort(); - } else { - host = uri.getHost(); - port = getPort(); + if( socket == null ) { + socket = new Socket( proxy ); + } else if( socket.isClosed() ) { + throw new IOException(); } - channel.connect( new InetSocketAddress( host, port ) ); - conn.channel = wrappedchannel = createProxyChannel( wsfactory.wrapChannel( channel, null, host, port ) ); + if( !socket.isBound() ) + socket.connect( new InetSocketAddress( uri.getHost(), getPort() ), connectTimeout ); + istream = socket.getInputStream(); + ostream = socket.getOutputStream(); - timeout = 0; // since connect is over sendHandshake(); - readthread = new Thread( new WebsocketWriteThread() ); - readthread.start(); - } catch ( ClosedByInterruptException e ) { - onWebsocketError( null, e ); - return; - } catch ( /*IOException | SecurityException | UnresolvedAddressException*/Exception e ) {// - onWebsocketError( conn, e ); - conn.closeConnection( CloseFrame.NEVER_CONNECTED, e.getMessage() ); + } catch ( /*IOException | SecurityException | UnresolvedAddressException | InvalidHandshakeException | ClosedByInterruptException | SocketTimeoutException */Exception e ) { + onWebsocketError( engine, e ); + engine.closeConnection( CloseFrame.NEVER_CONNECTED, e.getMessage() ); return; } - ByteBuffer buff = ByteBuffer.allocate( WebSocketImpl.RCVBUF ); - try/*IO*/{ - while ( channel.isOpen() ) { - if( SocketChannelIOHelper.read( buff, this.conn, wrappedchannel ) ) { - conn.decode( buff ); - } else { - conn.eot(); - } + writeThread = new Thread( new WebsocketWriteThread() ); + writeThread.start(); - if( wrappedchannel instanceof WrappedByteChannel ) { - WrappedByteChannel w = (WrappedByteChannel) wrappedchannel; - if( w.isNeedRead() ) { - while ( SocketChannelIOHelper.readMore( buff, conn, w ) ) { - conn.decode( buff ); - } - conn.decode( buff ); - } - } - } + byte[] rawbuffer = new byte[ WebSocketImpl.RCVBUF ]; + int readBytes; - } catch ( CancelledKeyException e ) { - conn.eot(); + try { + while ( ( readBytes = istream.read( rawbuffer ) ) != -1 ) { + engine.decode( ByteBuffer.wrap( rawbuffer, 0, readBytes ) ); + } + engine.eot(); } catch ( IOException e ) { - conn.eot(); + engine.eot(); } catch ( RuntimeException e ) { // this catch case covers internal errors only and indicates a bug in this websocket implementation onError( e ); - conn.closeConnection( CloseFrame.ABNORMAL_CLOSE, e.getMessage() ); + engine.closeConnection( CloseFrame.ABNORMAL_CLOSE, e.getMessage() ); } + assert ( socket.isClosed() ); } - private int getPort() { int port = uri.getPort(); if( port == -1 ) { @@ -294,22 +230,18 @@ private void sendHandshake() throws InvalidHandshakeException { handshake.put( kv.getKey(), kv.getValue() ); } } - conn.startHandshake( handshake ); + engine.startHandshake( handshake ); } /** * This represents the state of the connection. - * You can use this method instead of */ public READYSTATE getReadyState() { - return conn.getReadyState(); + return engine.getReadyState(); } /** * Calls subclass' implementation of onMessage. - * - * @param conn - * @param message */ @Override public final void onWebsocketMessage( WebSocket conn, String message ) { @@ -328,8 +260,6 @@ public void onWebsocketMessageFragment( WebSocket conn, Framedata frame ) { /** * Calls subclass' implementation of onOpen. - * - * @param conn */ @Override public final void onWebsocketOpen( WebSocket conn, Handshakedata handshake ) { @@ -339,22 +269,18 @@ public final void onWebsocketOpen( WebSocket conn, Handshakedata handshake ) { /** * Calls subclass' implementation of onClose. - * - * @param conn */ @Override public final void onWebsocketClose( WebSocket conn, int code, String reason, boolean remote ) { connectLatch.countDown(); closeLatch.countDown(); - if( readthread != null ) - readthread.interrupt(); + if( writeThread != null ) + writeThread.interrupt(); onClose( code, reason, remote ); } /** * Calls subclass' implementation of onIOError. - * - * @param conn */ @Override public final void onWebsocketError( WebSocket conn, Exception ex ) { @@ -383,28 +309,20 @@ public void onClosing( int code, String reason, boolean remote ) { } public WebSocket getConnection() { - return conn; - } - - public final void setWebSocketFactory( WebSocketClientFactory wsf ) { - this.wsfactory = wsf; - } - - public final WebSocketFactory getWebSocketFactory() { - return wsfactory; + return engine; } @Override public InetSocketAddress getLocalSocketAddress( WebSocket conn ) { - if( channel != null ) - return (InetSocketAddress) channel.socket().getLocalSocketAddress(); + if( socket != null ) + return (InetSocketAddress) socket.getLocalSocketAddress(); return null; } @Override public InetSocketAddress getRemoteSocketAddress( WebSocket conn ) { - if( channel != null ) - return (InetSocketAddress) channel.socket().getLocalSocketAddress(); + if( socket != null ) + return (InetSocketAddress) socket.getLocalSocketAddress(); return null; } @@ -418,123 +336,107 @@ public void onMessage( ByteBuffer bytes ) { public void onFragment( Framedata frame ) { } - public class DefaultClientProxyChannel extends AbstractClientProxyChannel { - public DefaultClientProxyChannel( ByteChannel towrap ) { - super( towrap ); - } - @Override - public String buildHandShake() { - StringBuilder b = new StringBuilder(); - String host = uri.getHost(); - b.append( "CONNECT " ); - b.append( host ); - b.append( ":" ); - b.append( getPort() ); - b.append( " HTTP/1.1\n" ); - b.append( "Host: " ); - b.append( host ); - b.append( "\n" ); - return b.toString(); - } - } - - public interface WebSocketClientFactory extends WebSocketFactory { - public ByteChannel wrapChannel( SocketChannel channel, SelectionKey key, String host, int port ) throws IOException; - } - private class WebsocketWriteThread implements Runnable { @Override public void run() { Thread.currentThread().setName( "WebsocketWriteThread" ); try { while ( !Thread.interrupted() ) { - SocketChannelIOHelper.writeBlocking( conn, wrappedchannel ); + ByteBuffer buffer = engine.outQueue.take(); + ostream.write( buffer.array(), 0, buffer.limit() ); } } catch ( IOException e ) { - conn.eot(); + engine.eot(); } catch ( InterruptedException e ) { // this thread is regularly terminated via an interrupt } } } - public ByteChannel createProxyChannel( ByteChannel towrap ) { - if( proxyAddress != null ) { - return new DefaultClientProxyChannel( towrap ); - } - return towrap;// no proxy in use + public void setProxy( Proxy proxy ) { + if( proxy == null ) + throw new IllegalArgumentException(); + this.proxy = proxy; } - public void setProxy( InetSocketAddress proxyaddress ) { - proxyAddress = proxyaddress; + /** + * Accepts bound and unbound sockets.
+ * This method must be called before connect. + * If the given socket is not yet bound it will be bound to the uri specified in the constructor. + **/ + public void setSocket( Socket socket ) { + if( this.socket != null ) { + throw new IllegalStateException( "socket has already been set" ); + } + this.socket = socket; } @Override public void sendFragmentedFrame( Opcode op, ByteBuffer buffer, boolean fin ) { - conn.sendFragmentedFrame( op, buffer, fin ); + engine.sendFragmentedFrame( op, buffer, fin ); } @Override public boolean isOpen() { - return conn.isOpen(); + return engine.isOpen(); } @Override public boolean isFlushAndClose() { - return conn.isFlushAndClose(); + return engine.isFlushAndClose(); } @Override public boolean isClosed() { - return conn.isClosed(); + return engine.isClosed(); } @Override public boolean isClosing() { - return conn.isClosing(); + return engine.isClosing(); } @Override public boolean isConnecting() { - return conn.isConnecting(); + return engine.isConnecting(); } @Override public boolean hasBufferedData() { - return conn.hasBufferedData(); + return engine.hasBufferedData(); } @Override public void close( int code ) { - conn.close(); + engine.close(); } @Override public void close( int code, String message ) { - conn.close( code, message ); + engine.close( code, message ); } @Override public void closeConnection( int code, String message ) { - conn.closeConnection( code, message ); + engine.closeConnection( code, message ); } @Override public void send( ByteBuffer bytes ) throws IllegalArgumentException , NotYetConnectedException { - conn.send( bytes ); + engine.send( bytes ); } @Override public void sendFrame( Framedata framedata ) { - conn.sendFrame( framedata ); + engine.sendFrame( framedata ); } @Override public InetSocketAddress getLocalSocketAddress() { - return conn.getLocalSocketAddress(); + return engine.getLocalSocketAddress(); } @Override public InetSocketAddress getRemoteSocketAddress() { - return conn.getRemoteSocketAddress(); + return engine.getRemoteSocketAddress(); } } From e927c29eab226e6accd7da602f9105b29aff671f Mon Sep 17 00:00:00 2001 From: Davidiusdadi Date: Wed, 4 Sep 2013 20:39:30 +0200 Subject: [PATCH 64/73] prevented unnecessary attempts to decode of empty buffers --- .../server/WebSocketServer.java | 31 +++++++++++-------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/java_websocket/server/WebSocketServer.java b/src/main/java/org/java_websocket/server/WebSocketServer.java index 6257d7d81..cd8bfc913 100644 --- a/src/main/java/org/java_websocket/server/WebSocketServer.java +++ b/src/main/java/org/java_websocket/server/WebSocketServer.java @@ -326,16 +326,18 @@ public void run() { conn = (WebSocketImpl) key.attachment(); ByteBuffer buf = takeBuffer(); try { - if( SocketChannelIOHelper.read( buf, conn, (ByteChannel) conn.channel ) ) { - assert ( buf.hasRemaining() ); - conn.inQueue.put( buf ); - queue( conn ); - i.remove(); - if( conn.channel instanceof WrappedByteChannel ) { - if( ( (WrappedByteChannel) conn.channel ).isNeedRead() ) { - iqueue.add( conn ); + if( SocketChannelIOHelper.read( buf, conn, conn.channel ) ) { + if( buf.hasRemaining() ) { + conn.inQueue.put( buf ); + queue( conn ); + i.remove(); + if( conn.channel instanceof WrappedByteChannel ) { + if( ( (WrappedByteChannel) conn.channel ).isNeedRead() ) { + iqueue.add( conn ); + } } - } + } else + pushBuffer( buf ); } else { pushBuffer( buf ); } @@ -346,7 +348,7 @@ public void run() { } if( key.isWritable() ) { conn = (WebSocketImpl) key.attachment(); - if( SocketChannelIOHelper.batch( conn, (ByteChannel) conn.channel ) ) { + if( SocketChannelIOHelper.batch( conn, conn.channel ) ) { if( key.isValid() ) key.interestOps( SelectionKey.OP_READ ); } @@ -359,9 +361,12 @@ public void run() { try { if( SocketChannelIOHelper.readMore( buf, conn, c ) ) iqueue.add( conn ); - assert ( buf.hasRemaining() ); - conn.inQueue.put( buf ); - queue( conn ); + if( buf.hasRemaining() ) { + conn.inQueue.put( buf ); + queue( conn ); + } else { + pushBuffer( buf ); + } } catch ( IOException e ) { pushBuffer( buf ); throw e; From 2b0bb8d0e85430a91ae2d6f438e83a9c995ddd9e Mon Sep 17 00:00:00 2001 From: Davidiusdadi Date: Wed, 4 Sep 2013 21:17:01 +0200 Subject: [PATCH 65/73] fixed deadlock in server( #195 ) thanks @DeHecht for isolating the problem --- .../server/WebSocketServer.java | 34 +++++++++++++++---- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/java_websocket/server/WebSocketServer.java b/src/main/java/org/java_websocket/server/WebSocketServer.java index cd8bfc913..38adb067e 100644 --- a/src/main/java/org/java_websocket/server/WebSocketServer.java +++ b/src/main/java/org/java_websocket/server/WebSocketServer.java @@ -35,10 +35,12 @@ import org.java_websocket.WebSocketImpl; import org.java_websocket.WrappedByteChannel; import org.java_websocket.drafts.Draft; +import org.java_websocket.exceptions.InvalidDataException; import org.java_websocket.framing.CloseFrame; import org.java_websocket.framing.Framedata; import org.java_websocket.handshake.ClientHandshake; import org.java_websocket.handshake.Handshakedata; +import org.java_websocket.handshake.ServerHandshakeBuilder; /** * WebSocketServer is an abstract class that only takes care of the @@ -200,15 +202,21 @@ public void start() { * @throws InterruptedException */ public void stop( int timeout ) throws IOException , InterruptedException { - if( !isclosed.compareAndSet( false, true ) ) { + if( !isclosed.compareAndSet( false, true ) ) { // this also makes sure that no further connections will be added to this.connections return; } + List socketsToClose = null; + + // copy the connections in a list (prevent callback deadlocks) synchronized ( connections ) { - for( WebSocket ws : connections ) { - ws.close( CloseFrame.GOING_AWAY ); - } + socketsToClose = new ArrayList( connections ); + } + + for( WebSocket ws : socketsToClose ) { + ws.close( CloseFrame.GOING_AWAY ); } + synchronized ( this ) { if( selectorthread != null ) { if( Thread.currentThread() != selectorthread ) { @@ -525,13 +533,25 @@ protected boolean removeConnection( WebSocket ws ) { } } + @Override + public ServerHandshakeBuilder onWebsocketHandshakeReceivedAsServer( WebSocket conn, Draft draft, ClientHandshake request ) throws InvalidDataException { + return super.onWebsocketHandshakeReceivedAsServer( conn, draft, request ); + } + /** @see #removeConnection(WebSocket) */ protected boolean addConnection( WebSocket ws ) { - synchronized ( connections ) { - return this.connections.add( ws ); + if( isclosed.get() ) { + synchronized ( connections ) { + boolean succ = this.connections.add( ws ); + assert ( succ ); + return succ; + } + } else { + // This case will happen when a new connection gets ready while the server is already stopping. + ws.close( CloseFrame.GOING_AWAY ); + return true;// for consistency sake we will make sure that both onOpen will be called } } - /** * @param conn * may be null if the error does not belong to a single connection From dbad4a276d6d8e1537b58f7c0256e24765db1b3e Mon Sep 17 00:00:00 2001 From: Davidiusdadi Date: Wed, 4 Sep 2013 23:12:26 +0200 Subject: [PATCH 66/73] tweaked WebsocketServer.stop to perform a graceful shutdown --- .../server/WebSocketServer.java | 41 ++++++++++++------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/src/main/java/org/java_websocket/server/WebSocketServer.java b/src/main/java/org/java_websocket/server/WebSocketServer.java index 38adb067e..a45f7e133 100644 --- a/src/main/java/org/java_websocket/server/WebSocketServer.java +++ b/src/main/java/org/java_websocket/server/WebSocketServer.java @@ -195,13 +195,13 @@ public void start() { * If this method is called before the server is started it will never start. * * @param timeout - * Specifies how many milliseconds shall pass between initiating the close handshakes with the connected clients and closing the servers socket channel. + * Specifies how many milliseconds the overall close handshaking may take altogether before the connections are closed without proper close handshaking.
* * @throws IOException * When {@link ServerSocketChannel}.close throws an IOException * @throws InterruptedException */ - public void stop( int timeout ) throws IOException , InterruptedException { + public void stop( int timeout ) throws InterruptedException { if( !isclosed.compareAndSet( false, true ) ) { // this also makes sure that no further connections will be added to this.connections return; } @@ -223,21 +223,14 @@ public void stop( int timeout ) throws IOException , InterruptedException { } if( selectorthread != Thread.currentThread() ) { - selectorthread.interrupt(); + if( socketsToClose.size() > 0 ) + selectorthread.join( timeout );// isclosed will tell the selectorthread to go down after the last connection was closed + selectorthread.interrupt();// in case the selectorthread did not terminate in time we send the interrupt selectorthread.join(); } } - if( decoders != null ) { - for( WebSocketWorker w : decoders ) { - w.interrupt(); - } - } - if( server != null ) { - server.close(); - } } } - public void stop() throws IOException , InterruptedException { stop( 0 ); } @@ -397,6 +390,19 @@ public void run() { } catch ( RuntimeException e ) { // should hopefully never occur handleFatal( null, e ); + } finally { + if( decoders != null ) { + for( WebSocketWorker w : decoders ) { + w.interrupt(); + } + } + if( server != null ) { + try { + server.close(); + } catch ( IOException e ) { + onError( null, e ); + } + } } } protected void allocateBuffers( WebSocket c ) throws InterruptedException { @@ -528,11 +534,16 @@ public final void onWebsocketClose( WebSocket conn, int code, String reason, boo * Depending on the type on the connection, modifications of that collection may have to be synchronized. **/ protected boolean removeConnection( WebSocket ws ) { + boolean removed; synchronized ( connections ) { - return this.connections.remove( ws ); + removed = this.connections.remove( ws ); + assert ( removed ); } + if( isclosed.get() && connections.size() == 0 ) { + selectorthread.interrupt(); + } + return removed; } - @Override public ServerHandshakeBuilder onWebsocketHandshakeReceivedAsServer( WebSocket conn, Draft draft, ClientHandshake request ) throws InvalidDataException { return super.onWebsocketHandshakeReceivedAsServer( conn, draft, request ); @@ -540,7 +551,7 @@ public ServerHandshakeBuilder onWebsocketHandshakeReceivedAsServer( WebSocket co /** @see #removeConnection(WebSocket) */ protected boolean addConnection( WebSocket ws ) { - if( isclosed.get() ) { + if( !isclosed.get() ) { synchronized ( connections ) { boolean succ = this.connections.add( ws ); assert ( succ ); From f1cf98491e378b6ecdfb92bb37e3cd43f57dbfa3 Mon Sep 17 00:00:00 2001 From: Davidiusdadi Date: Wed, 4 Sep 2013 23:17:10 +0200 Subject: [PATCH 67/73] -made socket output stream gets flushed after every frame --- src/main/java/org/java_websocket/client/WebSocketClient.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/java_websocket/client/WebSocketClient.java b/src/main/java/org/java_websocket/client/WebSocketClient.java index 635cb17f8..b21225f0a 100644 --- a/src/main/java/org/java_websocket/client/WebSocketClient.java +++ b/src/main/java/org/java_websocket/client/WebSocketClient.java @@ -344,6 +344,7 @@ public void run() { while ( !Thread.interrupted() ) { ByteBuffer buffer = engine.outQueue.take(); ostream.write( buffer.array(), 0, buffer.limit() ); + ostream.flush(); } } catch ( IOException e ) { engine.eot(); From de9955afa8e68be7f2f265bfea86178f8c19b1e8 Mon Sep 17 00:00:00 2001 From: Davidiusdadi Date: Wed, 4 Sep 2013 23:21:40 +0200 Subject: [PATCH 68/73] -made close frames require a close frame in response before closing the underlying socket --- .../org/java_websocket/SocketChannelIOHelper.java | 4 ++-- src/main/java/org/java_websocket/WebSocketImpl.java | 12 ++---------- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/java_websocket/SocketChannelIOHelper.java b/src/main/java/org/java_websocket/SocketChannelIOHelper.java index 9752098c4..379aaf014 100644 --- a/src/main/java/org/java_websocket/SocketChannelIOHelper.java +++ b/src/main/java/org/java_websocket/SocketChannelIOHelper.java @@ -59,11 +59,11 @@ public static boolean batch( WebSocketImpl ws, ByteChannel sockchannel ) throws } while ( buffer != null ); } - if( ws.outQueue.isEmpty() && ws.isFlushAndClose() /*&& ( c == null || c.isNeedWrite() )*/) { + /*if( ws.outQueue.isEmpty() && ws.isFlushAndClose() ) {// synchronized ( ws ) { ws.closeConnection(); } - } + }*/ return c != null ? !( (WrappedByteChannel) sockchannel ).isNeedWrite() : true; } diff --git a/src/main/java/org/java_websocket/WebSocketImpl.java b/src/main/java/org/java_websocket/WebSocketImpl.java index 7bb7e8891..1780627a3 100644 --- a/src/main/java/org/java_websocket/WebSocketImpl.java +++ b/src/main/java/org/java_websocket/WebSocketImpl.java @@ -149,15 +149,11 @@ public WebSocketImpl( WebSocketListener listener , List drafts , Socket s public void decode( ByteBuffer socketBuffer ) { assert ( socketBuffer.hasRemaining() ); - if( flushandclosestate ) { - return; - } - if( DEBUG ) System.out.println( "process(" + socketBuffer.remaining() + "): {" + ( socketBuffer.remaining() > 1000 ? "too big to display" : new String( socketBuffer.array(), socketBuffer.position(), socketBuffer.remaining() ) ) + "}" ); - if( readystate == READYSTATE.OPEN ) { - decodeFrames( socketBuffer ); + if( readystate != READYSTATE.NOT_YET_CONNECTED ) { + decodeFrames( socketBuffer );; } else { if( decodeHandshake( socketBuffer ) ) { assert ( tmpHandshakeBytes.hasRemaining() != socketBuffer.hasRemaining() || !socketBuffer.hasRemaining() ); // the buffers will never have remaining bytes at the same time @@ -312,8 +308,6 @@ private boolean decodeHandshake( ByteBuffer socketBufferNew ) { } private void decodeFrames( ByteBuffer socketBuffer ) { - if( flushandclosestate ) - return; List frames; try { @@ -321,8 +315,6 @@ private void decodeFrames( ByteBuffer socketBuffer ) { for( Framedata f : frames ) { if( DEBUG ) System.out.println( "matched frame: " + f ); - if( flushandclosestate ) - return; Opcode curop = f.getOpcode(); boolean fin = f.isFin(); From 2c6f21d23afeb4fc7bd2effb666e11726b4193cf Mon Sep 17 00:00:00 2001 From: Davidiusdadi Date: Fri, 20 Sep 2013 01:14:18 +0200 Subject: [PATCH 69/73] added missing method getResourceDescriptor --- src/main/java/org/java_websocket/client/WebSocketClient.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/org/java_websocket/client/WebSocketClient.java b/src/main/java/org/java_websocket/client/WebSocketClient.java index 1aeb64688..7c5c90241 100644 --- a/src/main/java/org/java_websocket/client/WebSocketClient.java +++ b/src/main/java/org/java_websocket/client/WebSocketClient.java @@ -537,4 +537,9 @@ public InetSocketAddress getLocalSocketAddress() { public InetSocketAddress getRemoteSocketAddress() { return conn.getRemoteSocketAddress(); } + + @Override + public String getResourceDescriptor() { + return uri.getPath(); + } } From aef92290adbd53850337cfc2c651699764988038 Mon Sep 17 00:00:00 2001 From: Davidiusdadi Date: Fri, 20 Sep 2013 02:05:13 +0200 Subject: [PATCH 70/73] fixed npe cause by racing condition #205 --- .../org/java_websocket/WebSocketAdapter.java | 20 ++++++++++++++++--- .../org/java_websocket/WebSocketImpl.java | 8 ++++++-- .../org/java_websocket/WebSocketListener.java | 3 ++- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/java_websocket/WebSocketAdapter.java b/src/main/java/org/java_websocket/WebSocketAdapter.java index 31a48ab16..290e1049a 100644 --- a/src/main/java/org/java_websocket/WebSocketAdapter.java +++ b/src/main/java/org/java_websocket/WebSocketAdapter.java @@ -1,7 +1,10 @@ package org.java_websocket; +import java.net.InetSocketAddress; + import org.java_websocket.drafts.Draft; import org.java_websocket.exceptions.InvalidDataException; +import org.java_websocket.exceptions.InvalidHandshakeException; import org.java_websocket.framing.Framedata; import org.java_websocket.framing.Framedata.Opcode; import org.java_websocket.framing.FramedataImpl1; @@ -79,12 +82,23 @@ public void onWebsocketPong( WebSocket conn, Framedata f ) { * This is specifically implemented for gitime's WebSocket client for Flash: * http://github.com/gimite/web-socket-js * - * @return An XML String that comforms to Flash's security policy. You MUST + * @return An XML String that comforts to Flash's security policy. You MUST * not include the null char at the end, it is appended automatically. + * @throws InvalidDataException thrown when some data that is required to generate the flash-policy like the websocket local port could not be obtained e.g because the websocket is not connected. */ @Override - public String getFlashPolicy( WebSocket conn ) { - return "\0"; + public String getFlashPolicy( WebSocket conn ) throws InvalidDataException { + InetSocketAddress adr = conn.getLocalSocketAddress(); + if(null == adr){ + throw new InvalidHandshakeException( "socket not bound" ); + } + + StringBuffer sb = new StringBuffer( 90 ); + sb.append( "\0" ); + + return sb.toString(); } } diff --git a/src/main/java/org/java_websocket/WebSocketImpl.java b/src/main/java/org/java_websocket/WebSocketImpl.java index 44a8fc971..1d8f0f892 100644 --- a/src/main/java/org/java_websocket/WebSocketImpl.java +++ b/src/main/java/org/java_websocket/WebSocketImpl.java @@ -198,8 +198,12 @@ private boolean decodeHandshake( ByteBuffer socketBufferNew ) { if( draft == null ) { HandshakeState isflashedgecase = isFlashEdgeCase( socketBuffer ); if( isflashedgecase == HandshakeState.MATCHED ) { - write( ByteBuffer.wrap( Charsetfunctions.utf8Bytes( wsl.getFlashPolicy( this ) ) ) ); - close( CloseFrame.FLASHPOLICY, "" ); + try { + write( ByteBuffer.wrap( Charsetfunctions.utf8Bytes( wsl.getFlashPolicy( this ) ) ) ); + close( CloseFrame.FLASHPOLICY, "" ); + } catch ( InvalidDataException e ) { + close( CloseFrame.ABNORMAL_CLOSE, "remote peer closed connection before flashpolicy could be transmitted", true ); + } return false; } } diff --git a/src/main/java/org/java_websocket/WebSocketListener.java b/src/main/java/org/java_websocket/WebSocketListener.java index 4b35f245c..93478d940 100644 --- a/src/main/java/org/java_websocket/WebSocketListener.java +++ b/src/main/java/org/java_websocket/WebSocketListener.java @@ -139,8 +139,9 @@ public interface WebSocketListener { /** * Gets the XML string that should be returned if a client requests a Flash * security policy. + * @throws InvalidDataException thrown when some data that is required to generate the flash-policy like the websocket local port could not be obtained. */ - public String getFlashPolicy( WebSocket conn ); + public String getFlashPolicy( WebSocket conn ) throws InvalidDataException; /** This method is used to inform the selector thread that there is data queued to be written to the socket. */ public void onWriteDemand( WebSocket conn ); From 15f49ed574130bc1047abf009adb79f5aa50d939 Mon Sep 17 00:00:00 2001 From: Davidiusdadi Date: Mon, 23 Sep 2013 02:06:26 +0200 Subject: [PATCH 71/73] fixed websocket client socket not being closed in some cases and server waiting for clients to close connection first --- .../java_websocket/SocketChannelIOHelper.java | 16 ++++------------ .../java_websocket/client/WebSocketClient.java | 8 +++++++- .../java/org/java_websocket/drafts/Draft.java | 4 ++++ 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/java_websocket/SocketChannelIOHelper.java b/src/main/java/org/java_websocket/SocketChannelIOHelper.java index 379aaf014..e0da2bdc3 100644 --- a/src/main/java/org/java_websocket/SocketChannelIOHelper.java +++ b/src/main/java/org/java_websocket/SocketChannelIOHelper.java @@ -5,6 +5,8 @@ import java.nio.channels.ByteChannel; import java.nio.channels.spi.AbstractSelectableChannel; +import org.java_websocket.WebSocket.Role; + public class SocketChannelIOHelper { public static boolean read( final ByteBuffer buf, WebSocketImpl ws, ByteChannel channel ) throws IOException { @@ -59,21 +61,11 @@ public static boolean batch( WebSocketImpl ws, ByteChannel sockchannel ) throws } while ( buffer != null ); } - /*if( ws.outQueue.isEmpty() && ws.isFlushAndClose() ) {// + if( ws.outQueue.isEmpty() && ws.isFlushAndClose() && ws.getDraft().getRole() == Role.SERVER ) {// synchronized ( ws ) { ws.closeConnection(); } - }*/ + } return c != null ? !( (WrappedByteChannel) sockchannel ).isNeedWrite() : true; } - - public static void writeBlocking( WebSocketImpl ws, ByteChannel channel ) throws InterruptedException , IOException { - assert ( channel instanceof AbstractSelectableChannel == true ? ( (AbstractSelectableChannel) channel ).isBlocking() : true ); - assert ( channel instanceof WrappedByteChannel == true ? ( (WrappedByteChannel) channel ).isBlocking() : true ); - - ByteBuffer buf = ws.outQueue.take(); - while ( buf.hasRemaining() ) - channel.write( buf ); - } - } diff --git a/src/main/java/org/java_websocket/client/WebSocketClient.java b/src/main/java/org/java_websocket/client/WebSocketClient.java index e7e4f6c8d..13d7a9d2c 100644 --- a/src/main/java/org/java_websocket/client/WebSocketClient.java +++ b/src/main/java/org/java_websocket/client/WebSocketClient.java @@ -181,7 +181,7 @@ public void run() { int readBytes; try { - while ( ( readBytes = istream.read( rawbuffer ) ) != -1 ) { + while ( !isClosed() && ( readBytes = istream.read( rawbuffer ) ) != -1 ) { engine.decode( ByteBuffer.wrap( rawbuffer, 0, readBytes ) ); } engine.eot(); @@ -276,6 +276,12 @@ public final void onWebsocketClose( WebSocket conn, int code, String reason, boo closeLatch.countDown(); if( writeThread != null ) writeThread.interrupt(); + try { + if( socket != null ) + socket.close(); + } catch ( IOException e ) { + onWebsocketError( this, e ); + } onClose( code, reason, remote ); } diff --git a/src/main/java/org/java_websocket/drafts/Draft.java b/src/main/java/org/java_websocket/drafts/Draft.java index 1bad2ad7b..65b34de8f 100644 --- a/src/main/java/org/java_websocket/drafts/Draft.java +++ b/src/main/java/org/java_websocket/drafts/Draft.java @@ -220,5 +220,9 @@ public int checkAlloc( int bytecount ) throws LimitExedeedException , InvalidDat public void setParseMode( Role role ) { this.role = role; } + + public Role getRole() { + return role; + } } From 782b31c862c1c3a0cf67d9231361bf8b103c5a83 Mon Sep 17 00:00:00 2001 From: Jeff Williams Date: Fri, 4 Oct 2013 18:27:58 -0700 Subject: [PATCH 72/73] updates that include a new interface (& name) --- src/main/example/ChatClient.java | 3 +- src/main/example/ServerStressTest.java | 3 +- .../client/IWebSocketClient.java | 85 +++++++++++++++++++ .../client/WebSocketClient.java | 21 ++++- 4 files changed, 109 insertions(+), 3 deletions(-) create mode 100644 src/main/java/org/java_websocket/client/IWebSocketClient.java diff --git a/src/main/example/ChatClient.java b/src/main/example/ChatClient.java index ee2bdbe24..1c0afedfb 100644 --- a/src/main/example/ChatClient.java +++ b/src/main/example/ChatClient.java @@ -14,6 +14,7 @@ import javax.swing.JTextField; import org.java_websocket.WebSocket; +import org.java_websocket.client.IWebSocketClient; import org.java_websocket.client.WebSocketClient; import org.java_websocket.drafts.Draft; import org.java_websocket.drafts.Draft_10; @@ -31,7 +32,7 @@ public class ChatClient extends JFrame implements ActionListener { private final JTextArea ta; private final JTextField chatField; private final JComboBox draft; - private WebSocketClient cc; + private IWebSocketClient cc; public ChatClient( String defaultlocation ) { super( "WebSocket Chat Client" ); diff --git a/src/main/example/ServerStressTest.java b/src/main/example/ServerStressTest.java index 8fe2e97a4..03512b813 100644 --- a/src/main/example/ServerStressTest.java +++ b/src/main/example/ServerStressTest.java @@ -21,6 +21,7 @@ import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; +import org.java_websocket.client.IWebSocketClient; import org.java_websocket.client.WebSocketClient; public class ServerStressTest extends JFrame { @@ -197,7 +198,7 @@ public void send() { String payload = text.getText(); long time1 = System.currentTimeMillis(); synchronized ( websockets ) { - for( WebSocketClient cl : websockets ) { + for( IWebSocketClient cl : websockets ) { try { cl.send( payload ); } catch ( NotYetConnectedException e ) { diff --git a/src/main/java/org/java_websocket/client/IWebSocketClient.java b/src/main/java/org/java_websocket/client/IWebSocketClient.java new file mode 100644 index 000000000..4a83a79ad --- /dev/null +++ b/src/main/java/org/java_websocket/client/IWebSocketClient.java @@ -0,0 +1,85 @@ +package org.java_websocket.client; + +import java.net.URI; +import java.nio.ByteBuffer; +import java.nio.channels.NotYetConnectedException; + +import org.java_websocket.IWebSocket; +import org.java_websocket.WebSocketFactory; +import org.java_websocket.IWebSocket.READYSTATE; +import org.java_websocket.client.WebSocketClient.WebSocketClientFactory; +import org.java_websocket.drafts.Draft; +import org.java_websocket.handshake.ServerHandshake; + +public interface IWebSocketClient { + + /** + * Gets the URI that this WebSocketClient is connected to. + * + * @return The URI for this WebSocketClient. + */ + public abstract URI getURI(); + + /** Returns the protocol version this channel uses. */ + public abstract Draft getDraft(); + + /** + * Starts a background thread that attempts and maintains a WebSocket + * connection to the URI specified in the constructor or via setURI. + * setURI. + */ + public abstract void connect(); + + /** + * Same as connect but blocks until the websocket connected or failed to do so.
+ * Returns whether it succeeded or not. + **/ + public abstract boolean connectBlocking() throws InterruptedException; + + public abstract void close(); + + public abstract void closeBlocking() throws InterruptedException; + + /** + * Sends text to the connected WebSocket server. + * + * @param text + * The String to send to the WebSocket server. + */ + public abstract void send(String text) throws NotYetConnectedException; + + /** + * Sends data to the connected WebSocket server. + * + * @param data + * The Byte-Array of data to send to the WebSocket server. + */ + public abstract void send(byte[] data) throws NotYetConnectedException; + + /** + * This represents the state of the connection. + * You can use this method instead of + */ + public abstract READYSTATE getReadyState(); + + public abstract void onCloseInitiated(int code, String reason); + + public abstract void onClosing(int code, String reason, boolean remote); + + public abstract IWebSocket getConnection(); + + public abstract void setWebSocketFactory(WebSocketClientFactory wsf); + + public abstract WebSocketFactory getWebSocketFactory(); + + public abstract void onOpen(ServerHandshake handshakedata); + + public abstract void onMessage(String message); + + public abstract void onClose(int code, String reason, boolean remote); + + public abstract void onError(Exception ex); + + public abstract void onMessage(ByteBuffer bytes); + +} \ No newline at end of file diff --git a/src/main/java/org/java_websocket/client/WebSocketClient.java b/src/main/java/org/java_websocket/client/WebSocketClient.java index dda308849..3a1bc8648 100644 --- a/src/main/java/org/java_websocket/client/WebSocketClient.java +++ b/src/main/java/org/java_websocket/client/WebSocketClient.java @@ -44,7 +44,7 @@ * * @author Nathan Rajlich */ -public abstract class WebSocketClient extends WebSocketAdapter implements Runnable { +public abstract class WebSocketClient extends WebSocketAdapter implements Runnable, IWebSocketClient { /** * The URI this channel is supposed to connect to. @@ -125,11 +125,13 @@ public WebSocketClient( URI serverUri , Draft draft , Map headers * * @return The URI for this WebSocketClient. */ + @Override public URI getURI() { return uri; } /** Returns the protocol version this channel uses. */ + @Override public Draft getDraft() { return draft; } @@ -139,6 +141,7 @@ public Draft getDraft() { * connection to the URI specified in the constructor or via setURI. * setURI. */ + @Override public void connect() { if( thread != null ) throw new IllegalStateException( "WebSocketClient objects are not reuseable" ); @@ -150,18 +153,21 @@ public void connect() { * Same as connect but blocks until the websocket connected or failed to do so.
* Returns whether it succeeded or not. **/ + @Override public boolean connectBlocking() throws InterruptedException { connect(); connectLatch.await(); return conn.isOpen(); } + @Override public void close() { if( thread != null && conn != null ) { conn.close( CloseFrame.NORMAL ); } } + @Override public void closeBlocking() throws InterruptedException { close(); closeLatch.await(); @@ -173,6 +179,7 @@ public void closeBlocking() throws InterruptedException { * @param text * The String to send to the WebSocket server. */ + @Override public void send( String text ) throws NotYetConnectedException { if( conn != null ) { conn.send( text ); @@ -185,6 +192,7 @@ public void send( String text ) throws NotYetConnectedException { * @param data * The Byte-Array of data to send to the WebSocket server. */ + @Override public void send( byte[] data ) throws NotYetConnectedException { if( conn != null ) { conn.send( data ); @@ -342,6 +350,7 @@ private void sendHandshake() throws InvalidHandshakeException { * This represents the state of the connection. * You can use this method instead of */ + @Override public READYSTATE getReadyState() { if( conn == null ) { return READYSTATE.NOT_YET_CONNECTED; @@ -418,29 +427,39 @@ public void onWebsocketClosing( IWebSocket conn, int code, String reason, boolea onClosing( code, reason, remote ); } + @Override public void onCloseInitiated( int code, String reason ) { } + @Override public void onClosing( int code, String reason, boolean remote ) { } + @Override public IWebSocket getConnection() { return conn; } + @Override public final void setWebSocketFactory( WebSocketClientFactory wsf ) { this.wf = wsf; } + @Override public final WebSocketFactory getWebSocketFactory() { return wf; } // ABTRACT METHODS ///////////////////////////////////////////////////////// + @Override public abstract void onOpen( ServerHandshake handshakedata ); + @Override public abstract void onMessage( String message ); + @Override public abstract void onClose( int code, String reason, boolean remote ); + @Override public abstract void onError( Exception ex ); + @Override public void onMessage( ByteBuffer bytes ) { }; From d964307fa0c7deea6f93a681f5b1db3812f30370 Mon Sep 17 00:00:00 2001 From: Ramki Date: Sat, 19 Oct 2013 00:05:46 +0530 Subject: [PATCH 73/73] Fix WebSocketClient.getRemoteSocketAddress --- src/main/java/org/java_websocket/client/WebSocketClient.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/java_websocket/client/WebSocketClient.java b/src/main/java/org/java_websocket/client/WebSocketClient.java index 13d7a9d2c..86eff7947 100644 --- a/src/main/java/org/java_websocket/client/WebSocketClient.java +++ b/src/main/java/org/java_websocket/client/WebSocketClient.java @@ -328,7 +328,7 @@ public InetSocketAddress getLocalSocketAddress( WebSocket conn ) { @Override public InetSocketAddress getRemoteSocketAddress( WebSocket conn ) { if( socket != null ) - return (InetSocketAddress) socket.getLocalSocketAddress(); + return (InetSocketAddress) socket.getRemoteSocketAddress(); return null; }