324369 Implement draft-ietf-hybi-thewebsocketprotocol-01

git-svn-id: svn+ssh://dev.eclipse.org/svnroot/rt/org.eclipse.jetty/jetty/trunk@2242 7e9141cc-0065-0410-87d8-b60c137991c4
This commit is contained in:
Greg Wilkins 2010-09-03 03:41:05 +00:00
parent e613d5b484
commit 995df89b22
18 changed files with 1229 additions and 484 deletions

View File

@ -23,6 +23,7 @@ jetty-7.2-SNAPSHOT
+ 324260 Jetty-6 continuations handle complete calls
+ 324359 illegal actions on AsyncContext should not change its state.
+ 324360 validate input on getResource since loop logic obscures subclass input validation.
+ 324369 Implement draft-ietf-hybi-thewebsocketprotocol-01
+ JETTY-912 added per exchange timeout api
+ JETTY-1245 Do not use direct buffers with NIO SSL
+ JETTY-1249 Apply max idle time to all connectors

View File

@ -119,7 +119,7 @@ public class WebSocketUpgradeTest extends TestCase
protected Connection onSwitchProtocol(EndPoint endp) throws IOException
{
waitFor(3);
WebSocketConnection connection = new WebSocketConnection(clientWS,endp);
WebSocketConnection connection = new WebSocketConnection(clientWS,endp,0);
_results.add("onSwitchProtocol");
_results.add(connection);

View File

@ -15,6 +15,7 @@ import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.nio.IndirectNIOBuffer;
import org.eclipse.jetty.io.nio.SelectChannelEndPoint;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.Utf8StringBuilder;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.thread.Timeout;
@ -30,13 +31,13 @@ public class WebSocketConnection implements Connection, WebSocket.Outbound
String _key2;
ByteArrayBuffer _hixie;
public WebSocketConnection(WebSocket websocket, EndPoint endpoint)
public WebSocketConnection(WebSocket websocket, EndPoint endpoint,int draft)
throws IOException
{
this(websocket,endpoint,new WebSocketBuffers(8192),System.currentTimeMillis(),300000);
this(websocket,endpoint,new WebSocketBuffers(8192),System.currentTimeMillis(),300000,draft);
}
public WebSocketConnection(WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime)
public WebSocketConnection(WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, int draft)
throws IOException
{
// TODO - can we use the endpoint idle mechanism?
@ -48,32 +49,35 @@ public class WebSocketConnection implements Connection, WebSocket.Outbound
_timestamp = timestamp;
_websocket = websocket;
_generator = new WebSocketGenerator(buffers, _endp);
_parser = new WebSocketParser(buffers, endpoint, new WebSocketParser.EventHandler()
final WebSocketParser.FrameHandler handler = new WebSocketParser.FrameHandler()
{
public void onFrame(byte frame, String data)
{
try
{
_websocket.onMessage(frame,data);
}
catch(ThreadDeath th)
{
throw th;
}
catch(Throwable th)
{
Log.warn(th);
}
}
Utf8StringBuilder _utf8 = new Utf8StringBuilder();
public void onFrame(byte frame, Buffer buffer)
public void onFrame(boolean more, byte flags, byte opcode, Buffer buffer)
{
try
{
byte[] array=buffer.array();
_websocket.onMessage(frame,array,buffer.getIndex(),buffer.length());
if (opcode==0)
{
if (more)
_utf8.append(buffer.array(),buffer.getIndex(),buffer.length());
else if (_utf8.length()==0)
_websocket.onMessage(opcode,buffer.toString("utf-8"));
else
{
_utf8.append(buffer.array(),buffer.getIndex(),buffer.length());
_websocket.onMessage(opcode,_utf8.toString());
_utf8.reset();
}
}
else
{
if (more)
throw new IllegalStateException("not implemented");
_websocket.onMessage(opcode,array,buffer.getIndex(),buffer.length());
}
}
catch(ThreadDeath th)
{
@ -84,7 +88,19 @@ public class WebSocketConnection implements Connection, WebSocket.Outbound
Log.warn(th);
}
}
});
};
// Select the parser/generators to use
switch(draft)
{
case 1:
_generator = new WebSocketGeneratorD01(buffers, _endp);
_parser = new WebSocketParserD01(buffers, endpoint, handler);
break;
default:
_generator = new WebSocketGeneratorD00(buffers, _endp);
_parser = new WebSocketParserD00(buffers, endpoint, handler);
}
// TODO should these be AsyncEndPoint checks/calls?
if (_endp instanceof SelectChannelEndPoint)
@ -205,9 +221,9 @@ public class WebSocketConnection implements Connection, WebSocket.Outbound
private void doTheHixieHixieShake()
{
byte[] result=WebSocketGenerator.doTheHixieHixieShake(
WebSocketParser.hixieCrypt(_key1),
WebSocketParser.hixieCrypt(_key2),
byte[] result=WebSocketConnection.doTheHixieHixieShake(
WebSocketConnection.hixieCrypt(_key1),
WebSocketConnection.hixieCrypt(_key2),
_hixie.asArray());
_hixie.clear();
_hixie.put(result);
@ -284,6 +300,51 @@ public class WebSocketConnection implements Connection, WebSocket.Outbound
((AsyncEndPoint)_endp).scheduleWrite();
}
/* ------------------------------------------------------------ */
static long hixieCrypt(String key)
{
// Don't ask me what all this is about.
// I think it's pretend secret stuff, kind of
// like talking in pig latin!
long number=0;
int spaces=0;
for (char c : key.toCharArray())
{
if (Character.isDigit(c))
number=number*10+(c-'0');
else if (c==' ')
spaces++;
}
return number/spaces;
}
public static byte[] doTheHixieHixieShake(long key1,long key2,byte[] key3)
{
try
{
MessageDigest md = MessageDigest.getInstance("MD5");
byte [] fodder = new byte[16];
fodder[0]=(byte)(0xff&(key1>>24));
fodder[1]=(byte)(0xff&(key1>>16));
fodder[2]=(byte)(0xff&(key1>>8));
fodder[3]=(byte)(0xff&key1);
fodder[4]=(byte)(0xff&(key2>>24));
fodder[5]=(byte)(0xff&(key2>>16));
fodder[6]=(byte)(0xff&(key2>>8));
fodder[7]=(byte)(0xff&key2);
for (int i=0;i<8;i++)
fodder[8+i]=key3[i];
md.update(fodder);
byte[] result=md.digest();
return result;
}
catch (NoSuchAlgorithmException e)
{
throw new IllegalStateException(e);
}
}
private interface IdleCheck
{
void access(EndPoint endp);

View File

@ -88,9 +88,10 @@ public class WebSocketFactory
if (!"HTTP/1.1".equals(request.getProtocol()))
throw new IllegalStateException("!HTTP/1.1");
int draft=request.getIntHeader("Sec-WebSocket-Draft");
HttpConnection http = HttpConnection.getCurrentConnection();
ConnectedEndPoint endp = (ConnectedEndPoint)http.getEndPoint();
WebSocketConnection connection = new WebSocketConnection(websocket,endp,_buffers,http.getTimeStamp(), _maxIdleTime);
WebSocketConnection connection = new WebSocketConnection(websocket,endp,_buffers,http.getTimeStamp(), _maxIdleTime,draft);
String uri=request.getRequestURI();
String query=request.getQueryString();

View File

@ -1,197 +1,18 @@
package org.eclipse.jetty.websocket;
import java.io.IOException;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import org.eclipse.jetty.io.Buffer;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.EofException;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.log.Log;
/* ------------------------------------------------------------ */
/** WebSocketGenerator.
* This class generates websocket packets.
* It is fully synchronized because it is likely that async
* threads will call the addMessage methods while other
* threads are flushing the generator.
*/
public class WebSocketGenerator
public interface WebSocketGenerator
{
final private WebSocketBuffers _buffers;
final private EndPoint _endp;
private Buffer _buffer;
public WebSocketGenerator(WebSocketBuffers buffers, EndPoint endp)
{
_buffers=buffers;
_endp=endp;
}
public synchronized void addFrame(byte frame,byte[] content, int blockFor) throws IOException
{
addFrame(frame,content,0,content.length,blockFor);
}
public synchronized void addFrame(byte frame,byte[] content, int offset, int length, int blockFor) throws IOException
{
if (_buffer==null)
_buffer=_buffers.getDirectBuffer();
if (_buffer.space() == 0)
expelBuffer(blockFor);
bufferPut(frame, blockFor);
if (isLengthFrame(frame))
{
// Send a length delimited frame
// How many bytes we need for the length ?
// We have 7 bits available, so log2(length) / 7 + 1
// For example, 50000 bytes is 2 8-bytes: 11000011 01010000
// but we need to write it in 3 7-bytes 0000011 0000110 1010000
// 65536 == 1 00000000 00000000 => 100 0000000 0000000
int lengthBytes = new BigInteger(String.valueOf(length)).bitLength() / 7 + 1;
for (int i = lengthBytes - 1; i > 0; --i)
{
byte lengthByte = (byte)(0x80 | (0x7F & (length >> 7 * i)));
bufferPut(lengthByte, blockFor);
}
bufferPut((byte)(0x7F & length), blockFor);
}
int remaining = length;
while (remaining > 0)
{
int chunk = remaining < _buffer.space() ? remaining : _buffer.space();
_buffer.put(content, offset + (length - remaining), chunk);
remaining -= chunk;
if (_buffer.space() > 0)
{
if (!isLengthFrame(frame))
_buffer.put((byte)0xFF);
// Gently flush the data, issuing a non-blocking write
flushBuffer();
}
else
{
// Forcibly flush the data, issuing a blocking write
expelBuffer(blockFor);
if (remaining == 0)
{
if (!isLengthFrame(frame))
_buffer.put((byte)0xFF);
// Gently flush the data, issuing a non-blocking write
flushBuffer();
}
}
}
}
private synchronized boolean isLengthFrame(byte frame)
{
return (frame & WebSocket.LENGTH_FRAME) == WebSocket.LENGTH_FRAME;
}
private synchronized void bufferPut(byte datum, long blockFor) throws IOException
{
if (_buffer==null)
_buffer=_buffers.getDirectBuffer();
_buffer.put(datum);
if (_buffer.space() == 0)
expelBuffer(blockFor);
}
public synchronized void addFrame(byte frame, String content, int blockFor) throws IOException
{
byte[] bytes = content.getBytes("UTF-8");
addFrame(frame, bytes, 0, bytes.length, blockFor);
}
public synchronized int flush(long blockFor) throws IOException
{
return expelBuffer(blockFor);
}
public synchronized int flush() throws IOException
{
int flushed = flushBuffer();
if (_buffer!=null && _buffer.length()==0)
{
_buffers.returnBuffer(_buffer);
_buffer=null;
}
return flushed;
}
private synchronized int flushBuffer() throws IOException
{
if (!_endp.isOpen())
throw new EofException();
if (_buffer!=null)
return _endp.flush(_buffer);
return 0;
}
private synchronized int expelBuffer(long blockFor) throws IOException
{
if (_buffer==null)
return 0;
int result = flushBuffer();
_buffer.compact();
if (!_endp.isBlocking())
{
while (_buffer.space()==0)
{
// TODO: in case the I/O system signals write ready, but when we attempt to write we cannot
// TODO: we should decrease the blockFor timeout instead of waiting again the whole timeout
boolean ready = _endp.blockWritable(blockFor);
if (!ready)
throw new IOException("Write timeout");
result += flushBuffer();
_buffer.compact();
}
}
return result;
}
public synchronized boolean isBufferEmpty()
{
return _buffer==null || _buffer.length()==0;
}
public static byte[] doTheHixieHixieShake(long key1,long key2,byte[] key3)
{
try
{
MessageDigest md = MessageDigest.getInstance("MD5");
byte [] fodder = new byte[16];
fodder[0]=(byte)(0xff&(key1>>24));
fodder[1]=(byte)(0xff&(key1>>16));
fodder[2]=(byte)(0xff&(key1>>8));
fodder[3]=(byte)(0xff&key1);
fodder[4]=(byte)(0xff&(key2>>24));
fodder[5]=(byte)(0xff&(key2>>16));
fodder[6]=(byte)(0xff&(key2>>8));
fodder[7]=(byte)(0xff&key2);
for (int i=0;i<8;i++)
fodder[8+i]=key3[i];
md.update(fodder);
byte[] result=md.digest();
return result;
}
catch (NoSuchAlgorithmException e)
{
throw new IllegalStateException(e);
}
}
int flush() throws IOException;
boolean isBufferEmpty();
void addFrame(byte opcode, String content, int maxIdleTime) throws IOException;
void addFrame(byte opcode, byte[] content, int offset, int length, int maxIdleTime) throws IOException;
void addFrame(byte opcode, byte[] content, int maxIdleTime)throws IOException;
int flush(int maxIdleTime) throws IOException;
}

View File

@ -0,0 +1,168 @@
package org.eclipse.jetty.websocket;
import java.io.IOException;
import java.math.BigInteger;
import org.eclipse.jetty.io.Buffer;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.EofException;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.log.Log;
/* ------------------------------------------------------------ */
/** WebSocketGenerator.
* This class generates websocket packets.
* It is fully synchronized because it is likely that async
* threads will call the addMessage methods while other
* threads are flushing the generator.
*/
public class WebSocketGeneratorD00 implements WebSocketGenerator
{
final private WebSocketBuffers _buffers;
final private EndPoint _endp;
private Buffer _buffer;
public WebSocketGeneratorD00(WebSocketBuffers buffers, EndPoint endp)
{
_buffers=buffers;
_endp=endp;
}
public synchronized void addFrame(byte frame,byte[] content, int blockFor) throws IOException
{
addFrame(frame,content,0,content.length,blockFor);
}
public synchronized void addFrame(byte opcode,byte[] content, int offset, int length, int blockFor) throws IOException
{
if (_buffer==null)
_buffer=_buffers.getDirectBuffer();
if (_buffer.space() == 0)
expelBuffer(blockFor);
bufferPut(opcode, blockFor);
if (isLengthFrame(opcode))
{
// Send a length delimited frame
// How many bytes we need for the length ?
// We have 7 bits available, so log2(length) / 7 + 1
// For example, 50000 bytes is 2 8-bytes: 11000011 01010000
// but we need to write it in 3 7-bytes 0000011 0000110 1010000
// 65536 == 1 00000000 00000000 => 100 0000000 0000000
int lengthBytes = new BigInteger(String.valueOf(length)).bitLength() / 7 + 1;
for (int i = lengthBytes - 1; i > 0; --i)
{
byte lengthByte = (byte)(0x80 | (0x7F & (length >> 7 * i)));
bufferPut(lengthByte, blockFor);
}
bufferPut((byte)(0x7F & length), blockFor);
}
int remaining = length;
while (remaining > 0)
{
int chunk = remaining < _buffer.space() ? remaining : _buffer.space();
_buffer.put(content, offset + (length - remaining), chunk);
remaining -= chunk;
if (_buffer.space() > 0)
{
if (!isLengthFrame(opcode))
_buffer.put((byte)0xFF);
// Gently flush the data, issuing a non-blocking write
flushBuffer();
}
else
{
// Forcibly flush the data, issuing a blocking write
expelBuffer(blockFor);
if (remaining == 0)
{
if (!isLengthFrame(opcode))
_buffer.put((byte)0xFF);
// Gently flush the data, issuing a non-blocking write
flushBuffer();
}
}
}
}
private synchronized boolean isLengthFrame(byte frame)
{
return (frame & WebSocket.LENGTH_FRAME) == WebSocket.LENGTH_FRAME;
}
private synchronized void bufferPut(byte datum, long blockFor) throws IOException
{
if (_buffer==null)
_buffer=_buffers.getDirectBuffer();
_buffer.put(datum);
if (_buffer.space() == 0)
expelBuffer(blockFor);
}
public synchronized void addFrame(byte frame, String content, int blockFor) throws IOException
{
byte[] bytes = content.getBytes("UTF-8");
addFrame(frame, bytes, 0, bytes.length, blockFor);
}
public synchronized int flush(int blockFor) throws IOException
{
return expelBuffer(blockFor);
}
public synchronized int flush() throws IOException
{
int flushed = flushBuffer();
if (_buffer!=null && _buffer.length()==0)
{
_buffers.returnBuffer(_buffer);
_buffer=null;
}
return flushed;
}
private synchronized int flushBuffer() throws IOException
{
if (!_endp.isOpen())
throw new EofException();
if (_buffer!=null)
return _endp.flush(_buffer);
return 0;
}
private synchronized int expelBuffer(long blockFor) throws IOException
{
if (_buffer==null)
return 0;
int result = flushBuffer();
_buffer.compact();
if (!_endp.isBlocking())
{
while (_buffer.space()==0)
{
// TODO: in case the I/O system signals write ready, but when we attempt to write we cannot
// TODO: we should decrease the blockFor timeout instead of waiting again the whole timeout
boolean ready = _endp.blockWritable(blockFor);
if (!ready)
throw new IOException("Write timeout");
result += flushBuffer();
_buffer.compact();
}
}
return result;
}
public synchronized boolean isBufferEmpty()
{
return _buffer==null || _buffer.length()==0;
}
}

View File

@ -0,0 +1,181 @@
package org.eclipse.jetty.websocket;
import java.io.IOException;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import org.eclipse.jetty.io.Buffer;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.EofException;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.log.Log;
/* ------------------------------------------------------------ */
/** WebSocketGenerator.
* This class generates websocket packets.
* It is fully synchronized because it is likely that async
* threads will call the addMessage methods while other
* threads are flushing the generator.
*/
public class WebSocketGeneratorD01 implements WebSocketGenerator
{
final private WebSocketBuffers _buffers;
final private EndPoint _endp;
private Buffer _buffer;
public WebSocketGeneratorD01(WebSocketBuffers buffers, EndPoint endp)
{
_buffers=buffers;
_endp=endp;
}
public synchronized void addFrame(byte opcode,byte[] content, int blockFor) throws IOException
{
addFrame(opcode,content,0,content.length,blockFor);
}
public synchronized void addFrame(byte opcode,byte[] content, int offset, int length, int blockFor) throws IOException
{
if (_buffer==null)
_buffer=_buffers.getDirectBuffer();
if (_buffer.space() == 0)
expelBuffer(blockFor);
opcode = (byte)(opcode & 0x0f);
while (length>0)
{
// slice a fragment off
int fragment=length;
if (fragment+10>_buffer.capacity())
{
fragment=_buffer.capacity()-10;
bufferPut((byte)(0x80|opcode), blockFor);
}
else
bufferPut(opcode, blockFor);
if (fragment>0xffff)
{
bufferPut((byte)127, blockFor);
bufferPut((byte)((fragment>>56)&0x7f), blockFor);
bufferPut((byte)((fragment>>48)&0xff), blockFor);
bufferPut((byte)((fragment>>40)&0xff), blockFor);
bufferPut((byte)((fragment>>32)&0xff), blockFor);
bufferPut((byte)((fragment>>24)&0xff), blockFor);
bufferPut((byte)((fragment>>16)&0xff), blockFor);
bufferPut((byte)((fragment>>8)&0xff), blockFor);
bufferPut((byte)(fragment&0xff), blockFor);
}
else if (fragment >=0x7e)
{
bufferPut((byte)126, blockFor);
bufferPut((byte)(fragment>>8), blockFor);
bufferPut((byte)(fragment&0xff), blockFor);
}
else
{
bufferPut((byte)fragment, blockFor);
}
int remaining = fragment;
while (remaining > 0)
{
_buffer.compact();
int chunk = remaining < _buffer.space() ? remaining : _buffer.space();
_buffer.put(content, offset + (fragment - remaining), chunk);
remaining -= chunk;
if (_buffer.space() > 0)
{
// Gently flush the data, issuing a non-blocking write
flushBuffer();
}
else
{
// Forcibly flush the data, issuing a blocking write
expelBuffer(blockFor);
if (remaining == 0)
{
// Gently flush the data, issuing a non-blocking write
flushBuffer();
}
}
}
offset+=fragment;
length-=fragment;
}
}
private synchronized void bufferPut(byte datum, long blockFor) throws IOException
{
if (_buffer==null)
_buffer=_buffers.getDirectBuffer();
_buffer.put(datum);
if (_buffer.space() == 0)
expelBuffer(blockFor);
}
public synchronized void addFrame(byte frame, String content, int blockFor) throws IOException
{
byte[] bytes = content.getBytes("UTF-8");
addFrame(frame, bytes, 0, bytes.length, blockFor);
}
public synchronized int flush(int blockFor) throws IOException
{
return expelBuffer(blockFor);
}
public synchronized int flush() throws IOException
{
int flushed = flushBuffer();
if (_buffer!=null && _buffer.length()==0)
{
_buffers.returnBuffer(_buffer);
_buffer=null;
}
return flushed;
}
private synchronized int flushBuffer() throws IOException
{
if (!_endp.isOpen())
throw new EofException();
if (_buffer!=null)
return _endp.flush(_buffer);
return 0;
}
private synchronized int expelBuffer(long blockFor) throws IOException
{
if (_buffer==null)
return 0;
int result = flushBuffer();
_buffer.compact();
if (!_endp.isBlocking())
{
while (_buffer.space()==0)
{
// TODO: in case the I/O system signals write ready, but when we attempt to write we cannot
// TODO: we should decrease the blockFor timeout instead of waiting again the whole timeout
boolean ready = _endp.blockWritable(blockFor);
if (!ready)
throw new IOException("Write timeout");
result += flushBuffer();
_buffer.compact();
}
}
return result;
}
public synchronized boolean isBufferEmpty()
{
return _buffer==null || _buffer.length()==0;
}
}

View File

@ -1,13 +1,6 @@
package org.eclipse.jetty.websocket;
import java.io.IOException;
import org.eclipse.jetty.io.Buffer;
import org.eclipse.jetty.io.Buffers;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.util.Utf8StringBuilder;
import org.eclipse.jetty.util.log.Log;
@ -16,207 +9,23 @@ import org.eclipse.jetty.util.log.Log;
* Parser the WebSocket protocol.
*
*/
public class WebSocketParser
public interface WebSocketParser
{
public static final int STATE_START=0;
public static final int STATE_SENTINEL_DATA=1;
public static final int STATE_LENGTH=2;
public static final int STATE_DATA=3;
private final WebSocketBuffers _buffers;
private final EndPoint _endp;
private final EventHandler _handler;
private int _state;
private Buffer _buffer;
private byte _frame;
private int _length;
private Utf8StringBuilder _utf8;
/* ------------------------------------------------------------ */
/**
* @param buffers The buffers to use for parsing. Only the {@link Buffers#getBuffer()} is used.
* This should be a direct buffer if binary data is mostly used or an indirect buffer if utf-8 data
* is mostly used.
* @param endp
* @param handler
*/
public WebSocketParser(WebSocketBuffers buffers, EndPoint endp, EventHandler handler)
{
_buffers=buffers;
_endp=endp;
_handler=handler;
}
/* ------------------------------------------------------------ */
public boolean isBufferEmpty()
{
return _buffer==null || _buffer.length()==0;
}
/* ------------------------------------------------------------ */
public Buffer getBuffer()
{
return _buffer;
}
/* ------------------------------------------------------------ */
/** Parse to next event.
* Parse to the next {@link EventHandler} event or until no more data is
* available. Fill data from the {@link EndPoint} only as necessary.
* @return An indication of progress or otherwise. -1 indicates EOF, 0 indicates
* that no bytes were read and no messages parsed. A positive number indicates either
* the bytes filled or the messages parsed.
*/
public int parseNext()
{
if (_buffer==null)
_buffer=_buffers.getBuffer();
int total_filled=0;
// Loop until an datagram call back or can't fill anymore
boolean progress=true;
while(true)
{
int length=_buffer.length();
// Fill buffer if we need a byte or need length
if (length == 0 || _state==STATE_DATA && length<_length)
{
// compact to mark (set at start of data)
_buffer.compact();
// if no space, then the data is too big for buffer
if (_buffer.space() == 0)
throw new IllegalStateException("FULL");
// catch IOExceptions (probably EOF) and try to parse what we have
try
{
int filled=_endp.isOpen()?_endp.fill(_buffer):-1;
if (filled<=0)
return total_filled;
total_filled+=filled;
length=_buffer.length();
}
catch(IOException e)
{
Log.debug(e);
return total_filled>0?total_filled:-1;
}
}
// Parse the buffer byte by byte (unless it is STATE_DATA)
byte b;
charloop: while (length-->0)
{
switch (_state)
{
case STATE_START:
b=_buffer.get();
_frame=b;
if (_frame<0)
{
_length=0;
_state=STATE_LENGTH;
}
else
{
if (_utf8==null)
_utf8=new Utf8StringBuilder();
_state=STATE_SENTINEL_DATA;
_buffer.mark();
}
continue;
case STATE_SENTINEL_DATA:
b=_buffer.get();
if ((b&0xff)==0xff)
{
String data=_utf8.toString();
_utf8.reset();
_state=STATE_START;
_handler.onFrame(_frame,data);
_buffer.setMarkIndex(-1);
if (_buffer.length()==0)
{
_buffers.returnBuffer(_buffer);
_buffer=null;
}
return total_filled;
}
_utf8.append(b);
continue;
case STATE_LENGTH:
b=_buffer.get();
_length=_length<<7 | (0x7f&b);
if (b>=0)
{
_state=STATE_DATA;
_buffer.mark(0);
}
continue;
case STATE_DATA:
if (_buffer.markIndex()<0)
if (_buffer.length()<_length)
break charloop;
Buffer data=_buffer.sliceFromMark(_length);
_buffer.skip(_length);
_state=STATE_START;
_handler.onFrame(_frame,data);
if (_buffer.length()==0)
{
_buffers.returnBuffer(_buffer);
_buffer=null;
}
return total_filled;
}
}
}
}
/* ------------------------------------------------------------ */
public void fill(Buffer buffer)
{
if (buffer!=null && buffer.length()>0)
{
if (_buffer==null)
_buffer=_buffers.getBuffer();
_buffer.put(buffer);
buffer.clear();
}
}
/* ------------------------------------------------------------ */
static long hixieCrypt(String key)
{
// Don't ask me what all this is about.
// I think it's pretend secret stuff, kind of
// like talking in pig latin!
long number=0;
int spaces=0;
for (char c : key.toCharArray())
{
if (Character.isDigit(c))
number=number*10+(c-'0');
else if (c==' ')
spaces++;
}
return number/spaces;
}
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
public interface EventHandler
public interface FrameHandler
{
void onFrame(byte frame,String data);
void onFrame(byte frame,Buffer buffer);
void onFrame(boolean more,byte flags, byte opcode, Buffer buffer);
}
Buffer getBuffer();
int parseNext();
boolean isBufferEmpty();
void fill(Buffer buffer);
}

View File

@ -0,0 +1,191 @@
package org.eclipse.jetty.websocket;
import java.io.IOException;
import org.eclipse.jetty.io.Buffer;
import org.eclipse.jetty.io.Buffers;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.util.Utf8StringBuilder;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.websocket.WebSocketParser.FrameHandler;
/* ------------------------------------------------------------ */
/**
* Parser the WebSocket protocol.
*
*/
public class WebSocketParserD00 implements WebSocketParser
{
public static final int STATE_START=0;
public static final int STATE_SENTINEL_DATA=1;
public static final int STATE_LENGTH=2;
public static final int STATE_DATA=3;
private final WebSocketBuffers _buffers;
private final EndPoint _endp;
private final FrameHandler _handler;
private int _state;
private Buffer _buffer;
private byte _opcode;
private int _length;
/* ------------------------------------------------------------ */
/**
* @param buffers The buffers to use for parsing. Only the {@link Buffers#getBuffer()} is used.
* This should be a direct buffer if binary data is mostly used or an indirect buffer if utf-8 data
* is mostly used.
* @param endp
* @param handler
*/
public WebSocketParserD00(WebSocketBuffers buffers, EndPoint endp, FrameHandler handler)
{
_buffers=buffers;
_endp=endp;
_handler=handler;
}
/* ------------------------------------------------------------ */
public boolean isBufferEmpty()
{
return _buffer==null || _buffer.length()==0;
}
/* ------------------------------------------------------------ */
public Buffer getBuffer()
{
return _buffer;
}
/* ------------------------------------------------------------ */
/** Parse to next event.
* Parse to the next {@link FrameHandler} event or until no more data is
* available. Fill data from the {@link EndPoint} only as necessary.
* @return An indication of progress or otherwise. -1 indicates EOF, 0 indicates
* that no bytes were read and no messages parsed. A positive number indicates either
* the bytes filled or the messages parsed.
*/
public int parseNext()
{
if (_buffer==null)
_buffer=_buffers.getBuffer();
int total_filled=0;
// Loop until an datagram call back or can't fill anymore
boolean progress=true;
while(true)
{
int length=_buffer.length();
// Fill buffer if we need a byte or need length
if (length == 0 || _state==STATE_DATA && length<_length)
{
// compact to mark (set at start of data)
_buffer.compact();
// if no space, then the data is too big for buffer
if (_buffer.space() == 0)
throw new IllegalStateException("FULL");
// catch IOExceptions (probably EOF) and try to parse what we have
try
{
int filled=_endp.isOpen()?_endp.fill(_buffer):-1;
if (filled<=0)
return total_filled;
total_filled+=filled;
length=_buffer.length();
}
catch(IOException e)
{
Log.debug(e);
return total_filled>0?total_filled:-1;
}
}
// Parse the buffer byte by byte (unless it is STATE_DATA)
byte b;
charloop: while (length-->0)
{
switch (_state)
{
case STATE_START:
b=_buffer.get();
_opcode=b;
if (_opcode<0)
{
_length=0;
_state=STATE_LENGTH;
}
else
{
_state=STATE_SENTINEL_DATA;
_buffer.mark(0);
}
continue;
case STATE_SENTINEL_DATA:
b=_buffer.get();
if ((b&0xff)==0xff)
{
_state=STATE_START;
int l=_buffer.getIndex()-_buffer.markIndex()-1;
_handler.onFrame(false,(byte)0,_opcode,_buffer.sliceFromMark(l));
_buffer.setMarkIndex(-1);
if (_buffer.length()==0)
{
_buffers.returnBuffer(_buffer);
_buffer=null;
}
return total_filled;
}
continue;
case STATE_LENGTH:
b=_buffer.get();
_length=_length<<7 | (0x7f&b);
if (b>=0)
{
_state=STATE_DATA;
_buffer.mark(0);
}
continue;
case STATE_DATA:
if (_buffer.markIndex()<0)
if (_buffer.length()<_length)
break charloop;
Buffer data=_buffer.sliceFromMark(_length);
_buffer.skip(_length);
_state=STATE_START;
_handler.onFrame(false,(byte)0, _opcode, data);
if (_buffer.length()==0)
{
_buffers.returnBuffer(_buffer);
_buffer=null;
}
return total_filled;
}
}
}
}
/* ------------------------------------------------------------ */
public void fill(Buffer buffer)
{
if (buffer!=null && buffer.length()>0)
{
if (_buffer==null)
_buffer=_buffers.getBuffer();
_buffer.put(buffer);
buffer.clear();
}
}
}

View File

@ -0,0 +1,214 @@
package org.eclipse.jetty.websocket;
import java.io.IOException;
import org.eclipse.jetty.io.Buffer;
import org.eclipse.jetty.io.Buffers;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.util.Utf8StringBuilder;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.websocket.WebSocketParser.FrameHandler;
/* ------------------------------------------------------------ */
/**
* Parser the WebSocket protocol.
*
*/
public class WebSocketParserD01 implements WebSocketParser
{
public enum State {
START(1), LENGTH_7(2), LENGTH_16(4), LENGTH_63(10), DATA(10);
int _minSize;
State(int minSize)
{
_minSize=minSize;
}
int getMinSize()
{
return _minSize;
}
};
private final WebSocketBuffers _buffers;
private final EndPoint _endp;
private final FrameHandler _handler;
private State _state=State.START;
private Buffer _buffer;
private boolean _more;
private byte _flags;
private byte _opcode;
private int _count;
private long _length;
private Utf8StringBuilder _utf8;
/* ------------------------------------------------------------ */
/**
* @param buffers The buffers to use for parsing. Only the {@link Buffers#getBuffer()} is used.
* This should be a direct buffer if binary data is mostly used or an indirect buffer if utf-8 data
* is mostly used.
* @param endp
* @param handler
*/
public WebSocketParserD01(WebSocketBuffers buffers, EndPoint endp, FrameHandler handler)
{
_buffers=buffers;
_endp=endp;
_handler=handler;
}
/* ------------------------------------------------------------ */
public boolean isBufferEmpty()
{
return _buffer==null || _buffer.length()==0;
}
/* ------------------------------------------------------------ */
public Buffer getBuffer()
{
return _buffer;
}
/* ------------------------------------------------------------ */
/** Parse to next event.
* Parse to the next {@link FrameHandler} event or until no more data is
* available. Fill data from the {@link EndPoint} only as necessary.
* @return An indication of progress or otherwise. -1 indicates EOF, 0 indicates
* that no bytes were read and no messages parsed. A positive number indicates either
* the bytes filled or the messages parsed.
*/
public int parseNext()
{
if (_buffer==null)
_buffer=_buffers.getBuffer();
int total_filled=0;
// Loop until an datagram call back or can't fill anymore
while(true)
{
int available=_buffer.length();
// Fill buffer if we need a byte or need length
if (available < _state.getMinSize() || _state==State.DATA && available<_count)
{
// compact to mark (set at start of data)
_buffer.compact();
// if no space, then the data is too big for buffer
if (_buffer.space() == 0)
throw new IllegalStateException("FULL");
// catch IOExceptions (probably EOF) and try to parse what we have
try
{
int filled=_endp.isOpen()?_endp.fill(_buffer):-1;
if (filled<=0)
return total_filled;
total_filled+=filled;
available=_buffer.length();
}
catch(IOException e)
{
Log.debug(e);
return total_filled>0?total_filled:-1;
}
}
// Parse the buffer byte by byte (unless it is STATE_DATA)
byte b;
while (_state!=State.DATA && available-->0)
{
switch (_state)
{
case START:
b=_buffer.get();
_opcode=(byte)(b&0xf);
_flags=(byte)(b>>4);
_more=(_flags&8)!=0;
_state=State.LENGTH_7;
continue;
case LENGTH_7:
b=_buffer.get();
switch(b)
{
case 127:
_length=0;
_count=8;
_state=State.LENGTH_63;
break;
case 126:
_length=0;
_count=2;
_state=State.LENGTH_16;
break;
default:
_length=(0x7f&b);
_count=(int)_length;
_state=State.DATA;
}
continue;
case LENGTH_16:
b=_buffer.get();
_length = _length<<8 | b;
if (--_count==0)
{
if (_length>=_buffer.capacity()-4)
throw new IllegalStateException("TOO LARGE");
_count=(int)_length;
_state=State.DATA;
}
continue;
case LENGTH_63:
b=_buffer.get();
_length = _length<<8 | b;
if (--_count==0)
{
if (_length>=_buffer.capacity()-10)
throw new IllegalStateException("TOO LARGE");
_count=(int)_length;
_state=State.DATA;
}
continue;
}
}
if (_state==State.DATA && available>=_count)
{
_handler.onFrame(_more,_flags, _opcode, _buffer.get(_count));
_count=0;
_state=State.START;
if (_buffer.length()==0)
{
_buffers.returnBuffer(_buffer);
_buffer=null;
}
return total_filled;
}
}
}
/* ------------------------------------------------------------ */
public void fill(Buffer buffer)
{
if (buffer!=null && buffer.length()>0)
{
if (_buffer==null)
_buffer=_buffers.getBuffer();
_buffer.put(buffer);
buffer.clear();
}
}
}

View File

@ -0,0 +1,112 @@
package org.eclipse.jetty.websocket;
import static junit.framework.Assert.assertEquals;
import org.eclipse.jetty.io.ByteArrayBuffer;
import org.eclipse.jetty.io.ByteArrayEndPoint;
import org.junit.Before;
import org.junit.Test;
/**
* @version $Revision: 1441 $ $Date: 2010-04-02 12:28:17 +0200 (Fri, 02 Apr 2010) $
*/
public class WebSocketGeneratorD00Test
{
private ByteArrayBuffer _out;
private WebSocketGenerator _generator;
@Before
public void setUp() throws Exception
{
WebSocketBuffers buffers = new WebSocketBuffers(1024);
ByteArrayEndPoint endPoint = new ByteArrayEndPoint();
_generator = new WebSocketGeneratorD01(buffers, endPoint);
_out = new ByteArrayBuffer(4096);
endPoint.setOut(_out);
}
@Test
public void testOneString() throws Exception
{
_generator.addFrame((byte)0x04,"Hell\uFF4F W\uFF4Frld",0);
_generator.flush();
assertEquals(4,_out.get());
assertEquals(15,_out.get());
assertEquals('H',_out.get());
assertEquals('e',_out.get());
assertEquals('l',_out.get());
assertEquals('l',_out.get());
assertEquals(0xEF,0xff&_out.get());
assertEquals(0xBD,0xff&_out.get());
assertEquals(0x8F,0xff&_out.get());
assertEquals(' ',_out.get());
assertEquals('W',_out.get());
assertEquals(0xEF,0xff&_out.get());
assertEquals(0xBD,0xff&_out.get());
assertEquals(0x8F,0xff&_out.get());
assertEquals('r',_out.get());
assertEquals('l',_out.get());
assertEquals('d',_out.get());
}
@Test
public void testOneMediumBuffer() throws Exception
{
byte[] b=new byte[501];
for (int i=0;i<b.length;i++)
b[i]=(byte)('0'+(i%10));
_generator.addFrame((byte)0xf,b,0);
_generator.flush();
assertEquals(0x0f,_out.get());
assertEquals(0x7e,_out.get());
assertEquals((b.length>>8),0xff&_out.get());
assertEquals(0xff&b.length,0xff&_out.get());
for (int i=0;i<b.length;i++)
assertEquals('0'+(i%10),0xff&_out.get());
}
@Test
public void testFragmentBuffer() throws Exception
{
byte[] b=new byte[3001];
for (int i=0;i<b.length;i++)
b[i]=(byte)('0'+(i%10));
_generator.addFrame((byte)0xf,b,0);
_generator.flush();
assertEquals(0x8f,_out.get()&0xff);
assertEquals(0x7e,_out.get()&0xff);
int m=0;
int frag=((_out.get()&0xff)<<8)|_out.get()&0xff;
for (int i=0;i<frag;i++)
{
assertEquals("b="+i,'0'+(m%10),0xff&_out.get());
m++;
}
assertEquals(0x8f,_out.get()&0xff);
assertEquals(0x7e,_out.get()&0xff);
frag=((_out.get()&0xff)<<8)|_out.get()&0xff;
for (int i=0;i<frag;i++)
{
assertEquals("b="+i,'0'+(m%10),0xff&_out.get());
m++;
}
assertEquals(0x0f,_out.get()&0xff);
assertEquals(0x7e,_out.get()&0xff);
frag=((_out.get()&0xff)<<8)|_out.get()&0xff;
for (int i=0;i<frag;i++)
{
assertEquals("b="+i,'0'+(m%10),0xff&_out.get());
m++;
}
assertEquals(b.length,m);
}
}

View File

@ -1,20 +1,17 @@
package org.eclipse.jetty.websocket;
import java.security.MessageDigest;
import static junit.framework.Assert.assertEquals;
import org.eclipse.jetty.io.ByteArrayBuffer;
import org.eclipse.jetty.io.ByteArrayEndPoint;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.TypeUtil;
import org.junit.Before;
import org.junit.Test;
import static junit.framework.Assert.assertEquals;
/**
* @version $Revision: 1441 $ $Date: 2010-04-02 12:28:17 +0200 (Fri, 02 Apr 2010) $
*/
public class WebSocketGeneratorTest
public class WebSocketGeneratorD01Test
{
private ByteArrayBuffer _out;
private WebSocketGenerator _generator;
@ -24,7 +21,7 @@ public class WebSocketGeneratorTest
{
WebSocketBuffers buffers = new WebSocketBuffers(1024);
ByteArrayEndPoint endPoint = new ByteArrayEndPoint();
_generator = new WebSocketGenerator(buffers, endPoint);
_generator = new WebSocketGeneratorD00(buffers, endPoint);
_out = new ByteArrayBuffer(2048);
endPoint.setOut(_out);
}
@ -94,34 +91,4 @@ public class WebSocketGeneratorTest
assertEquals('0'+(i%10),0xff&_out.get());
}
@Test
public void testHixie() throws Exception
{
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] result;
byte[] expected;
expected=md.digest(TypeUtil.fromHexString("00000000000000000000000000000000"));
result=WebSocketGenerator.doTheHixieHixieShake(
0 ,0, new byte[8]);
assertEquals(TypeUtil.toHexString(expected),TypeUtil.toHexString(result));
expected=md.digest(TypeUtil.fromHexString("01020304050607080000000000000000"));
result=WebSocketGenerator.doTheHixieHixieShake(
0x01020304,
0x05060708,
new byte[8]);
assertEquals(TypeUtil.toHexString(expected),TypeUtil.toHexString(result));
byte[] random = new byte[8];
for (int i=0;i<8;i++)
random[i]=(byte)(0xff&"Tm[K T2u".charAt(i));
result=WebSocketGenerator.doTheHixieHixieShake(
155712099,173347027,random);
StringBuilder b = new StringBuilder();
for (int i=0;i<16;i++)
b.append((char)result[i]);
assertEquals("fQJ,fN/4F4!~K~MH",b.toString());
}
}

View File

@ -1,5 +1,8 @@
package org.eclipse.jetty.websocket;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.EOFException;
@ -12,6 +15,7 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import javax.servlet.http.HttpServletRequest;
import org.eclipse.jetty.server.Connector;
@ -23,9 +27,6 @@ import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
/**
* @version $Revision$ $Date$
*/

View File

@ -1,5 +1,9 @@
package org.eclipse.jetty.websocket;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
@ -8,6 +12,7 @@ import java.io.OutputStream;
import java.net.Socket;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import javax.servlet.http.HttpServletRequest;
import org.eclipse.jetty.server.Connector;
@ -19,10 +24,6 @@ import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
/**
* @version $Revision$ $Date$
*/

View File

@ -1,5 +1,9 @@
package org.eclipse.jetty.websocket;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.List;
@ -12,14 +16,10 @@ import org.eclipse.jetty.util.StringUtil;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* @version $Revision: 1441 $ $Date: 2010-04-02 12:28:17 +0200 (Fri, 02 Apr 2010) $
*/
public class WebSocketParserTest
public class WebSocketParserD00Test
{
private ByteArrayBuffer _in;
private Handler _handler;
@ -31,7 +31,7 @@ public class WebSocketParserTest
WebSocketBuffers buffers = new WebSocketBuffers(1024);
ByteArrayEndPoint endPoint = new ByteArrayEndPoint();
_handler = new Handler();
_parser=new WebSocketParser(buffers, endPoint,_handler);
_parser=new WebSocketParserD00(buffers, endPoint,_handler);
_in = new ByteArrayBuffer(2048);
endPoint.setIn(_in);
}
@ -130,13 +130,6 @@ public class WebSocketParserTest
}
@Test
public void testHixieCrypt() throws Exception
{
assertEquals(155712099,WebSocketParser.hixieCrypt("18x 6]8vM;54 *(5: { U1]8 z [ 8"));
assertEquals(173347027,WebSocketParser.hixieCrypt("1_ tx7X d < nw 334J702) 7]o}` 0"));
}
// TODO test:
// blocking,
// async
@ -144,18 +137,13 @@ public class WebSocketParserTest
// EOF
// errors
private class Handler implements WebSocketParser.EventHandler
private class Handler implements WebSocketParser.FrameHandler
{
public List<String> _data = new ArrayList<String>();
public void onFrame(byte frame, Buffer buffer)
public void onFrame(boolean more, byte flags, byte opcode, Buffer buffer)
{
_data.add(buffer.toString(StringUtil.__UTF8));
}
public void onFrame(byte frame, String data)
{
_data.add(data);
}
}
}

