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:
parent
a70bbc004d
commit
88bfdd6631
|
@ -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 '\'.
|
||||
*
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package org.eclipse.jetty.websocket;
|
||||
|
||||
public class IdentityExtension extends AbstractExtension
|
||||
{
|
||||
public IdentityExtension()
|
||||
{
|
||||
super("identity",0,0,0);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
@ -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
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue