356421 Upgraded websocket to draft 13 support

This commit is contained in:
Greg Wilkins 2011-09-01 12:11:10 +10:00
parent 9528870b23
commit 0b489b8877
13 changed files with 3611 additions and 21 deletions

View File

@ -84,7 +84,7 @@ public class DeflateFrameExtension extends AbstractExtension
catch(DataFormatException e) catch(DataFormatException e)
{ {
LOG.warn(e); LOG.warn(e);
getConnection().close(WebSocketConnectionD12.CLOSE_PROTOCOL,e.toString()); getConnection().close(WebSocketConnectionD13.CLOSE_PROTOCOL,e.toString());
} }
} }

View File

@ -138,7 +138,7 @@ public class TestClient implements WebSocket.OnFrame
{ {
__framesSent++; __framesSent++;
byte flags= (byte)(off+len==data.length?0x8:0); byte flags= (byte)(off+len==data.length?0x8:0);
byte op=(byte)(off==0?opcode:WebSocketConnectionD12.OP_CONTINUATION); byte op=(byte)(off==0?opcode:WebSocketConnectionD13.OP_CONTINUATION);
if (_verbose) if (_verbose)
System.err.printf("%s#addFrame %s|%s %s\n",this.getClass().getSimpleName(),TypeUtil.toHexString(flags),TypeUtil.toHexString(op),TypeUtil.toHexString(data,off,len)); System.err.printf("%s#addFrame %s|%s %s\n",this.getClass().getSimpleName(),TypeUtil.toHexString(flags),TypeUtil.toHexString(op),TypeUtil.toHexString(data,off,len));
@ -240,11 +240,11 @@ public class TestClient implements WebSocket.OnFrame
{ {
long next = System.currentTimeMillis()+delay; long next = System.currentTimeMillis()+delay;
byte opcode=binary?WebSocketConnectionD12.OP_BINARY:WebSocketConnectionD12.OP_TEXT; byte opcode=binary?WebSocketConnectionD13.OP_BINARY:WebSocketConnectionD13.OP_TEXT;
byte data[]=null; byte data[]=null;
if (opcode==WebSocketConnectionD12.OP_TEXT) if (opcode==WebSocketConnectionD13.OP_TEXT)
{ {
StringBuilder b = new StringBuilder(); StringBuilder b = new StringBuilder();
while (b.length()<size) while (b.length()<size)
@ -258,7 +258,7 @@ public class TestClient implements WebSocket.OnFrame
} }
for (int i=0;i<clients;i++) for (int i=0;i<clients;i++)
client[i].ping(opcode,data,opcode==WebSocketConnectionD12.OP_PING?-1:fragment); client[i].ping(opcode,data,opcode==WebSocketConnectionD13.OP_PING?-1:fragment);
while(System.currentTimeMillis()<next) while(System.currentTimeMillis()<next)
Thread.sleep(10); Thread.sleep(10);

View File

@ -373,9 +373,9 @@ public class WebSocketClient
if (channel!=null) if (channel!=null)
{ {
if (ex instanceof ProtocolException) if (ex instanceof ProtocolException)
closeChannel(channel,WebSocketConnectionD12.CLOSE_PROTOCOL,ex.getMessage()); closeChannel(channel,WebSocketConnectionD13.CLOSE_PROTOCOL,ex.getMessage());
else else
closeChannel(channel,WebSocketConnectionD12.CLOSE_NOCLOSE,ex.getMessage()); closeChannel(channel,WebSocketConnectionD13.CLOSE_NO_CLOSE,ex.getMessage());
} }
} }
finally finally
@ -440,7 +440,7 @@ public class WebSocketClient
if (channel!=null) if (channel!=null)
{ {
closeChannel(channel,WebSocketConnectionD12.CLOSE_NOCLOSE,"cancelled"); closeChannel(channel,WebSocketConnectionD13.CLOSE_NO_CLOSE,"cancelled");
return true; return true;
} }
return false; return false;
@ -500,7 +500,7 @@ public class WebSocketClient
} }
if (channel!=null) if (channel!=null)
closeChannel(channel,WebSocketConnectionD12.CLOSE_NOCLOSE,"timeout"); closeChannel(channel,WebSocketConnectionD13.CLOSE_NO_CLOSE,"timeout");
if (exception!=null) if (exception!=null)
throw new ExecutionException(exception); throw new ExecutionException(exception);
if (connection!=null) if (connection!=null)

View File

