Adding Java WebSocket lib for unit tests.

This commit is contained in:
lotodore
2015-01-31 22:48:58 +01:00
parent a2109df5e9
commit 2252c180c0
43 changed files with 6933 additions and 0 deletions
+22
View File
@@ -0,0 +1,22 @@
Copyright (c) 2010-2012 Nathan Rajlich
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
@@ -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;
}
}
@@ -0,0 +1,380 @@
/**
* Copyright (C) 2003 Alexander Kout
* Originally from the jFxp project (http://jfxp.sourceforge.net/).
* Copied with permission June 11, 2012 by Femi Omojola (fomojola@ideasynthesis.com).
*/
package org.java_websocket;
import java.io.IOException;
import java.net.Socket;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ByteChannel;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
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;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLEngineResult;
import javax.net.ssl.SSLEngineResult.HandshakeStatus;
import javax.net.ssl.SSLEngineResult.Status;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSession;
/**
* 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;
protected List<Future<?>> tasks;
/** raw payload incomming */
protected ByteBuffer inData;
/** encrypted data outgoing */
protected ByteBuffer outCrypt;
/** encrypted data incoming */
protected ByteBuffer inCrypt;
/** the underlying channel */
protected SocketChannel socketChannel;
/** used to set interestOP SelectionKey.OP_WRITE for the underlying channel */
protected SelectionKey selectionKey;
protected SSLEngine sslEngine;
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 {
if( channel == null || sslEngine == null || exec == null )
throw new IllegalArgumentException( "parameter must not be null" );
this.socketChannel = channel;
this.sslEngine = sslEngine;
this.exec = exec;
readEngineResult = writeEngineResult = new SSLEngineResult( Status.BUFFER_UNDERFLOW, sslEngine.getHandshakeStatus(), 0, 0 ); // init to prevent NPEs
tasks = new ArrayList<Future<?>>( 3 );
if( key != null ) {
key.interestOps( key.interestOps() | SelectionKey.OP_WRITE );
this.selectionKey = key;
}
createBuffers( sslEngine.getSession() );
// kick off handshake
socketChannel.write( wrap( emptybuffer ) );// initializes res
processHandshake();
}
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 );
}
}
/**
* 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( 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<Future<?>> it = tasks.iterator();
while ( it.hasNext() ) {
Future<?> f = it.next();
if( f.isDone() ) {
it.remove();
} else {
if( isBlocking() )
consumeFutureUninterruptible( f );
return;
}
}
}
if( sslEngine.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_UNWRAP ) {
if( !isBlocking() || readEngineResult.getStatus() == Status.BUFFER_UNDERFLOW ) {
inCrypt.compact();
int read = socketChannel.read( inCrypt );
if( read == -1 ) {
throw new IOException( "connection closed unexpectedly by peer" );
}
inCrypt.flip();
}
inData.compact();
unwrap();
if( readEngineResult.getHandshakeStatus() == HandshakeStatus.FINISHED ) {
createBuffers( sslEngine.getSession() );
return;
}
}
consumeDelegatedTasks();
if( tasks.isEmpty() || sslEngine.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_WRAP ) {
socketChannel.write( wrap( emptybuffer ) );
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 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();
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();
readEngineResult = sslEngine.unwrap( inCrypt, inData );
} while ( readEngineResult.getStatus() == SSLEngineResult.Status.OK && ( rem != inData.remaining() || sslEngine.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP ) );
inData.flip();
return inData;
}
protected void consumeDelegatedTasks() {
Runnable task;
while ( ( task = sslEngine.getDelegatedTask() ) != null ) {
tasks.add( exec.submit( task ) );
// task.run();
}
}
protected void createBuffers( SSLSession session ) {
int netBufferMax = session.getPacketBufferSize();
int appBufferMax = Math.max(session.getApplicationBufferSize(), 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();
bufferallocations++;
}
public int write( ByteBuffer src ) throws IOException {
if( !isHandShakeComplete() ) {
processHandshake();
return 0;
}
// assert ( bufferallocations > 1 ); //see #190
//if( bufferallocations <= 1 ) {
// createBuffers( sslEngine.getSession() );
//}
int num = socketChannel.write( wrap( src ) );
return num;
}
/**
* Blocks when in blocking mode until at least one byte has been decoded.<br>
* 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() )
return 0;
if( !isHandShakeComplete() ) {
if( isBlocking() ) {
while ( !isHandShakeComplete() ) {
processHandshake();
}
} else {
processHandshake();
if( !isHandShakeComplete() ) {
return 0;
}
}
}
// 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)
*/
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();
if( !inCrypt.hasRemaining() )
inCrypt.clear();
else
inCrypt.compact();
if( isBlocking() || readEngineResult.getStatus() == Status.BUFFER_UNDERFLOW )
if( socketChannel.read( inCrypt ) == -1 ) {
return -1;
}
inCrypt.flip();
unwrap();
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;
}
/**
* {@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 );
}
if( !inData.hasRemaining() )
inData.clear();
// test if some bytes left from last read (e.g. BUFFER_UNDERFLOW)
if( inCrypt.hasRemaining() ) {
unwrap();
int amount = transfereTo( inData, dst );
if( amount > 0 )
return amount;
}
return 0;
}
public boolean isConnected() {
return socketChannel.isConnected();
}
public void close() throws IOException {
sslEngine.closeOutbound();
sslEngine.getSession().invalidate();
if( socketChannel.isOpen() )
socketChannel.write( wrap( emptybuffer ) );// FIXME what if not all bytes can be written
socketChannel.close();
exec.shutdownNow();
}
private boolean isHandShakeComplete() {
HandshakeStatus status = sslEngine.getHandshakeStatus();
return status == SSLEngineResult.HandshakeStatus.FINISHED || status == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING;
}
public SelectableChannel configureBlocking( boolean b ) throws IOException {
return socketChannel.configureBlocking( b );
}
public boolean connect( SocketAddress remote ) throws IOException {
return socketChannel.connect( remote );
}
public boolean finishConnect() throws IOException {
return socketChannel.finishConnect();
}
public Socket socket() {
return socketChannel.socket();
}
public boolean isInboundDone() {
return sslEngine.isInboundDone();
}
@Override
public boolean isOpen() {
return socketChannel.isOpen();
}
@Override
public boolean isNeedWrite() {
return outCrypt.hasRemaining() || !isHandShakeComplete(); // FIXME this condition can cause high cpu load during handshaking when network is slow
}
@Override
public void writeMore() throws IOException {
write( outCrypt );
}
@Override
public boolean isNeedRead() {
return inData.hasRemaining() || ( inCrypt.hasRemaining() && readEngineResult.getStatus() != Status.BUFFER_UNDERFLOW && readEngineResult.getStatus() != Status.CLOSED );
}
@Override
public int readMore( ByteBuffer dst ) throws SSLException {
return readRemaining( dst );
}
private int transfereTo( ByteBuffer from, ByteBuffer to ) {
int fremain = from.remaining();
int toremain = to.remaining();
if( fremain > toremain ) {
// FIXME there should be a more efficient transfer method
int limit = Math.min( fremain, toremain );
for( int i = 0 ; i < limit ; i++ ) {
to.put( from.get() );
}
return limit;
} else {
to.put( from );
return fremain;
}
}
@Override
public boolean isBlocking() {
return socketChannel.isBlocking();
}
}
@@ -0,0 +1,71 @@
package org.java_websocket;
import java.io.IOException;
import java.nio.ByteBuffer;
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 {
buf.clear();
int read = channel.read( buf );
buf.flip();
if( read == -1 ) {
ws.eot();
return false;
}
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 );
buf.flip();
if( read == -1 ) {
ws.eot();
return false;
}
return channel.isNeedRead();
}
/** 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 ) {
c = (WrappedByteChannel) sockchannel;
if( c.isNeedWrite() ) {
c.writeMore();
}
}
} else {
do {// FIXME writing as much as possible is unfair!!
/*int written = */sockchannel.write( buffer );
if( buffer.remaining() > 0 ) {
return false;
} else {
ws.outQueue.poll(); // Buffer finished. Remove it.
buffer = ws.outQueue.peek();
}
} while ( buffer != null );
}
if( ws.outQueue.isEmpty() && ws.isFlushAndClose() && ws.getDraft().getRole() == Role.SERVER ) {//
synchronized ( ws ) {
ws.closeConnection();
}
}
return c != null ? !( (WrappedByteChannel) sockchannel ).isNeedWrite() : true;
}
}
@@ -0,0 +1,124 @@
package org.java_websocket;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.NotYetConnectedException;
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 {
CLIENT, SERVER
}
public enum READYSTATE {
NOT_YET_CONNECTED, CONNECTING, OPEN, CLOSING, CLOSED;
}
/**
* The default port of WebSockets, as defined in the spec. If the nullary
* constructor is used, DEFAULT_PORT will be the port the WebSocketServer
* is binded to. Note that ports under 1024 usually require root permissions.
*/
public static final int DEFAULT_PORT = 80;
public static final int DEFAULT_WSS_PORT = 443;
/**
* sends the closing handshake.
* may be send in response to an other handshake.
*/
public void close( int code, String message );
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.
**/
public abstract void closeConnection( int code, String message );
/**
* Send Text data to the other end.
*
* @throws IllegalArgumentException
* @throws NotYetConnectedException
*/
public abstract void send( String text ) throws NotYetConnectedException;
/**
* Send Binary data (plain bytes) to the other end.
*
* @throws IllegalArgumentException
* @throws NotYetConnectedException
*/
public abstract void send( ByteBuffer bytes ) throws IllegalArgumentException , NotYetConnectedException;
public abstract void send( byte[] bytes ) throws IllegalArgumentException , NotYetConnectedException;
public abstract void sendFrame( Framedata framedata );
/**
* Allows to send continuous/fragmented frames conveniently. <br>
* For more into on this frame type see http://tools.ietf.org/html/rfc6455#section-5.4<br>
*
* 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();
/**
* @returns never returns null
*/
public abstract InetSocketAddress getRemoteSocketAddress();
/**
* @returns never returns null
*/
public abstract InetSocketAddress getLocalSocketAddress();
public abstract boolean isConnecting();
public abstract boolean isOpen();
public abstract boolean isClosing();
/**
* Returns true when no further frames may be submitted<br>
* This happens before the socket connection is closed.
*/
public abstract boolean isFlushAndClose();
/** Returns whether the close handshake has been completed and the socket is closed. */
public abstract boolean isClosed();
public abstract Draft getDraft();
/**
* Retrieve the WebSocket 'readyState'.
* This represents the state of the connection.
* It returns a numerical value, as per W3C WebSockets specs.
*
* @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<br>
* If the opening handshake has not yet happened it will return null.
**/
public abstract String getResourceDescriptor();
}
@@ -0,0 +1,104 @@
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;
import org.java_websocket.handshake.ClientHandshake;
import org.java_websocket.handshake.HandshakeImpl1Server;
import org.java_websocket.handshake.ServerHandshake;
import org.java_websocket.handshake.ServerHandshakeBuilder;
/**
* This class default implements all methods of the WebSocketListener that can be overridden optionally when advances functionalities is needed.<br>
**/
public abstract class WebSocketAdapter implements WebSocketListener {
/**
* This default implementation does not do anything. Go ahead and overwrite it.
*
* @see org.java_websocket.WebSocketListener#onWebsocketHandshakeReceivedAsServer(WebSocket, Draft, ClientHandshake)
*/
@Override
public ServerHandshakeBuilder onWebsocketHandshakeReceivedAsServer( WebSocket conn, Draft draft, ClientHandshake request ) throws InvalidDataException {
return new HandshakeImpl1Server();
}
@Override
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#onWebsocketHandshakeSentAsClient(WebSocket, ClientHandshake)
*/
@Override
public void onWebsocketHandshakeSentAsClient( WebSocket conn, ClientHandshake request ) throws InvalidDataException {
}
/**
* This default implementation does not do anything. Go ahead and overwrite it
*
* @see org.java_websocket.WebSocketListener#onWebsocketMessageFragment(WebSocket, Framedata)
*/
@Override
public void onWebsocketMessageFragment( WebSocket conn, Framedata frame ) {
}
/**
* This default implementation will send a pong in response to the received ping.
* The pong frame will have the same payload as the ping frame.
*
* @see org.java_websocket.WebSocketListener#onWebsocketPing(WebSocket, Framedata)
*/
@Override
public void onWebsocketPing( WebSocket conn, Framedata f ) {
FramedataImpl1 resp = new FramedataImpl1( f );
resp.setOptcode( Opcode.PONG );
conn.sendFrame( resp );
}
/**
* This default implementation does not do anything. Go ahead and overwrite it.
*
* @see @see org.java_websocket.WebSocketListener#onWebsocketPong(WebSocket, Framedata)
*/
@Override
public void onWebsocketPong( WebSocket conn, Framedata f ) {
}
/**
* Gets the XML string that should be returned if a client requests a Flash
* security policy.
*
* The default implementation allows access from all remote domains, but
* only on the port that this WebSocketServer is listening on.
*
* This is specifically implemented for gitime's WebSocket client for Flash:
* http://github.com/gimite/web-socket-js
*
* @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 ) throws InvalidDataException {
InetSocketAddress adr = conn.getLocalSocketAddress();
if(null == adr){
throw new InvalidHandshakeException( "socket not bound" );
}
StringBuffer sb = new StringBuffer( 90 );
sb.append( "<cross-domain-policy><allow-access-from domain=\"*\" to-ports=\"" );
sb.append(adr.getPort());
sb.append( "\" /></cross-domain-policy>\0" );
return sb.toString();
}
}
@@ -0,0 +1,12 @@
package org.java_websocket;
import java.net.Socket;
import java.util.List;
import org.java_websocket.drafts.Draft;
public interface WebSocketFactory {
public WebSocket createWebSocket( WebSocketAdapter a, Draft d, Socket s );
public WebSocket createWebSocket( WebSocketAdapter a, List<Draft> drafts, Socket s );
}
@@ -0,0 +1,737 @@
package org.java_websocket;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.ByteChannel;
import java.nio.channels.NotYetConnectedException;
import java.nio.channels.SelectionKey;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import org.java_websocket.drafts.Draft;
import org.java_websocket.drafts.Draft.CloseHandshakeType;
import org.java_websocket.drafts.Draft.HandshakeState;
import org.java_websocket.drafts.Draft_10;
import org.java_websocket.drafts.Draft_17;
import org.java_websocket.drafts.Draft_75;
import org.java_websocket.drafts.Draft_76;
import org.java_websocket.exceptions.IncompleteHandshakeException;
import org.java_websocket.exceptions.InvalidDataException;
import org.java_websocket.exceptions.InvalidHandshakeException;
import org.java_websocket.exceptions.WebsocketNotConnectedException;
import org.java_websocket.framing.CloseFrame;
import org.java_websocket.framing.CloseFrameBuilder;
import org.java_websocket.framing.Framedata;
import org.java_websocket.framing.Framedata.Opcode;
import org.java_websocket.handshake.ClientHandshake;
import org.java_websocket.handshake.ClientHandshakeBuilder;
import org.java_websocket.handshake.Handshakedata;
import org.java_websocket.handshake.ServerHandshake;
import org.java_websocket.handshake.ServerHandshakeBuilder;
import org.java_websocket.server.WebSocketServer.WebSocketWorker;
import org.java_websocket.util.Charsetfunctions;
/**
* Represents one end (client or server) of a single WebSocketImpl connection.
* Takes care of the "handshake" phase, then allows for easy sending of
* text frames, and receiving frames through an event-based model.
*
*/
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<Draft> defaultdraftlist = new ArrayList<Draft>( 4 );
static {
defaultdraftlist.add( new Draft_17() );
defaultdraftlist.add( new Draft_10() );
defaultdraftlist.add( new Draft_76() );
defaultdraftlist.add( new Draft_75() );
}
public SelectionKey key;
/** the possibly wrapped channel object whose selection is controlled by {@link #key} */
public ByteChannel channel;
/**
* Queue of buffers that need to be sent to the client.
*/
public final BlockingQueue<ByteBuffer> outQueue;
/**
* Queue of buffers that need to be processed
*/
public final BlockingQueue<ByteBuffer> inQueue;
/**
* Helper variable meant to store the thread which ( exclusively ) triggers this objects decode method.
**/
public volatile WebSocketWorker workerThread; // TODO reset worker?
/** When true no further frames may be submitted to be sent */
private volatile boolean flushandclosestate = false;
private READYSTATE readystate = READYSTATE.NOT_YET_CONNECTED;
/**
* The listener to notify of WebSocket events.
*/
private final WebSocketListener wsl;
private List<Draft> knownDrafts;
private Draft draft = null;
private Role role;
private Opcode current_continuous_frame_opcode = null;
/** the bytes of an incomplete received handshake */
private ByteBuffer tmpHandshakeBytes = ByteBuffer.allocate( 0 );
/** stores the handshake sent by this websocket ( Role.CLIENT only ) */
private ClientHandshake handshakerequest = null;
private String closemessage = null;
private Integer closecode = null;
private Boolean closedremotely = null;
private String resourceDescriptor = null;
/**
* crates a websocket with server role
*/
public WebSocketImpl( WebSocketListener listener , List<Draft> 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() ) {
knownDrafts = defaultdraftlist;
} else {
knownDrafts = drafts;
}
}
/**
* crates a websocket with client role
*
* @param socket
* may be unbound
*/
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<ByteBuffer>();
inQueue = new LinkedBlockingQueue<ByteBuffer>();
this.wsl = listener;
this.role = Role.CLIENT;
if( draft != null )
this.draft = draft.copyInstance();
}
@Deprecated
public WebSocketImpl( WebSocketListener listener , Draft draft , Socket socket ) {
this( listener, draft );
}
@Deprecated
public WebSocketImpl( WebSocketListener listener , List<Draft> drafts , Socket socket ) {
this( listener, drafts );
}
/**
*
*/
public void decode( ByteBuffer socketBuffer ) {
assert ( socketBuffer.hasRemaining() );
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.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
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.capacity() == 0 ) {
socketBuffer = socketBufferNew;
} else {
if( tmpHandshakeBytes.remaining() < socketBufferNew.remaining() ) {
ByteBuffer buf = ByteBuffer.allocate( tmpHandshakeBytes.capacity() + socketBufferNew.remaining() );
tmpHandshakeBytes.flip();
buf.put( tmpHandshakeBytes );
tmpHandshakeBytes = buf;
}
tmpHandshakeBytes.put( socketBufferNew );
tmpHandshakeBytes.flip();
socketBuffer = tmpHandshakeBytes;
}
socketBuffer.mark();
try {
if( draft == null ) {
HandshakeState isflashedgecase = isFlashEdgeCase( socketBuffer );
if( isflashedgecase == HandshakeState.MATCHED ) {
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;
}
}
HandshakeState handshakestate = null;
try {
if( role == Role.SERVER ) {
if( draft == null ) {
for( Draft d : knownDrafts ) {
d = d.copyInstance();
try {
d.setParseMode( role );
socketBuffer.reset();
Handshakedata tmphandshake = d.translateHandshake( socketBuffer );
if( tmphandshake instanceof ClientHandshake == false ) {
flushAndClose( CloseFrame.PROTOCOL_ERROR, "wrong http function", false );
return false;
}
ClientHandshake handshake = (ClientHandshake) tmphandshake;
handshakestate = d.acceptHandshakeAsServer( handshake );
if( handshakestate == HandshakeState.MATCHED ) {
resourceDescriptor = handshake.getResourceDescriptor();
ServerHandshakeBuilder response;
try {
response = wsl.onWebsocketHandshakeReceivedAsServer( this, d, handshake );
} catch ( InvalidDataException e ) {
flushAndClose( e.getCloseCode(), e.getMessage(), false );
return false;
} catch ( RuntimeException e ) {
wsl.onWebsocketError( this, e );
flushAndClose( CloseFrame.NEVER_CONNECTED, e.getMessage(), false );
return false;
}
write( d.createHandshake( d.postProcessHandshakeResponseAsServer( handshake, response ), role ) );
draft = d;
open( handshake );
return true;
}
} catch ( InvalidHandshakeException e ) {
// go on with an other draft
}
}
if( draft == null ) {
close( CloseFrame.PROTOCOL_ERROR, "no draft matches" );
}
return false;
} else {
// special case for multiple step handshakes
Handshakedata tmphandshake = draft.translateHandshake( socketBuffer );
if( tmphandshake instanceof ClientHandshake == false ) {
flushAndClose( CloseFrame.PROTOCOL_ERROR, "wrong http function", false );
return false;
}
ClientHandshake handshake = (ClientHandshake) tmphandshake;
handshakestate = draft.acceptHandshakeAsServer( handshake );
if( handshakestate == HandshakeState.MATCHED ) {
open( handshake );
return true;
} else {
close( CloseFrame.PROTOCOL_ERROR, "the handshake did finaly not match" );
}
return false;
}
} else if( role == Role.CLIENT ) {
draft.setParseMode( role );
Handshakedata tmphandshake = draft.translateHandshake( socketBuffer );
if( tmphandshake instanceof ServerHandshake == false ) {
flushAndClose( CloseFrame.PROTOCOL_ERROR, "wrong http function", false );
return false;
}
ServerHandshake handshake = (ServerHandshake) tmphandshake;
handshakestate = draft.acceptHandshakeAsClient( handshakerequest, handshake );
if( handshakestate == HandshakeState.MATCHED ) {
try {
wsl.onWebsocketHandshakeReceivedAsClient( this, handshakerequest, handshake );
} catch ( InvalidDataException e ) {
flushAndClose( e.getCloseCode(), e.getMessage(), false );
return false;
} catch ( RuntimeException e ) {
wsl.onWebsocketError( this, e );
flushAndClose( CloseFrame.NEVER_CONNECTED, e.getMessage(), false );
return false;
}
open( handshake );
return true;
} else {
close( CloseFrame.PROTOCOL_ERROR, "draft " + draft + " refuses handshake" );
}
}
} catch ( InvalidHandshakeException e ) {
close( e );
}
} catch ( IncompleteHandshakeException e ) {
if( tmpHandshakeBytes.capacity() == 0 ) {
socketBuffer.reset();
int newsize = e.getPreferedSize();
if( newsize == 0 ) {
newsize = socketBuffer.capacity() + 16;
} else {
assert ( e.getPreferedSize() >= socketBuffer.remaining() );
}
tmpHandshakeBytes = ByteBuffer.allocate( newsize );
tmpHandshakeBytes.put( socketBufferNew );
// tmpHandshakeBytes.flip();
} else {
tmpHandshakeBytes.position( tmpHandshakeBytes.limit() );
tmpHandshakeBytes.limit( tmpHandshakeBytes.capacity() );
}
}
return false;
}
private void decodeFrames( ByteBuffer socketBuffer ) {
List<Framedata> frames;
try {
frames = draft.translateFrame( socketBuffer );
for( Framedata f : frames ) {
if( DEBUG )
System.out.println( "matched frame: " + f );
Opcode curop = f.getOpcode();
boolean fin = f.isFin();
if( curop == Opcode.CLOSING ) {
int code = CloseFrame.NOCODE;
String reason = "";
if( f instanceof CloseFrame ) {
CloseFrame cf = (CloseFrame) f;
code = cf.getCloseCode();
reason = cf.getMessage();
}
if( readystate == READYSTATE.CLOSING ) {
// complete the close handshake by disconnecting
closeConnection( code, reason, true );
} else {
// echo close handshake
if( draft.getCloseHandshakeType() == CloseHandshakeType.TWOWAY )
close( code, reason, true );
else
flushAndClose( code, reason, false );
}
continue;
} else if( curop == Opcode.PING ) {
wsl.onWebsocketPing( this, f );
continue;
} else if( curop == Opcode.PONG ) {
wsl.onWebsocketPong( this, f );
continue;
} else if( !fin || curop == Opcode.CONTINUOUS ) {
if( curop != Opcode.CONTINUOUS ) {
if( current_continuous_frame_opcode != null )
throw new InvalidDataException( CloseFrame.PROTOCOL_ERROR, "Previous continuous frame sequence not completed." );
current_continuous_frame_opcode = curop;
} else if( fin ) {
if( current_continuous_frame_opcode == null )
throw new InvalidDataException( CloseFrame.PROTOCOL_ERROR, "Continuous frame sequence was not started." );
current_continuous_frame_opcode = null;
} else if( current_continuous_frame_opcode == null ) {
throw new InvalidDataException( CloseFrame.PROTOCOL_ERROR, "Continuous frame sequence was not started." );
}
try {
wsl.onWebsocketMessageFragment( this, f );
} catch ( RuntimeException e ) {
wsl.onWebsocketError( this, e );
}
} else if( current_continuous_frame_opcode != null ) {
throw new InvalidDataException( CloseFrame.PROTOCOL_ERROR, "Continuous frame sequence not completed." );
} else if( curop == Opcode.TEXT ) {
try {
wsl.onWebsocketMessage( this, Charsetfunctions.stringUtf8( f.getPayloadData() ) );
} catch ( RuntimeException e ) {
wsl.onWebsocketError( this, e );
}
} else if( curop == Opcode.BINARY ) {
try {
wsl.onWebsocketMessage( this, f.getPayloadData() );
} catch ( RuntimeException e ) {
wsl.onWebsocketError( this, e );
}
} else {
throw new InvalidDataException( CloseFrame.PROTOCOL_ERROR, "non control or continious frame expected" );
}
}
} catch ( InvalidDataException e1 ) {
wsl.onWebsocketError( this, e1 );
close( e1 );
return;
}
}
private void close( int code, String message, boolean remote ) {
if( readystate != READYSTATE.CLOSING && readystate != READYSTATE.CLOSED ) {
if( readystate == READYSTATE.OPEN ) {
if( code == CloseFrame.ABNORMAL_CLOSE ) {
assert ( remote == false );
readystate = READYSTATE.CLOSING;
flushAndClose( code, message, false );
return;
}
if( draft.getCloseHandshakeType() != CloseHandshakeType.NONE ) {
try {
if( !remote ) {
try {
wsl.onWebsocketCloseInitiated( this, code, message );
} catch ( RuntimeException e ) {
wsl.onWebsocketError( this, e );
}
}
sendFrame( new CloseFrameBuilder( code, message ) );
} catch ( InvalidDataException e ) {
wsl.onWebsocketError( this, e );
flushAndClose( CloseFrame.ABNORMAL_CLOSE, "generated frame is invalid", false );
}
}
flushAndClose( code, message, remote );
} else if( code == CloseFrame.FLASHPOLICY ) {
assert ( remote );
flushAndClose( CloseFrame.FLASHPOLICY, message, true );
} else {
flushAndClose( CloseFrame.NEVER_CONNECTED, message, false );
}
if( code == CloseFrame.PROTOCOL_ERROR )// this endpoint found a PROTOCOL_ERROR
flushAndClose( code, message, remote );
readystate = READYSTATE.CLOSING;
tmpHandshakeBytes = null;
return;
}
}
@Override
public void close( int code, String message ) {
close( code, message, false );
}
/**
*
* @param remote
* Indicates who "generated" <code>code</code>.<br>
* <code>true</code> means that this endpoint received the <code>code</code> from the other endpoint.<br>
* false means this endpoint decided to send the given code,<br>
* <code>remote</code> may also be true if this endpoint started the closing handshake since the other endpoint may not simply echo the <code>code</code> but close the connection the same time this endpoint does do but with an other <code>code</code>. <br>
**/
protected synchronized void closeConnection( int code, String message, boolean remote ) {
if( readystate == READYSTATE.CLOSED ) {
return;
}
if( key != null ) {
// key.attach( null ); //see issue #114
key.cancel();
}
if( channel != null ) {
try {
channel.close();
} catch ( IOException e ) {
wsl.onWebsocketError( this, e );
}
}
try {
this.wsl.onWebsocketClose( this, code, message, remote );
} catch ( RuntimeException e ) {
wsl.onWebsocketError( this, e );
}
if( draft != null )
draft.reset();
handshakerequest = null;
readystate = READYSTATE.CLOSED;
this.outQueue.clear();
}
protected void closeConnection( int code, boolean remote ) {
closeConnection( code, "", remote );
}
public void closeConnection() {
if( closedremotely == null ) {
throw new IllegalStateException( "this method must be used in conjuction with flushAndClose" );
}
closeConnection( closecode, closemessage, closedremotely );
}
public void closeConnection( int code, String message ) {
closeConnection( code, message, false );
}
protected synchronized void flushAndClose( int code, String message, boolean remote ) {
if( flushandclosestate ) {
return;
}
closecode = code;
closemessage = message;
closedremotely = remote;
flushandclosestate = true;
wsl.onWriteDemand( this ); // ensures that all outgoing frames are flushed before closing the connection
try {
wsl.onWebsocketClosing( this, code, message, remote );
} catch ( RuntimeException e ) {
wsl.onWebsocketError( this, e );
}
if( draft != null )
draft.reset();
handshakerequest = null;
}
public void eot() {
if( getReadyState() == READYSTATE.NOT_YET_CONNECTED ) {
closeConnection( CloseFrame.NEVER_CONNECTED, true );
} else if( flushandclosestate ) {
closeConnection( closecode, closemessage, closedremotely );
} else if( draft.getCloseHandshakeType() == CloseHandshakeType.NONE ) {
closeConnection( CloseFrame.NORMAL, true );
} else if( draft.getCloseHandshakeType() == CloseHandshakeType.ONEWAY ) {
if( role == Role.SERVER )
closeConnection( CloseFrame.ABNORMAL_CLOSE, true );
else
closeConnection( CloseFrame.NORMAL, true );
} else {
closeConnection( CloseFrame.ABNORMAL_CLOSE, true );
}
}
@Override
public void close( int code ) {
close( code, "", false );
}
public void close( InvalidDataException e ) {
close( e.getCloseCode(), e.getMessage(), false );
}
/**
* Send Text data to the other end.
*
* @throws IllegalArgumentException
* @throws NotYetConnectedException
*/
@Override
public void send( String text ) throws WebsocketNotConnectedException {
if( text == null )
throw new IllegalArgumentException( "Cannot send 'null' data to a WebSocketImpl." );
send( draft.createFrames( text, role == Role.CLIENT ) );
}
/**
* Send Binary data (plain bytes) to the other end.
*
* @throws IllegalArgumentException
* @throws NotYetConnectedException
*/
@Override
public void send( ByteBuffer bytes ) throws IllegalArgumentException , WebsocketNotConnectedException {
if( bytes == null )
throw new IllegalArgumentException( "Cannot send 'null' data to a WebSocketImpl." );
send( draft.createFrames( bytes, role == Role.CLIENT ) );
}
@Override
public void send( byte[] bytes ) throws IllegalArgumentException , WebsocketNotConnectedException {
send( ByteBuffer.wrap( bytes ) );
}
private void send( Collection<Framedata> frames ) {
if( !isOpen() )
throw new WebsocketNotConnectedException();
for( Framedata f : frames ) {
sendFrame( f );
}
}
@Override
public void sendFragmentedFrame( Opcode op, ByteBuffer buffer, boolean fin ) {
send( draft.continuousFrame( op, buffer, fin ) );
}
@Override
public void sendFrame( Framedata framedata ) {
if( DEBUG )
System.out.println( "send frame: " + framedata );
write( draft.createBinaryFrame( framedata ) );
}
@Override
public boolean hasBufferedData() {
return !this.outQueue.isEmpty();
}
private HandshakeState isFlashEdgeCase( ByteBuffer request ) throws IncompleteHandshakeException {
request.mark();
if( request.limit() > Draft.FLASH_POLICY_REQUEST.length ) {
return HandshakeState.NOT_MATCHED;
} else if( request.limit() < Draft.FLASH_POLICY_REQUEST.length ) {
throw new IncompleteHandshakeException( Draft.FLASH_POLICY_REQUEST.length );
} else {
for( int flash_policy_index = 0 ; request.hasRemaining() ; flash_policy_index++ ) {
if( Draft.FLASH_POLICY_REQUEST[ flash_policy_index ] != request.get() ) {
request.reset();
return HandshakeState.NOT_MATCHED;
}
}
return HandshakeState.MATCHED;
}
}
public void startHandshake( ClientHandshakeBuilder handshakedata ) throws InvalidHandshakeException {
assert ( readystate != READYSTATE.CONNECTING ) : "shall only be called once";
// 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 );
} catch ( InvalidDataException e ) {
// Stop if the client code throws an exception
throw new InvalidHandshakeException( "Handshake data rejected by client." );
} catch ( RuntimeException e ) {
wsl.onWebsocketError( this, e );
throw new InvalidHandshakeException( "rejected because of" + e );
}
// Send
write( draft.createHandshake( this.handshakerequest, role ) );
}
private void write( ByteBuffer buf ) {
if( DEBUG )
System.out.println( "write(" + buf.remaining() + "): {" + ( buf.remaining() > 1000 ? "too big to display" : new String( buf.array() ) ) + "}" );
outQueue.add( buf );
/*try {
outQueue.put( buf );
} catch ( InterruptedException e ) {
write( buf );
Thread.currentThread().interrupt(); // keep the interrupted status
e.printStackTrace();
}*/
wsl.onWriteDemand( this );
}
private void write( List<ByteBuffer> bufs ) {
for( ByteBuffer b : bufs ) {
write( b );
}
}
private void open( Handshakedata d ) {
if( DEBUG )
System.out.println( "open using draft: " + draft.getClass().getSimpleName() );
readystate = READYSTATE.OPEN;
try {
wsl.onWebsocketOpen( this, d );
} catch ( RuntimeException e ) {
wsl.onWebsocketError( this, e );
}
}
@Override
public boolean isConnecting() {
assert ( flushandclosestate ? readystate == READYSTATE.CONNECTING : true );
return readystate == READYSTATE.CONNECTING; // ifflushandclosestate
}
@Override
public boolean isOpen() {
assert ( readystate == READYSTATE.OPEN ? !flushandclosestate : true );
return readystate == READYSTATE.OPEN;
}
@Override
public boolean isClosing() {
return readystate == READYSTATE.CLOSING;
}
@Override
public boolean isFlushAndClose() {
return flushandclosestate;
}
@Override
public boolean isClosed() {
return readystate == READYSTATE.CLOSED;
}
@Override
public READYSTATE getReadyState() {
return readystate;
}
@Override
public int hashCode() {
return super.hashCode();
}
@Override
public String toString() {
return super.toString(); // its nice to be able to set breakpoints here
}
@Override
public InetSocketAddress getRemoteSocketAddress() {
return wsl.getRemoteSocketAddress( this );
}
@Override
public InetSocketAddress getLocalSocketAddress() {
return wsl.getLocalSocketAddress( this );
}
@Override
public Draft getDraft() {
return draft;
}
@Override
public void close() {
close( CloseFrame.NORMAL );
}
@Override
public String getResourceDescriptor() {
return resourceDescriptor;
}
}
@@ -0,0 +1,151 @@
package org.java_websocket;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import org.java_websocket.drafts.Draft;
import org.java_websocket.exceptions.InvalidDataException;
import org.java_websocket.framing.Framedata;
import org.java_websocket.handshake.ClientHandshake;
import org.java_websocket.handshake.Handshakedata;
import org.java_websocket.handshake.ServerHandshake;
import org.java_websocket.handshake.ServerHandshakeBuilder;
/**
* Implemented by <tt>WebSocketClient</tt> and <tt>WebSocketServer</tt>.
* The methods within are called by <tt>WebSocket</tt>.
* Almost every method takes a first parameter conn which represents the source of the respective event.
*/
public interface WebSocketListener {
/**
* Called on the server side when the socket connection is first established, and the WebSocket
* handshake has been received. This method allows to deny connections based on the received handshake.<br>
* By default this method only requires protocol compliance.
*
* @param conn
* The WebSocket related to this event
* @param draft
* The protocol draft the client uses to connect
* @param request
* The opening http message send by the client. Can be used to access additional fields like cookies.
* @return Returns an incomplete handshake containing all optional fields
* @throws InvalidDataException
* Throwing this exception will cause this handshake to be rejected
*/
public ServerHandshakeBuilder onWebsocketHandshakeReceivedAsServer( WebSocket conn, Draft draft, ClientHandshake request ) throws InvalidDataException;
/**
* Called on the client side when the socket connection is first established, and the WebSocketImpl
* handshake response has been received.
*
* @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.
* @throws InvalidDataException
* Allows the client to reject the connection with the server in respect of its handshake response.
*/
public void onWebsocketHandshakeReceivedAsClient( WebSocket conn, ClientHandshake request, ServerHandshake response ) throws InvalidDataException;
/**
* Called on the client side when the socket connection is first established, and the WebSocketImpl
* handshake has just been sent.
*
* @param conn
* The WebSocket related to this event
* @param request
* The handshake sent to the server by this websocket
* @throws InvalidDataException
* Allows the client to stop the connection from progressing
*/
public void onWebsocketHandshakeSentAsClient( WebSocket conn, ClientHandshake request ) throws InvalidDataException;
/**
* Called when an entire text frame has been received. Do whatever you want
* here...
*
* @param conn
* The <tt>WebSocket</tt> instance this event is occurring on.
* @param message
* The UTF-8 decoded message that was received.
*/
public void onWebsocketMessage( WebSocket conn, String message );
/**
* Called when an entire binary frame has been received. Do whatever you want
* here...
*
* @param conn
* The <tt>WebSocket</tt> instance this event is occurring on.
* @param blob
* The binary message that was received.
*/
public void onWebsocketMessage( WebSocket conn, ByteBuffer blob );
public void onWebsocketMessageFragment( WebSocket conn, Framedata frame );
/**
* Called after <var>onHandshakeReceived</var> returns <var>true</var>.
* Indicates that a complete WebSocket connection has been established,
* and we are ready to send/receive data.
*
* @param conn
* The <tt>WebSocket</tt> instance this event is occuring on.
*/
public void onWebsocketOpen( WebSocket conn, Handshakedata d );
/**
* Called after <tt>WebSocket#close</tt> is explicity called, or when the
* other end of the WebSocket connection is closed.
*
* @param conn
* The <tt>WebSocket</tt> instance this event is occuring on.
*/
public void onWebsocketClose( WebSocket ws, int code, String reason, boolean remote );
/** called as soon as no further frames are accepted */
public void onWebsocketClosing( WebSocket ws, int code, String reason, boolean remote );
/** send when this peer sends a close handshake */
public void onWebsocketCloseInitiated( WebSocket ws, int code, String reason );
/**
* Called if an exception worth noting occurred.
* If an error causes the connection to fail onClose will be called additionally afterwards.
*
* @param ex
* The exception that occurred. <br>
* Might be null if the exception is not related to any specific connection. For example if the server port could not be bound.
*/
public void onWebsocketError( WebSocket conn, Exception ex );
/**
* Called a ping frame has been received.
* This method must send a corresponding pong by itself.
*
* @param f
* The ping frame. Control frames may contain payload.
*/
public void onWebsocketPing( WebSocket conn, Framedata f );
/**
* Called when a pong frame is received.
**/
public void onWebsocketPong( WebSocket conn, Framedata f );
/**
* 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 ) 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 );
public InetSocketAddress getLocalSocketAddress( WebSocket conn );
public InetSocketAddress getRemoteSocketAddress( WebSocket conn );
}
@@ -0,0 +1,26 @@
package org.java_websocket;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ByteChannel;
import javax.net.ssl.SSLException;
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();
}
@@ -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();
}
@@ -0,0 +1,454 @@
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.NotYetConnectedException;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import org.java_websocket.WebSocket;
import org.java_websocket.WebSocketAdapter;
import org.java_websocket.WebSocketImpl;
import org.java_websocket.drafts.Draft;
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;
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;
/**
* A subclass must implement at least <var>onOpen</var>, <var>onClose</var>, and <var>onMessage</var> 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 {
/**
* The URI this channel is supposed to connect to.
*/
protected URI uri = null;
private WebSocketImpl engine = null;
private Socket socket = null;
private InputStream istream;
private OutputStream ostream;
private Proxy proxy = Proxy.NO_PROXY;
private Thread writeThread;
private Draft draft;
private Map<String,String> headers;
private CountDownLatch connectLatch = new CountDownLatch( 1 );
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() );
}
/**
* Constructs a WebSocketClient instance and sets it to the connect to the
* specified URI. The channel does not attampt to connect automatically. The connection
* will be established once you call <var>connect</var>.
*/
public WebSocketClient( URI serverUri , Draft draft ) {
this( serverUri, draft, null, 0 );
}
public WebSocketClient( URI serverUri , Draft protocolDraft , Map<String,String> httpHeaders , int connectTimeout ) {
if( serverUri == null ) {
throw new IllegalArgumentException();
} else if( protocolDraft == null ) {
throw new IllegalArgumentException( "null as draft is permitted for `WebSocketServer` only!" );
}
this.uri = serverUri;
this.draft = protocolDraft;
this.headers = httpHeaders;
this.connectTimeout = connectTimeout;
this.engine = new WebSocketImpl( this, protocolDraft );
}
/**
* Returns the URI that this WebSocketClient is connected to.
*/
public URI getURI() {
return uri;
}
/**
* Returns the protocol version this channel uses.<br>
* For more infos see https://github.com/TooTallNate/Java-WebSocket/wiki/Drafts
*/
public Draft getDraft() {
return draft;
}
/**
* Initiates the websocket connection. This method does not block.
*/
public void connect() {
if( writeThread != null )
throw new IllegalStateException( "WebSocketClient objects are not reuseable" );
writeThread = new Thread( this );
writeThread.start();
}
/**
* Same as <code>connect</code> but blocks until the websocket connected or failed to do so.<br>
* Returns whether it succeeded or not.
**/
public boolean connectBlocking() throws InterruptedException {
connect();
connectLatch.await();
return engine.isOpen();
}
/**
* Initiates the websocket close handshake. This method does not block<br>
* In oder to make sure the connection is closed use <code>closeBlocking</code>
*/
public void close() {
if( writeThread != null ) {
engine.close( CloseFrame.NORMAL );
}
}
public void closeBlocking() throws InterruptedException {
close();
closeLatch.await();
}
/**
* Sends <var>text</var> to the connected websocket server.
*
* @param text
* The string which will be transmitted.
*/
public void send( String text ) throws NotYetConnectedException {
engine.send( text );
}
/**
* Sends binary <var> data</var> to the connected webSocket server.
*
* @param data
* The byte-Array of data to send to the WebSocket server.
*/
public void send( byte[] data ) throws NotYetConnectedException {
engine.send( data );
}
public void run() {
try {
if( socket == null ) {
socket = new Socket( proxy );
} else if( socket.isClosed() ) {
throw new IOException();
}
if( !socket.isBound() )
socket.connect( new InetSocketAddress( uri.getHost(), getPort() ), connectTimeout );
istream = socket.getInputStream();
ostream = socket.getOutputStream();
sendHandshake();
} catch ( /*IOException | SecurityException | UnresolvedAddressException | InvalidHandshakeException | ClosedByInterruptException | SocketTimeoutException */Exception e ) {
onWebsocketError( engine, e );
engine.closeConnection( CloseFrame.NEVER_CONNECTED, e.getMessage() );
return;
}
writeThread = new Thread( new WebsocketWriteThread() );
writeThread.start();
byte[] rawbuffer = new byte[ WebSocketImpl.RCVBUF ];
int readBytes;
try {
while ( !isClosed() && ( readBytes = istream.read( rawbuffer ) ) != -1 ) {
engine.decode( ByteBuffer.wrap( rawbuffer, 0, readBytes ) );
}
engine.eot();
} catch ( IOException e ) {
engine.eot();
} catch ( RuntimeException e ) {
// this catch case covers internal errors only and indicates a bug in this websocket implementation
onError( e );
engine.closeConnection( CloseFrame.ABNORMAL_CLOSE, e.getMessage() );
}
assert ( socket.isClosed() );
}
private int getPort() {
int port = uri.getPort();
if( port == -1 ) {
String scheme = uri.getScheme();
if( scheme.equals( "wss" ) ) {
return WebSocket.DEFAULT_WSS_PORT;
} else if( scheme.equals( "ws" ) ) {
return WebSocket.DEFAULT_PORT;
} else {
throw new RuntimeException( "unkonow scheme" + scheme );
}
}
return port;
}
private void sendHandshake() throws InvalidHandshakeException {
String path;
String part1 = uri.getPath();
String part2 = uri.getQuery();
if( part1 == null || part1.length() == 0 )
path = "/";
else
path = part1;
if( part2 != null )
path += "?" + part2;
int port = getPort();
String host = uri.getHost() + ( port != WebSocket.DEFAULT_PORT ? ":" + port : "" );
HandshakeImpl1Client handshake = new HandshakeImpl1Client();
handshake.setResourceDescriptor( path );
handshake.put( "Host", host );
if( headers != null ) {
for( Map.Entry<String,String> kv : headers.entrySet() ) {
handshake.put( kv.getKey(), kv.getValue() );
}
}
engine.startHandshake( handshake );
}
/**
* This represents the state of the connection.
*/
public READYSTATE getReadyState() {
return engine.getReadyState();
}
/**
* Calls subclass' implementation of <var>onMessage</var>.
*/
@Override
public final void onWebsocketMessage( WebSocket conn, String message ) {
onMessage( message );
}
@Override
public final void onWebsocketMessage( WebSocket conn, ByteBuffer blob ) {
onMessage( blob );
}
@Override
public void onWebsocketMessageFragment( WebSocket conn, Framedata frame ) {
onFragment( frame );
}
/**
* Calls subclass' implementation of <var>onOpen</var>.
*/
@Override
public final void onWebsocketOpen( WebSocket conn, Handshakedata handshake ) {
connectLatch.countDown();
onOpen( (ServerHandshake) handshake );
}
/**
* Calls subclass' implementation of <var>onClose</var>.
*/
@Override
public final void onWebsocketClose( WebSocket conn, int code, String reason, boolean remote ) {
connectLatch.countDown();
closeLatch.countDown();
if( writeThread != null )
writeThread.interrupt();
try {
if( socket != null )
socket.close();
} catch ( IOException e ) {
onWebsocketError( this, e );
}
onClose( code, reason, remote );
}
/**
* Calls subclass' implementation of <var>onIOError</var>.
*/
@Override
public final void onWebsocketError( WebSocket conn, Exception ex ) {
onError( ex );
}
@Override
public final void onWriteDemand( WebSocket conn ) {
// nothing to do
}
@Override
public void onWebsocketCloseInitiated( WebSocket conn, int code, String reason ) {
onCloseInitiated( code, reason );
}
@Override
public void onWebsocketClosing( WebSocket conn, int code, String reason, boolean remote ) {
onClosing( code, reason, remote );
}
public void onCloseInitiated( int code, String reason ) {
}
public void onClosing( int code, String reason, boolean remote ) {
}
public WebSocket getConnection() {
return engine;
}
@Override
public InetSocketAddress getLocalSocketAddress( WebSocket conn ) {
if( socket != null )
return (InetSocketAddress) socket.getLocalSocketAddress();
return null;
}
@Override
public InetSocketAddress getRemoteSocketAddress( WebSocket conn ) {
if( socket != null )
return (InetSocketAddress) socket.getRemoteSocketAddress();
return null;
}
// ABTRACT METHODS /////////////////////////////////////////////////////////
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 void onMessage( ByteBuffer bytes ) {
}
public void onFragment( Framedata frame ) {
}
private class WebsocketWriteThread implements Runnable {
@Override
public void run() {
Thread.currentThread().setName( "WebsocketWriteThread" );
try {
while ( !Thread.interrupted() ) {
ByteBuffer buffer = engine.outQueue.take();
ostream.write( buffer.array(), 0, buffer.limit() );
ostream.flush();
}
} catch ( IOException e ) {
engine.eot();
} catch ( InterruptedException e ) {
// this thread is regularly terminated via an interrupt
}
}
}
public void setProxy( Proxy proxy ) {
if( proxy == null )
throw new IllegalArgumentException();
this.proxy = proxy;
}
/**
* Accepts bound and unbound sockets.<br>
* This method must be called before <code>connect</code>.
* 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 ) {
engine.sendFragmentedFrame( op, buffer, fin );
}
@Override
public boolean isOpen() {
return engine.isOpen();
}
@Override
public boolean isFlushAndClose() {
return engine.isFlushAndClose();
}
@Override
public boolean isClosed() {
return engine.isClosed();
}
@Override
public boolean isClosing() {
return engine.isClosing();
}
@Override
public boolean isConnecting() {
return engine.isConnecting();
}
@Override
public boolean hasBufferedData() {
return engine.hasBufferedData();
}
@Override
public void close( int code ) {
engine.close();
}
@Override
public void close( int code, String message ) {
engine.close( code, message );
}
@Override
public void closeConnection( int code, String message ) {
engine.closeConnection( code, message );
}
@Override
public void send( ByteBuffer bytes ) throws IllegalArgumentException , NotYetConnectedException {
engine.send( bytes );
}
@Override
public void sendFrame( Framedata framedata ) {
engine.sendFrame( framedata );
}
@Override
public InetSocketAddress getLocalSocketAddress() {
return engine.getLocalSocketAddress();
}
@Override
public InetSocketAddress getRemoteSocketAddress() {
return engine.getRemoteSocketAddress();
}
@Override
public String getResourceDescriptor() {
return uri.getPath();
}
}
@@ -0,0 +1,228 @@
package org.java_websocket.drafts;
import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import org.java_websocket.WebSocket.Role;
import org.java_websocket.exceptions.IncompleteHandshakeException;
import org.java_websocket.exceptions.InvalidDataException;
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;
import org.java_websocket.handshake.HandshakeImpl1Client;
import org.java_websocket.handshake.HandshakeImpl1Server;
import org.java_websocket.handshake.Handshakedata;
import org.java_websocket.handshake.ServerHandshake;
import org.java_websocket.handshake.ServerHandshakeBuilder;
import org.java_websocket.util.Charsetfunctions;
/**
* Base class for everything of a websocket specification which is not common such as the way the handshake is read or frames are transfered.
**/
public abstract class Draft {
public enum HandshakeState {
/** Handshake matched this Draft successfully */
MATCHED,
/** Handshake is does not match this Draft */
NOT_MATCHED
}
public enum CloseHandshakeType {
NONE, ONEWAY, TWOWAY
}
public static int MAX_FAME_SIZE = 1000 * 1;
public static int INITIAL_FAMESIZE = 64;
public static final byte[] FLASH_POLICY_REQUEST = Charsetfunctions.utf8Bytes( "<policy-file-request/>\0" );
/** 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';
byte cur = '0';
while ( buf.hasRemaining() ) {
prev = cur;
cur = buf.get();
sbuf.put( cur );
if( prev == (byte) '\r' && cur == (byte) '\n' ) {
sbuf.limit( sbuf.position() - 2 );
sbuf.position( 0 );
return sbuf;
}
}
// ensure that there wont be any bytes skipped
buf.position( buf.position() - sbuf.position() );
return null;
}
public static String readStringLine( ByteBuffer buf ) {
ByteBuffer b = readLine( buf );
return b == null ? null : Charsetfunctions.stringAscii( b.array(), 0, b.limit() );
}
public static HandshakeBuilder translateHandshakeHttp( ByteBuffer buf, Role role ) throws InvalidHandshakeException , IncompleteHandshakeException {
HandshakeBuilder handshake;
String line = readStringLine( buf );
if( line == null )
throw new IncompleteHandshakeException( buf.capacity() + 128 );
String[] firstLineTokens = line.split( " ", 3 );// eg. HTTP/1.1 101 Switching the Protocols
if( firstLineTokens.length != 3 ) {
throw new InvalidHandshakeException();
}
if( role == Role.CLIENT ) {
// translating/parsing the response from the SERVER
handshake = new HandshakeImpl1Server();
ServerHandshakeBuilder serverhandshake = (ServerHandshakeBuilder) handshake;
serverhandshake.setHttpStatus( Short.parseShort( firstLineTokens[ 1 ] ) );
serverhandshake.setHttpStatusMessage( firstLineTokens[ 2 ] );
} else {
// translating/parsing the request from the CLIENT
ClientHandshakeBuilder clienthandshake = new HandshakeImpl1Client();
clienthandshake.setResourceDescriptor( firstLineTokens[ 1 ] );
handshake = clienthandshake;
}
line = readStringLine( buf );
while ( line != null && line.length() > 0 ) {
String[] pair = line.split( ":", 2 );
if( pair.length != 2 )
throw new InvalidHandshakeException( "not an http header" );
handshake.put( pair[ 0 ], pair[ 1 ].replaceFirst( "^ +", "" ) );
line = readStringLine( buf );
}
if( line == null )
throw new IncompleteHandshakeException();
return handshake;
}
public abstract HandshakeState acceptHandshakeAsClient( ClientHandshake request, ServerHandshake response ) throws InvalidHandshakeException;
public abstract HandshakeState acceptHandshakeAsServer( ClientHandshake handshakedata ) throws InvalidHandshakeException;
protected boolean basicAccept( Handshakedata handshakedata ) {
return handshakedata.getFieldValue( "Upgrade" ).equalsIgnoreCase( "websocket" ) && handshakedata.getFieldValue( "Connection" ).toLowerCase( Locale.ENGLISH ).contains( "upgrade" );
}
public abstract ByteBuffer createBinaryFrame( Framedata framedata ); // TODO Allow to send data on the base of an Iterator or InputStream
public abstract List<Framedata> createFrames( ByteBuffer binary, boolean mask );
public abstract List<Framedata> createFrames( String text, boolean mask );
public List<Framedata> 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 {
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<ByteBuffer> createHandshake( Handshakedata handshakedata, Role ownrole ) {
return createHandshake( handshakedata, ownrole, true );
}
public List<ByteBuffer> createHandshake( Handshakedata handshakedata, Role ownrole, boolean withcontent ) {
StringBuilder bui = new StringBuilder( 100 );
if( handshakedata instanceof ClientHandshake ) {
bui.append( "GET " );
bui.append( ( (ClientHandshake) handshakedata ).getResourceDescriptor() );
bui.append( " HTTP/1.1" );
} else if( handshakedata instanceof ServerHandshake ) {
bui.append( "HTTP/1.1 101 " + ( (ServerHandshake) handshakedata ).getHttpStatusMessage() );
} else {
throw new RuntimeException( "unknow role" );
}
bui.append( "\r\n" );
Iterator<String> it = handshakedata.iterateHttpFields();
while ( it.hasNext() ) {
String fieldname = it.next();
String fieldvalue = handshakedata.getFieldValue( fieldname );
bui.append( fieldname );
bui.append( ": " );
bui.append( fieldvalue );
bui.append( "\r\n" );
}
bui.append( "\r\n" );
byte[] httpheader = Charsetfunctions.asciiBytes( bui.toString() );
byte[] content = withcontent ? handshakedata.getContent() : null;
ByteBuffer bytebuffer = ByteBuffer.allocate( ( content == null ? 0 : content.length ) + httpheader.length );
bytebuffer.put( httpheader );
if( content != null )
bytebuffer.put( content );
bytebuffer.flip();
return Collections.singletonList( bytebuffer );
}
public abstract ClientHandshakeBuilder postProcessHandshakeRequestAsClient( ClientHandshakeBuilder request ) throws InvalidHandshakeException;
public abstract HandshakeBuilder postProcessHandshakeResponseAsServer( ClientHandshake request, ServerHandshakeBuilder response ) throws InvalidHandshakeException;
public abstract List<Framedata> translateFrame( ByteBuffer buffer ) throws InvalidDataException;
public abstract CloseHandshakeType getCloseHandshakeType();
/**
* Drafts must only be by one websocket at all. To prevent drafts to be used more than once the Websocket implementation should call this method in order to create a new usable version of a given draft instance.<br>
* The copy can be safely used in conjunction with a new websocket connection.
* */
public abstract Draft copyInstance();
public Handshakedata translateHandshake( ByteBuffer buf ) throws InvalidHandshakeException {
return translateHandshakeHttp( buf, role );
}
public int checkAlloc( int bytecount ) throws LimitExedeedException , InvalidDataException {
if( bytecount < 0 )
throw new InvalidDataException( CloseFrame.PROTOCOL_ERROR, "Negative count" );
return bytecount;
}
public void setParseMode( Role role ) {
this.role = role;
}
public Role getRole() {
return role;
}
}
@@ -0,0 +1,397 @@
package org.java_websocket.drafts;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import org.java_websocket.WebSocket.Role;
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.CloseFrameBuilder;
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;
import org.java_websocket.handshake.Handshakedata;
import org.java_websocket.handshake.ServerHandshake;
import org.java_websocket.handshake.ServerHandshakeBuilder;
import org.java_websocket.util.Base64;
import org.java_websocket.util.Charsetfunctions;
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;
}
public int getPreferedSize() {
return preferedsize;
}
}
public static int readVersion( Handshakedata handshakedata ) {
String vers = handshakedata.getFieldValue( "Sec-WebSocket-Version" );
if( vers.length() > 0 ) {
int v;
try {
v = new Integer( vers.trim() );
return v;
} catch ( NumberFormatException e ) {
return -1;
}
}
return -1;
}
private ByteBuffer incompleteframe;
private Framedata fragmentedframe = null;
private final Random reuseableRandom = new Random();
@Override
public HandshakeState acceptHandshakeAsClient( ClientHandshake request, ServerHandshake response ) throws InvalidHandshakeException {
if( !request.hasFieldValue( "Sec-WebSocket-Key" ) || !response.hasFieldValue( "Sec-WebSocket-Accept" ) )
return HandshakeState.NOT_MATCHED;
String seckey_answere = response.getFieldValue( "Sec-WebSocket-Accept" );
String seckey_challenge = request.getFieldValue( "Sec-WebSocket-Key" );
seckey_challenge = generateFinalKey( seckey_challenge );
if( seckey_challenge.equals( seckey_answere ) )
return HandshakeState.MATCHED;
return HandshakeState.NOT_MATCHED;
}
@Override
public HandshakeState acceptHandshakeAsServer( ClientHandshake handshakedata ) throws InvalidHandshakeException {
// Sec-WebSocket-Origin is only required for browser clients
int v = readVersion( handshakedata );
if( v == 7 || v == 8 )// g
return basicAccept( handshakedata ) ? HandshakeState.MATCHED : HandshakeState.NOT_MATCHED;
return HandshakeState.NOT_MATCHED;
}
@Override
public ByteBuffer createBinaryFrame( Framedata framedata ) {
ByteBuffer mes = framedata.getPayloadData();
boolean mask = role == Role.CLIENT; // framedata.getTransfereMasked();
int sizebytes = mes.remaining() <= 125 ? 1 : mes.remaining() <= 65535 ? 2 : 8;
ByteBuffer buf = ByteBuffer.allocate( 1 + ( sizebytes > 1 ? sizebytes + 1 : sizebytes ) + ( mask ? 4 : 0 ) + mes.remaining() );
byte optcode = fromOpcode( framedata.getOpcode() );
byte one = (byte) ( framedata.isFin() ? -128 : 0 );
one |= optcode;
buf.put( one );
byte[] payloadlengthbytes = toByteArray( mes.remaining(), sizebytes );
assert ( payloadlengthbytes.length == sizebytes );
if( sizebytes == 1 ) {
buf.put( (byte) ( (byte) payloadlengthbytes[ 0 ] | ( mask ? (byte) -128 : 0 ) ) );
} else if( sizebytes == 2 ) {
buf.put( (byte) ( (byte) 126 | ( mask ? (byte) -128 : 0 ) ) );
buf.put( payloadlengthbytes );
} else if( sizebytes == 8 ) {
buf.put( (byte) ( (byte) 127 | ( mask ? (byte) -128 : 0 ) ) );
buf.put( payloadlengthbytes );
} else
throw new RuntimeException( "Size representation not supported/specified" );
if( mask ) {
ByteBuffer maskkey = ByteBuffer.allocate( 4 );
maskkey.putInt( reuseableRandom.nextInt() );
buf.put( maskkey.array() );
for( int i = 0 ; mes.hasRemaining() ; i++ ) {
buf.put( (byte) ( mes.get() ^ maskkey.get( i % 4 ) ) );
}
} else
buf.put( mes );
// translateFrame ( buf.array () , buf.array ().length );
assert ( buf.remaining() == 0 ) : buf.remaining();
buf.flip();
return buf;
}
@Override
public List<Framedata> createFrames( ByteBuffer binary, boolean mask ) {
FrameBuilder curframe = new FramedataImpl1();
try {
curframe.setPayload( binary );
} catch ( InvalidDataException e ) {
throw new NotSendableException( e );
}
curframe.setFin( true );
curframe.setOptcode( Opcode.BINARY );
curframe.setTransferemasked( mask );
return Collections.singletonList( (Framedata) curframe );
}
@Override
public List<Framedata> createFrames( String text, boolean mask ) {
FrameBuilder curframe = new FramedataImpl1();
try {
curframe.setPayload( ByteBuffer.wrap( Charsetfunctions.utf8Bytes( text ) ) );
} catch ( InvalidDataException e ) {
throw new NotSendableException( e );
}
curframe.setFin( true );
curframe.setOptcode( Opcode.TEXT );
curframe.setTransferemasked( mask );
return Collections.singletonList( (Framedata) curframe );
}
private byte fromOpcode( Opcode opcode ) {
if( opcode == Opcode.CONTINUOUS )
return 0;
else if( opcode == Opcode.TEXT )
return 1;
else if( opcode == Opcode.BINARY )
return 2;
else if( opcode == Opcode.CLOSING )
return 8;
else if( opcode == Opcode.PING )
return 9;
else if( opcode == Opcode.PONG )
return 10;
throw new RuntimeException( "Don't know how to handle " + opcode.toString() );
}
private String generateFinalKey( String in ) {
String seckey = in.trim();
String acc = seckey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
MessageDigest sh1;
try {
sh1 = MessageDigest.getInstance( "SHA1" );
} catch ( NoSuchAlgorithmException e ) {
throw new RuntimeException( e );
}
return Base64.encodeBytes( sh1.digest( acc.getBytes() ) );
}
@Override
public ClientHandshakeBuilder postProcessHandshakeRequestAsClient( ClientHandshakeBuilder request ) {
request.put( "Upgrade", "websocket" );
request.put( "Connection", "Upgrade" ); // to respond to a Connection keep alives
request.put( "Sec-WebSocket-Version", "8" );
byte[] random = new byte[ 16 ];
reuseableRandom.nextBytes( random );
request.put( "Sec-WebSocket-Key", Base64.encodeBytes( random ) );
return request;
}
@Override
public HandshakeBuilder postProcessHandshakeResponseAsServer( ClientHandshake request, ServerHandshakeBuilder response ) throws InvalidHandshakeException {
response.put( "Upgrade", "websocket" );
response.put( "Connection", request.getFieldValue( "Connection" ) ); // to respond to a Connection keep alives
response.setHttpStatusMessage( "Switching Protocols" );
String seckey = request.getFieldValue( "Sec-WebSocket-Key" );
if( seckey == null )
throw new InvalidHandshakeException( "missing Sec-WebSocket-Key" );
response.put( "Sec-WebSocket-Accept", generateFinalKey( seckey ) );
return response;
}
private byte[] toByteArray( long val, int bytecount ) {
byte[] buffer = new byte[ bytecount ];
int highest = 8 * bytecount - 8;
for( int i = 0 ; i < bytecount ; i++ ) {
buffer[ i ] = (byte) ( val >>> ( highest - 8 * i ) );
}
return buffer;
}
private Opcode toOpcode( byte opcode ) throws InvalidFrameException {
switch ( opcode ) {
case 0:
return Opcode.CONTINUOUS;
case 1:
return Opcode.TEXT;
case 2:
return Opcode.BINARY;
// 3-7 are not yet defined
case 8:
return Opcode.CLOSING;
case 9:
return Opcode.PING;
case 10:
return Opcode.PONG;
// 11-15 are not yet defined
default :
throw new InvalidFrameException( "unknow optcode " + (short) opcode );
}
}
@Override
public List<Framedata> translateFrame( ByteBuffer buffer ) throws LimitExedeedException , InvalidDataException {
List<Framedata> frames = new LinkedList<Framedata>();
Framedata cur;
if( incompleteframe != null ) {
// complete an incomplete frame
while ( true ) {
try {
buffer.mark();
int available_next_byte_count = buffer.remaining();// The number of bytes received
int expected_next_byte_count = incompleteframe.remaining();// The number of bytes to complete the incomplete frame
if( expected_next_byte_count > available_next_byte_count ) {
// did not receive enough bytes to complete the frame
incompleteframe.put( buffer.array(), buffer.position(), available_next_byte_count );
buffer.position( buffer.position() + available_next_byte_count );
return Collections.emptyList();
}
incompleteframe.put( buffer.array(), buffer.position(), expected_next_byte_count );
buffer.position( buffer.position() + expected_next_byte_count );
cur = translateSingleFrame( (ByteBuffer) incompleteframe.duplicate().position( 0 ) );
frames.add( cur );
incompleteframe = null;
break; // go on with the normal frame receival
} catch ( IncompleteException e ) {
// extending as much as suggested
int oldsize = incompleteframe.limit();
ByteBuffer extendedframe = ByteBuffer.allocate( checkAlloc( e.getPreferedSize() ) );
assert ( extendedframe.limit() > incompleteframe.limit() );
incompleteframe.rewind();
extendedframe.put( incompleteframe );
incompleteframe = extendedframe;
return translateFrame( buffer );
}
}
}
while ( buffer.hasRemaining() ) {// Read as much as possible full frames
buffer.mark();
try {
cur = translateSingleFrame( buffer );
frames.add( cur );
} catch ( IncompleteException e ) {
// remember the incomplete data
buffer.reset();
int pref = e.getPreferedSize();
incompleteframe = ByteBuffer.allocate( checkAlloc( pref ) );
incompleteframe.put( buffer );
break;
}
}
return frames;
}
public Framedata translateSingleFrame( ByteBuffer buffer ) throws IncompleteException , InvalidDataException {
int maxpacketsize = buffer.remaining();
int realpacketsize = 2;
if( maxpacketsize < realpacketsize )
throw new IncompleteException( realpacketsize );
byte b1 = buffer.get( /*0*/);
boolean FIN = b1 >> 8 != 0;
byte rsv = (byte) ( ( b1 & ~(byte) 128 ) >> 4 );
if( rsv != 0 )
throw new InvalidFrameException( "bad rsv " + rsv );
byte b2 = buffer.get( /*1*/);
boolean MASK = ( b2 & -128 ) != 0;
int payloadlength = (byte) ( b2 & ~(byte) 128 );
Opcode optcode = toOpcode( (byte) ( b1 & 15 ) );
if( !FIN ) {
if( optcode == Opcode.PING || optcode == Opcode.PONG || optcode == Opcode.CLOSING ) {
throw new InvalidFrameException( "control frames may no be fragmented" );
}
}
if( payloadlength >= 0 && payloadlength <= 125 ) {
} else {
if( optcode == Opcode.PING || optcode == Opcode.PONG || optcode == Opcode.CLOSING ) {
throw new InvalidFrameException( "more than 125 octets" );
}
if( payloadlength == 126 ) {
realpacketsize += 2; // additional length bytes
if( maxpacketsize < realpacketsize )
throw new IncompleteException( realpacketsize );
byte[] sizebytes = new byte[ 3 ];
sizebytes[ 1 ] = buffer.get( /*1 + 1*/);
sizebytes[ 2 ] = buffer.get( /*1 + 2*/);
payloadlength = new BigInteger( sizebytes ).intValue();
} else {
realpacketsize += 8; // additional length bytes
if( maxpacketsize < realpacketsize )
throw new IncompleteException( realpacketsize );
byte[] bytes = new byte[ 8 ];
for( int i = 0 ; i < 8 ; i++ ) {
bytes[ i ] = buffer.get( /*1 + i*/);
}
long length = new BigInteger( bytes ).longValue();
if( length > Integer.MAX_VALUE ) {
throw new LimitExedeedException( "Payloadsize is to big..." );
} else {
payloadlength = (int) length;
}
}
}
// int maskskeystart = foff + realpacketsize;
realpacketsize += ( MASK ? 4 : 0 );
// int payloadstart = foff + realpacketsize;
realpacketsize += payloadlength;
if( maxpacketsize < realpacketsize )
throw new IncompleteException( realpacketsize );
ByteBuffer payload = ByteBuffer.allocate( checkAlloc( payloadlength ) );
if( MASK ) {
byte[] maskskey = new byte[ 4 ];
buffer.get( maskskey );
for( int i = 0 ; i < payloadlength ; i++ ) {
payload.put( (byte) ( (byte) buffer.get( /*payloadstart + i*/) ^ (byte) maskskey[ i % 4 ] ) );
}
} else {
payload.put( buffer.array(), buffer.position(), payload.limit() );
buffer.position( buffer.position() + payload.limit() );
}
FrameBuilder frame;
if( optcode == Opcode.CLOSING ) {
frame = new CloseFrameBuilder();
} else {
frame = new FramedataImpl1();
frame.setFin( FIN );
frame.setOptcode( optcode );
}
payload.flip();
frame.setPayload( payload );
return frame;
}
@Override
public void reset() {
incompleteframe = null;
}
@Override
public Draft copyInstance() {
return new Draft_10();
}
@Override
public CloseHandshakeType getCloseHandshakeType() {
return CloseHandshakeType.TWOWAY;
}
}
@@ -0,0 +1,28 @@
package org.java_websocket.drafts;
import org.java_websocket.exceptions.InvalidHandshakeException;
import org.java_websocket.handshake.ClientHandshake;
import org.java_websocket.handshake.ClientHandshakeBuilder;
public class Draft_17 extends Draft_10 {
@Override
public HandshakeState acceptHandshakeAsServer( ClientHandshake handshakedata ) throws InvalidHandshakeException {
int v = readVersion( handshakedata );
if( v == 13 )
return HandshakeState.MATCHED;
return HandshakeState.NOT_MATCHED;
}
@Override
public ClientHandshakeBuilder postProcessHandshakeRequestAsClient( ClientHandshakeBuilder request ) {
super.postProcessHandshakeRequestAsClient( request );
request.put( "Sec-WebSocket-Version", "13" );// overwriting the previous
return request;
}
@Override
public Draft copyInstance() {
return new Draft_17();
}
}
@@ -0,0 +1,206 @@
package org.java_websocket.drafts;
import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
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;
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;
import org.java_websocket.handshake.ServerHandshake;
import org.java_websocket.handshake.ServerHandshakeBuilder;
import org.java_websocket.util.Charsetfunctions;
public class Draft_75 extends Draft {
/**
* The byte representing CR, or Carriage Return, or \r
*/
public static final byte CR = (byte) 0x0D;
/**
* The byte representing LF, or Line Feed, or \n
*/
public static final byte LF = (byte) 0x0A;
/**
* The byte representing the beginning of a WebSocket text frame.
*/
public static final byte START_OF_FRAME = (byte) 0x00;
/**
* The byte representing the end of a WebSocket text frame.
*/
public static final byte END_OF_FRAME = (byte) 0xFF;
/** Is only used to detect protocol violations */
protected boolean readingState = false;
protected List<Framedata> readyframes = new LinkedList<Framedata>();
protected ByteBuffer currentFrame;
private final Random reuseableRandom = new Random();
@Override
public HandshakeState acceptHandshakeAsClient( ClientHandshake request, ServerHandshake response ) {
return request.getFieldValue( "WebSocket-Origin" ).equals( response.getFieldValue( "Origin" ) ) && basicAccept( response ) ? HandshakeState.MATCHED : HandshakeState.NOT_MATCHED;
}
@Override
public HandshakeState acceptHandshakeAsServer( ClientHandshake handshakedata ) {
if( handshakedata.hasFieldValue( "Origin" ) && basicAccept( handshakedata ) ) {
return HandshakeState.MATCHED;
}
return HandshakeState.NOT_MATCHED;
}
@Override
public ByteBuffer createBinaryFrame( Framedata framedata ) {
if( framedata.getOpcode() != Opcode.TEXT ) {
throw new RuntimeException( "only text frames supported" );
}
ByteBuffer pay = framedata.getPayloadData();
ByteBuffer b = ByteBuffer.allocate( pay.remaining() + 2 );
b.put( START_OF_FRAME );
pay.mark();
b.put( pay );
pay.reset();
b.put( END_OF_FRAME );
b.flip();
return b;
}
@Override
public List<Framedata> createFrames( ByteBuffer binary, boolean mask ) {
throw new RuntimeException( "not yet implemented" );
}
@Override
public List<Framedata> createFrames( String text, boolean mask ) {
FrameBuilder frame = new FramedataImpl1();
try {
frame.setPayload( ByteBuffer.wrap( Charsetfunctions.utf8Bytes( text ) ) );
} catch ( InvalidDataException e ) {
throw new NotSendableException( e );
}
frame.setFin( true );
frame.setOptcode( Opcode.TEXT );
frame.setTransferemasked( mask );
return Collections.singletonList( (Framedata) frame );
}
@Override
public ClientHandshakeBuilder postProcessHandshakeRequestAsClient( ClientHandshakeBuilder request ) throws InvalidHandshakeException {
request.put( "Upgrade", "WebSocket" );
request.put( "Connection", "Upgrade" );
if( !request.hasFieldValue( "Origin" ) ) {
request.put( "Origin", "random" + reuseableRandom.nextInt() );
}
return request;
}
@Override
public HandshakeBuilder postProcessHandshakeResponseAsServer( ClientHandshake request, ServerHandshakeBuilder response ) throws InvalidHandshakeException {
response.setHttpStatusMessage( "Web Socket Protocol Handshake" );
response.put( "Upgrade", "WebSocket" );
response.put( "Connection", request.getFieldValue( "Connection" ) ); // to respond to a Connection keep alive
response.put( "WebSocket-Origin", request.getFieldValue( "Origin" ) );
String location = "ws://" + request.getFieldValue( "Host" ) + request.getResourceDescriptor();
response.put( "WebSocket-Location", location );
// TODO handle Sec-WebSocket-Protocol and Set-Cookie
return response;
}
protected List<Framedata> translateRegularFrame( ByteBuffer buffer ) throws InvalidDataException {
while ( buffer.hasRemaining() ) {
byte newestByte = buffer.get();
if( newestByte == START_OF_FRAME ) { // Beginning of Frame
if( readingState )
throw new InvalidFrameException( "unexpected START_OF_FRAME" );
readingState = true;
} else if( newestByte == END_OF_FRAME ) { // End of Frame
if( !readingState )
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 ) {
currentFrame.flip();
FramedataImpl1 curframe = new FramedataImpl1();
curframe.setPayload( currentFrame );
curframe.setFin( true );
curframe.setOptcode( Opcode.TEXT );
readyframes.add( curframe );
this.currentFrame = null;
buffer.mark();
}
readingState = 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();
} else if( !currentFrame.hasRemaining() ) {
currentFrame = increaseBuffer( currentFrame );
}
currentFrame.put( newestByte );
} else {
return null;
}
}
// if no error occurred this block will be reached
/*if( readingState ) {
checkAlloc(currentFrame.position()+1);
}*/
List<Framedata> frames = readyframes;
readyframes = new LinkedList<Framedata>();
return frames;
}
@Override
public List<Framedata> translateFrame( ByteBuffer buffer ) throws InvalidDataException {
List<Framedata> frames = translateRegularFrame( buffer );
if( frames == null ) {
throw new InvalidDataException( CloseFrame.PROTOCOL_ERROR );
}
return frames;
}
@Override
public void reset() {
readingState = false;
this.currentFrame = null;
}
@Override
public CloseHandshakeType getCloseHandshakeType() {
return CloseHandshakeType.NONE;
}
public ByteBuffer createBuffer() {
return ByteBuffer.allocate( INITIAL_FAMESIZE );
}
public ByteBuffer increaseBuffer( ByteBuffer full ) throws LimitExedeedException , InvalidDataException {
full.flip();
ByteBuffer newbuffer = ByteBuffer.allocate( checkAlloc( full.capacity() * 2 ) );
newbuffer.put( full );
return newbuffer;
}
@Override
public Draft copyInstance() {
return new Draft_75();
}
}
@@ -0,0 +1,242 @@
package org.java_websocket.drafts;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import org.java_websocket.WebSocket.Role;
import org.java_websocket.exceptions.IncompleteHandshakeException;
import org.java_websocket.exceptions.InvalidDataException;
import org.java_websocket.exceptions.InvalidFrameException;
import org.java_websocket.exceptions.InvalidHandshakeException;
import org.java_websocket.framing.CloseFrame;
import org.java_websocket.framing.CloseFrameBuilder;
import org.java_websocket.framing.Framedata;
import org.java_websocket.framing.Framedata.Opcode;
import org.java_websocket.handshake.ClientHandshake;
import org.java_websocket.handshake.ClientHandshakeBuilder;
import org.java_websocket.handshake.HandshakeBuilder;
import org.java_websocket.handshake.Handshakedata;
import org.java_websocket.handshake.ServerHandshake;
import org.java_websocket.handshake.ServerHandshakeBuilder;
public class Draft_76 extends Draft_75 {
private boolean failed = false;
private static final byte[] closehandshake = { -1, 0 };
private final Random reuseableRandom = new Random();
public static byte[] createChallenge( String key1, String key2, byte[] key3 ) throws InvalidHandshakeException {
byte[] part1 = getPart( key1 );
byte[] part2 = getPart( key2 );
byte[] challenge = new byte[ 16 ];
challenge[ 0 ] = part1[ 0 ];
challenge[ 1 ] = part1[ 1 ];
challenge[ 2 ] = part1[ 2 ];
challenge[ 3 ] = part1[ 3 ];
challenge[ 4 ] = part2[ 0 ];
challenge[ 5 ] = part2[ 1 ];
challenge[ 6 ] = part2[ 2 ];
challenge[ 7 ] = part2[ 3 ];
challenge[ 8 ] = key3[ 0 ];
challenge[ 9 ] = key3[ 1 ];
challenge[ 10 ] = key3[ 2 ];
challenge[ 11 ] = key3[ 3 ];
challenge[ 12 ] = key3[ 4 ];
challenge[ 13 ] = key3[ 5 ];
challenge[ 14 ] = key3[ 6 ];
challenge[ 15 ] = key3[ 7 ];
MessageDigest md5;
try {
md5 = MessageDigest.getInstance( "MD5" );
} catch ( NoSuchAlgorithmException e ) {
throw new RuntimeException( e );
}
return md5.digest( challenge );
}
private static String generateKey() {
Random r = new Random();
long maxNumber = 4294967295L;
long spaces = r.nextInt( 12 ) + 1;
int max = new Long( maxNumber / spaces ).intValue();
max = Math.abs( max );
int number = r.nextInt( max ) + 1;
long product = number * spaces;
String key = Long.toString( product );
// always insert atleast one random character
int numChars = r.nextInt( 12 ) + 1;
for( int i = 0 ; i < numChars ; i++ ) {
int position = r.nextInt( key.length() );
position = Math.abs( position );
char randChar = (char) ( r.nextInt( 95 ) + 33 );
// exclude numbers here
if( randChar >= 48 && randChar <= 57 ) {
randChar -= 15;
}
key = new StringBuilder( key ).insert( position, randChar ).toString();
}
for( int i = 0 ; i < spaces ; i++ ) {
int position = r.nextInt( key.length() - 1 ) + 1;
position = Math.abs( position );
key = new StringBuilder( key ).insert( position, "\u0020" ).toString();
}
return key;
}
private static byte[] getPart( String key ) throws InvalidHandshakeException {
try {
long keyNumber = Long.parseLong( key.replaceAll( "[^0-9]", "" ) );
long keySpace = key.split( "\u0020" ).length - 1;
if( keySpace == 0 ) {
throw new InvalidHandshakeException( "invalid Sec-WebSocket-Key (/key2/)" );
}
long part = new Long( keyNumber / keySpace );
return new byte[]{ (byte) ( part >> 24 ), (byte) ( ( part << 8 ) >> 24 ), (byte) ( ( part << 16 ) >> 24 ), (byte) ( ( part << 24 ) >> 24 ) };
} catch ( NumberFormatException e ) {
throw new InvalidHandshakeException( "invalid Sec-WebSocket-Key (/key1/ or /key2/)" );
}
}
@Override
public HandshakeState acceptHandshakeAsClient( ClientHandshake request, ServerHandshake response ) {
if( failed ) {
return HandshakeState.NOT_MATCHED;
}
try {
if( !response.getFieldValue( "Sec-WebSocket-Origin" ).equals( request.getFieldValue( "Origin" ) ) || !basicAccept( response ) ) {
return HandshakeState.NOT_MATCHED;
}
byte[] content = response.getContent();
if( content == null || content.length == 0 ) {
throw new IncompleteHandshakeException();
}
if( Arrays.equals( content, createChallenge( request.getFieldValue( "Sec-WebSocket-Key1" ), request.getFieldValue( "Sec-WebSocket-Key2" ), request.getContent() ) ) ) {
return HandshakeState.MATCHED;
} else {
return HandshakeState.NOT_MATCHED;
}
} catch ( InvalidHandshakeException e ) {
throw new RuntimeException( "bad handshakerequest", e );
}
}
@Override
public HandshakeState acceptHandshakeAsServer( ClientHandshake handshakedata ) {
if( handshakedata.getFieldValue( "Upgrade" ).equals( "WebSocket" ) && handshakedata.getFieldValue( "Connection" ).contains( "Upgrade" ) && handshakedata.getFieldValue( "Sec-WebSocket-Key1" ).length() > 0 && !handshakedata.getFieldValue( "Sec-WebSocket-Key2" ).isEmpty() && handshakedata.hasFieldValue( "Origin" ) )
return HandshakeState.MATCHED;
return HandshakeState.NOT_MATCHED;
}
@Override
public ClientHandshakeBuilder postProcessHandshakeRequestAsClient( ClientHandshakeBuilder request ) {
request.put( "Upgrade", "WebSocket" );
request.put( "Connection", "Upgrade" );
request.put( "Sec-WebSocket-Key1", generateKey() );
request.put( "Sec-WebSocket-Key2", generateKey() );
if( !request.hasFieldValue( "Origin" ) ) {
request.put( "Origin", "random" + reuseableRandom.nextInt() );
}
byte[] key3 = new byte[ 8 ];
reuseableRandom.nextBytes( key3 );
request.setContent( key3 );
return request;
}
@Override
public HandshakeBuilder postProcessHandshakeResponseAsServer( ClientHandshake request, ServerHandshakeBuilder response ) throws InvalidHandshakeException {
response.setHttpStatusMessage( "WebSocket Protocol Handshake" );
response.put( "Upgrade", "WebSocket" );
response.put( "Connection", request.getFieldValue( "Connection" ) ); // to respond to a Connection keep alive
response.put( "Sec-WebSocket-Origin", request.getFieldValue( "Origin" ) );
String location = "ws://" + request.getFieldValue( "Host" ) + request.getResourceDescriptor();
response.put( "Sec-WebSocket-Location", location );
String key1 = request.getFieldValue( "Sec-WebSocket-Key1" );
String key2 = request.getFieldValue( "Sec-WebSocket-Key2" );
byte[] key3 = request.getContent();
if( key1 == null || key2 == null || key3 == null || key3.length != 8 ) {
throw new InvalidHandshakeException( "Bad keys" );
}
response.setContent( createChallenge( key1, key2, key3 ) );
return response;
}
@Override
public Handshakedata translateHandshake( ByteBuffer buf ) throws InvalidHandshakeException {
HandshakeBuilder bui = translateHandshakeHttp( buf, role );
// the first drafts are lacking a protocol number which makes them difficult to distinguish. Sec-WebSocket-Key1 is typical for draft76
if( ( bui.hasFieldValue( "Sec-WebSocket-Key1" ) || role == Role.CLIENT ) && !bui.hasFieldValue( "Sec-WebSocket-Version" ) ) {
byte[] key3 = new byte[ role == Role.SERVER ? 8 : 16 ];
try {
buf.get( key3 );
} catch ( BufferUnderflowException e ) {
throw new IncompleteHandshakeException( buf.capacity() + 16 );
}
bui.setContent( key3 );
}
return bui;
}
@Override
public List<Framedata> translateFrame( ByteBuffer buffer ) throws InvalidDataException {
buffer.mark();
List<Framedata> frames = super.translateRegularFrame( buffer );
if( frames == null ) {
buffer.reset();
frames = readyframes;
readingState = true;
if( currentFrame == null )
currentFrame = ByteBuffer.allocate( 2 );
else {
throw new InvalidFrameException();
}
if( buffer.remaining() > currentFrame.remaining() ) {
throw new InvalidFrameException();
} else {
currentFrame.put( buffer );
}
if( !currentFrame.hasRemaining() ) {
if( Arrays.equals( currentFrame.array(), closehandshake ) ) {
frames.add( new CloseFrameBuilder( CloseFrame.NORMAL ) );
return frames;
}
else{
throw new InvalidFrameException();
}
} else {
readyframes = new LinkedList<Framedata>();
return frames;
}
} else {
return frames;
}
}
@Override
public ByteBuffer createBinaryFrame( Framedata framedata ) {
if( framedata.getOpcode() == Opcode.CLOSING )
return ByteBuffer.wrap( closehandshake );
return super.createBinaryFrame( framedata );
}
@Override
public CloseHandshakeType getCloseHandshakeType() {
return CloseHandshakeType.ONEWAY;
}
@Override
public Draft copyInstance() {
return new Draft_76();
}
}
@@ -0,0 +1,20 @@
package org.java_websocket.exceptions;
public class IncompleteHandshakeException extends RuntimeException {
private static final long serialVersionUID = 7906596804233893092L;
private int newsize;
public IncompleteHandshakeException( int newsize ) {
this.newsize = newsize;
}
public IncompleteHandshakeException() {
this.newsize = 0;
}
public int getPreferedSize() {
return newsize;
}
}
@@ -0,0 +1,34 @@
package org.java_websocket.exceptions;
public class InvalidDataException extends Exception {
/**
* Serializable
*/
private static final long serialVersionUID = 3731842424390998726L;
private int closecode;
public InvalidDataException( int closecode ) {
this.closecode = closecode;
}
public InvalidDataException( int closecode , String s ) {
super( s );
this.closecode = closecode;
}
public InvalidDataException( int closecode , Throwable t ) {
super( t );
this.closecode = closecode;
}
public InvalidDataException( int closecode , String s , Throwable t ) {
super( s, t );
this.closecode = closecode;
}
public int getCloseCode() {
return closecode;
}
}
@@ -0,0 +1,27 @@
package org.java_websocket.exceptions;
import org.java_websocket.framing.CloseFrame;
public class InvalidFrameException extends InvalidDataException {
/**
* Serializable
*/
private static final long serialVersionUID = -9016496369828887591L;
public InvalidFrameException() {
super( CloseFrame.PROTOCOL_ERROR );
}
public InvalidFrameException( String arg0 ) {
super( CloseFrame.PROTOCOL_ERROR, arg0 );
}
public InvalidFrameException( Throwable arg0 ) {
super( CloseFrame.PROTOCOL_ERROR, arg0 );
}
public InvalidFrameException( String arg0 , Throwable arg1 ) {
super( CloseFrame.PROTOCOL_ERROR, arg0, arg1 );
}
}
@@ -0,0 +1,28 @@
package org.java_websocket.exceptions;
import org.java_websocket.framing.CloseFrame;
public class InvalidHandshakeException extends InvalidDataException {
/**
* Serializable
*/
private static final long serialVersionUID = -1426533877490484964L;
public InvalidHandshakeException() {
super( CloseFrame.PROTOCOL_ERROR );
}
public InvalidHandshakeException( String arg0 , Throwable arg1 ) {
super( CloseFrame.PROTOCOL_ERROR, arg0, arg1 );
}
public InvalidHandshakeException( String arg0 ) {
super( CloseFrame.PROTOCOL_ERROR, arg0 );
}
public InvalidHandshakeException( Throwable arg0 ) {
super( CloseFrame.PROTOCOL_ERROR, arg0 );
}
}
@@ -0,0 +1,20 @@
package org.java_websocket.exceptions;
import org.java_websocket.framing.CloseFrame;
public class LimitExedeedException extends InvalidDataException {
/**
* Serializable
*/
private static final long serialVersionUID = 6908339749836826785L;
public LimitExedeedException() {
super( CloseFrame.TOOBIG );
}
public LimitExedeedException( String s ) {
super( CloseFrame.TOOBIG, s );
}
}
@@ -0,0 +1,25 @@
package org.java_websocket.exceptions;
public class NotSendableException extends RuntimeException {
/**
* Serializable
*/
private static final long serialVersionUID = -6468967874576651628L;
public NotSendableException() {
}
public NotSendableException( String message ) {
super( message );
}
public NotSendableException( Throwable cause ) {
super( cause );
}
public NotSendableException( String message , Throwable cause ) {
super( message, cause );
}
}
@@ -0,0 +1,5 @@
package org.java_websocket.exceptions;
public class WebsocketNotConnectedException extends RuntimeException {
}
@@ -0,0 +1,98 @@
package org.java_websocket.framing;
import org.java_websocket.exceptions.InvalidDataException;
import org.java_websocket.exceptions.InvalidFrameException;
public interface CloseFrame extends Framedata {
/**
* indicates a normal closure, meaning whatever purpose the
* connection was established for has been fulfilled.
*/
public static final int NORMAL = 1000;
/**
* 1001 indicates that an endpoint is "going away", such as a server
* going down, or a browser having navigated away from a page.
*/
public static final int GOING_AWAY = 1001;
/**
* 1002 indicates that an endpoint is terminating the connection due
* to a protocol error.
*/
public static final int PROTOCOL_ERROR = 1002;
/**
* 1003 indicates that an endpoint is terminating the connection
* because it has received a type of data it cannot accept (e.g. an
* endpoint that understands only text data MAY send this if it
* receives a binary message).
*/
public static final int REFUSE = 1003;
/*1004: Reserved. The specific meaning might be defined in the future.*/
/**
* 1005 is a reserved value and MUST NOT be set as a status code in a
* Close control frame by an endpoint. It is designated for use in
* applications expecting a status code to indicate that no status
* code was actually present.
*/
public static final int NOCODE = 1005;
/**
* 1006 is a reserved value and MUST NOT be set as a status code in a
* Close control frame by an endpoint. It is designated for use in
* applications expecting a status code to indicate that the
* connection was closed abnormally, e.g. without sending or
* receiving a Close control frame.
*/
public static final int ABNORMAL_CLOSE = 1006;
/**
* 1007 indicates that an endpoint is terminating the connection
* because it has received data within a message that was not
* consistent with the type of the message (e.g., non-UTF-8 [RFC3629]
* data within a text message).
*/
public static final int NO_UTF8 = 1007;
/**
* 1008 indicates that an endpoint is terminating the connection
* because it has received a message that violates its policy. This
* is a generic status code that can be returned when there is no
* other more suitable status code (e.g. 1003 or 1009), or if there
* is a need to hide specific details about the policy.
*/
public static final int POLICY_VALIDATION = 1008;
/**
* 1009 indicates that an endpoint is terminating the connection
* because it has received a message which is too big for it to
* process.
*/
public static final int TOOBIG = 1009;
/**
* 1010 indicates that an endpoint (client) is terminating the
* connection because it has expected the server to negotiate one or
* more extension, but the server didn't return them in the response
* message of the WebSocket handshake. The list of extensions which
* are needed SHOULD appear in the /reason/ part of the Close frame.
* Note that this status code is not used by the server, because it
* can fail the WebSocket handshake instead.
*/
public static final int EXTENSION = 1010;
/**
* 1011 indicates that a server is terminating the connection because
* it encountered an unexpected condition that prevented it from
* fulfilling the request.
**/
public static final int UNEXPECTED_CONDITION = 1011;
/**
* 1015 is a reserved value and MUST NOT be set as a status code in a
* Close control frame by an endpoint. It is designated for use in
* applications expecting a status code to indicate that the
* connection was closed due to a failure to perform a TLS handshake
* (e.g., the server certificate can't be verified).
**/
public static final int TLS_ERROR = 1015;
/** The connection had never been established */
public static final int NEVER_CONNECTED = -1;
public static final int BUGGYCLOSE = -2;
public static final int FLASHPOLICY = -3;
public int getCloseCode() throws InvalidFrameException;
public String getMessage() throws InvalidDataException;
}
@@ -0,0 +1,123 @@
package org.java_websocket.framing;
import java.nio.ByteBuffer;
import org.java_websocket.exceptions.InvalidDataException;
import org.java_websocket.exceptions.InvalidFrameException;
import org.java_websocket.util.Charsetfunctions;
public class CloseFrameBuilder extends FramedataImpl1 implements CloseFrame {
static final ByteBuffer emptybytebuffer = ByteBuffer.allocate( 0 );
private int code;
private String reason;
public CloseFrameBuilder() {
super( Opcode.CLOSING );
setFin( true );
}
public CloseFrameBuilder( int code ) throws InvalidDataException {
super( Opcode.CLOSING );
setFin( true );
setCodeAndMessage( code, "" );
}
public CloseFrameBuilder( int code , String m ) throws InvalidDataException {
super( Opcode.CLOSING );
setFin( true );
setCodeAndMessage( code, m );
}
private void setCodeAndMessage( int code, String m ) throws InvalidDataException {
if( m == null ) {
m = "";
}
// CloseFrame.TLS_ERROR is not allowed to be transfered over the wire
if( code == CloseFrame.TLS_ERROR ) {
code = CloseFrame.NOCODE;
m = "";
}
if( code == CloseFrame.NOCODE ) {
if( 0 < m.length() ) {
throw new InvalidDataException( PROTOCOL_ERROR, "A close frame must have a closecode if it has a reason" );
}
return;// empty payload
}
byte[] by = Charsetfunctions.utf8Bytes( m );
ByteBuffer buf = ByteBuffer.allocate( 4 );
buf.putInt( code );
buf.position( 2 );
ByteBuffer pay = ByteBuffer.allocate( 2 + by.length );
pay.put( buf );
pay.put( by );
pay.rewind();
setPayload( pay );
}
private void initCloseCode() throws InvalidFrameException {
code = CloseFrame.NOCODE;
ByteBuffer payload = super.getPayloadData();
payload.mark();
if( payload.remaining() >= 2 ) {
ByteBuffer bb = ByteBuffer.allocate( 4 );
bb.position( 2 );
bb.putShort( payload.getShort() );
bb.position( 0 );
code = bb.getInt();
if( code == CloseFrame.ABNORMAL_CLOSE || code == CloseFrame.TLS_ERROR || code == CloseFrame.NOCODE || code > 4999 || code < 1000 || code == 1004 ) {
throw new InvalidFrameException( "closecode must not be sent over the wire: " + code );
}
}
payload.reset();
}
@Override
public int getCloseCode() {
return code;
}
private void initMessage() throws InvalidDataException {
if( code == CloseFrame.NOCODE ) {
reason = Charsetfunctions.stringUtf8( super.getPayloadData() );
} else {
ByteBuffer b = super.getPayloadData();
int mark = b.position();// because stringUtf8 also creates a mark
try {
b.position( b.position() + 2 );
reason = Charsetfunctions.stringUtf8( b );
} catch ( IllegalArgumentException e ) {
throw new InvalidFrameException( e );
} finally {
b.position( mark );
}
}
}
@Override
public String getMessage() {
return reason;
}
@Override
public String toString() {
return super.toString() + "code: " + code;
}
@Override
public void setPayload( ByteBuffer payload ) throws InvalidDataException {
super.setPayload( payload );
initCloseCode();
initMessage();
}
@Override
public ByteBuffer getPayloadData() {
if( code == NOCODE )
return emptybytebuffer;
return super.getPayloadData();
}
}
@@ -0,0 +1,17 @@
package org.java_websocket.framing;
import java.nio.ByteBuffer;
import org.java_websocket.exceptions.InvalidDataException;
public interface FrameBuilder extends Framedata {
public abstract void setFin( boolean fin );
public abstract void setOptcode( Opcode optcode );
public abstract void setPayload( ByteBuffer payload ) throws InvalidDataException;
public abstract void setTransferemasked( boolean transferemasked );
}
@@ -0,0 +1,17 @@
package org.java_websocket.framing;
import java.nio.ByteBuffer;
import org.java_websocket.exceptions.InvalidFrameException;
public interface Framedata {
public enum Opcode {
CONTINUOUS, TEXT, BINARY, PING, PONG, CLOSING
// more to come
}
public boolean isFin();
public boolean getTransfereMasked();
public Opcode getOpcode();
public ByteBuffer getPayloadData();// TODO the separation of the application data and the extension data is yet to be done
public abstract void append( Framedata nextframe ) throws InvalidFrameException;
}
@@ -0,0 +1,110 @@
package org.java_websocket.framing;
import java.nio.ByteBuffer;
import java.util.Arrays;
import org.java_websocket.exceptions.InvalidDataException;
import org.java_websocket.exceptions.InvalidFrameException;
import org.java_websocket.util.Charsetfunctions;
public class FramedataImpl1 implements FrameBuilder {
protected static byte[] emptyarray = {};
protected boolean fin;
protected Opcode optcode;
private ByteBuffer unmaskedpayload;
protected boolean transferemasked;
public FramedataImpl1() {
}
public FramedataImpl1( Opcode op ) {
this.optcode = op;
unmaskedpayload = ByteBuffer.wrap( emptyarray );
}
/**
* Helper constructor which helps to create "echo" frames.
* The new object will use the same underlying payload data.
**/
public FramedataImpl1( Framedata f ) {
fin = f.isFin();
optcode = f.getOpcode();
unmaskedpayload = f.getPayloadData();
transferemasked = f.getTransfereMasked();
}
@Override
public boolean isFin() {
return fin;
}
@Override
public Opcode getOpcode() {
return optcode;
}
@Override
public boolean getTransfereMasked() {
return transferemasked;
}
@Override
public ByteBuffer getPayloadData() {
return unmaskedpayload;
}
@Override
public void setFin( boolean fin ) {
this.fin = fin;
}
@Override
public void setOptcode( Opcode optcode ) {
this.optcode = optcode;
}
@Override
public void setPayload( ByteBuffer payload ) throws InvalidDataException {
unmaskedpayload = payload;
}
@Override
public void setTransferemasked( boolean transferemasked ) {
this.transferemasked = transferemasked;
}
@Override
public void append( Framedata nextframe ) throws InvalidFrameException {
ByteBuffer b = nextframe.getPayloadData();
if( unmaskedpayload == null ) {
unmaskedpayload = ByteBuffer.allocate( b.remaining() );
b.mark();
unmaskedpayload.put( b );
b.reset();
} else {
b.mark();
unmaskedpayload.position( unmaskedpayload.limit() );
unmaskedpayload.limit( unmaskedpayload.capacity() );
if( b.remaining() > unmaskedpayload.remaining() ) {
ByteBuffer tmp = ByteBuffer.allocate( b.remaining() + unmaskedpayload.capacity() );
unmaskedpayload.flip();
tmp.put( unmaskedpayload );
tmp.put( b );
unmaskedpayload = tmp;
} else {
unmaskedpayload.put( b );
}
unmaskedpayload.rewind();
b.reset();
}
fin = nextframe.isFin();
}
@Override
public String toString() {
return "Framedata{ optcode:" + getOpcode() + ", fin:" + isFin() + ", payloadlength:[pos:" + unmaskedpayload.position() + ", len:" + unmaskedpayload.remaining() + "], payload:" + Arrays.toString( Charsetfunctions.utf8Bytes( new String( unmaskedpayload.array() ) ) ) + "}";
}
}
@@ -0,0 +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();
}
@@ -0,0 +1,5 @@
package org.java_websocket.handshake;
public interface ClientHandshakeBuilder extends HandshakeBuilder, ClientHandshake {
public void setResourceDescriptor( String resourceDescriptor );
}
@@ -0,0 +1,6 @@
package org.java_websocket.handshake;
public interface HandshakeBuilder extends Handshakedata {
public abstract void setContent( byte[] content );
public abstract void put( String name, String value );
}
@@ -0,0 +1,18 @@
package org.java_websocket.handshake;
public class HandshakeImpl1Client extends HandshakedataImpl1 implements ClientHandshakeBuilder {
private String resourceDescriptor = "*";
public HandshakeImpl1Client() {
}
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;
}
}
@@ -0,0 +1,29 @@
package org.java_websocket.handshake;
public class HandshakeImpl1Server extends HandshakedataImpl1 implements ServerHandshakeBuilder {
private short httpstatus;
private String httpstatusmessage;
public HandshakeImpl1Server() {
}
@Override
public String getHttpStatusMessage() {
return httpstatusmessage;
}
@Override
public short getHttpStatus() {
return httpstatus;
}
public void setHttpStatusMessage( String message ) {
this.httpstatusmessage = message;
}
public void setHttpStatus( short status ) {
httpstatus = status;
}
}
@@ -0,0 +1,10 @@
package org.java_websocket.handshake;
import java.util.Iterator;
public interface Handshakedata {
public Iterator<String> iterateHttpFields();
public String getFieldValue( String name );
public boolean hasFieldValue( String name );
public byte[] getContent();
}
@@ -0,0 +1,60 @@
package org.java_websocket.handshake;
import java.util.Collections;
import java.util.Iterator;
import java.util.TreeMap;
public class HandshakedataImpl1 implements HandshakeBuilder {
private byte[] content;
private TreeMap<String,String> map;
public HandshakedataImpl1() {
map = new TreeMap<String,String>( String.CASE_INSENSITIVE_ORDER );
}
/*public HandshakedataImpl1( Handshakedata h ) {
httpstatusmessage = h.getHttpStatusMessage();
resourcedescriptor = h.getResourceDescriptor();
content = h.getContent();
map = new LinkedHashMap<String,String>();
Iterator<String> it = h.iterateHttpFields();
while ( it.hasNext() ) {
String key = (String) it.next();
map.put( key, h.getFieldValue( key ) );
}
}*/
@Override
public Iterator<String> iterateHttpFields() {
return Collections.unmodifiableSet( map.keySet() ).iterator();// Safety first
}
@Override
public String getFieldValue( String name ) {
String s = map.get( name );
if ( s == null ) {
return "";
}
return s;
}
@Override
public byte[] getContent() {
return content;
}
@Override
public void setContent( byte[] content ) {
this.content = content;
}
@Override
public void put( String name, String value ) {
map.put( name, value );
}
@Override
public boolean hasFieldValue( String name ) {
return map.containsKey( name );
}
}
@@ -0,0 +1,6 @@
package org.java_websocket.handshake;
public interface ServerHandshake extends Handshakedata {
public short getHttpStatus();
public String getHttpStatusMessage();
}
@@ -0,0 +1,6 @@
package org.java_websocket.handshake;
public interface ServerHandshakeBuilder extends HandshakeBuilder, ServerHandshake {
public void setHttpStatus( short status );
public void setHttpStatusMessage( String message );
}
@@ -0,0 +1,51 @@
package org.java_websocket.server;
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.drafts.Draft;
public class DefaultSSLWebSocketServerFactory implements WebSocketServer.WebSocketServerFactory {
protected SSLContext sslcontext;
protected ExecutorService exec;
public DefaultSSLWebSocketServerFactory( SSLContext sslContext ) {
this( sslContext, Executors.newSingleThreadScheduledExecutor() );
}
public DefaultSSLWebSocketServerFactory( 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 ) throws IOException {
SSLEngine e = sslcontext.createSSLEngine();
e.setUseClientMode( false );
return new SSLSocketChannel2( channel, e, exec, key );
}
@Override
public WebSocketImpl createWebSocket( WebSocketAdapter a, Draft d, Socket c ) {
return new WebSocketImpl( a, d );
}
@Override
public WebSocketImpl createWebSocket( WebSocketAdapter a, List<Draft> d, Socket s ) {
return new WebSocketImpl( a, d );
}
}
@@ -0,0 +1,26 @@
package org.java_websocket.server;
import java.net.Socket;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.List;
import org.java_websocket.WebSocketAdapter;
import org.java_websocket.WebSocketImpl;
import org.java_websocket.drafts.Draft;
import org.java_websocket.server.WebSocketServer.WebSocketServerFactory;
public class DefaultWebSocketServerFactory implements WebSocketServerFactory {
@Override
public WebSocketImpl createWebSocket( WebSocketAdapter a, Draft d, Socket s ) {
return new WebSocketImpl( a, d );
}
@Override
public WebSocketImpl createWebSocket( WebSocketAdapter a, List<Draft> d, Socket s ) {
return new WebSocketImpl( a, d );
}
@Override
public SocketChannel wrapChannel( SocketChannel channel, SelectionKey key ) {
return (SocketChannel) channel;
}
}
@@ -0,0 +1,736 @@
package org.java_websocket.server;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
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;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
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.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;
/**
* <tt>WebSocketServer</tt> is an abstract class that only takes care of the
* HTTP handshake portion of WebSockets. It's up to a subclass to add
* functionality/purpose to the server.
*
*/
public abstract class WebSocketServer extends WebSocketAdapter implements Runnable {
public static int DECODERS = Runtime.getRuntime().availableProcessors();
/**
* Holds the list of active WebSocket connections. "Active" means WebSocket
* handshake is complete and socket can be written to, or read from.
*/
private final Collection<WebSocket> connections;
/**
* The port number that this WebSocket server should listen on. Default is
* WebSocket.DEFAULT_PORT.
*/
private final InetSocketAddress address;
/**
* The socket channel for this WebSocket server.
*/
private ServerSocketChannel server;
/**
* The 'Selector' used to get event keys from the underlying socket.
*/
private Selector selector;
/**
* The Draft of the WebSocket protocol the Server is adhering to.
*/
private List<Draft> drafts;
private Thread selectorthread;
private volatile AtomicBoolean isclosed = new AtomicBoolean( false );
private List<WebSocketWorker> decoders;
private List<WebSocketImpl> iqueue;
private BlockingQueue<ByteBuffer> buffers;
private int queueinvokes = 0;
private AtomicInteger queuesize = new AtomicInteger( 0 );
private WebSocketServerFactory wsf = new DefaultWebSocketServerFactory();
/**
* Creates a WebSocketServer that will attempt to
* listen on port <var>WebSocket.DEFAULT_PORT</var>.
*
* @see #WebSocketServer(InetSocketAddress, int, List, Collection) more details here
*/
public WebSocketServer() throws UnknownHostException {
this( new InetSocketAddress( WebSocket.DEFAULT_PORT ), DECODERS, null );
}
/**
* Creates a WebSocketServer that will attempt to bind/listen on the given <var>address</var>.
*
* @see #WebSocketServer(InetSocketAddress, int, List, Collection) more details here
*/
public WebSocketServer( InetSocketAddress address ) {
this( address, DECODERS, null );
}
/**
* @see #WebSocketServer(InetSocketAddress, int, List, Collection) more details here
*/
public WebSocketServer( InetSocketAddress address , int decoders ) {
this( address, decoders, null );
}
/**
* @see #WebSocketServer(InetSocketAddress, int, List, Collection) more details here
*/
public WebSocketServer( InetSocketAddress address , List<Draft> drafts ) {
this( address, DECODERS, drafts );
}
/**
* @see #WebSocketServer(InetSocketAddress, int, List, Collection) more details here
*/
public WebSocketServer( InetSocketAddress address , int decodercount , List<Draft> drafts ) {
this( address, decodercount, drafts, new HashSet<WebSocket>() );
}
/**
* Creates a WebSocketServer that will attempt to bind/listen on the given <var>address</var>,
* and comply with <tt>Draft</tt> version <var>draft</var>.
*
* @param address
* The address (host:port) this server should listen on.
* @param decodercount
* The number of {@link WebSocketWorker}s that will be used to process the incoming network data. By default this will be <code>Runtime.getRuntime().availableProcessors()</code>
* @param drafts
* The versions of the WebSocket protocol that this server
* instance should comply to. Clients that use an other protocol version will be rejected.
*
* @param connectionscontainer
* Allows to specify a collection that will be used to store the websockets in. <br>
* If you plan to often iterate through the currently connected websockets you may want to use a collection that does not require synchronization like a {@link CopyOnWriteArraySet}. In that case make sure that you overload {@link #removeConnection(WebSocket)} and {@link #addConnection(WebSocket)}.<br>
* By default a {@link HashSet} will be used.
*
* @see #removeConnection(WebSocket) for more control over syncronized operation
* @see <a href="https://github.com/TooTallNate/Java-WebSocket/wiki/Drafts" > more about drafts
*/
public WebSocketServer( InetSocketAddress address , int decodercount , List<Draft> drafts , Collection<WebSocket> connectionscontainer ) {
if( address == null || decodercount < 1 || connectionscontainer == null ) {
throw new IllegalArgumentException( "address and connectionscontainer must not be null and you need at least 1 decoder" );
}
if( drafts == null )
this.drafts = Collections.emptyList();
else
this.drafts = drafts;
this.address = address;
this.connections = connectionscontainer;
iqueue = new LinkedList<WebSocketImpl>();
decoders = new ArrayList<WebSocketWorker>( decodercount );
buffers = new LinkedBlockingQueue<ByteBuffer>();
for( int i = 0 ; i < decodercount ; i++ ) {
WebSocketWorker ex = new WebSocketWorker();
decoders.add( ex );
ex.start();
}
}
/**
* Starts the server selectorthread that binds to the currently set port number and
* listeners for WebSocket connection requests. Creates a fixed thread pool with the size {@link WebSocketServer#DECODERS}<br>
* May only be called once.
*
* Alternatively you can call {@link WebSocketServer#run()} directly.
*
* @throws IllegalStateException
*/
public void start() {
if( selectorthread != null )
throw new IllegalStateException( getClass().getName() + " can only be started once." );
new Thread( this ).start();;
}
/**
* Closes all connected clients sockets, then closes the underlying
* ServerSocketChannel, effectively killing the server socket selectorthread,
* freeing the port the server was bound to and stops all internal workerthreads.
*
* If this method is called before the server is started it will never start.
*
* @param timeout
* Specifies how many milliseconds the overall close handshaking may take altogether before the connections are closed without proper close handshaking.<br>
*
* @throws IOException
* When {@link ServerSocketChannel}.close throws an IOException
* @throws 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;
}
List<WebSocket> socketsToClose = null;
// copy the connections in a list (prevent callback deadlocks)
synchronized ( connections ) {
socketsToClose = new ArrayList<WebSocket>( connections );
}
for( WebSocket ws : socketsToClose ) {
ws.close( CloseFrame.GOING_AWAY );
}
synchronized ( this ) {
if( selectorthread != null ) {
if( Thread.currentThread() != selectorthread ) {
}
if( selectorthread != Thread.currentThread() ) {
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();
}
}
}
}
public void stop() throws IOException , InterruptedException {
stop( 0 );
}
/**
* Returns a WebSocket[] of currently connected clients.
* Its iterators will be failfast and its not judicious
* to modify it.
*
* @return The currently connected clients.
*/
public Collection<WebSocket> connections() {
return this.connections;
}
public InetSocketAddress getAddress() {
return this.address;
}
/**
* Gets the port number that this server listens on.
*
* @return The port number.
*/
public int getPort() {
int port = getAddress().getPort();
if( port == 0 && server != null ) {
port = server.socket().getLocalPort();
}
return port;
}
public List<Draft> getDraft() {
return Collections.unmodifiableList( drafts );
}
// Runnable IMPLEMENTATION /////////////////////////////////////////////////
public void run() {
synchronized ( this ) {
if( selectorthread != null )
throw new IllegalStateException( getClass().getName() + " can only be started once." );
selectorthread = Thread.currentThread();
if( isclosed.get() ) {
return;
}
}
selectorthread.setName( "WebsocketSelector" + selectorthread.getId() );
try {
server = ServerSocketChannel.open();
server.configureBlocking( false );
ServerSocket socket = server.socket();
socket.setReceiveBufferSize( WebSocketImpl.RCVBUF );
socket.bind( address );
selector = Selector.open();
server.register( selector, server.validOps() );
} catch ( IOException ex ) {
handleFatal( null, ex );
return;
}
try {
while ( !selectorthread.isInterrupted() ) {
SelectionKey key = null;
WebSocketImpl conn = null;
try {
selector.select();
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> i = keys.iterator();
while ( i.hasNext() ) {
key = i.next();
if( !key.isValid() ) {
// Object o = key.attachment();
continue;
}
if( key.isAcceptable() ) {
if( !onConnect( key ) ) {
key.cancel();
continue;
}
SocketChannel channel = server.accept();
channel.configureBlocking( false );
WebSocketImpl w = wsf.createWebSocket( this, drafts, channel.socket() );
w.key = channel.register( selector, SelectionKey.OP_READ, w );
w.channel = wsf.wrapChannel( channel, w.key );
i.remove();
allocateBuffers( w );
continue;
}
if( key.isReadable() ) {
conn = (WebSocketImpl) key.attachment();
ByteBuffer buf = takeBuffer();
try {
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 );
}
} catch ( IOException e ) {
pushBuffer( buf );
throw e;
}
}
if( key.isWritable() ) {
conn = (WebSocketImpl) key.attachment();
if( SocketChannelIOHelper.batch( conn, conn.channel ) ) {
if( key.isValid() )
key.interestOps( SelectionKey.OP_READ );
}
}
}
while ( !iqueue.isEmpty() ) {
conn = iqueue.remove( 0 );
WrappedByteChannel c = ( (WrappedByteChannel) conn.channel );
ByteBuffer buf = takeBuffer();
try {
if( SocketChannelIOHelper.readMore( buf, conn, c ) )
iqueue.add( conn );
if( buf.hasRemaining() ) {
conn.inQueue.put( buf );
queue( conn );
} else {
pushBuffer( buf );
}
} catch ( IOException e ) {
pushBuffer( buf );
throw e;
}
}
} 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();
handleIOException( key, conn, ex );
} catch ( InterruptedException e ) {
return;// FIXME controlled shutdown (e.g. take care of buffermanagement)
}
}
} 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 {
if( queuesize.get() >= 2 * decoders.size() + 1 ) {
return;
}
queuesize.incrementAndGet();
buffers.put( createBuffer() );
}
protected void releaseBuffers( WebSocket c ) throws InterruptedException {
// queuesize.decrementAndGet();
// takeBuffer();
}
public ByteBuffer createBuffer() {
return ByteBuffer.allocate( WebSocketImpl.RCVBUF );
}
private void queue( WebSocketImpl ws ) throws InterruptedException {
if( ws.workerThread == null ) {
ws.workerThread = decoders.get( queueinvokes % decoders.size() );
queueinvokes++;
}
ws.workerThread.put( ws );
}
private ByteBuffer takeBuffer() throws InterruptedException {
return buffers.take();
}
private void pushBuffer( ByteBuffer buf ) throws InterruptedException {
if( buffers.size() > queuesize.intValue() )
return;
buffers.put( buf );
}
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 );
}
}
}
private void handleFatal( WebSocket conn, Exception e ) {
onError( conn, e );
try {
stop();
} catch ( IOException e1 ) {
onError( null, e1 );
} catch ( InterruptedException e1 ) {
Thread.currentThread().interrupt();
onError( null, e1 );
}
}
/**
* Gets the XML string that should be returned if a client requests a Flash
* security policy.
*
* The default implementation allows access from all remote domains, but
* only on the port that this WebSocketServer is listening on.
*
* 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
* not include the null char at the end, it is appended automatically.
*/
protected String getFlashSecurityPolicy() {
return "<cross-domain-policy><allow-access-from domain=\"*\" to-ports=\"" + getPort() + "\" /></cross-domain-policy>";
}
@Override
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 );
}
@Override
public final void onWebsocketOpen( WebSocket conn, Handshakedata handshake ) {
if( addConnection( conn ) ) {
onOpen( conn, (ClientHandshake) handshake );
}
}
@Override
public final void onWebsocketClose( WebSocket conn, int code, String reason, boolean remote ) {
selector.wakeup();
try {
if( removeConnection( conn ) ) {
onClose( conn, code, reason, remote );
}
} finally {
try {
releaseBuffers( conn );
} catch ( InterruptedException e ) {
Thread.currentThread().interrupt();
}
}
}
/**
* This method performs remove operations on the connection and therefore also gives control over whether the operation shall be synchronized
* <p>
* {@link #WebSocketServer(InetSocketAddress, int, List, Collection)} allows to specify a collection which will be used to store current connections in.<br>
* 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 ) {
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 );
}
/** @see #removeConnection(WebSocket) */
protected boolean addConnection( WebSocket 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
*/
@Override
public final void onWebsocketError( WebSocket conn, Exception ex ) {
onError( conn, 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();
}
selector.wakeup();
}
@Override
public void onWebsocketCloseInitiated( WebSocket conn, int code, String reason ) {
onCloseInitiated( conn, code, reason );
}
@Override
public void onWebsocketClosing( WebSocket conn, int code, String reason, boolean remote ) {
onClosing( conn, code, reason, remote );
}
public void onCloseInitiated( WebSocket conn, int code, String reason ) {
}
public void onClosing( WebSocket conn, int code, String reason, boolean remote ) {
}
public final void setWebSocketFactory( WebSocketServerFactory wsf ) {
this.wsf = wsf;
}
public final WebSocketFactory getWebSocketFactory() {
return wsf;
}
/**
* Returns whether a new connection shall be accepted or not.<br>
* Therefore method is well suited to implement some kind of connection limitation.<br>
*
* @see {@link #onOpen(WebSocket, ClientHandshake)}, {@link #onWebsocketHandshakeReceivedAsServer(WebSocket, Draft, ClientHandshake)}
**/
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 ).getRemoteSocketAddress();
}
/** 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 );
/**
* Called after the websocket connection has been closed.
*
* @param code
* The codes can be looked up here: {@link CloseFrame}
* @param reason
* Additional information string
* @param remote
* Returns whether or not the closing of the connection was initiated by the remote host.
**/
public abstract void onClose( WebSocket conn, int code, String reason, boolean remote );
/**
* Callback for string messages received from the remote host
*
* @see #onMessage(WebSocket, ByteBuffer)
**/
public abstract void onMessage( WebSocket conn, String message );
/**
* Called when errors occurs. If an error causes the websocket connection to fail {@link #onClose(WebSocket, int, String, boolean)} will be called additionally.<br>
* This method will be called primarily because of IO or protocol errors.<br>
* If the given exception is an RuntimeException that probably means that you encountered a bug.<br>
*
* @param con
* Can be null if there error does not belong to one specific websocket. For example if the servers port could not be bound.
**/
public abstract void onError( WebSocket conn, Exception ex );
/**
* Callback for binary messages received from the remote host
*
* @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 {
private BlockingQueue<WebSocketImpl> iqueue;
public WebSocketWorker() {
iqueue = new LinkedBlockingQueue<WebSocketImpl>();
setName( "WebSocketWorker-" + getId() );
setUncaughtExceptionHandler( new UncaughtExceptionHandler() {
@Override
public void uncaughtException( Thread t, Throwable e ) {
getDefaultUncaughtExceptionHandler().uncaughtException( t, e );
}
} );
}
public void put( WebSocketImpl ws ) throws InterruptedException {
iqueue.put( ws );
}
@Override
public void run() {
WebSocketImpl ws = null;
try {
while ( true ) {
ByteBuffer buf = null;
ws = iqueue.take();
buf = ws.inQueue.poll();
assert ( buf != null );
try {
ws.decode( buf );
} finally {
pushBuffer( buf );
}
}
} catch ( InterruptedException e ) {
} catch ( RuntimeException e ) {
handleFatal( ws, e );
}
}
}
public interface WebSocketServerFactory extends WebSocketFactory {
@Override
public WebSocketImpl createWebSocket( WebSocketAdapter a, Draft d, Socket s );
public WebSocketImpl createWebSocket( WebSocketAdapter a, List<Draft> drafts, Socket s );
/**
* Allows to wrap the Socketchannel( key.channel() ) to insert a protocol layer( like ssl or proxy authentication) beyond the ws layer.
*
* @param key
* a SelectionKey of an open SocketChannel.
* @return The channel on which the read and write operations will be performed.<br>
*/
public ByteChannel wrapChannel( SocketChannel channel, SelectionKey key ) throws IOException;
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,90 @@
package org.java_websocket.util;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CodingErrorAction;
import org.java_websocket.exceptions.InvalidDataException;
import org.java_websocket.framing.CloseFrame;
public class Charsetfunctions {
public static CodingErrorAction codingErrorAction = CodingErrorAction.REPORT;
/*
* @return UTF-8 encoding in bytes
*/
public static byte[] utf8Bytes( String s ) {
try {
return s.getBytes( "UTF8" );
} catch ( UnsupportedEncodingException e ) {
throw new RuntimeException( e );
}
}
/*
* @return ASCII encoding in bytes
*/
public static byte[] asciiBytes( String s ) {
try {
return s.getBytes( "ASCII" );
} catch ( UnsupportedEncodingException e ) {
throw new RuntimeException( e );
}
}
public static String stringAscii( byte[] bytes ) {
return stringAscii( bytes, 0, bytes.length );
}
public static String stringAscii( byte[] bytes, int offset, int length ){
try {
return new String( bytes, offset, length, "ASCII" );
} catch ( UnsupportedEncodingException e ) {
throw new RuntimeException( e );
}
}
public static String stringUtf8( byte[] bytes ) throws InvalidDataException {
return stringUtf8( ByteBuffer.wrap( bytes ) );
}
/*public static String stringUtf8( byte[] bytes, int off, int length ) throws InvalidDataException {
CharsetDecoder decode = Charset.forName( "UTF8" ).newDecoder();
decode.onMalformedInput( codingErrorAction );
decode.onUnmappableCharacter( codingErrorAction );
//decode.replaceWith( "X" );
String s;
try {
s = decode.decode( ByteBuffer.wrap( bytes, off, length ) ).toString();
} catch ( CharacterCodingException e ) {
throw new InvalidDataException( CloseFrame.NO_UTF8, e );
}
return s;
}*/
public static String stringUtf8( ByteBuffer bytes ) throws InvalidDataException {
CharsetDecoder decode = Charset.forName( "UTF8" ).newDecoder();
decode.onMalformedInput( codingErrorAction );
decode.onUnmappableCharacter( codingErrorAction );
// decode.replaceWith( "X" );
String s;
try {
bytes.mark();
s = decode.decode( bytes ).toString();
bytes.reset();
} catch ( CharacterCodingException e ) {
throw new InvalidDataException( CloseFrame.NO_UTF8, e );
}
return s;
}
public static void main( String[] args ) throws InvalidDataException {
stringUtf8( utf8Bytes( "\0" ) );
stringAscii( asciiBytes( "\0" ) );
}
}