344059 added extensions

git-svn-id: svn+ssh://dev.eclipse.org/svnroot/rt/org.eclipse.jetty/jetty/trunk@3053 7e9141cc-0065-0410-87d8-b60c137991c4
This commit is contained in:
Greg Wilkins 2011-05-02 09:20:36 +00:00
parent a70bbc004d
commit 88bfdd6631
16 changed files with 656 additions and 39 deletions

View File

@ -21,8 +21,8 @@ import java.util.StringTokenizer;
/** StringTokenizer with Quoting support.
*
* This class is a copy of the java.util.StringTokenizer API and
* the behaviour is the same, except that single and doulbe quoted
* string values are recognized.
* the behaviour is the same, except that single and double quoted
* string values are recognised.
* Delimiters within quotes are not considered delimiters.
* Quotes can be escaped with '\'.
*

View File

@ -1,8 +1,12 @@
package org.eclipse.jetty.websocket;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.jetty.io.Buffer;
import org.eclipse.jetty.util.QuotedStringTokenizer;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.websocket.WebSocketParser.FrameHandler;
public class AbstractExtension implements Extension
@ -11,8 +15,10 @@ public class AbstractExtension implements Extension
private final byte[] _dataOpcodes;
private final byte[] _controlOpcodes;
private final byte[] _bitMasks;
private final Map<String,String> _parameters=new HashMap<String, String>();
private FrameHandler _inbound;
private WebSocketGenerator _outbound;
private WebSocket.FrameConnection _connection;
public AbstractExtension(String name,int dataCodes, int controlCodes, int flags)
{
@ -36,9 +42,42 @@ public class AbstractExtension implements Extension
{
return _bitMasks.length;
}
public void init(FrameHandler incoming, WebSocketGenerator outgoing, byte[] dataOpcodes, byte[] controlOpcodes, byte[] bitMasks)
public WebSocket.FrameConnection getConnection()
{
return _connection;
}
public boolean init(Map<String, String> parameters)
{
_parameters.putAll(parameters);
return true;
}
public String getInitParameter(String name)
{
return _parameters.get(name);
}
public String getInitParameter(String name,String dft)
{
if (!_parameters.containsKey(name))
return dft;
return _parameters.get(name);
}
public int getInitParameter(String name, int dft)
{
String v=_parameters.get(name);
if (v==null)
return dft;
return Integer.valueOf(v);
}
public void bind(WebSocket.FrameConnection connection, FrameHandler incoming, WebSocketGenerator outgoing, byte[] dataOpcodes, byte[] controlOpcodes, byte[] bitMasks)
{
_connection=connection;
_inbound=incoming;
_outbound=outgoing;
if (dataOpcodes!=null)
@ -47,13 +86,24 @@ public class AbstractExtension implements Extension
System.arraycopy(controlOpcodes,0,_dataOpcodes,0,controlOpcodes.length);
if (bitMasks!=null)
System.arraycopy(bitMasks,0,_bitMasks,0,bitMasks.length);
// System.err.printf("bind %s[%s|%s|%s]\n",_name,TypeUtil.toHexString(dataOpcodes),TypeUtil.toHexString(controlOpcodes),TypeUtil.toHexString(bitMasks));
}
public String getExtensionName()
public String getName()
{
return _name;
}
public String getParameterizedName()
{
StringBuilder name = new StringBuilder();
name.append(_name);
for (String param : _parameters.keySet())
name.append(';').append(param).append('=').append(QuotedStringTokenizer.quoteIfNeeded(_parameters.get(param),";="));
return name.toString();
}
public void onFrame(byte flags, byte opcode, Buffer buffer)
{
// System.err.printf("onFrame %s %x %x %d\n",getExtensionName(),flags,opcode,buffer.length());
@ -121,4 +171,9 @@ public class AbstractExtension implements Extension
{
return (flags & _bitMasks[flag])!=0;
}
public String toString()
{
return getParameterizedName();
}
}

View File

@ -0,0 +1,140 @@
package org.eclipse.jetty.websocket;
import java.io.IOException;
import java.util.Map;
import java.util.zip.DataFormatException;
import java.util.zip.Deflater;
import java.util.zip.Inflater;
import org.eclipse.jetty.io.Buffer;
import org.eclipse.jetty.io.ByteArrayBuffer;
import org.eclipse.jetty.util.ByteArrayOutputStream2;
import org.eclipse.jetty.util.log.Log;
public class DeflateFrameExtension extends AbstractExtension
{
private int _minLength=64;
private Deflater _deflater;
private Inflater _inflater;
public DeflateFrameExtension()
{
super("deflate-frame",0,0,1);
}
@Override
public boolean init(Map<String, String> parameters)
{
if (!parameters.containsKey("minLength"))
parameters.put("minLength",Integer.toString(_minLength));
if(super.init(parameters))
{
_minLength=getInitParameter("minLength",_minLength);
_deflater=new Deflater();
_inflater=new Inflater();
return true;
}
return false;
}
/* (non-Javadoc)
* @see org.eclipse.jetty.websocket.AbstractExtension#onFrame(byte, byte, org.eclipse.jetty.io.Buffer)
*/
@Override
public void onFrame(byte flags, byte opcode, Buffer buffer)
{
if (getConnection().isControl(opcode) || !isFlag(flags,0))
{
super.onFrame(flags,opcode,buffer);
return;
}
if (buffer.array()==null)
buffer=buffer.asMutableBuffer();
int length=0xff&buffer.get();
if (length>=0x7e)
{
int b=(length==0x7f)?8:2;
length=0;
while(b-->0)
length=0x100*length+(0xff&buffer.get());
}
// TODO check a max framesize
_inflater.setInput(buffer.array(),buffer.getIndex(),buffer.length());
ByteArrayBuffer buf = new ByteArrayBuffer(length);
try
{
while(_inflater.getRemaining()>0)
{
int inflated=_inflater.inflate(buf.array(),buf.putIndex(),buf.space());
if (inflated==0)
throw new DataFormatException("insufficient data");
buf.setPutIndex(buf.putIndex()+inflated);
}
super.onFrame(clearFlag(flags,0),opcode,buf);
}
catch(DataFormatException e)
{
Log.warn(e);
getConnection().close(WebSocketConnectionD07.CLOSE_PROTOCOL,e.toString());
}
}
/* (non-Javadoc)
* @see org.eclipse.jetty.websocket.AbstractExtension#addFrame(byte, byte, byte[], int, int)
*/
@Override
public void addFrame(byte flags, byte opcode, byte[] content, int offset, int length) throws IOException
{
if (getConnection().isControl(opcode) && length<_minLength)
{
super.addFrame(clearFlag(flags,0),opcode,content,offset,length);
return;
}
// prepare the uncompressed input
_deflater.reset();
_deflater.setInput(content,offset,length);
_deflater.finish();
// prepare the output buffer
byte[] out= new byte[length];
int out_offset=0;
// write the uncompressed length
if (length>0xffff)
{
out[out_offset++]=0x7f;
out[out_offset++]=(byte)((length>>56)&0x7f);
out[out_offset++]=(byte)((length>>48)&0xff);
out[out_offset++]=(byte)((length>>40)&0xff);
out[out_offset++]=(byte)((length>>32)&0xff);
out[out_offset++]=(byte)((length>>24)&0xff);
out[out_offset++]=(byte)((length>>16)&0xff);
out[out_offset++]=(byte)((length>>8)&0xff);
out[out_offset++]=(byte)(length&0xff);
}
else if (length >=0x7e)
{
out[out_offset++]=0x7e;
out[out_offset++]=(byte)(byte)(length>>8);
out[out_offset++]=(byte)(length&0xff);
}
else
{
out[out_offset++]=(byte)(length&0x7f);
}
int l = _deflater.deflate(out,out_offset,length-out_offset);
if (_deflater.finished())
super.addFrame(setFlag(flags,0),opcode,out,0,l+out_offset);
else
super.addFrame(clearFlag(flags,0),opcode,content,offset,length);
}
}