View File

@ -0,0 +1,187 @@
package org.eclipse.jetty.websocket;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jetty.http.HttpHeaderValues;
import org.eclipse.jetty.io.Buffer;
import org.eclipse.jetty.io.BufferCache.CachedBuffer;
import org.eclipse.jetty.io.ByteArrayBuffer;
import org.eclipse.jetty.io.ByteArrayEndPoint;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.Utf8StringBuilder;
import org.junit.Before;
import org.junit.Test;
/**
* @version $Revision: 1441 $ $Date: 2010-04-02 12:28:17 +0200 (Fri, 02 Apr 2010) $
*/
public class WebSocketParserD01Test
{
private ByteArrayBuffer _in;
private Handler _handler;
private WebSocketParser _parser;
@Before
public void setUp() throws Exception
{
WebSocketBuffers buffers = new WebSocketBuffers(1024);
ByteArrayEndPoint endPoint = new ByteArrayEndPoint();
_handler = new Handler();
_parser=new WebSocketParserD01(buffers, endPoint,_handler);
_in = new ByteArrayBuffer(2048);
endPoint.setIn(_in);
}
@Test
public void testCache() throws Exception
{
assertEquals(HttpHeaderValues.UPGRADE_ORDINAL ,((CachedBuffer)HttpHeaderValues.CACHE.lookup("Upgrade")).getOrdinal());
}
@Test
public void testShortText() throws Exception
{
_in.put((byte)0x00);
_in.put((byte)11);
_in.put("Hello World".getBytes(StringUtil.__UTF8));
int filled =_parser.parseNext();
assertEquals(13,filled);
assertEquals("Hello World",_handler._data.get(0));
assertTrue(_parser.isBufferEmpty());
assertTrue(_parser.getBuffer()==null);
}
@Test
public void testShortUtf8() throws Exception
{
String string = "Hell\uFF4f W\uFF4Frld";
byte[] bytes = string.getBytes("UTF-8");
_in.put((byte)0x00);
_in.put((byte)bytes.length);
_in.put(bytes);
int filled =_parser.parseNext();
assertEquals(bytes.length+2,filled);
assertEquals(string,_handler._data.get(0));
assertTrue(_parser.isBufferEmpty());
assertTrue(_parser.getBuffer()==null);
}
@Test
public void testMediumText() throws Exception
{
String string = "Hell\uFF4f Medium W\uFF4Frld ";
for (int i=0;i<4;i++)
string = string+string;
string += ". The end.";
byte[] bytes = string.getBytes("UTF-8");
_in.put((byte)0x00);
_in.put((byte)0x7E);
_in.put((byte)(bytes.length>>8));
_in.put((byte)(bytes.length&0xff));
_in.put(bytes);
int filled =_parser.parseNext();
assertEquals(bytes.length+4,filled);
assertEquals(string,_handler._data.get(0));
assertTrue(_parser.isBufferEmpty());
assertTrue(_parser.getBuffer()==null);
}
@Test
public void testLongText() throws Exception
{
WebSocketBuffers buffers = new WebSocketBuffers(0x20000);
ByteArrayEndPoint endPoint = new ByteArrayEndPoint();
WebSocketParser parser=new WebSocketParserD01(buffers, endPoint,_handler);
ByteArrayBuffer in = new ByteArrayBuffer(0x20000);
endPoint.setIn(in);
String string = "Hell\uFF4f Big W\uFF4Frld ";
for (int i=0;i<12;i++)
string = string+string;
string += ". The end.";
byte[] bytes = string.getBytes("UTF-8");
System.err.println(Long.toHexString(bytes.length));
in.put((byte)0x00);
in.put((byte)0x7F);
in.put((byte)0x00);
in.put((byte)0x00);
in.put((byte)0x00);
in.put((byte)0x00);
in.put((byte)0x00);
in.put((byte)(bytes.length>>16));
in.put((byte)((bytes.length>>8)&0xff));
in.put((byte)(bytes.length&0xff));
in.put(bytes);
int filled =parser.parseNext();
assertEquals(bytes.length+10,filled);
assertEquals(string,_handler._data.get(0));
assertTrue(parser.isBufferEmpty());
assertTrue(parser.getBuffer()==null);
}
@Test
public void testShortFragmentTest() throws Exception
{
_in.put((byte)0x80);
_in.put((byte)0x06);
_in.put("Hello ".getBytes(StringUtil.__UTF8));
_in.put((byte)0x00);
_in.put((byte)0x05);
_in.put("World".getBytes(StringUtil.__UTF8));
int filled =_parser.parseNext();
assertEquals(15,filled);
assertEquals(0,_handler._data.size());
assertFalse(_parser.isBufferEmpty());
assertFalse(_parser.getBuffer()==null);
filled =_parser.parseNext();
assertEquals(0,filled);
assertEquals("Hello World",_handler._data.get(0));
assertTrue(_parser.isBufferEmpty());
assertTrue(_parser.getBuffer()==null);
}
private class Handler implements WebSocketParser.FrameHandler
{
Utf8StringBuilder _utf8 = new Utf8StringBuilder();
public List<String> _data = new ArrayList<String>();
public void onFrame(boolean more, byte flags, byte opcode, Buffer buffer)
{
if (more)
_utf8.append(buffer.array(),buffer.getIndex(),buffer.length());
else if (_utf8.length()==0)
_data.add(opcode,buffer.toString("utf-8"));
else
{
_utf8.append(buffer.array(),buffer.getIndex(),buffer.length());
_data.add(opcode,_utf8.toString());
_utf8.reset();
}
}
}
}