@ -327,7 +327,7 @@ public class WebSocketClientFactory extends AggregateLifeCycle
"Connection: Upgrade\r\n"+ "Connection: Upgrade\r\n"+
"Sec-WebSocket-Key: "+_key+"\r\n"+ "Sec-WebSocket-Key: "+_key+"\r\n"+
(origin==null?"":"Origin: "+origin+"\r\n")+ (origin==null?"":"Origin: "+origin+"\r\n")+
"Sec-WebSocket-Version: "+WebSocketConnectionD12.VERSION+"\r\n"; "Sec-WebSocket-Version: "+WebSocketConnectionD13.VERSION+"\r\n";
if (future.getProtocol()!=null) if (future.getProtocol()!=null)
request+="Sec-WebSocket-Protocol: "+future.getProtocol()+"\r\n"; request+="Sec-WebSocket-Protocol: "+future.getProtocol()+"\r\n";
@ -378,13 +378,13 @@ public class WebSocketClientFactory extends AggregateLifeCycle
{ {
if (_accept==null) if (_accept==null)
_error="No Sec-WebSocket-Accept"; _error="No Sec-WebSocket-Accept";
else if (!WebSocketConnectionD12.hashKey(_key).equals(_accept)) else if (!WebSocketConnectionD13.hashKey(_key).equals(_accept))
_error="Bad Sec-WebSocket-Accept"; _error="Bad Sec-WebSocket-Accept";
else else
{ {
Buffer header=_parser.getHeaderBuffer(); Buffer header=_parser.getHeaderBuffer();
MaskGen maskGen=_holder.getMaskGen(); MaskGen maskGen=_holder.getMaskGen();
WebSocketConnectionD12 connection = new WebSocketConnectionD12(_holder.getWebSocket(),_endp,_buffers,System.currentTimeMillis(),_holder.getMaxIdleTime(),_holder.getProtocol(),null,10,maskGen); WebSocketConnectionD13 connection = new WebSocketConnectionD13(_holder.getWebSocket(),_endp,_buffers,System.currentTimeMillis(),_holder.getMaxIdleTime(),_holder.getProtocol(),null,10,maskGen);
if (header.hasContent()) if (header.hasContent())
connection.fillBuffersFrom(header); connection.fillBuffersFrom(header);

View File

@ -0,0 +1,884 @@
// ========================================================================
// Copyright (c) 2010 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
package org.eclipse.jetty.websocket;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.util.Collections;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.io.AbstractConnection;
import org.eclipse.jetty.io.AsyncEndPoint;
import org.eclipse.jetty.io.Buffer;
import org.eclipse.jetty.io.ByteArrayBuffer;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.nio.SelectChannelEndPoint;
import org.eclipse.jetty.util.B64Code;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.Utf8StringBuilder;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.WebSocket.OnBinaryMessage;
import org.eclipse.jetty.websocket.WebSocket.OnControl;
import org.eclipse.jetty.websocket.WebSocket.OnFrame;
import org.eclipse.jetty.websocket.WebSocket.OnTextMessage;
public class WebSocketConnectionD13 extends AbstractConnection implements WebSocketConnection
{
private static final Logger LOG = Log.getLogger(WebSocketConnectionD13.class);
final static byte OP_CONTINUATION = 0x00;
final static byte OP_TEXT = 0x01;
final static byte OP_BINARY = 0x02;
final static byte OP_EXT_DATA = 0x03;
final static byte OP_CONTROL = 0x08;
final static byte OP_CLOSE = 0x08;
final static byte OP_PING = 0x09;
final static byte OP_PONG = 0x0A;
final static byte OP_EXT_CTRL = 0x0B;
final static int CLOSE_NORMAL=1000;
final static int CLOSE_SHUTDOWN=1001;
final static int CLOSE_PROTOCOL=1002;
final static int CLOSE_BAD_DATA=1003;
final static int CLOSE_UNDEFINED=1004;
final static int CLOSE_NO_CODE=1005;
final static int CLOSE_NO_CLOSE=1006;
final static int CLOSE_NOT_UTF8=1007;
final static int CLOSE_POLICY_VIOLATION=1008;
final static int CLOSE_MESSAGE_TOO_LARGE=1009;
final static int CLOSE_REQUIRED_EXTENSION=1010;
final static int FLAG_FIN=0x8;
final static int VERSION=13;
static boolean isLastFrame(byte flags)
{
return (flags&FLAG_FIN)!=0;
}
static boolean isControlFrame(byte opcode)
{
return (opcode&OP_CONTROL)!=0;
}
private final static byte[] MAGIC;
private final IdleCheck _idle;
private final List<Extension> _extensions;
private final WebSocketParserD13 _parser;
private final WebSocketParser.FrameHandler _inbound;
private final WebSocketGeneratorD13 _generator;
private final WebSocketGenerator _outbound;
private final WebSocket _webSocket;
private final OnFrame _onFrame;
private final OnBinaryMessage _onBinaryMessage;
private final OnTextMessage _onTextMessage;
private final OnControl _onControl;
private final String _protocol;
private final int _draft;
private final ClassLoader _context;
private volatile int _closeCode;
private volatile String _closeMessage;
private volatile boolean _closedIn;
private volatile boolean _closedOut;
private int _maxTextMessageSize=-1;
private int _maxBinaryMessageSize=-1;
static
{
try
{
MAGIC="258EAFA5-E914-47DA-95CA-C5AB0DC85B11".getBytes(StringUtil.__ISO_8859_1);
}
catch (UnsupportedEncodingException e)
{
throw new RuntimeException(e);
}
}
private final WebSocketParser.FrameHandler _frameHandler= new WSFrameHandler();
private final WebSocket.FrameConnection _connection = new WSFrameConnection();
/* ------------------------------------------------------------ */
public WebSocketConnectionD13(WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, String protocol, List<Extension> extensions,int draft)
throws IOException
{
this(websocket,endpoint,buffers,timestamp,maxIdleTime,protocol,extensions,draft,null);
}
/* ------------------------------------------------------------ */
public WebSocketConnectionD13(WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, String protocol, List<Extension> extensions,int draft, MaskGen maskgen)
throws IOException
{
super(endpoint,timestamp);
_context=Thread.currentThread().getContextClassLoader();
if (endpoint instanceof AsyncEndPoint)
((AsyncEndPoint)endpoint).cancelIdle();
_draft=draft;
_endp.setMaxIdleTime(maxIdleTime);
_webSocket = websocket;
_onFrame=_webSocket instanceof OnFrame ? (OnFrame)_webSocket : null;
_onTextMessage=_webSocket instanceof OnTextMessage ? (OnTextMessage)_webSocket : null;
_onBinaryMessage=_webSocket instanceof OnBinaryMessage ? (OnBinaryMessage)_webSocket : null;
_onControl=_webSocket instanceof OnControl ? (OnControl)_webSocket : null;
_generator = new WebSocketGeneratorD13(buffers, _endp,maskgen);
_extensions=extensions;
if (_extensions!=null)
{
int e=0;
for (Extension extension : _extensions)
{
extension.bind(
_connection,
e==extensions.size()-1?_frameHandler:extensions.get(e+1),
e==0?_generator:extensions.get(e-1));
e++;
}
}
_outbound=(_extensions==null||_extensions.size()==0)?_generator:extensions.get(extensions.size()-1);
_inbound=(_extensions==null||_extensions.size()==0)?_frameHandler:extensions.get(0);
_parser = new WebSocketParserD13(buffers, endpoint,_inbound,maskgen==null);
_protocol=protocol;
// TODO should these be AsyncEndPoint checks/calls?
if (_endp instanceof SelectChannelEndPoint)
{
final SelectChannelEndPoint scep=(SelectChannelEndPoint)_endp;
scep.cancelIdle();
_idle=new IdleCheck()
{
public void access(EndPoint endp)
{
scep.scheduleIdle();
}
};
scep.scheduleIdle();
}
else
{
_idle = new IdleCheck()
{
public void access(EndPoint endp)
{}
};
}
}
/* ------------------------------------------------------------ */
public WebSocket.Connection getConnection()
{
return _connection;
}
/* ------------------------------------------------------------ */
public List<Extension> getExtensions()
{
if (_extensions==null)
return Collections.emptyList();
return _extensions;
}
/* ------------------------------------------------------------ */
public Connection handle() throws IOException
{
Thread current = Thread.currentThread();
ClassLoader oldcontext = current.getContextClassLoader();
current.setContextClassLoader(_context);
try
{
// handle the framing protocol
boolean progress=true;
while (progress)
{
int flushed=_generator.flushBuffer();
int filled=_parser.parseNext();
progress = flushed>0 || filled>0;
if (filled<0 || flushed<0)
{
_endp.close();
break;
}
}
}
catch(IOException e)
{
try
{
_endp.close();
}
catch(IOException e2)
{
LOG.ignore(e2);
}
throw e;
}
finally
{
current.setContextClassLoader(oldcontext);
_parser.returnBuffer();
_generator.returnBuffer();
if (_endp.isOpen())
{
_idle.access(_endp);
if (_closedIn && _closedOut && _outbound.isBufferEmpty())
_endp.close();
else if (_endp.isInputShutdown() && !_closedIn)
closeIn(CLOSE_NO_CLOSE,null);
else
checkWriteable();
}
}
return this;
}
/* ------------------------------------------------------------ */
public boolean isIdle()
{
return _parser.isBufferEmpty() && _outbound.isBufferEmpty();
}
/* ------------------------------------------------------------ */
@Override
public void idleExpired()
{
long idle = System.currentTimeMillis()-((SelectChannelEndPoint)_endp).getIdleTimestamp();
closeOut(WebSocketConnectionD13.CLOSE_NORMAL,"Idle for "+idle+"ms > "+_endp.getMaxIdleTime()+"ms");
}
/* ------------------------------------------------------------ */
public boolean isSuspended()
{
return false;
}
/* ------------------------------------------------------------ */
public void closed()
{
final boolean closed;
synchronized (this)
{
closed=_closeCode==0;
if (closed)
_closeCode=WebSocketConnectionD13.CLOSE_NO_CLOSE;
}
if (closed)
_webSocket.onClose(WebSocketConnectionD13.CLOSE_NO_CLOSE,"closed");
}
/* ------------------------------------------------------------ */
public void closeIn(int code,String message)
{
LOG.debug("ClosedIn {} {}",this,message);
final boolean closedOut;
final boolean closed;
synchronized (this)
{
closedOut=_closedOut;
_closedIn=true;
closed=_closeCode==0;
if (closed)
{
_closeCode=code;
_closeMessage=message;
}
}
try
{
if (closed)
_webSocket.onClose(code,message);
}
finally
{
try
{
if (closedOut)
_endp.close();
else
closeOut(code,message);
}
catch(IOException e)
{
LOG.ignore(e);
}
}
}
/* ------------------------------------------------------------ */
public void closeOut(int code,String message)
{
LOG.debug("ClosedOut {} {}",this,message);
final boolean close;
final boolean closed;
synchronized (this)
{
close=_closedIn || _closedOut;
_closedOut=true;
closed=_closeCode==0;
if (closed)
{
_closeCode=code;
_closeMessage=message;
}
}
try
{
if (closed)
_webSocket.onClose(code,message);
}
finally
{
try
{
if (close)
_endp.close();
else
{
if (code<=0)
code=WebSocketConnectionD13.CLOSE_NORMAL;
byte[] bytes = ("xx"+(message==null?"":message)).getBytes(StringUtil.__ISO_8859_1);
bytes[0]=(byte)(code/0x100);
bytes[1]=(byte)(code%0x100);
_outbound.addFrame((byte)FLAG_FIN,WebSocketConnectionD13.OP_CLOSE,bytes,0,bytes.length);
}
_outbound.flush();
}
catch(IOException e)
{
LOG.ignore(e);
}
}
}
/* ------------------------------------------------------------ */
public void fillBuffersFrom(Buffer buffer)
{
_parser.fill(buffer);
}
/* ------------------------------------------------------------ */
private void checkWriteable()
{
if (!_outbound.isBufferEmpty() && _endp instanceof AsyncEndPoint)
{
((AsyncEndPoint)_endp).scheduleWrite();
}
}
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
private class WSFrameConnection implements WebSocket.FrameConnection
{
volatile boolean _disconnecting;
/* ------------------------------------------------------------ */
public void sendMessage(String content) throws IOException
{
if (_closedOut)
throw new IOException("closedOut "+_closeCode+":"+_closeMessage);
byte[] data = content.getBytes(StringUtil.__UTF8);
_outbound.addFrame((byte)FLAG_FIN,WebSocketConnectionD13.OP_TEXT,data,0,data.length);
checkWriteable();
_idle.access(_endp);
}
/* ------------------------------------------------------------ */
public void sendMessage(byte[] content, int offset, int length) throws IOException
{
if (_closedOut)
throw new IOException("closedOut "+_closeCode+":"+_closeMessage);
_outbound.addFrame((byte)FLAG_FIN,WebSocketConnectionD13.OP_BINARY,content,offset,length);
checkWriteable();
_idle.access(_endp);
}
/* ------------------------------------------------------------ */
public void sendFrame(byte flags,byte opcode, byte[] content, int offset, int length) throws IOException
{
if (_closedOut)
throw new IOException("closedOut "+_closeCode+":"+_closeMessage);
_outbound.addFrame(flags,opcode,content,offset,length);
checkWriteable();
_idle.access(_endp);
}
/* ------------------------------------------------------------ */
public void sendControl(byte ctrl, byte[] data, int offset, int length) throws IOException
{
if (_closedOut)
throw new IOException("closedOut "+_closeCode+":"+_closeMessage);
_outbound.addFrame((byte)FLAG_FIN,ctrl,data,offset,length);
checkWriteable();
_idle.access(_endp);
}
/* ------------------------------------------------------------ */
public boolean isMessageComplete(byte flags)
{
return isLastFrame(flags);
}
/* ------------------------------------------------------------ */
public boolean isOpen()
{
return _endp!=null&&_endp.isOpen();
}
/* ------------------------------------------------------------ */
public void close(int code, String message)
{
if (_disconnecting)
return;
_disconnecting=true;
WebSocketConnectionD13.this.closeOut(code,message);
}
/* ------------------------------------------------------------ */
public void setMaxIdleTime(int ms)
{
try
{
_endp.setMaxIdleTime(ms);
}
catch(IOException e)
{
LOG.warn(e);
}
}
/* ------------------------------------------------------------ */
public void setMaxTextMessageSize(int size)
{
_maxTextMessageSize=size;
}
/* ------------------------------------------------------------ */
public void setMaxBinaryMessageSize(int size)
{
_maxBinaryMessageSize=size;
}
/* ------------------------------------------------------------ */
public int getMaxIdleTime()
{
return _endp.getMaxIdleTime();
}
/* ------------------------------------------------------------ */
public int getMaxTextMessageSize()
{
return _maxTextMessageSize;
}
/* ------------------------------------------------------------ */
public int getMaxBinaryMessageSize()
{
return _maxBinaryMessageSize;
}
/* ------------------------------------------------------------ */
public String getProtocol()
{
return _protocol;
}
/* ------------------------------------------------------------ */
public byte binaryOpcode()
{
return OP_BINARY;
}
/* ------------------------------------------------------------ */
public byte textOpcode()
{
return OP_TEXT;
}
/* ------------------------------------------------------------ */
public byte continuationOpcode()
{
return OP_CONTINUATION;
}
/* ------------------------------------------------------------ */
public byte finMask()
{
return FLAG_FIN;
}
/* ------------------------------------------------------------ */
public boolean isControl(byte opcode)
{
return isControlFrame(opcode);
}
/* ------------------------------------------------------------ */
public boolean isText(byte opcode)
{
return opcode==OP_TEXT;
}
/* ------------------------------------------------------------ */
public boolean isBinary(byte opcode)
{
return opcode==OP_BINARY;
}
/* ------------------------------------------------------------ */
public boolean isContinuation(byte opcode)
{
return opcode==OP_CONTINUATION;
}
/* ------------------------------------------------------------ */
public boolean isClose(byte opcode)
{
return opcode==OP_CLOSE;
}
/* ------------------------------------------------------------ */
public boolean isPing(byte opcode)
{
return opcode==OP_PING;
}
/* ------------------------------------------------------------ */
public boolean isPong(byte opcode)
{
return opcode==OP_PONG;
}
/* ------------------------------------------------------------ */
public void disconnect()
{
close(CLOSE_NORMAL,null);
}
/* ------------------------------------------------------------ */
public void setFakeFragments(boolean fake)
{
_parser.setFakeFragments(fake);
}
/* ------------------------------------------------------------ */
public boolean isFakeFragments()
{
return _parser.isFakeFragments();
}
/* ------------------------------------------------------------ */
@Override
public String toString()
{
return this.getClass().getSimpleName()+"@"+_endp.getLocalAddr()+":"+_endp.getLocalPort()+"<->"+_endp.getRemoteAddr()+":"+_endp.getRemotePort();
}
}
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
private class WSFrameHandler implements WebSocketParser.FrameHandler
{
private final Utf8StringBuilder _utf8 = new Utf8StringBuilder();
private ByteArrayBuffer _aggregate;
private byte _opcode=-1;
public void onFrame(final byte flags, final byte opcode, final Buffer buffer)
{
boolean lastFrame = isLastFrame(flags);
synchronized(WebSocketConnectionD13.this)
{
// Ignore incoming after a close
if (_closedIn)
return;
}
try
{
byte[] array=buffer.array();
// Deliver frame if websocket is a FrameWebSocket
if (_onFrame!=null)
{
if (_onFrame.onFrame(flags,opcode,array,buffer.getIndex(),buffer.length()))
return;
}
if (_onControl!=null && isControlFrame(opcode))
{
if (_onControl.onControl(opcode,array,buffer.getIndex(),buffer.length()))
return;
}
switch(opcode)
{
case WebSocketConnectionD13.OP_CONTINUATION:
{
// If text, append to the message buffer
if (_onTextMessage!=null && _opcode==WebSocketConnectionD13.OP_TEXT)
{
if (_utf8.append(buffer.array(),buffer.getIndex(),buffer.length(),_connection.getMaxTextMessageSize()))
{
// If this is the last fragment, deliver the text buffer
if (lastFrame)
{
_opcode=-1;
String msg =_utf8.toString();
_utf8.reset();
_onTextMessage.onMessage(msg);
}
}
else
textMessageTooLarge();
}
if (_opcode>=0 && _connection.getMaxBinaryMessageSize()>=0)
{
if (checkBinaryMessageSize(_aggregate.length(),buffer.length()))
{
_aggregate.put(buffer);
// If this is the last fragment, deliver
if (lastFrame && _onBinaryMessage!=null)
{
try
{
_onBinaryMessage.onMessage(_aggregate.array(),_aggregate.getIndex(),_aggregate.length());
}
finally
{
_opcode=-1;
_aggregate.clear();
}
}
}
}
break;
}
case WebSocketConnectionD13.OP_PING:
{
LOG.debug("PING {}",this);
if (!_closedOut)
_connection.sendControl(WebSocketConnectionD13.OP_PONG,buffer.array(),buffer.getIndex(),buffer.length());
break;
}
case WebSocketConnectionD13.OP_PONG:
{
LOG.debug("PONG {}",this);
break;
}
case WebSocketConnectionD13.OP_CLOSE:
{
int code=WebSocketConnectionD13.CLOSE_NO_CODE;
String message=null;
if (buffer.length()>=2)
{
code=buffer.array()[buffer.getIndex()]*0x100+buffer.array()[buffer.getIndex()+1];
if (buffer.length()>2)
message=new String(buffer.array(),buffer.getIndex()+2,buffer.length()-2,StringUtil.__UTF8);
}
closeIn(code,message);
break;
}
case WebSocketConnectionD13.OP_TEXT:
{
if(_onTextMessage!=null)
{
if (_connection.getMaxTextMessageSize()<=0)
{
// No size limit, so handle only final frames
if (lastFrame)
_onTextMessage.onMessage(buffer.toString(StringUtil.__UTF8));
else
{
LOG.warn("Frame discarded. Text aggregation disabed for {}",_endp);
_connection.close(WebSocketConnectionD13.CLOSE_POLICY_VIOLATION,"Text frame aggregation disabled");
}
}
// append bytes to message buffer (if they fit)
else if (_utf8.append(buffer.array(),buffer.getIndex(),buffer.length(),_connection.getMaxTextMessageSize()))
{
if (lastFrame)
{
String msg =_utf8.toString();
_utf8.reset();
_onTextMessage.onMessage(msg);
}
else
{
_opcode=WebSocketConnectionD13.OP_TEXT;
}
}
else
textMessageTooLarge();
}
break;
}
default:
{
if (_onBinaryMessage!=null && checkBinaryMessageSize(0,buffer.length()))
{
if (lastFrame)
{
_onBinaryMessage.onMessage(array,buffer.getIndex(),buffer.length());
}
else if (_connection.getMaxBinaryMessageSize()>=0)
{
_opcode=opcode;
// TODO use a growing buffer rather than a fixed one.
if (_aggregate==null)
_aggregate=new ByteArrayBuffer(_connection.getMaxBinaryMessageSize());
_aggregate.put(buffer);
}
else
{
LOG.warn("Frame discarded. Binary aggregation disabed for {}",_endp);
_connection.close(WebSocketConnectionD13.CLOSE_POLICY_VIOLATION,"Binary frame aggregation disabled");
}
}
}
}
}
catch(ThreadDeath th)
{
throw th;
}
catch(Throwable th)
{
LOG.warn(th);
}
}
private boolean checkBinaryMessageSize(int bufferLen, int length)
{
int max = _connection.getMaxBinaryMessageSize();
if (max>0 && (bufferLen+length)>max)
{
LOG.warn("Binary message too large > {}B for {}",_connection.getMaxBinaryMessageSize(),_endp);
_connection.close(WebSocketConnectionD13.CLOSE_MESSAGE_TOO_LARGE,"Message size > "+_connection.getMaxBinaryMessageSize());
_opcode=-1;
if (_aggregate!=null)
_aggregate.clear();
return false;
}
return true;
}
private void textMessageTooLarge()
{
LOG.warn("Text message too large > {} chars for {}",_connection.getMaxTextMessageSize(),_endp);
_connection.close(WebSocketConnectionD13.CLOSE_MESSAGE_TOO_LARGE,"Text message size > "+_connection.getMaxTextMessageSize()+" chars");
_opcode=-1;
_utf8.reset();
}
public void close(int code,String message)
{
if (code!=CLOSE_NORMAL)
LOG.warn("Close: "+code+" "+message);
_connection.close(code,message);
}
@Override
public String toString()
{
return WebSocketConnectionD13.this.toString()+"FH";
}
}
/* ------------------------------------------------------------ */
private interface IdleCheck
{
void access(EndPoint endp);
}
/* ------------------------------------------------------------ */
public void handshake(HttpServletRequest request, HttpServletResponse response, String subprotocol) throws IOException
{
String uri=request.getRequestURI();
String query=request.getQueryString();
if (query!=null && query.length()>0)
uri+="?"+query;
String key = request.getHeader("Sec-WebSocket-Key");
response.setHeader("Upgrade","WebSocket");
response.addHeader("Connection","Upgrade");
response.addHeader("Sec-WebSocket-Accept",hashKey(key));
if (subprotocol!=null)
response.addHeader("Sec-WebSocket-Protocol",subprotocol);
for(Extension ext : _extensions)
response.addHeader("Sec-WebSocket-Extensions",ext.getParameterizedName());
response.sendError(101);
if (_onFrame!=null)
_onFrame.onHandshake(_connection);
_webSocket.onOpen(_connection);
}
/* ------------------------------------------------------------ */
public static String hashKey(String key)
{
try
{
MessageDigest md = MessageDigest.getInstance("SHA1");
md.update(key.getBytes("UTF-8"));
md.update(MAGIC);
return new String(B64Code.encode(md.digest()));
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}
/* ------------------------------------------------------------ */
@Override
public String toString()
{
return "WS/D"+_draft+"-"+_endp;
}
}

View File

@ -221,9 +221,13 @@ public class WebSocketFactory
case 10: case 10:
case 11: case 11:
case 12: case 12:
extensions= initExtensions(extensions_requested,8-WebSocketConnectionD12.OP_EXT_DATA, 16-WebSocketConnectionD12.OP_EXT_CTRL,3); extensions= initExtensions(extensions_requested,8-WebSocketConnectionD12.OP_EXT_DATA, 16-WebSocketConnectionD13.OP_EXT_CTRL,3);
connection = new WebSocketConnectionD12(websocket, endp, _buffers, http.getTimeStamp(), _maxIdleTime, protocol,extensions,draft); connection = new WebSocketConnectionD12(websocket, endp, _buffers, http.getTimeStamp(), _maxIdleTime, protocol,extensions,draft);
break; break;
case 13:
extensions= initExtensions(extensions_requested,8-WebSocketConnectionD13.OP_EXT_DATA, 16-WebSocketConnectionD13.OP_EXT_CTRL,3);
connection = new WebSocketConnectionD13(websocket, endp, _buffers, http.getTimeStamp(), _maxIdleTime, protocol,extensions,draft);
break;
default: default:
LOG.warn("Unsupported Websocket version: "+draft); LOG.warn("Unsupported Websocket version: "+draft);
throw new HttpException(400, "Unsupported draft specification: " + draft); throw new HttpException(400, "Unsupported draft specification: " + draft);

View File

@ -0,0 +1,234 @@
// ========================================================================
// Copyright (c) 2010 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
package org.eclipse.jetty.websocket;
import java.io.IOException;
import org.eclipse.jetty.io.Buffer;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.EofException;
/* ------------------------------------------------------------ */
/** 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 WebSocketGeneratorD13 implements WebSocketGenerator
{
final private WebSocketBuffers _buffers;
final private EndPoint _endp;
private Buffer _buffer;
private final byte[] _mask=new byte[4];
private int _m;
private boolean _opsent;
private final MaskGen _maskGen;
public WebSocketGeneratorD13(WebSocketBuffers buffers, EndPoint endp)
{
_buffers=buffers;
_endp=endp;
_maskGen=null;
}
public WebSocketGeneratorD13(WebSocketBuffers buffers, EndPoint endp, MaskGen maskGen)
{
_buffers=buffers;
_endp=endp;
_maskGen=maskGen;
}
public synchronized Buffer getBuffer()
{
return _buffer;
}
public synchronized void addFrame(byte flags, byte opcode, byte[] content, int offset, int length) throws IOException
{
// System.err.printf("<< %s %s %s\n",TypeUtil.toHexString(flags),TypeUtil.toHexString(opcode),length);
boolean mask=_maskGen!=null;
if (_buffer==null)
_buffer=mask?_buffers.getBuffer():_buffers.getDirectBuffer();
boolean last=WebSocketConnectionD13.isLastFrame(flags);
byte orig=opcode;
int space=mask?14:10;
do
{
opcode = _opsent?WebSocketConnectionD13.OP_CONTINUATION:opcode;
opcode=(byte)(((0xf&flags)<<4)+(0xf&opcode));
_opsent=true;
int payload=length;
if (payload+space>_buffer.capacity())
{
// We must fragement, so clear FIN bit
opcode=(byte)(opcode&0x7F); // Clear the FIN bit
payload=_buffer.capacity()-space;
}
else if (last)
opcode= (byte)(opcode|0x80); // Set the FIN bit
// ensure there is space for header
if (_buffer.space() <= space)
{
flushBuffer();
if (_buffer.space() <= space)
flush();
}
// write the opcode and length
if (payload>0xffff)
{
_buffer.put(new byte[]{
opcode,
mask?(byte)0xff:(byte)0x7f,
(byte)((payload>>56)&0x7f),
(byte)((payload>>48)&0xff),
(byte)((payload>>40)&0xff),
(byte)((payload>>32)&0xff),
(byte)((payload>>24)&0xff),
(byte)((payload>>16)&0xff),
(byte)((payload>>8)&0xff),
(byte)(payload&0xff)});
}
else if (payload >=0x7e)
{
_buffer.put(new byte[]{
opcode,
mask?(byte)0xfe:(byte)0x7e,
(byte)(payload>>8),
(byte)(payload&0xff)});
}
else
{
_buffer.put(new byte[]{
opcode,
(byte)(mask?(0x80|payload):payload)});
}
// write mask
if (mask)
{
_maskGen.genMask(_mask);
_m=0;
_buffer.put(_mask);
}
// write payload
int remaining = payload;
while (remaining > 0)
{
_buffer.compact();
int chunk = remaining < _buffer.space() ? remaining : _buffer.space();
if (mask)
{
for (int i=0;i<chunk;i++)
_buffer.put((byte)(content[offset+ (payload-remaining)+i]^_mask[+_m++%4]));
}
else
_buffer.put(content, offset + (payload - 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
flush();
if (remaining == 0)
{
// Gently flush the data, issuing a non-blocking write
flushBuffer();
}
}
}
offset+=payload;
length-=payload;
}
while (length>0);
_opsent=!last;
if (_buffer!=null && _buffer.length()==0)
{
_buffers.returnBuffer(_buffer);
_buffer=null;
}
}
public synchronized int flushBuffer() throws IOException
{
if (!_endp.isOpen())
throw new EofException();
if (_buffer!=null)
return _endp.flush(_buffer);
return 0;
}
public synchronized int flush() throws IOException
{
if (_buffer==null)
return 0;
int result = flushBuffer();
if (!_endp.isBlocking())
{
long now = System.currentTimeMillis();
long end=now+_endp.getMaxIdleTime();
while (_buffer.length()>0)
{
boolean ready = _endp.blockWritable(end-now);
if (!ready)
{
now = System.currentTimeMillis();
if (now<end)
continue;
throw new IOException("Write timeout");
}
result += flushBuffer();
}
}
_buffer.compact();
return result;
}
public synchronized boolean isBufferEmpty()
{
return _buffer==null || _buffer.length()==0;
}
public synchronized void returnBuffer()
{
if (_buffer!=null && _buffer.length()==0)
{
_buffers.returnBuffer(_buffer);
_buffer=null;
}
}
}

View File

@ -0,0 +1,372 @@
// ========================================================================
// Copyright (c) 2010 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
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.log.Log;
import org.eclipse.jetty.util.log.Logger;
/* ------------------------------------------------------------ */
/**
* Parser the WebSocket protocol.
*
*/
public class WebSocketParserD13 implements WebSocketParser
{
private static final Logger LOG = Log.getLogger(WebSocketParserD13.class);
public enum State {
START(0), OPCODE(1), LENGTH_7(1), LENGTH_16(2), LENGTH_63(8), MASK(4), PAYLOAD(0), DATA(0), SKIP(1);
int _needs;
State(int needs)
{
_needs=needs;
}
int getNeeds()
{
return _needs;
}
};
private final WebSocketBuffers _buffers;
private final EndPoint _endp;
private final FrameHandler _handler;
private final boolean _shouldBeMasked;
private State _state;
private Buffer _buffer;
private byte _flags;
private byte _opcode;
private int _bytesNeeded;
private long _length;
private boolean _masked;
private final byte[] _mask = new byte[4];
private int _m;
private boolean _skip;
private boolean _fakeFragments=true;
/* ------------------------------------------------------------ */
/**
* @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 WebSocketParserD13(WebSocketBuffers buffers, EndPoint endp, FrameHandler handler, boolean shouldBeMasked)
{
_buffers=buffers;
_endp=endp;
_handler=handler;
_shouldBeMasked=shouldBeMasked;
_state=State.START;
}
/* ------------------------------------------------------------ */
/**
* @return True if fake fragments should be created for frames larger than the buffer.
*/
public boolean isFakeFragments()
{
return _fakeFragments;
}
/* ------------------------------------------------------------ */
/**
* @param fakeFragments True if fake fragments should be created for frames larger than the buffer.
*/
public void setFakeFragments(boolean fakeFragments)
{
_fakeFragments = fakeFragments;
}
/* ------------------------------------------------------------ */
public boolean isBufferEmpty()
{
return _buffer==null || _buffer.length()==0;
}
/* ------------------------------------------------------------ */
public Buffer getBuffer()
{
return _buffer;
}
/* ------------------------------------------------------------ */
/** Parse to next event.
* Parse to the next {@link WebSocketParser.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;
int events=0;
// Loop until a datagram call back or can't fill anymore
while(true)
{
int available=_buffer.length();
// Fill buffer if we need a byte or need length
while (available<(_state==State.SKIP?1:_bytesNeeded))
{
// 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)
{
// Can we send a fake frame?
if (_fakeFragments && _state==State.DATA)
{
Buffer data =_buffer.get(4*(available/4));
_buffer.compact();
if (_masked)
{
if (data.array()==null)
data=_buffer.asMutableBuffer();
byte[] array = data.array();
final int end=data.putIndex();
for (int i=data.getIndex();i<end;i++)
array[i]^=_mask[_m++%4];
}
// System.err.printf("%s %s %s >>\n",TypeUtil.toHexString(_flags),TypeUtil.toHexString(_opcode),data.length());
events++;
_bytesNeeded-=data.length();
_handler.onFrame((byte)(_flags&(0xff^WebSocketConnectionD13.FLAG_FIN)), _opcode, data);
_opcode=WebSocketConnectionD13.OP_CONTINUATION;
}
if (_buffer.space() == 0)
throw new IllegalStateException("FULL: "+_state+" "+_bytesNeeded+">"+_buffer.capacity());
}
// 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+events)>0?(total_filled+events):filled;
total_filled+=filled;
available=_buffer.length();
}
catch(IOException e)
{
LOG.debug(e);
return (total_filled+events)>0?(total_filled+events):-1;
}
}
// if we are here, then we have sufficient bytes to process the current state.
// Parse the buffer byte by byte (unless it is STATE_DATA)
byte b;
while (_state!=State.DATA && available>=(_state==State.SKIP?1:_bytesNeeded))
{
switch (_state)
{
case START:
_skip=false;
_state=State.OPCODE;
_bytesNeeded=_state.getNeeds();
continue;
case OPCODE:
b=_buffer.get();
available--;
_opcode=(byte)(b&0xf);
_flags=(byte)(0xf&(b>>4));
if (WebSocketConnectionD13.isControlFrame(_opcode)&&!WebSocketConnectionD13.isLastFrame(_flags))
{
events++;
LOG.warn("Fragmented Control from "+_endp);
_handler.close(WebSocketConnectionD13.CLOSE_PROTOCOL,"Fragmented control");
_skip=true;
}
_state=State.LENGTH_7;
_bytesNeeded=_state.getNeeds();
continue;
case LENGTH_7:
b=_buffer.get();
available--;
_masked=(b&0x80)!=0;
b=(byte)(0x7f&b);
switch(b)
{
case 0x7f:
_length=0;
_state=State.LENGTH_63;
break;
case 0x7e:
_length=0;
_state=State.LENGTH_16;
break;
default:
_length=(0x7f&b);
_state=_masked?State.MASK:State.PAYLOAD;
}
_bytesNeeded=_state.getNeeds();
continue;
case LENGTH_16:
b=_buffer.get();
available--;
_length = _length*0x100 + (0xff&b);
if (--_bytesNeeded==0)
{
if (_length>_buffer.capacity() && !_fakeFragments)
{
events++;
_handler.close(WebSocketConnectionD13.CLOSE_POLICY_VIOLATION,"frame size "+_length+">"+_buffer.capacity());
_skip=true;
}
_state=_masked?State.MASK:State.PAYLOAD;
_bytesNeeded=_state.getNeeds();
}
continue;
case LENGTH_63:
b=_buffer.get();
available--;
_length = _length*0x100 + (0xff&b);
if (--_bytesNeeded==0)
{
_bytesNeeded=(int)_length;
if (_length>=_buffer.capacity())
{
events++;
_handler.close(WebSocketConnectionD13.CLOSE_POLICY_VIOLATION,"frame size "+_length+">"+_buffer.capacity());
_skip=true;
}
_state=_masked?State.MASK:State.PAYLOAD;
_bytesNeeded=_state.getNeeds();
}
continue;
case MASK:
_buffer.get(_mask,0,4);
_m=0;
available-=4;
_state=State.PAYLOAD;
_bytesNeeded=_state.getNeeds();
break;
case PAYLOAD:
_bytesNeeded=(int)_length;
_state=_skip?State.SKIP:State.DATA;
break;
case DATA:
break;
case SKIP:
int skip=Math.min(available,_bytesNeeded);
_buffer.skip(skip);
available-=skip;
_bytesNeeded-=skip;
if (_bytesNeeded==0)
_state=State.START;
}
}
if (_state==State.DATA && available>=_bytesNeeded)
{
if ( _masked!=_shouldBeMasked)
{
_buffer.skip(_bytesNeeded);
_state=State.START;
events++;
_handler.close(WebSocketConnectionD13.CLOSE_PROTOCOL,"bad mask");
}
else
{
Buffer data =_buffer.get(_bytesNeeded);
if (_masked)
{
if (data.array()==null)
data=_buffer.asMutableBuffer();
byte[] array = data.array();
final int end=data.putIndex();
for (int i=data.getIndex();i<end;i++)
array[i]^=_mask[_m++%4];
}
// System.err.printf("%s %s %s >>\n",TypeUtil.toHexString(_flags),TypeUtil.toHexString(_opcode),data.length());
events++;
_handler.onFrame(_flags, _opcode, data);
_bytesNeeded=0;
_state=State.START;
}
return total_filled+events;
}
}
}
/* ------------------------------------------------------------ */
public void fill(Buffer buffer)
{
if (buffer!=null && buffer.length()>0)
{
if (_buffer==null)
_buffer=_buffers.getBuffer();
_buffer.put(buffer);
buffer.clear();
}
}
/* ------------------------------------------------------------ */
public void returnBuffer()
{
if (_buffer!=null && _buffer.length()==0)
{
_buffers.returnBuffer(_buffer);
_buffer=null;
}
}
/* ------------------------------------------------------------ */
@Override
public String toString()
{
Buffer buffer=_buffer;
return WebSocketParserD13.class.getSimpleName()+"@"+ Integer.toHexString(hashCode())+"|"+_state+"|"+(buffer==null?"<>":buffer.toDetailString());
}
}

View File

@ -167,7 +167,7 @@ public class WebSocketClientTest
} }
Assert.assertFalse(open.get()); Assert.assertFalse(open.get());
Assert.assertEquals(WebSocketConnectionD12.CLOSE_NOCLOSE,close.get()); Assert.assertEquals(WebSocketConnectionD13.CLOSE_NO_CLOSE,close.get());
Assert.assertTrue(error instanceof ConnectException); Assert.assertTrue(error instanceof ConnectException);
} }
@ -207,7 +207,7 @@ public class WebSocketClientTest
} }
Assert.assertFalse(open.get()); Assert.assertFalse(open.get());
Assert.assertEquals(WebSocketConnectionD12.CLOSE_NOCLOSE,close.get()); Assert.assertEquals(WebSocketConnectionD13.CLOSE_NO_CLOSE,close.get());
Assert.assertTrue(error instanceof TimeoutException); Assert.assertTrue(error instanceof TimeoutException);
} }
@ -246,7 +246,7 @@ public class WebSocketClientTest
} }
Assert.assertFalse(open.get()); Assert.assertFalse(open.get());
Assert.assertEquals(WebSocketConnectionD12.CLOSE_NOCLOSE,close.get()); Assert.assertEquals(WebSocketConnectionD13.CLOSE_NO_CLOSE,close.get());
Assert.assertTrue(error instanceof TimeoutException); Assert.assertTrue(error instanceof TimeoutException);
} }
@ -287,7 +287,7 @@ public class WebSocketClientTest
} }
Assert.assertFalse(open.get()); Assert.assertFalse(open.get());
Assert.assertEquals(WebSocketConnectionD12.CLOSE_PROTOCOL,close.get()); Assert.assertEquals(WebSocketConnectionD13.CLOSE_PROTOCOL,close.get());
Assert.assertTrue(error instanceof IOException); Assert.assertTrue(error instanceof IOException);
Assert.assertTrue(error.getMessage().indexOf("404 NOT FOUND")>0); Assert.assertTrue(error.getMessage().indexOf("404 NOT FOUND")>0);
@ -330,7 +330,7 @@ public class WebSocketClientTest
error=e.getCause(); error=e.getCause();
} }
Assert.assertFalse(open.get()); Assert.assertFalse(open.get());
Assert.assertEquals(WebSocketConnectionD12.CLOSE_PROTOCOL,close.get()); Assert.assertEquals(WebSocketConnectionD13.CLOSE_PROTOCOL,close.get());
Assert.assertTrue(error instanceof IOException); Assert.assertTrue(error instanceof IOException);
Assert.assertTrue(error.getMessage().indexOf("Bad Sec-WebSocket-Accept")>=0); Assert.assertTrue(error.getMessage().indexOf("Bad Sec-WebSocket-Accept")>=0);
} }
@ -368,7 +368,7 @@ public class WebSocketClientTest
socket.close(); socket.close();
_latch.await(10,TimeUnit.SECONDS); _latch.await(10,TimeUnit.SECONDS);
Assert.assertEquals(WebSocketConnectionD12.CLOSE_NOCLOSE,close.get()); Assert.assertEquals(WebSocketConnectionD13.CLOSE_NO_CLOSE,close.get());
} }
@ -406,7 +406,7 @@ public class WebSocketClientTest
long start=System.currentTimeMillis(); long start=System.currentTimeMillis();
_latch.await(10,TimeUnit.SECONDS); _latch.await(10,TimeUnit.SECONDS);
Assert.assertTrue(System.currentTimeMillis()-start<5000); Assert.assertTrue(System.currentTimeMillis()-start<5000);
Assert.assertEquals(WebSocketConnectionD12.CLOSE_NORMAL,close.get()); Assert.assertEquals(WebSocketConnectionD13.CLOSE_NORMAL,close.get());
} }
@ -742,7 +742,7 @@ public class WebSocketClientTest
} }
connection.getOutputStream().write(( connection.getOutputStream().write((
"HTTP/1.1 101 Upgrade\r\n" + "HTTP/1.1 101 Upgrade\r\n" +
"Sec-WebSocket-Accept: "+ WebSocketConnectionD12.hashKey(key) +"\r\n" + "Sec-WebSocket-Accept: "+ WebSocketConnectionD13.hashKey(key) +"\r\n" +
"\r\n").getBytes()); "\r\n").getBytes());
} }
} }