View File

@ -1,12 +1,16 @@
package org.eclipse.jetty.websocket;
import java.util.Map;
public interface Extension extends WebSocketParser.FrameHandler, WebSocketGenerator
{
public String getExtensionName();
public String getName();
public String getParameterizedName();
public int getDataOpcodes();
public int getControlOpcodes();
public int getReservedBits();
public void init(WebSocketParser.FrameHandler inbound, WebSocketGenerator outbound,byte[] dataOpCodes, byte[] controlOpcodes, byte[] bitMasks);
public boolean init(Map<String,String> parameters);
public void bind(WebSocket.FrameConnection connection, WebSocketParser.FrameHandler inbound, WebSocketGenerator outbound,byte[] dataOpCodes, byte[] controlOpcodes, byte[] bitMasks);
}

View File

@ -0,0 +1,62 @@
package org.eclipse.jetty.websocket;
import java.io.IOException;
import java.util.Map;
public class FragmentExtension extends AbstractExtension
{
private int _maxLength=-1;
private int _fragments=1;
public FragmentExtension()
{
super("fragment",0,0,0);
}
@Override
public boolean init(Map<String, String> parameters)
{
if(super.init(parameters))
{
_maxLength=getInitParameter("maxLength",_maxLength);
_fragments=getInitParameter("fragments",_fragments);
return true;
}
return false;
}
@Override
public void addFrame(byte flags, byte opcode, byte[] content, int offset, int length) throws IOException
{
if (getConnection().isControl(opcode))
{
super.addFrame(flags,opcode,content,offset,length);
return;
}
int fragments=1;
while (_maxLength>0 && length>_maxLength)
{
fragments++;
super.addFrame((byte)(flags&~getConnection().finMask()),opcode,content,offset,_maxLength);
length-=_maxLength;
offset+=_maxLength;
opcode=getConnection().continuationOpcode();
}
while (fragments<_fragments)
{
int frag=length/2;
fragments++;
super.addFrame((byte)(flags&0x7),opcode,content,offset,frag);
length-=frag;
offset+=frag;
opcode=getConnection().continuationOpcode();
}
super.addFrame((byte)(flags|getConnection().finMask()),opcode,content,offset,length);
}
}