View File

@ -1,6 +1,11 @@
package org.eclipse.jetty.websocket;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.security.MessageDigest;
import javax.servlet.http.HttpServletRequest;
import org.eclipse.jetty.io.Buffer;
@ -9,14 +14,12 @@ import org.eclipse.jetty.server.LocalConnector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.log.Log;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public class WebSocketTest
{
private static TestWebSocket _websocket;
@ -51,6 +54,44 @@ public class WebSocketTest
_server.join();
}
@Test
public void testHixieCrypt() throws Exception
{
assertEquals(155712099,WebSocketConnection.hixieCrypt("18x 6]8vM;54 *(5: { U1]8 z [ 8"));
assertEquals(173347027,WebSocketConnection.hixieCrypt("1_ tx7X d < nw 334J702) 7]o}` 0"));
}
@Test
public void testHixie() throws Exception
{
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] result;
byte[] expected;
expected=md.digest(TypeUtil.fromHexString("00000000000000000000000000000000"));
result=WebSocketConnection.doTheHixieHixieShake(
0 ,0, new byte[8]);
assertEquals(TypeUtil.toHexString(expected),TypeUtil.toHexString(result));
expected=md.digest(TypeUtil.fromHexString("01020304050607080000000000000000"));
result=WebSocketConnection.doTheHixieHixieShake(
0x01020304,
0x05060708,
new byte[8]);
assertEquals(TypeUtil.toHexString(expected),TypeUtil.toHexString(result));
byte[] random = new byte[8];
for (int i=0;i<8;i++)
random[i]=(byte)(0xff&"Tm[K T2u".charAt(i));
result=WebSocketConnection.doTheHixieHixieShake(
155712099,173347027,random);
StringBuilder b = new StringBuilder();
for (int i=0;i<16;i++)
b.append((char)result[i]);
assertEquals("fQJ,fN/4F4!~K~MH",b.toString());
}
@Test
public void testNoWebSocket() throws Exception
{

View File

@ -2,6 +2,7 @@ package org.eclipse.jetty.websocket;
import java.io.IOException;
import java.util.concurrent.ConcurrentLinkedQueue;
import javax.servlet.http.HttpServletRequest;
import org.eclipse.jetty.server.Server;