View File

@ -0,0 +1,200 @@
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.eclipse.jetty.util.StringUtil;
import org.junit.Before;
import org.junit.Test;
/**
* @version $Revision$ $Date$
*/
public class WebSocketGeneratorD13Test
{
private ByteArrayBuffer _out;
private WebSocketGenerator _generator;
ByteArrayEndPoint _endPoint;
WebSocketBuffers _buffers;
byte[] _mask = new byte[4];
int _m;
public MaskGen _maskGen = new FixedMaskGen(
new byte[]{(byte)0x00,(byte)0x00,(byte)0x0f,(byte)0xff});
@Before
public void setUp() throws Exception
{
_endPoint = new ByteArrayEndPoint();
_out = new ByteArrayBuffer(2048);
_endPoint.setOut(_out);
_buffers = new WebSocketBuffers(1024);
_m=0;
}
byte getMasked()
{
return (byte)(_out.get()^_mask[_m++%4]);
}
@Test
public void testOneString() throws Exception
{
_generator = new WebSocketGeneratorD13(_buffers, _endPoint,null);
byte[] data = "Hell\uFF4F W\uFF4Frld".getBytes(StringUtil.__UTF8);
_generator.addFrame((byte)0x8,(byte)0x04,data,0,data.length);
_generator.flush();
assertEquals((byte)0x84,_out.get());
assertEquals(15,0xff&_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 testOneBuffer() throws Exception
{
_generator = new WebSocketGeneratorD13(_buffers, _endPoint,null);
String string = "Hell\uFF4F W\uFF4Frld";
byte[] bytes=string.getBytes(StringUtil.__UTF8);
_generator.addFrame((byte)0x8,(byte)0x04,bytes,0,bytes.length);
_generator.flush();
assertEquals((byte)0x84,_out.get());
assertEquals(15,0xff&_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 testOneLongBuffer() throws Exception
{
_generator = new WebSocketGeneratorD13(_buffers, _endPoint,null);
byte[] b=new byte[150];
for (int i=0;i<b.length;i++)
b[i]=(byte)('0'+(i%10));
_generator.addFrame((byte)0x8,(byte)0x4,b,0,b.length);
_generator.flush();
assertEquals((byte)0x84,_out.get());
assertEquals((byte)126,_out.get());
assertEquals((byte)0,_out.get());
assertEquals((byte)b.length,_out.get());
for (int i=0;i<b.length;i++)
assertEquals('0'+(i%10),0xff&_out.get());
}
@Test
public void testOneStringMasked() throws Exception
{
_generator = new WebSocketGeneratorD13(_buffers, _endPoint,_maskGen);
byte[] data = "Hell\uFF4F W\uFF4Frld".getBytes(StringUtil.__UTF8);
_generator.addFrame((byte)0x8,(byte)0x04,data,0,data.length);
_generator.flush();
assertEquals((byte)0x84,_out.get());
assertEquals(15,0x7f&_out.get());
_out.get(_mask,0,4);
assertEquals('H',getMasked());
assertEquals('e',getMasked());
assertEquals('l',getMasked());
assertEquals('l',getMasked());
assertEquals(0xEF,0xff&getMasked());
assertEquals(0xBD,0xff&getMasked());
assertEquals(0x8F,0xff&getMasked());
assertEquals(' ',getMasked());
assertEquals('W',getMasked());
assertEquals(0xEF,0xff&getMasked());
assertEquals(0xBD,0xff&getMasked());
assertEquals(0x8F,0xff&getMasked());
assertEquals('r',getMasked());
assertEquals('l',getMasked());
assertEquals('d',getMasked());
}
@Test
public void testOneBufferMasked() throws Exception
{
_generator = new WebSocketGeneratorD13(_buffers, _endPoint,_maskGen);
String string = "Hell\uFF4F W\uFF4Frld";
byte[] bytes=string.getBytes(StringUtil.__UTF8);
_generator.addFrame((byte)0x8,(byte)0x04,bytes,0,bytes.length);
_generator.flush();
assertEquals((byte)0x84,_out.get());
assertEquals(15,0x7f&_out.get());
_out.get(_mask,0,4);
assertEquals('H',getMasked());
assertEquals('e',getMasked());
assertEquals('l',getMasked());
assertEquals('l',getMasked());
assertEquals(0xEF,0xff&getMasked());
assertEquals(0xBD,0xff&getMasked());
assertEquals(0x8F,0xff&getMasked());
assertEquals(' ',getMasked());
assertEquals('W',getMasked());
assertEquals(0xEF,0xff&getMasked());
assertEquals(0xBD,0xff&getMasked());
assertEquals(0x8F,0xff&getMasked());
assertEquals('r',getMasked());
assertEquals('l',getMasked());
assertEquals('d',getMasked());
}
@Test
public void testOneLongBufferMasked() throws Exception
{
_generator = new WebSocketGeneratorD13(_buffers, _endPoint,_maskGen);
byte[] b=new byte[150];
for (int i=0;i<b.length;i++)
b[i]=(byte)('0'+(i%10));
_generator.addFrame((byte)0x8,(byte)0x04,b,0,b.length);
_generator.flush();
assertEquals((byte)0x84,_out.get());
assertEquals((byte)126,0x7f&_out.get());
assertEquals((byte)0,_out.get());
assertEquals((byte)b.length,_out.get());
_out.get(_mask,0,4);
for (int i=0;i<b.length;i++)
assertEquals('0'+(i%10),0xff&getMasked());
}
}

View File

@ -0,0 +1,230 @@
package org.eclipse.jetty.websocket;
import static org.junit.Assert.assertTrue;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.lang.management.ManagementFactory;
import java.net.Socket;
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 junit.framework.Assert;
import org.eclipse.jetty.io.Buffer;
import org.eclipse.jetty.io.bio.SocketEndPoint;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.nio.SelectChannelConnector;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
/**
* @version $Revision$ $Date$
*/
public class WebSocketLoadD13Test
{
private static Server _server;
private static Connector _connector;
@BeforeClass
public static void startServer() throws Exception
{
_server = new Server();
_connector = new SelectChannelConnector();
_server.addConnector(_connector);
QueuedThreadPool threadPool = new QueuedThreadPool(200);
threadPool.setMaxStopTimeMs(1000);
_server.setThreadPool(threadPool);
WebSocketHandler wsHandler = new WebSocketHandler()
{
public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol)
{
return new EchoWebSocket();
}
};
wsHandler.setHandler(new DefaultHandler());
_server.setHandler(wsHandler);
_server.start();
}
@AfterClass
public static void stopServer() throws Exception
{
_server.stop();
_server.join();
}
@Test
public void testLoad() throws Exception
{
int count = 50;
int iterations = 100;
ExecutorService threadPool = Executors.newCachedThreadPool();
try
{
CountDownLatch latch = new CountDownLatch(count * iterations);
WebSocketClient[] clients = new WebSocketClient[count];
for (int i = 0; i < clients.length; ++i)
{
clients[i] = new WebSocketClient("localhost", _connector.getLocalPort(), 1000, latch, iterations);
clients[i].open();
}
//long start = System.nanoTime();
for (WebSocketClient client : clients)
threadPool.execute(client);
int parallelism = ManagementFactory.getOperatingSystemMXBean().getAvailableProcessors();
long maxTimePerIteration = 5;
assertTrue(latch.await(iterations * (count / parallelism + 1) * maxTimePerIteration, TimeUnit.MILLISECONDS));
//long end = System.nanoTime();
// System.err.println("Elapsed: " + TimeUnit.NANOSECONDS.toMillis(end - start) + " ms");
for (WebSocketClient client : clients)
client.close();
}
finally
{
threadPool.shutdown();
assertTrue(threadPool.awaitTermination(2, TimeUnit.SECONDS));
}
}
private static class EchoWebSocket implements WebSocket.OnTextMessage
{
private volatile Connection outbound;
public void onOpen(Connection outbound)
{
this.outbound = outbound;
}
public void onMessage(String data)
{
try
{
// System.err.println(">> "+data);
outbound.sendMessage(data);
}
catch (IOException x)
{
outbound.disconnect();
}
}
public void onClose(int closeCode, String message)
{
}
}
private class WebSocketClient implements Runnable
{
private final Socket socket;
private final BufferedWriter output;
private final BufferedReader input;
private final int iterations;
private final CountDownLatch latch;
private final SocketEndPoint _endp;
private final WebSocketGeneratorD13 _generator;
private final WebSocketParserD13 _parser;
private final WebSocketParser.FrameHandler _handler = new WebSocketParser.FrameHandler()
{
public void onFrame(byte flags, byte opcode, Buffer buffer)
{
_response=buffer;
}
public void close(int code,String message)
{
}
};
private volatile Buffer _response;
public WebSocketClient(String host, int port, int readTimeout, CountDownLatch latch, int iterations) throws IOException
{
this.latch = latch;
socket = new Socket(host, port);
socket.setSoTimeout(readTimeout);
output = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), "ISO-8859-1"));
input = new BufferedReader(new InputStreamReader(socket.getInputStream(), "ISO-8859-1"));
this.iterations = iterations;
_endp=new SocketEndPoint(socket);
_generator = new WebSocketGeneratorD13(new WebSocketBuffers(32*1024),_endp,new FixedMaskGen());
_parser = new WebSocketParserD13(new WebSocketBuffers(32*1024),_endp,_handler,false);
}
private void open() throws IOException
{
output.write("GET /chat HTTP/1.1\r\n"+
"Host: server.example.com\r\n"+
"Upgrade: websocket\r\n"+
"Connection: Upgrade\r\n"+
"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+
"Sec-WebSocket-Origin: http://example.com\r\n"+
"Sec-WebSocket-Protocol: onConnect\r\n" +
"Sec-WebSocket-Version: 7\r\n"+
"\r\n");
output.flush();
String responseLine = input.readLine();
assertTrue(responseLine.startsWith("HTTP/1.1 101 Switching Protocols"));
// Read until we find an empty line, which signals the end of the http response
String line;
while ((line = input.readLine()) != null)
if (line.length() == 0)
break;
}
public void run()
{
try
{
String message = "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF";
for (int i = 0; i < iterations; ++i)
{
byte[] data = message.getBytes(StringUtil.__UTF8);
_generator.addFrame((byte)0x8,WebSocketConnectionD13.OP_TEXT,data,0,data.length);
_generator.flush();
//System.err.println("-> "+message);
_response=null;
while(_response==null)
_parser.parseNext();
//System.err.println("<- "+_response);
Assert.assertEquals(message,_response.toString());
latch.countDown();
}
}
catch (IOException x)
{
throw new RuntimeException(x);
}
}
public void close() throws IOException
{
socket.close();
}
}
}

