diff --git a/src/main/java/org/java_websocket/SocketChannelIOHelper.java b/src/main/java/org/java_websocket/SocketChannelIOHelper.java index e0da2bdc3..9ef18d355 100644 --- a/src/main/java/org/java_websocket/SocketChannelIOHelper.java +++ b/src/main/java/org/java_websocket/SocketChannelIOHelper.java @@ -61,7 +61,7 @@ public static boolean batch( WebSocketImpl ws, ByteChannel sockchannel ) throws } while ( buffer != null ); } - if( ws.outQueue.isEmpty() && ws.isFlushAndClose() && ws.getDraft().getRole() == Role.SERVER ) {// + if( ws.outQueue.isEmpty() && ws.isFlushAndClose() && ws.getRole() == Role.SERVER ) {// synchronized ( ws ) { ws.closeConnection(); } diff --git a/src/main/java/org/java_websocket/WebSocketAdapter.java b/src/main/java/org/java_websocket/WebSocketAdapter.java index 290e1049a..0150e1af0 100644 --- a/src/main/java/org/java_websocket/WebSocketAdapter.java +++ b/src/main/java/org/java_websocket/WebSocketAdapter.java @@ -32,6 +32,16 @@ public ServerHandshakeBuilder onWebsocketHandshakeReceivedAsServer( WebSocket co public void onWebsocketHandshakeReceivedAsClient( WebSocket conn, ClientHandshake request, ServerHandshake response ) throws InvalidDataException { } + /** + * This default implementation does not do anything which will cause the connections to always progress. + * + * @see org.java_websocket.WebSocketListener#onWebsocketHandshakeReceivedAsClientFailed(WebSocket, ClientHandshake, ServerHandshake) + */ + @Override + public void onWebsocketHandshakeReceivedAsClientFailed( WebSocket conn, ClientHandshake request, ServerHandshake response ) { + + } + /** * This default implementation does not do anything which will cause the connections to always progress. * diff --git a/src/main/java/org/java_websocket/WebSocketImpl.java b/src/main/java/org/java_websocket/WebSocketImpl.java index 669bee146..bf4a2f7ea 100644 --- a/src/main/java/org/java_websocket/WebSocketImpl.java +++ b/src/main/java/org/java_websocket/WebSocketImpl.java @@ -88,7 +88,7 @@ public class WebSocketImpl implements WebSocket { private Draft draft = null; - private Role role; + private final Role role; private Opcode current_continuous_frame_opcode = null; @@ -104,13 +104,42 @@ public class WebSocketImpl implements WebSocket { private String resourceDescriptor = null; + private static BlockingQueue createQueue(){ + return new LinkedBlockingQueue(); + } + /** * crates a websocket with server role + * @param listener Listener + * @param drafts Known drafts (may be null to use default drafts) + */ + public static WebSocketImpl createServer( WebSocketListener listener , List drafts ) { + return new WebSocketImpl( listener, drafts ); + } + + /** + * crates a websocket with client role + * + * @param @param listener Listener + * @param draft Draft to use + */ + public static WebSocketImpl createClient( WebSocketListener listener , Draft draft ) { + return new WebSocketImpl( listener, draft ); + } + + /** + * Constructor for a websocket with server role + * @param listener Listener + * @param drafts Known drafts (may be null to use default drafts) */ - 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 + private WebSocketImpl( WebSocketListener listener , List drafts ) { + if(null == listener){ + throw new IllegalArgumentException( "listener must not be null" ); + } + role = Role.SERVER; + wsl = listener; + outQueue = createQueue(); + inQueue = createQueue(); if( drafts == null || drafts.isEmpty() ) { knownDrafts = defaultdraftlist; } else { @@ -119,20 +148,23 @@ public WebSocketImpl( WebSocketListener listener , List drafts ) { } /** - * crates a websocket with client role + * Constructor for a websocket with client role * - * @param socket - * may be unbound + * @param @param listener Listener + * @param draft Draft to use */ - 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(); - this.wsl = listener; - this.role = Role.CLIENT; - if( draft != null ) - this.draft = draft.copyInstance(); + private WebSocketImpl( WebSocketListener listener , Draft draft ) { + if(null == listener){ + throw new IllegalArgumentException( "listener must not be null" ); + } + if(null == draft){ + throw new IllegalArgumentException( "draft must not be null" ); + } + role = Role.CLIENT; + wsl = listener; + outQueue = createQueue(); + inQueue = createQueue(); + this.draft = draft.copyInstance(); } @Deprecated @@ -221,6 +253,7 @@ private boolean decodeHandshake( ByteBuffer socketBufferNew ) { ClientHandshake handshake = (ClientHandshake) tmphandshake; handshakestate = d.acceptHandshakeAsServer( handshake ); if( handshakestate == HandshakeState.MATCHED ) { + draft = d; resourceDescriptor = handshake.getResourceDescriptor(); ServerHandshakeBuilder response; try { @@ -234,7 +267,6 @@ private boolean decodeHandshake( ByteBuffer socketBufferNew ) { return false; } write( d.createHandshake( d.postProcessHandshakeResponseAsServer( handshake, response ), role ) ); - draft = d; open( handshake ); return true; } @@ -287,6 +319,16 @@ private boolean decodeHandshake( ByteBuffer socketBufferNew ) { open( handshake ); return true; } else { + if (handshake.getHttpStatus() != 101) { + // if HTTP-status is not 101, let client get a chance to obtain the HTTP response + // and take action upon that as per regular HTTP procedures. + // (http://tools.ietf.org/html/rfc6455#section-4.1) + try { + wsl.onWebsocketHandshakeReceivedAsClientFailed(this, handshakerequest, handshake); + } catch (RuntimeException e) { + // do nothing, fall through and close socket + } + } close( CloseFrame.PROTOCOL_ERROR, "draft " + draft + " refuses handshake" ); } } @@ -719,6 +761,10 @@ public InetSocketAddress getLocalSocketAddress() { return wsl.getLocalSocketAddress( this ); } + Role getRole() { + return role; + } + @Override public Draft getDraft() { return draft; diff --git a/src/main/java/org/java_websocket/WebSocketListener.java b/src/main/java/org/java_websocket/WebSocketListener.java index 93478d940..26d0a9c87 100644 --- a/src/main/java/org/java_websocket/WebSocketListener.java +++ b/src/main/java/org/java_websocket/WebSocketListener.java @@ -50,6 +50,18 @@ public interface WebSocketListener { */ public void onWebsocketHandshakeReceivedAsClient( WebSocket conn, ClientHandshake request, ServerHandshake response ) throws InvalidDataException; + /** + * Called on the client side when the socket connection is about to be established, but failed due to the handshake + * could not be verified. E.g. the server rejected the request with a regular HTTP response, and the client + * can then use this callback as a way of obtain that HTTP response, e.g. status code. + * + * @param conn The WebSocket related to this event + * @param request The handshake initially send out to the server by this websocket. + * @param response The handshake the server sent in response to the request. + */ + public void onWebsocketHandshakeReceivedAsClientFailed(WebSocket conn, ClientHandshake request, ServerHandshake response); + + /** * Called on the client side when the socket connection is first established, and the WebSocketImpl * handshake has just been sent. diff --git a/src/main/java/org/java_websocket/client/WebSocketClient.java b/src/main/java/org/java_websocket/client/WebSocketClient.java index 86eff7947..e7f28c737 100644 --- a/src/main/java/org/java_websocket/client/WebSocketClient.java +++ b/src/main/java/org/java_websocket/client/WebSocketClient.java @@ -3,6 +3,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.lang.ref.WeakReference; import java.net.InetSocketAddress; import java.net.Proxy; import java.net.Socket; @@ -46,7 +47,8 @@ public abstract class WebSocketClient extends WebSocketAdapter implements Runnab private Proxy proxy = Proxy.NO_PROXY; - private Thread writeThread; + private WeakReference readThread; + private WeakReference writeThread; private Draft draft; @@ -57,7 +59,7 @@ public abstract class WebSocketClient extends WebSocketAdapter implements Runnab private CountDownLatch closeLatch = new CountDownLatch( 1 ); private int connectTimeout = 0; - + /** This open a websocket connection as specified by rfc6455 */ public WebSocketClient( URI serverURI ) { this( serverURI, new Draft_17() ); @@ -82,7 +84,7 @@ public WebSocketClient( URI serverUri , Draft protocolDraft , Map this.draft = protocolDraft; this.headers = httpHeaders; this.connectTimeout = connectTimeout; - this.engine = new WebSocketImpl( this, protocolDraft ); + this.engine = WebSocketImpl.createClient( this , protocolDraft ); } /** @@ -104,10 +106,12 @@ public Draft getDraft() { * Initiates the websocket connection. This method does not block. */ public void connect() { - if( writeThread != null ) + if( readThread != null ) throw new IllegalStateException( "WebSocketClient objects are not reuseable" ); - writeThread = new Thread( this ); - writeThread.start(); + + Thread reader = new Thread(this, "WSC Read"); + reader.start(); + readThread = new WeakReference(reader) ; } /** @@ -174,8 +178,9 @@ public void run() { return; } - writeThread = new Thread( new WebsocketWriteThread() ); - writeThread.start(); + Thread write = new Thread( new WebsocketWriteThread() ); + write.start(); + writeThread = new WeakReference(write); byte[] rawbuffer = new byte[ WebSocketImpl.RCVBUF ]; int readBytes; @@ -274,8 +279,13 @@ public final void onWebsocketOpen( WebSocket conn, Handshakedata handshake ) { public final void onWebsocketClose( WebSocket conn, int code, String reason, boolean remote ) { connectLatch.countDown(); closeLatch.countDown(); - if( writeThread != null ) - writeThread.interrupt(); + if( writeThread != null && writeThread.get() != null) { + writeThread.get().interrupt(); + } + if( readThread != null && readThread.get() != null) { + readThread.get().interrupt(); + } + try { if( socket != null ) socket.close(); @@ -345,7 +355,7 @@ public void onFragment( Framedata frame ) { private class WebsocketWriteThread implements Runnable { @Override public void run() { - Thread.currentThread().setName( "WebsocketWriteThread" ); + Thread.currentThread().setName( "WSC Write" ); try { while ( !Thread.interrupted() ) { ByteBuffer buffer = engine.outQueue.take(); diff --git a/src/main/java/org/java_websocket/server/DefaultSSLWebSocketServerFactory.java b/src/main/java/org/java_websocket/server/DefaultSSLWebSocketServerFactory.java index b871260f8..30cf73a89 100644 --- a/src/main/java/org/java_websocket/server/DefaultSSLWebSocketServerFactory.java +++ b/src/main/java/org/java_websocket/server/DefaultSSLWebSocketServerFactory.java @@ -4,6 +4,7 @@ import java.nio.channels.ByteChannel; import java.nio.channels.SelectionKey; import java.nio.channels.SocketChannel; +import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -41,11 +42,13 @@ public ByteChannel wrapChannel( SocketChannel channel, SelectionKey key ) throws @Override public WebSocketImpl createWebSocket( WebSocketAdapter a, Draft d, Socket c ) { - return new WebSocketImpl( a, d ); + List drafts = new ArrayList(1); + drafts.add(d); + return WebSocketImpl.createServer( a, drafts ); } @Override public WebSocketImpl createWebSocket( WebSocketAdapter a, List d, Socket s ) { - return new WebSocketImpl( a, d ); + return WebSocketImpl.createServer( 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 3b89cdc2f..f4891039c 100644 --- a/src/main/java/org/java_websocket/server/DefaultWebSocketServerFactory.java +++ b/src/main/java/org/java_websocket/server/DefaultWebSocketServerFactory.java @@ -3,6 +3,7 @@ import java.net.Socket; import java.nio.channels.SelectionKey; import java.nio.channels.SocketChannel; +import java.util.ArrayList; import java.util.List; import org.java_websocket.WebSocketAdapter; @@ -13,11 +14,13 @@ public class DefaultWebSocketServerFactory implements WebSocketServerFactory { @Override public WebSocketImpl createWebSocket( WebSocketAdapter a, Draft d, Socket s ) { - return new WebSocketImpl( a, d ); + List drafts = new ArrayList(1); + drafts.add(d); + return WebSocketImpl.createServer( a, drafts ); } @Override public WebSocketImpl createWebSocket( WebSocketAdapter a, List d, Socket s ) { - return new WebSocketImpl( a, d ); + return WebSocketImpl.createServer( 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 a45f7e133..cfa9264d3 100644 --- a/src/main/java/org/java_websocket/server/WebSocketServer.java +++ b/src/main/java/org/java_websocket/server/WebSocketServer.java @@ -514,7 +514,11 @@ public final void onWebsocketOpen( WebSocket conn, Handshakedata handshake ) { public final void onWebsocketClose( WebSocket conn, int code, String reason, boolean remote ) { selector.wakeup(); try { - if( removeConnection( conn ) ) { + if(conn.getReadyState() == WebSocket.READYSTATE.NOT_YET_CONNECTED){ + // if the WebSocket was never opened (i.e. rejected before opened), then the connection will not have + // been added, so removeConnection should not be called. + onClose( conn, code, reason, remote ); + } else if( removeConnection( conn ) ) { onClose( conn, code, reason, remote ); } } finally {