View File

@ -0,0 +1,9 @@
package org.eclipse.jetty.websocket;
public class IdentityExtension extends AbstractExtension
{
public IdentityExtension()
{
super("identity",0,0,0);
}
}

View File

@ -146,6 +146,8 @@ public interface WebSocket
void close(int closeCode,String message);
byte binaryOpcode();
byte textOpcode();
byte continuationOpcode();
byte finMask();
boolean isControl(byte opcode);
boolean isText(byte opcode);

View File

@ -2,6 +2,7 @@ package org.eclipse.jetty.websocket;
import java.io.IOException;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -14,4 +15,6 @@ public interface WebSocketConnection extends Connection
void fillBuffersFrom(Buffer buffer);
void handshake(HttpServletRequest request, HttpServletResponse response, String origin, String subprotocol) throws IOException;
List<Extension> getExtensions();
}

View File

@ -16,6 +16,8 @@ package org.eclipse.jetty.websocket;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Collections;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -506,4 +508,19 @@ public class WebSocketConnectionD00 extends AbstractConnection implements WebSoc
{
return false;
}
public List<Extension> getExtensions()
{
return Collections.emptyList();
}
public byte continuationOpcode()
{
return 0;
}
public byte finMask()
{
return 0;
}
}

View File

@ -16,6 +16,8 @@ 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;
@ -301,6 +303,12 @@ public class WebSocketConnectionD06 extends AbstractConnection implements WebSoc
}
}
/* ------------------------------------------------------------ */
public List<Extension> getExtensions()
{
return Collections.emptyList();
}
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
@ -427,6 +435,18 @@ public class WebSocketConnectionD06 extends AbstractConnection implements WebSoc
return OP_TEXT;
}
/* ------------------------------------------------------------ */
public byte continuationOpcode()
{
return OP_CONTINUATION;
}
/* ------------------------------------------------------------ */
public byte finMask()
{
return 0x8;
}
/* ------------------------------------------------------------ */
public boolean isControl(byte opcode)
{

View File

@ -16,6 +16,8 @@ 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;
@ -66,6 +68,7 @@ public class WebSocketConnectionD07 extends AbstractConnection implements WebSoc
private final static byte[] MAGIC;
private final IdleCheck _idle;
private final List<Extension> _extensions;
private final WebSocketParserD07 _parser;
private final WebSocketParser.FrameHandler _inbound;
private final WebSocketGeneratorD07 _generator;
@ -80,6 +83,7 @@ public class WebSocketConnectionD07 extends AbstractConnection implements WebSoc
private boolean _closedOut;
private int _maxTextMessageSize;
private int _maxBinaryMessageSize=-1;
static
{
@ -102,7 +106,7 @@ public class WebSocketConnectionD07 extends AbstractConnection implements WebSoc
/* ------------------------------------------------------------ */
public WebSocketConnectionD07(WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, String protocol, Extension[] extensions)
public WebSocketConnectionD07(WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, String protocol, List<Extension> extensions)
throws IOException
{
super(endpoint,timestamp);
@ -120,35 +124,39 @@ public class WebSocketConnectionD07 extends AbstractConnection implements WebSoc
_onControl=_webSocket instanceof OnControl ? (OnControl)_webSocket : null;
_generator = new WebSocketGeneratorD07(buffers, _endp,null);
if (extensions!=null)
_extensions=extensions;
if (_extensions!=null)
{
byte data_op=OP_EXT_DATA;
byte ctrl_op=OP_EXT_CTRL;
byte flag_mask=0x4;
for (int e=0;e<extensions.length;e++)
int e=0;
for (Extension extension : _extensions)
{
byte[] data_ops=new byte[extensions[e].getDataOpcodes()];
byte[] data_ops=new byte[extension.getDataOpcodes()];
for (int i=0;i<data_ops.length;i++)
data_ops[i]=data_op++;
byte[] ctrl_ops=new byte[extensions[e].getControlOpcodes()];
byte[] ctrl_ops=new byte[extension.getControlOpcodes()];
for (int i=0;i<ctrl_ops.length;i++)
ctrl_ops[i]=ctrl_op++;
byte[] flag_masks=new byte[extensions[e].getReservedBits()];
byte[] flag_masks=new byte[extension.getReservedBits()];
for (int i=0;i<flag_masks.length;i++)
{
flag_masks[i]=flag_mask;
flag_mask= (byte)(flag_mask>>1);
}
extensions[e].init(
e==extensions.length-1?_frameHandler:extensions[e+1],
e==0?_generator:extensions[e-1],
data_ops,ctrl_ops,flag_masks);
extension.bind(
_connection,
e==extensions.size()-1?_frameHandler:extensions.get(e+1),
e==0?_generator:extensions.get(e-1),
data_ops,ctrl_ops,flag_masks);
e++;
}
}
_outbound=(extensions==null || extensions.length==0)?_generator:extensions[extensions.length-1];
_inbound=(extensions==null || extensions.length==0)?_frameHandler:extensions[0];
_outbound=_extensions.size()==0?_generator:extensions.get(extensions.size()-1);
_inbound=_extensions.size()==0?_frameHandler:extensions.get(0);
_parser = new WebSocketParserD07(buffers, endpoint,_inbound,true);
@ -187,6 +195,15 @@ public class WebSocketConnectionD07 extends AbstractConnection implements WebSoc
return _connection;
}
/* ------------------------------------------------------------ */
public List<Extension> getExtensions()
{
if (_extensions==null)
return Collections.emptyList();
return _extensions;
}
/* ------------------------------------------------------------ */
public Connection handle() throws IOException
{
@ -452,6 +469,18 @@ public class WebSocketConnectionD07 extends AbstractConnection implements WebSoc
return OP_TEXT;
}
/* ------------------------------------------------------------ */
public byte continuationOpcode()
{
return OP_CONTINUATION;
}
/* ------------------------------------------------------------ */
public byte finMask()
{
return 0x8;
}
/* ------------------------------------------------------------ */
public boolean isControl(byte opcode)
{
@ -699,6 +728,8 @@ public class WebSocketConnectionD07 extends AbstractConnection implements WebSoc
public void close(int code,String message)
{
if (code!=CLOSE_NORMAL)
Log.warn("Close: "+code+" "+message);
_connection.close(code,message);
}
@ -728,8 +759,12 @@ public class WebSocketConnectionD07 extends AbstractConnection implements WebSoc
response.addHeader("Sec-WebSocket-Accept",hashKey(key));
if (subprotocol!=null)
response.addHeader("Sec-WebSocket-Protocol",subprotocol);
response.sendError(101);
for(Extension ext : _extensions)
response.addHeader("Sec-WebSocket-Extensions",ext.getParameterizedName());
response.sendError(101);
if (_onFrame!=null)
_onFrame.onHandshake(_connection);
_webSocket.onOpen(_connection);

View File

@ -14,6 +14,13 @@
package org.eclipse.jetty.websocket;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -21,6 +28,7 @@ import org.eclipse.jetty.http.HttpException;
import org.eclipse.jetty.http.HttpParser;
import org.eclipse.jetty.io.ConnectedEndPoint;
import org.eclipse.jetty.server.HttpConnection;
import org.eclipse.jetty.util.QuotedStringTokenizer;
import org.eclipse.jetty.util.log.Log;
/**
@ -118,18 +126,30 @@ public class WebSocketFactory
HttpConnection http = HttpConnection.getCurrentConnection();
ConnectedEndPoint endp = (ConnectedEndPoint)http.getEndPoint();
List<String> extensions_requested = new ArrayList<String>();
for (Enumeration e=request.getHeaders("Sec-WebSocket-Extensions");e.hasMoreElements();)
{
QuotedStringTokenizer tok = new QuotedStringTokenizer((String)e.nextElement(),",");
while (tok.hasMoreTokens())
extensions_requested.add(tok.nextToken());
}
final WebSocketConnection connection;
final List<Extension> extensions;
switch (draft)
{
case -1:
case 0:
extensions=Collections.emptyList();
connection = new WebSocketConnectionD00(websocket, endp, _buffers, http.getTimeStamp(), _maxIdleTime, protocol);
break;
case 6:
extensions=Collections.emptyList();
connection = new WebSocketConnectionD06(websocket, endp, _buffers, http.getTimeStamp(), _maxIdleTime, protocol);
break;
case 7:
connection = new WebSocketConnectionD07(websocket, endp, _buffers, http.getTimeStamp(), _maxIdleTime, protocol,null);
case 7:
extensions= initExtensions(extensions_requested,8-WebSocketConnectionD07.OP_EXT_DATA, 16-WebSocketConnectionD07.OP_EXT_CTRL,3);
connection = new WebSocketConnectionD07(websocket, endp, _buffers, http.getTimeStamp(), _maxIdleTime, protocol,extensions);
break;
default:
Log.warn("Unsupported Websocket version: "+draft);
@ -148,7 +168,7 @@ public class WebSocketFactory
request.setAttribute("org.eclipse.jetty.io.Connection", connection);
}
public static String[] parseProtocols(String protocol)
protected String[] parseProtocols(String protocol)
{
if (protocol == null)
return new String[]{null};
@ -161,7 +181,7 @@ public class WebSocketFactory
return protocols;
}
public boolean acceptWebSocket(HttpServletRequest request, HttpServletResponse response)
protected boolean acceptWebSocket(HttpServletRequest request, HttpServletResponse response)
throws IOException
{
if ("websocket".equalsIgnoreCase(request.getHeader("Upgrade")))
@ -171,7 +191,7 @@ public class WebSocketFactory
protocol = request.getHeader("WebSocket-Protocol");
WebSocket websocket = null;
for (String p : WebSocketFactory.parseProtocols(protocol))
for (String p : parseProtocols(protocol))
{
websocket = _acceptor.doWebSocketConnect(request, p);
if (websocket != null)
@ -196,4 +216,55 @@ public class WebSocketFactory
return false;
}
public List<Extension> initExtensions(List<String> requested,int maxDataOpcodes,int maxControlOpcodes,int maxReservedBits)
{
List<Extension> extensions = new ArrayList<Extension>();
for (String rExt : requested)
{
QuotedStringTokenizer tok = new QuotedStringTokenizer(rExt,";");
String extName=tok.nextToken().trim();
Map<String,String> parameters = new HashMap<String,String>();
while (tok.hasMoreTokens())
{
QuotedStringTokenizer nv = new QuotedStringTokenizer(tok.nextToken().trim(),"=");
String name=nv.nextToken().trim();
String value=nv.hasMoreTokens()?nv.nextToken().trim():null;
parameters.put(name,value);
}
Extension extension = newExtension(extName);
if (extension==null)
continue;
if (extension.init(parameters))
{
if (extension.getDataOpcodes()<=maxDataOpcodes && extension.getControlOpcodes()<=maxControlOpcodes && extension.getReservedBits()<=maxReservedBits)
{
Log.debug("add {} {}",extName,parameters);
extensions.add(extension);
maxDataOpcodes-=extension.getDataOpcodes();
maxControlOpcodes-=extension.getControlOpcodes();
maxReservedBits-=extension.getReservedBits();
}
}
}
Log.debug("extensions={}",extensions);
return extensions;
}
private Extension newExtension(String name)
{
if ("identity".equals(name))
return new IdentityExtension();
if ("fragment".equals(name))
return new FragmentExtension();
if ("deflate-frame".equals(name))
return new DeflateFrameExtension();
return null;
}
}

View File

@ -119,25 +119,26 @@ public class WebSocketGeneratorD07 implements WebSocketGenerator
_buffer=mask?_buffers.getBuffer():_buffers.getDirectBuffer();
boolean last=WebSocketConnectionD07.isLastFrame(flags);
opcode=(byte)(((0xf&flags)<<4)+0xf&opcode);
byte orig=opcode;
int space=mask?14:10;
do
{
opcode = _opsent?WebSocketConnectionD07.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)0x7F; // Clear the FIN bit
opcode=(byte)(opcode&0x7F); // Clear the FIN bit
payload=_buffer.capacity()-space;
}
else if (last)
opcode|=(byte)0x80; // Set the FIN bit
opcode= (byte)(opcode|0x80); // Set the FIN bit
// ensure there is space for header
if (_buffer.space() <= space)
{

View File

@ -181,11 +181,11 @@ public class WebSocketParserD07 implements WebSocketParser
switch(b)
{
case 127:
case 0x7f:
_length=0;
_state=State.LENGTH_63;
break;
case 126:
case 0x7e:
_length=0;
_state=State.LENGTH_16;
break;

View File

@ -33,7 +33,7 @@ import org.junit.Test;
/**
* @version $Revision$ $Date$
*/
public class WebSocketLoadTest
public class WebSocketLoadD07Test
{
private static Server _server;
private static Connector _connector;
@ -142,8 +142,8 @@ public class WebSocketLoadTest
private final int iterations;
private final CountDownLatch latch;
private final SocketEndPoint _endp;
private final WebSocketGeneratorD06 _generator;
private final WebSocketParserD06 _parser;
private final WebSocketGeneratorD07 _generator;
private final WebSocketParserD07 _parser;
private final WebSocketParser.FrameHandler _handler = new WebSocketParser.FrameHandler()
{
public void onFrame(byte flags, byte opcode, Buffer buffer)
@ -167,8 +167,8 @@ public class WebSocketLoadTest
this.iterations = iterations;
_endp=new SocketEndPoint(socket);
_generator = new WebSocketGeneratorD06(new WebSocketBuffers(32*1024),_endp,new WebSocketGeneratorD06.FixedMaskGen());
_parser = new WebSocketParserD06(new WebSocketBuffers(32*1024),_endp,_handler,false);
_generator = new WebSocketGeneratorD07(new WebSocketBuffers(32*1024),_endp,new WebSocketGeneratorD07.FixedMaskGen());
_parser = new WebSocketParserD07(new WebSocketBuffers(32*1024),_endp,_handler,false);
}
@ -181,7 +181,7 @@ public class WebSocketLoadTest
"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+
"Sec-WebSocket-Origin: http://example.com\r\n"+
"Sec-WebSocket-Protocol: onConnect\r\n" +
"Sec-WebSocket-Version: 6\r\n"+
"Sec-WebSocket-Version: 7\r\n"+
"\r\n");
output.flush();
@ -203,7 +203,7 @@ public class WebSocketLoadTest
{
byte[] data = message.getBytes(StringUtil.__UTF8);
_generator.addFrame((byte)0x8,WebSocketConnectionD06.OP_TEXT,data,0,data.length);
_generator.flush(10000);
_generator.flush();
//System.err.println("-> "+message);

View File

@ -12,6 +12,8 @@ import java.net.Socket;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.zip.Deflater;
import java.util.zip.Inflater;
import javax.servlet.http.HttpServletRequest;
@ -24,6 +26,7 @@ import org.eclipse.jetty.server.nio.SelectChannelConnector;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.Utf8StringBuilder;
import org.eclipse.jetty.util.log.Log;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
@ -158,6 +161,201 @@ public class WebSocketMessageD07Test
lookFor("sent on connect",input);
}
@Test
public void testIdentityExtension() throws Exception
{
Socket socket = new Socket("localhost", _connector.getLocalPort());
OutputStream output = socket.getOutputStream();
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"+
"Sec-WebSocket-Extensions: identity;param=0\r\n"+
"Sec-WebSocket-Extensions: identity;param=1, identity ; param = '2' ; other = ' some = value ' \r\n"+
"\r\n").getBytes("ISO-8859-1"));
output.flush();
// Make sure the read times out if there are problems with the implementation
socket.setSoTimeout(1000);
InputStream input = socket.getInputStream();
lookFor("HTTP/1.1 101 Switching Protocols\r\n",input);
skipTo("Sec-WebSocket-Accept: ",input);
lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input);
skipTo("Sec-WebSocket-Extensions: ",input);
lookFor("identity;param=0",input);
skipTo("Sec-WebSocket-Extensions: ",input);
lookFor("identity;param=1",input);
skipTo("Sec-WebSocket-Extensions: ",input);
lookFor("identity;",input);
skipTo("\r\n\r\n",input);
assertTrue(_serverWebSocket.awaitConnected(1000));
assertNotNull(_serverWebSocket.connection);
assertEquals(0x81,input.read());
assertEquals(0x0f,input.read());
lookFor("sent on connect",input);
}
@Test
public void testFragmentExtension() throws Exception
{
Socket socket = new Socket("localhost", _connector.getLocalPort());
OutputStream output = socket.getOutputStream();
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"+
"Sec-WebSocket-Extensions: fragment;maxLength=4;fragments=7\r\n"+
"\r\n").getBytes("ISO-8859-1"));
output.flush();
// Make sure the read times out if there are problems with the implementation
socket.setSoTimeout(1000);
InputStream input = socket.getInputStream();
lookFor("HTTP/1.1 101 Switching Protocols\r\n",input);
skipTo("Sec-WebSocket-Accept: ",input);
lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input);
skipTo("Sec-WebSocket-Extensions: ",input);
lookFor("fragment;",input);
skipTo("\r\n\r\n",input);
assertTrue(_serverWebSocket.awaitConnected(1000));
assertNotNull(_serverWebSocket.connection);
assertEquals(0x01,input.read());
assertEquals(0x04,input.read());
lookFor("sent",input);
assertEquals(0x00,input.read());
assertEquals(0x04,input.read());
lookFor(" on ",input);
assertEquals(0x00,input.read());
assertEquals(0x04,input.read());
lookFor("conn",input);
assertEquals(0x00,input.read());
assertEquals(0x01,input.read());
lookFor("e",input);
assertEquals(0x00,input.read());
assertEquals(0x01,input.read());
lookFor("c",input);
assertEquals(0x00,input.read());
assertEquals(0x00,input.read());
assertEquals(0x80,input.read());
assertEquals(0x01,input.read());
lookFor("t",input);
}
@Test
public void testDeflateFrameExtension() throws Exception
{
Socket socket = new Socket("localhost", _connector.getLocalPort());
OutputStream output = socket.getOutputStream();
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: echo\r\n" +
"Sec-WebSocket-Version: 7\r\n"+
"Sec-WebSocket-Extensions: deflate-frame\r\n"+
"Sec-WebSocket-Extensions: fragment;fragments=2\r\n"+
"\r\n").getBytes("ISO-8859-1"));
output.flush();
// Make sure the read times out if there are problems with the implementation
socket.setSoTimeout(1000);
InputStream input = socket.getInputStream();
lookFor("HTTP/1.1 101 Switching Protocols\r\n",input);
skipTo("Sec-WebSocket-Accept: ",input);
lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input);
skipTo("Sec-WebSocket-Extensions: ",input);
lookFor("deflate-frame;minLength=64",input);
skipTo("Sec-WebSocket-Extensions: ",input);
lookFor("fragment;",input);
skipTo("\r\n\r\n",input);
assertTrue(_serverWebSocket.awaitConnected(1000));
assertNotNull(_serverWebSocket.connection);
// Server sends a big message
String text = "0123456789ABCDEF ";
text=text+text+text+text;
text=text+text+text+text;
text=text+text+text+text+'X';
byte[] data=text.getBytes("utf-8");
Deflater deflater = new Deflater();
deflater.setInput(data);
deflater.finish();
byte[] buf=new byte[data.length];
buf[0]=(byte)((byte)0x7e);
buf[1]=(byte)(data.length>>8);
buf[2]=(byte)(data.length&0xff);
int l=deflater.deflate(buf,3,buf.length-3);
assertTrue(deflater.finished());
output.write(0xC1);
output.write((byte)(0x80|(0xff&(l+3))));
output.write(0x00);
output.write(0x00);
output.write(0x00);
output.write(0x00);
output.write(buf,0,l+3);
output.flush();
assertEquals(0x40+WebSocketConnectionD07.OP_TEXT,input.read());
assertEquals(0x20+3,input.read());
assertEquals(0x7e,input.read());
assertEquals(0x02,input.read());
assertEquals(0x20,input.read());
byte[] raw = new byte[32];
assertEquals(32,input.read(raw));
Inflater inflater = new Inflater();
inflater.setInput(raw);
byte[] result = new byte[544];
assertEquals(544,inflater.inflate(result));
assertEquals(TypeUtil.toHexString(data,0,544),TypeUtil.toHexString(result));
assertEquals((byte)0xC0,(byte)input.read());
assertEquals(0x21+3,input.read());
assertEquals(0x7e,input.read());
assertEquals(0x02,input.read());
assertEquals(0x21,input.read());
assertEquals(32,input.read(raw));
inflater.reset();
inflater.setInput(raw);
result = new byte[545];
assertEquals(545,inflater.inflate(result));
assertEquals(TypeUtil.toHexString(data,544,545),TypeUtil.toHexString(result));
}
@Test
public void testServerEcho() throws Exception
{