356421 Upgraded websocket to draft 13 support
This commit is contained in:
parent
9528870b23
commit
0b489b8877
|
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue