Adding Java WebSocket lib for unit tests.
This commit is contained in:
+22
@@ -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.
|
||||
+75
@@ -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();
|
||||
}
|
||||
+38
@@ -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();
|
||||
|
||||
}
|
||||
+454
@@ -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();
|
||||
}
|
||||
}
|
||||
Vendored
+20
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+34
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+27
@@ -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 );
|
||||
}
|
||||
}
|
||||
Vendored
+28
@@ -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 );
|
||||
}
|
||||
|
||||
}
|
||||
+20
@@ -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 );
|
||||
}
|
||||
|
||||
}
|
||||
+25
@@ -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 );
|
||||
}
|
||||
|
||||
}
|
||||
Vendored
+5
@@ -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;
|
||||
}
|
||||
+123
@@ -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;
|
||||
}
|
||||
+110
@@ -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() ) ) ) + "}";
|
||||
}
|
||||
|
||||
}
|
||||
+6
@@ -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();
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package org.java_websocket.handshake;
|
||||
|
||||
public interface ClientHandshakeBuilder extends HandshakeBuilder, ClientHandshake {
|
||||
public void setResourceDescriptor( String resourceDescriptor );
|
||||
}
|
||||
+6
@@ -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 );
|
||||
}
|
||||
+18
@@ -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;
|
||||
}
|
||||
}
|
||||
+29
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+10
@@ -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();
|
||||
}
|
||||
+60
@@ -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 );
|
||||
}
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
package org.java_websocket.handshake;
|
||||
|
||||
public interface ServerHandshake extends Handshakedata {
|
||||
public short getHttpStatus();
|
||||
public String getHttpStatusMessage();
|
||||
}
|
||||
+6
@@ -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 );
|
||||
}
|
||||
Vendored
+51
@@ -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 );
|
||||
}
|
||||
}
|
||||
Vendored
+26
@@ -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;
|
||||
}
|
||||
}
|
||||
+736
@@ -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" ) );
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user