View File

@ -0,0 +1,359 @@
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$ $Date$
*/
public class WebSocketParserD13Test
{
private MaskedByteArrayBuffer _in;
private Handler _handler;
private WebSocketParserD13 _parser;
private byte[] _mask = new byte[] {(byte)0x00,(byte)0xF0,(byte)0x0F,(byte)0xFF};
private int _m;
class MaskedByteArrayBuffer extends ByteArrayBuffer
{
MaskedByteArrayBuffer()
{
super(4096);
}
public void sendMask()
{
super.poke(putIndex(),_mask,0,4);
super.setPutIndex(putIndex()+4);
_m=0;
}
@Override
public int put(Buffer src)
{
return put(src.asArray(),0,src.length());
}
public void putUnmasked(byte b)
{
super.put(b);
}
@Override
public void put(byte b)
{
super.put((byte)(b^_mask[_m++%4]));
}
@Override
public int put(byte[] b, int offset, int length)
{
byte[] mb = new byte[b.length];
final int end=offset+length;
for (int i=offset;i<end;i++)
{
mb[i]=(byte)(b[i]^_mask[_m++%4]);
}
return super.put(mb,offset,length);
}
@Override
public int put(byte[] b)
{
return put(b,0,b.length);
}
};
@Before
public void setUp() throws Exception
{
WebSocketBuffers buffers = new WebSocketBuffers(1024);
ByteArrayEndPoint endPoint = new ByteArrayEndPoint();
endPoint.setNonBlocking(true);
_handler = new Handler();
_parser=new WebSocketParserD13(buffers, endPoint,_handler,true);
_parser.setFakeFragments(false);
_in = new MaskedByteArrayBuffer();
endPoint.setIn(_in);
}
@Test
public void testCache() throws Exception
{
assertEquals(HttpHeaderValues.UPGRADE_ORDINAL ,((CachedBuffer)HttpHeaderValues.CACHE.lookup("Upgrade")).getOrdinal());
}
@Test
public void testFlagsOppcode() throws Exception
{
_in.putUnmasked((byte)0xff);
_in.putUnmasked((byte)0x80);
_in.sendMask();
int progress =_parser.parseNext();
assertTrue(progress>0);
assertEquals(0xf,_handler._flags);
assertEquals(0xf,_handler._opcode);
assertTrue(_parser.isBufferEmpty());
_parser.returnBuffer();
assertTrue(_parser.getBuffer()==null);
}
@Test
public void testShortText() throws Exception
{
_in.putUnmasked((byte)0x81);
_in.putUnmasked((byte)(0x80|11));
_in.sendMask();
_in.put("Hello World".getBytes(StringUtil.__UTF8));
// System.err.println("tosend="+TypeUtil.toHexString(_in.asArray()));
int progress =_parser.parseNext();
assertEquals(18,progress);
assertEquals("Hello World",_handler._data.get(0));
assertEquals(0x8,_handler._flags);
assertEquals(0x1,_handler._opcode);
assertTrue(_parser.isBufferEmpty());
_parser.returnBuffer();
assertTrue(_parser.getBuffer()==null);
}
@Test
public void testShortUtf8() throws Exception
{
String string = "Hell\uFF4f W\uFF4Frld";
byte[] bytes = string.getBytes("UTF-8");
_in.putUnmasked((byte)0x81);
_in.putUnmasked((byte)(0x80|bytes.length));
_in.sendMask();
_in.put(bytes);
int progress =_parser.parseNext();
assertEquals(bytes.length+7,progress);
assertEquals(string,_handler._data.get(0));
assertEquals(0x8,_handler._flags);
assertEquals(0x1,_handler._opcode);
_parser.returnBuffer();
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(StringUtil.__UTF8);
_in.putUnmasked((byte)0x81);
_in.putUnmasked((byte)(0x80|0x7E));
_in.putUnmasked((byte)(bytes.length>>8));
_in.putUnmasked((byte)(bytes.length&0xff));
_in.sendMask();
_in.put(bytes);
int progress =_parser.parseNext();
assertEquals(bytes.length+9,progress);
assertEquals(string,_handler._data.get(0));
assertEquals(0x8,_handler._flags);
assertEquals(0x1,_handler._opcode);
_parser.returnBuffer();
assertTrue(_parser.isBufferEmpty());
assertTrue(_parser.getBuffer()==null);
}
@Test
public void testLongText() throws Exception
{
WebSocketBuffers buffers = new WebSocketBuffers(0x20000);
ByteArrayEndPoint endPoint = new ByteArrayEndPoint();
WebSocketParserD13 parser=new WebSocketParserD13(buffers, endPoint,_handler,false);
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");
_in.sendMask();
in.put((byte)0x84);
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 progress =parser.parseNext();
parser.returnBuffer();
assertEquals(bytes.length+11,progress);
assertEquals(string,_handler._data.get(0));
assertTrue(parser.isBufferEmpty());
assertTrue(parser.getBuffer()==null);
}
@Test
public void testShortFragmentTest() throws Exception
{
_in.putUnmasked((byte)0x01);
_in.putUnmasked((byte)0x86);
_in.sendMask();
_in.put("Hello ".getBytes(StringUtil.__UTF8));
_in.putUnmasked((byte)0x80);
_in.putUnmasked((byte)0x85);
_in.sendMask();
_in.put("World".getBytes(StringUtil.__UTF8));
int progress =_parser.parseNext();
assertEquals(24,progress);
assertEquals(0,_handler._data.size());
assertFalse(_parser.isBufferEmpty());
assertFalse(_parser.getBuffer()==null);
progress =_parser.parseNext();
_parser.returnBuffer();
assertEquals(1,progress);
assertEquals("Hello World",_handler._data.get(0));
assertTrue(_parser.isBufferEmpty());
assertTrue(_parser.getBuffer()==null);
}
@Test
public void testFrameTooLarge() throws Exception
{
// Buffers are only 1024, so this frame is too large
_parser.setFakeFragments(false);
_in.putUnmasked((byte)0x81);
_in.putUnmasked((byte)(0x80|0x7E));
_in.putUnmasked((byte)(2048>>8));
_in.putUnmasked((byte)(2048&0xff));
_in.sendMask();
int progress =_parser.parseNext();
assertTrue(progress>0);
assertEquals(WebSocketConnectionD13.CLOSE_POLICY_VIOLATION,_handler._code);
for (int i=0;i<2048;i++)
_in.put((byte)'a');
progress =_parser.parseNext();
assertEquals(2048,progress);
assertEquals(0,_handler._data.size());
assertEquals(0,_handler._utf8.length());
_handler._code=0;
_handler._message=null;
_in.putUnmasked((byte)0x81);
_in.putUnmasked((byte)0xFE);
_in.putUnmasked((byte)(1024>>8));
_in.putUnmasked((byte)(1024&0xff));
_in.sendMask();
for (int i=0;i<1024;i++)
_in.put((byte)'a');
progress =_parser.parseNext();
assertTrue(progress>0);
assertEquals(1,_handler._data.size());
assertEquals(1024,_handler._data.get(0).length());
}
@Test
public void testFakeFragement() throws Exception
{
// Buffers are only 1024, so this frame will be fake fragmented
_parser.setFakeFragments(true);
_in.putUnmasked((byte)0x81);
_in.putUnmasked((byte)(0x80|0x7E));
_in.putUnmasked((byte)(2048>>8));
_in.putUnmasked((byte)(2048&0xff));
_in.sendMask();
for (int i=0;i<2048;i++)
_in.put((byte)('a'+i%26));
int progress =_parser.parseNext();
assertTrue(progress>0);
assertEquals(2,_handler._frames);
assertEquals(WebSocketConnectionD13.OP_CONTINUATION,_handler._opcode);
assertEquals(1,_handler._data.size());
String mesg=_handler._data.remove(0);
assertEquals(2048,mesg.length());
for (int i=0;i<2048;i++)
assertEquals(('a'+i%26),mesg.charAt(i));
}
private class Handler implements WebSocketParser.FrameHandler
{
Utf8StringBuilder _utf8 = new Utf8StringBuilder();
public List<String> _data = new ArrayList<String>();
private byte _flags;
private byte _opcode;
int _code;
String _message;
int _frames;
public void onFrame(byte flags, byte opcode, Buffer buffer)
{
_frames++;
_flags=flags;
_opcode=opcode;
if ((flags&0x8)==0)
_utf8.append(buffer.array(),buffer.getIndex(),buffer.length());
else if (_utf8.length()==0)
_data.add(buffer.toString("utf-8"));
else
{
_utf8.append(buffer.array(),buffer.getIndex(),buffer.length());
_data.add(_utf8.toString());
_utf8.reset();
}
}
public void close(int code,String message)
{
_code=code;
_message=message;
}
}
}